Restore SEBPatch
This commit is contained in:
501
SafeExamBrowser.Browser/BrowserApplication.cs
Normal file
501
SafeExamBrowser.Browser/BrowserApplication.cs
Normal file
@@ -0,0 +1,501 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Contracts;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Browser.Proxy;
|
||||
using SafeExamBrowser.Settings.Logging;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
|
||||
namespace SafeExamBrowser.Browser
|
||||
{
|
||||
public class BrowserApplication : IBrowserApplication
|
||||
{
|
||||
private int windowIdCounter = default;
|
||||
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly Clipboard clipboard;
|
||||
private readonly IFileSystemDialog fileSystemDialog;
|
||||
private readonly IHashAlgorithm hashAlgorithm;
|
||||
private readonly IKeyGenerator keyGenerator;
|
||||
private readonly IModuleLogger logger;
|
||||
private readonly IMessageBox messageBox;
|
||||
private readonly INativeMethods nativeMethods;
|
||||
private readonly SessionMode sessionMode;
|
||||
private readonly BrowserSettings settings;
|
||||
private readonly IText text;
|
||||
private readonly IUserInterfaceFactory uiFactory;
|
||||
private readonly List<BrowserWindow> windows;
|
||||
|
||||
public bool AutoStart { get; private set; }
|
||||
public IconResource Icon { get; private set; }
|
||||
public Guid Id { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public string Tooltip { get; private set; }
|
||||
|
||||
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
public event LoseFocusRequestedEventHandler LoseFocusRequested;
|
||||
public event TerminationRequestedEventHandler TerminationRequested;
|
||||
public event UserIdentifierDetectedEventHandler UserIdentifierDetected;
|
||||
public event WindowsChangedEventHandler WindowsChanged;
|
||||
|
||||
public BrowserApplication(
|
||||
AppConfig appConfig,
|
||||
BrowserSettings settings,
|
||||
IFileSystemDialog fileSystemDialog,
|
||||
IHashAlgorithm hashAlgorithm,
|
||||
IKeyGenerator keyGenerator,
|
||||
IMessageBox messageBox,
|
||||
IModuleLogger logger,
|
||||
INativeMethods nativeMethods,
|
||||
SessionMode sessionMode,
|
||||
IText text,
|
||||
IUserInterfaceFactory uiFactory)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.clipboard = new Clipboard(logger.CloneFor(nameof(Clipboard)), settings);
|
||||
this.fileSystemDialog = fileSystemDialog;
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.logger = logger;
|
||||
this.messageBox = messageBox;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.sessionMode = sessionMode;
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
this.uiFactory = uiFactory;
|
||||
this.windows = new List<BrowserWindow>();
|
||||
}
|
||||
|
||||
public void Focus(bool forward)
|
||||
{
|
||||
windows.ForEach(window =>
|
||||
{
|
||||
window.Focus(forward);
|
||||
});
|
||||
}
|
||||
|
||||
public IEnumerable<IBrowserWindow> GetWindows()
|
||||
{
|
||||
return new List<IBrowserWindow>(windows);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
logger.Info("Starting initialization...");
|
||||
|
||||
var cefSettings = InitializeCefSettings();
|
||||
var success = Cef.Initialize(cefSettings, true, default(IApp));
|
||||
|
||||
InitializeApplicationInfo();
|
||||
|
||||
if (success)
|
||||
{
|
||||
InitializeIntegrityKeys();
|
||||
|
||||
if (settings.DeleteCookiesOnStartup)
|
||||
{
|
||||
DeleteCookies();
|
||||
}
|
||||
|
||||
if (settings.UseTemporaryDownAndUploadDirectory)
|
||||
{
|
||||
CreateTemporaryDownAndUploadDirectory();
|
||||
}
|
||||
|
||||
logger.Info("Initialized browser.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Failed to initialize browser!");
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
CreateNewWindow();
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
logger.Info("Initiating termination...");
|
||||
AwaitReady();
|
||||
|
||||
foreach (var window in windows)
|
||||
{
|
||||
window.Closed -= Window_Closed;
|
||||
window.Close();
|
||||
logger.Info($"Closed browser window #{window.Id}.");
|
||||
}
|
||||
|
||||
if (settings.UseTemporaryDownAndUploadDirectory)
|
||||
{
|
||||
DeleteTemporaryDownAndUploadDirectory();
|
||||
}
|
||||
|
||||
if (settings.DeleteCookiesOnShutdown)
|
||||
{
|
||||
DeleteCookies();
|
||||
}
|
||||
|
||||
Cef.Shutdown();
|
||||
logger.Info("Terminated browser.");
|
||||
|
||||
if (settings.DeleteCacheOnShutdown && settings.DeleteCookiesOnShutdown)
|
||||
{
|
||||
DeleteCache();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info("Retained browser cache.");
|
||||
}
|
||||
}
|
||||
|
||||
private void AwaitReady()
|
||||
{
|
||||
// We apparently need to let the browser finish any pending work before attempting to reset or terminate it, especially if the
|
||||
// reset or termination is initiated automatically (e.g. by a quit URL). Otherwise, the engine will crash on some occasions, seemingly
|
||||
// when it can't finish handling its events (like ChromiumWebBrowser.LoadError).
|
||||
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
|
||||
private void CreateNewWindow(PopupRequestedEventArgs args = default)
|
||||
{
|
||||
var id = ++windowIdCounter;
|
||||
var isMainWindow = windows.Count == 0;
|
||||
var startUrl = GenerateStartUrl();
|
||||
var windowLogger = logger.CloneFor($"Browser Window #{id}");
|
||||
var window = new BrowserWindow(
|
||||
appConfig,
|
||||
clipboard,
|
||||
fileSystemDialog,
|
||||
hashAlgorithm,
|
||||
id,
|
||||
isMainWindow,
|
||||
keyGenerator,
|
||||
windowLogger,
|
||||
messageBox,
|
||||
sessionMode,
|
||||
settings,
|
||||
startUrl,
|
||||
text,
|
||||
uiFactory);
|
||||
|
||||
window.Closed += Window_Closed;
|
||||
window.ConfigurationDownloadRequested += (f, a) => ConfigurationDownloadRequested?.Invoke(f, a);
|
||||
window.PopupRequested += Window_PopupRequested;
|
||||
window.ResetRequested += Window_ResetRequested;
|
||||
window.UserIdentifierDetected += (i) => UserIdentifierDetected?.Invoke(i);
|
||||
window.TerminationRequested += () => TerminationRequested?.Invoke();
|
||||
window.LoseFocusRequested += (forward) => LoseFocusRequested?.Invoke(forward);
|
||||
|
||||
window.InitializeControl();
|
||||
windows.Add(window);
|
||||
|
||||
if (args != default(PopupRequestedEventArgs))
|
||||
{
|
||||
args.Window = window;
|
||||
}
|
||||
else
|
||||
{
|
||||
window.InitializeWindow();
|
||||
}
|
||||
|
||||
logger.Info($"Created browser window #{window.Id}.");
|
||||
WindowsChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void CreateTemporaryDownAndUploadDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
settings.DownAndUploadDirectory = Path.Combine(appConfig.TemporaryDirectory, Path.GetRandomFileName());
|
||||
Directory.CreateDirectory(settings.DownAndUploadDirectory);
|
||||
logger.Info($"Created temporary down- and upload directory.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to create temporary down- and upload directory!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteTemporaryDownAndUploadDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(settings.DownAndUploadDirectory, true);
|
||||
logger.Info("Deleted temporary down- and upload directory.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to delete temporary down- and upload directory!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteCache()
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(appConfig.BrowserCachePath, true);
|
||||
logger.Info("Deleted browser cache.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to delete browser cache!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteCookies()
|
||||
{
|
||||
var callback = new TaskDeleteCookiesCallback();
|
||||
|
||||
callback.Task.ContinueWith(task =>
|
||||
{
|
||||
if (!task.IsCompleted || task.Result == TaskDeleteCookiesCallback.InvalidNoOfCookiesDeleted)
|
||||
{
|
||||
logger.Warn("Failed to delete cookies!");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Deleted {task.Result} cookies.");
|
||||
}
|
||||
});
|
||||
|
||||
if (Cef.GetGlobalCookieManager().DeleteCookies(callback: callback))
|
||||
{
|
||||
logger.Debug("Successfully initiated cookie deletion.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("Failed to initiate cookie deletion!");
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateStartUrl()
|
||||
{
|
||||
var url = settings.StartUrl;
|
||||
|
||||
if (settings.UseQueryParameter)
|
||||
{
|
||||
if (url.Contains("?") && settings.StartUrlQuery?.Length > 1 && Uri.TryCreate(url, UriKind.Absolute, out var uri))
|
||||
{
|
||||
url = url.Replace(uri.Query, $"{uri.Query}&{settings.StartUrlQuery.Substring(1)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
url = $"{url}{settings.StartUrlQuery}";
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
private void InitializeApplicationInfo()
|
||||
{
|
||||
AutoStart = true;
|
||||
Icon = new BrowserIconResource();
|
||||
Id = Guid.NewGuid();
|
||||
Name = text.Get(TextKey.Browser_Name);
|
||||
Tooltip = text.Get(TextKey.Browser_Tooltip);
|
||||
}
|
||||
|
||||
private CefSettings InitializeCefSettings()
|
||||
{
|
||||
var warning = logger.LogLevel == LogLevel.Warning;
|
||||
var error = logger.LogLevel == LogLevel.Error;
|
||||
var cefSettings = new CefSettings();
|
||||
|
||||
cefSettings.AcceptLanguageList = CultureInfo.CurrentUICulture.Name;
|
||||
cefSettings.CachePath = appConfig.BrowserCachePath;
|
||||
cefSettings.CefCommandLineArgs.Add("touch-events", "enabled");
|
||||
cefSettings.LogFile = appConfig.BrowserLogFilePath;
|
||||
cefSettings.LogSeverity = error ? LogSeverity.Error : (warning ? LogSeverity.Warning : LogSeverity.Info);
|
||||
cefSettings.PersistSessionCookies = !settings.DeleteCookiesOnStartup || !settings.DeleteCookiesOnShutdown;
|
||||
cefSettings.UserAgent = InitializeUserAgent();
|
||||
|
||||
if (!settings.AllowPageZoom)
|
||||
{
|
||||
cefSettings.CefCommandLineArgs.Add("disable-pinch");
|
||||
}
|
||||
|
||||
if (!settings.AllowPdfReader)
|
||||
{
|
||||
cefSettings.CefCommandLineArgs.Add("disable-pdf-extension");
|
||||
}
|
||||
|
||||
if (!settings.AllowSpellChecking)
|
||||
{
|
||||
cefSettings.CefCommandLineArgs.Add("disable-spell-checking");
|
||||
}
|
||||
|
||||
cefSettings.CefCommandLineArgs.Add("enable-media-stream");
|
||||
cefSettings.CefCommandLineArgs.Add("enable-usermedia-screen-capturing");
|
||||
cefSettings.CefCommandLineArgs.Add("use-fake-ui-for-media-stream");
|
||||
|
||||
InitializeProxySettings(cefSettings);
|
||||
|
||||
logger.Debug($"Accept Language: {cefSettings.AcceptLanguageList}");
|
||||
logger.Debug($"Cache Path: {cefSettings.CachePath}");
|
||||
logger.Debug($"Engine Version: Chromium {Cef.ChromiumVersion}, CEF {Cef.CefVersion}, CefSharp {Cef.CefSharpVersion}");
|
||||
logger.Debug($"Log File: {cefSettings.LogFile}");
|
||||
logger.Debug($"Log Severity: {cefSettings.LogSeverity}.");
|
||||
logger.Debug($"PDF Reader: {(settings.AllowPdfReader ? "Enabled" : "Disabled")}.");
|
||||
logger.Debug($"Session Persistence: {(cefSettings.PersistSessionCookies ? "Enabled" : "Disabled")}.");
|
||||
|
||||
return cefSettings;
|
||||
}
|
||||
|
||||
private void InitializeIntegrityKeys()
|
||||
{
|
||||
logger.Debug($"Browser Exam Key (BEK) transmission is {(settings.SendBrowserExamKey ? "enabled" : "disabled")}.");
|
||||
logger.Debug($"Configuration Key (CK) transmission is {(settings.SendConfigurationKey ? "enabled" : "disabled")}.");
|
||||
|
||||
if (settings.CustomBrowserExamKey != default)
|
||||
{
|
||||
keyGenerator.UseCustomBrowserExamKey(settings.CustomBrowserExamKey);
|
||||
logger.Debug($"The browser application will be using a custom browser exam key.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"The browser application will be using the default browser exam key.");
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeProxySettings(CefSettings cefSettings)
|
||||
{
|
||||
if (settings.Proxy.Policy == ProxyPolicy.Custom)
|
||||
{
|
||||
if (settings.Proxy.AutoConfigure)
|
||||
{
|
||||
cefSettings.CefCommandLineArgs.Add("proxy-pac-url", settings.Proxy.AutoConfigureUrl);
|
||||
}
|
||||
|
||||
if (settings.Proxy.AutoDetect)
|
||||
{
|
||||
cefSettings.CefCommandLineArgs.Add("proxy-auto-detect", "");
|
||||
}
|
||||
|
||||
if (settings.Proxy.BypassList.Any())
|
||||
{
|
||||
cefSettings.CefCommandLineArgs.Add("proxy-bypass-list", string.Join(";", settings.Proxy.BypassList));
|
||||
}
|
||||
|
||||
if (settings.Proxy.Proxies.Any())
|
||||
{
|
||||
var proxies = new List<string>();
|
||||
|
||||
foreach (var proxy in settings.Proxy.Proxies)
|
||||
{
|
||||
proxies.Add($"{ToScheme(proxy.Protocol)}={proxy.Host}:{proxy.Port}");
|
||||
}
|
||||
|
||||
cefSettings.CefCommandLineArgs.Add("proxy-server", string.Join(";", proxies));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string InitializeUserAgent()
|
||||
{
|
||||
var osVersion = $"{Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}";
|
||||
var sebVersion = $"SEB/{appConfig.ProgramInformationalVersion}";
|
||||
var userAgent = default(string);
|
||||
|
||||
if (settings.UseCustomUserAgent)
|
||||
{
|
||||
userAgent = $"{settings.CustomUserAgent} {sebVersion}";
|
||||
}
|
||||
else
|
||||
{
|
||||
userAgent = $"Mozilla/5.0 (Windows NT {osVersion}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{Cef.ChromiumVersion} {sebVersion}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(settings.UserAgentSuffix))
|
||||
{
|
||||
userAgent = $"{userAgent} {settings.UserAgentSuffix}";
|
||||
}
|
||||
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
private string ToScheme(ProxyProtocol protocol)
|
||||
{
|
||||
switch (protocol)
|
||||
{
|
||||
case ProxyProtocol.Ftp:
|
||||
return Uri.UriSchemeFtp;
|
||||
case ProxyProtocol.Http:
|
||||
return Uri.UriSchemeHttp;
|
||||
case ProxyProtocol.Https:
|
||||
return Uri.UriSchemeHttps;
|
||||
case ProxyProtocol.Socks:
|
||||
return "socks";
|
||||
}
|
||||
|
||||
throw new NotImplementedException($"Mapping for proxy protocol '{protocol}' is not yet implemented!");
|
||||
}
|
||||
|
||||
private void Window_Closed(int id)
|
||||
{
|
||||
windows.Remove(windows.First(i => i.Id == id));
|
||||
WindowsChanged?.Invoke();
|
||||
logger.Info($"Window #{id} has been closed.");
|
||||
}
|
||||
|
||||
private void Window_PopupRequested(PopupRequestedEventArgs args)
|
||||
{
|
||||
logger.Info($"Received request to create new window...");
|
||||
CreateNewWindow(args);
|
||||
}
|
||||
|
||||
private void Window_ResetRequested()
|
||||
{
|
||||
logger.Info("Attempting to reset browser...");
|
||||
AwaitReady();
|
||||
|
||||
foreach (var window in windows)
|
||||
{
|
||||
window.Closed -= Window_Closed;
|
||||
window.Close();
|
||||
logger.Info($"Closed browser window #{window.Id}.");
|
||||
}
|
||||
|
||||
windows.Clear();
|
||||
WindowsChanged?.Invoke();
|
||||
|
||||
if (settings.DeleteCookiesOnStartup && settings.DeleteCookiesOnShutdown)
|
||||
{
|
||||
DeleteCookies();
|
||||
}
|
||||
|
||||
nativeMethods.EmptyClipboard();
|
||||
CreateNewWindow();
|
||||
logger.Info("Successfully reset browser.");
|
||||
}
|
||||
}
|
||||
}
|
195
SafeExamBrowser.Browser/BrowserControl.cs
Normal file
195
SafeExamBrowser.Browser/BrowserControl.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Wrapper;
|
||||
using SafeExamBrowser.Browser.Wrapper.Events;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
|
||||
|
||||
namespace SafeExamBrowser.Browser
|
||||
{
|
||||
internal class BrowserControl : IBrowserControl
|
||||
{
|
||||
private readonly Clipboard clipboard;
|
||||
private readonly ICefSharpControl control;
|
||||
private readonly IDialogHandler dialogHandler;
|
||||
private readonly IDisplayHandler displayHandler;
|
||||
private readonly IDownloadHandler downloadHandler;
|
||||
private readonly IKeyboardHandler keyboardHandler;
|
||||
private readonly ILogger logger;
|
||||
private readonly IRenderProcessMessageHandler renderProcessMessageHandler;
|
||||
private readonly IRequestHandler requestHandler;
|
||||
|
||||
public string Address => control.Address;
|
||||
public bool CanNavigateBackwards => control.IsBrowserInitialized && control.BrowserCore.CanGoBack;
|
||||
public bool CanNavigateForwards => control.IsBrowserInitialized && control.BrowserCore.CanGoForward;
|
||||
public object EmbeddableControl => control;
|
||||
|
||||
public event AddressChangedEventHandler AddressChanged;
|
||||
public event LoadFailedEventHandler LoadFailed;
|
||||
public event LoadingStateChangedEventHandler LoadingStateChanged;
|
||||
public event TitleChangedEventHandler TitleChanged;
|
||||
|
||||
public BrowserControl(
|
||||
Clipboard clipboard,
|
||||
ICefSharpControl control,
|
||||
IDialogHandler dialogHandler,
|
||||
IDisplayHandler displayHandler,
|
||||
IDownloadHandler downloadHandler,
|
||||
IKeyboardHandler keyboardHandler,
|
||||
ILogger logger,
|
||||
IRenderProcessMessageHandler renderProcessMessageHandler,
|
||||
IRequestHandler requestHandler)
|
||||
{
|
||||
this.control = control;
|
||||
this.clipboard = clipboard;
|
||||
this.dialogHandler = dialogHandler;
|
||||
this.displayHandler = displayHandler;
|
||||
this.downloadHandler = downloadHandler;
|
||||
this.keyboardHandler = keyboardHandler;
|
||||
this.logger = logger;
|
||||
this.renderProcessMessageHandler = renderProcessMessageHandler;
|
||||
this.requestHandler = requestHandler;
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
if (!control.IsDisposed)
|
||||
{
|
||||
control.Dispose(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void ExecuteJavaScript(string code, Action<JavaScriptResult> callback = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (control.BrowserCore != default && control.BrowserCore.MainFrame != default)
|
||||
{
|
||||
control.BrowserCore.EvaluateScriptAsync(code).ContinueWith(t =>
|
||||
{
|
||||
callback?.Invoke(new JavaScriptResult
|
||||
{
|
||||
Message = t.Result.Message,
|
||||
Result = t.Result.Result,
|
||||
Success = t.Result.Success
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Task.Run(() => callback?.Invoke(new JavaScriptResult
|
||||
{
|
||||
Message = "JavaScript can't be executed in main frame!",
|
||||
Success = false
|
||||
}));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to execute JavaScript '{(code.Length > 50 ? code.Take(50) : code)}'!", e);
|
||||
Task.Run(() => callback?.Invoke(new JavaScriptResult
|
||||
{
|
||||
Message = $"Failed to execute JavaScript '{(code.Length > 50 ? code.Take(50) : code)}'! Reason: {e.Message}",
|
||||
Success = false
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public void Find(string term, bool isInitial, bool caseSensitive, bool forward = true)
|
||||
{
|
||||
control.Find(term, forward, caseSensitive, !isInitial);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
clipboard.Changed += Clipboard_Changed;
|
||||
|
||||
control.AddressChanged += (o, e) => AddressChanged?.Invoke(e.Address);
|
||||
control.AuthCredentialsRequired += (w, b, o, i, h, p, r, s, c, a) => a.Value = requestHandler.GetAuthCredentials(w, b, o, i, h, p, r, s, c);
|
||||
control.BeforeBrowse += (w, b, f, r, u, i, a) => a.Value = requestHandler.OnBeforeBrowse(w, b, f, r, u, i);
|
||||
control.BeforeDownload += (w, b, d, c) => downloadHandler.OnBeforeDownload(w, b, d, c);
|
||||
control.CanDownload += (w, b, u, r, a) => a.Value = downloadHandler.CanDownload(w, b, u, r);
|
||||
control.ContextCreated += (w, b, f) => renderProcessMessageHandler.OnContextCreated(w, b, f);
|
||||
control.ContextReleased += (w, b, f) => renderProcessMessageHandler.OnContextReleased(w, b, f);
|
||||
control.DownloadUpdated += (w, b, d, c) => downloadHandler.OnDownloadUpdated(w, b, d, c);
|
||||
control.FaviconUrlChanged += (w, b, u) => displayHandler.OnFaviconUrlChange(w, b, u);
|
||||
control.FileDialogRequested += (w, b, m, t, d, f, c) => dialogHandler.OnFileDialog(w, b, m, t, d, f, c);
|
||||
control.FocusedNodeChanged += (w, b, f, n) => renderProcessMessageHandler.OnFocusedNodeChanged(w, b, f, n);
|
||||
control.IsBrowserInitializedChanged += Control_IsBrowserInitializedChanged;
|
||||
control.KeyEvent += (w, b, t, k, n, m, s) => keyboardHandler.OnKeyEvent(w, b, t, k, n, m, s);
|
||||
control.LoadError += (o, e) => LoadFailed?.Invoke((int) e.ErrorCode, e.ErrorText, e.Frame.IsMain, e.FailedUrl);
|
||||
control.LoadingProgressChanged += (w, b, p) => displayHandler.OnLoadingProgressChange(w, b, p);
|
||||
control.LoadingStateChanged += (o, e) => LoadingStateChanged?.Invoke(e.IsLoading);
|
||||
control.OpenUrlFromTab += (w, b, f, u, t, g, a) => a.Value = requestHandler.OnOpenUrlFromTab(w, b, f, u, t, g);
|
||||
control.PreKeyEvent += (IWebBrowser w, IBrowser b, KeyType t, int k, int n, CefEventFlags m, bool i, ref bool s, GenericEventArgs a) => a.Value = keyboardHandler.OnPreKeyEvent(w, b, t, k, n, m, i, ref s);
|
||||
control.ResourceRequestHandlerRequired += (IWebBrowser w, IBrowser b, IFrame f, IRequest r, bool n, bool d, string i, ref bool h, ResourceRequestEventArgs a) => a.Handler = requestHandler.GetResourceRequestHandler(w, b, f, r, n, d, i, ref h);
|
||||
control.TitleChanged += (o, e) => TitleChanged?.Invoke(e.Title);
|
||||
control.UncaughtExceptionEvent += (w, b, f, e) => renderProcessMessageHandler.OnUncaughtException(w, b, f, e);
|
||||
|
||||
if (control is IWebBrowser webBrowser)
|
||||
{
|
||||
webBrowser.JavascriptMessageReceived += WebBrowser_JavascriptMessageReceived;
|
||||
}
|
||||
}
|
||||
|
||||
public void NavigateBackwards()
|
||||
{
|
||||
control.BrowserCore.GoBack();
|
||||
}
|
||||
|
||||
public void NavigateForwards()
|
||||
{
|
||||
control.BrowserCore.GoForward();
|
||||
}
|
||||
|
||||
public void NavigateTo(string address)
|
||||
{
|
||||
control.Load(address);
|
||||
}
|
||||
|
||||
public void ShowDeveloperConsole()
|
||||
{
|
||||
control.BrowserCore.ShowDevTools();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
control.BrowserCore.Reload();
|
||||
}
|
||||
|
||||
public void Zoom(double level)
|
||||
{
|
||||
control.BrowserCore.SetZoomLevel(level);
|
||||
}
|
||||
|
||||
private void Clipboard_Changed(long id)
|
||||
{
|
||||
ExecuteJavaScript($"SafeExamBrowser.clipboard.update({id}, '{clipboard.Content}');");
|
||||
}
|
||||
|
||||
private void Control_IsBrowserInitializedChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (control.IsBrowserInitialized)
|
||||
{
|
||||
control.BrowserCore.GetHost().SetFocus(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void WebBrowser_JavascriptMessageReceived(object sender, JavascriptMessageReceivedEventArgs e)
|
||||
{
|
||||
clipboard.Process(e);
|
||||
}
|
||||
}
|
||||
}
|
21
SafeExamBrowser.Browser/BrowserIconResource.cs
Normal file
21
SafeExamBrowser.Browser/BrowserIconResource.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
|
||||
namespace SafeExamBrowser.Browser
|
||||
{
|
||||
public class BrowserIconResource : BitmapIconResource
|
||||
{
|
||||
public BrowserIconResource(string uri = null)
|
||||
{
|
||||
Uri = new Uri(uri ?? "pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/SafeExamBrowser.ico");
|
||||
}
|
||||
}
|
||||
}
|
788
SafeExamBrowser.Browser/BrowserWindow.cs
Normal file
788
SafeExamBrowser.Browser/BrowserWindow.cs
Normal file
@@ -0,0 +1,788 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms.Handler;
|
||||
using CefSharp.WinForms.Host;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Browser.Filters;
|
||||
using SafeExamBrowser.Browser.Handlers;
|
||||
using SafeExamBrowser.Browser.Wrapper;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
using Syroot.Windows.IO;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
using DisplayHandler = SafeExamBrowser.Browser.Handlers.DisplayHandler;
|
||||
using Request = SafeExamBrowser.Browser.Contracts.Filters.Request;
|
||||
using ResourceHandler = SafeExamBrowser.Browser.Handlers.ResourceHandler;
|
||||
using TitleChangedEventHandler = SafeExamBrowser.Applications.Contracts.Events.TitleChangedEventHandler;
|
||||
|
||||
namespace SafeExamBrowser.Browser
|
||||
{
|
||||
internal class BrowserWindow : Contracts.IBrowserWindow
|
||||
{
|
||||
private const string CLEAR_FIND_TERM = "thisisahacktoclearthesearchresultsasitappearsthatthereisnosuchfunctionalityincef";
|
||||
private const double ZOOM_FACTOR = 0.2;
|
||||
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly Clipboard clipboard;
|
||||
private readonly IFileSystemDialog fileSystemDialog;
|
||||
private readonly IHashAlgorithm hashAlgorithm;
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly IKeyGenerator keyGenerator;
|
||||
private readonly IModuleLogger logger;
|
||||
private readonly IMessageBox messageBox;
|
||||
private readonly SessionMode sessionMode;
|
||||
private readonly Dictionary<int, BrowserWindow> popups;
|
||||
private readonly BrowserSettings settings;
|
||||
private readonly string startUrl;
|
||||
private readonly IText text;
|
||||
private readonly IUserInterfaceFactory uiFactory;
|
||||
|
||||
private (string term, bool isInitial, bool caseSensitive, bool forward) findParameters;
|
||||
private IBrowserWindow window;
|
||||
private double zoomLevel;
|
||||
|
||||
private WindowSettings WindowSettings
|
||||
{
|
||||
get { return IsMainWindow ? settings.MainWindow : settings.AdditionalWindow; }
|
||||
}
|
||||
|
||||
internal IBrowserControl Control { get; private set; }
|
||||
internal int Id { get; }
|
||||
|
||||
public IntPtr Handle { get; private set; }
|
||||
public IconResource Icon { get; private set; }
|
||||
public bool IsMainWindow { get; private set; }
|
||||
public string Title { get; private set; }
|
||||
public string Url { get; private set; }
|
||||
|
||||
internal event WindowClosedEventHandler Closed;
|
||||
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
internal event LoseFocusRequestedEventHandler LoseFocusRequested;
|
||||
internal event PopupRequestedEventHandler PopupRequested;
|
||||
internal event ResetRequestedEventHandler ResetRequested;
|
||||
internal event TerminationRequestedEventHandler TerminationRequested;
|
||||
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
|
||||
|
||||
public event IconChangedEventHandler IconChanged;
|
||||
public event TitleChangedEventHandler TitleChanged;
|
||||
|
||||
public BrowserWindow(
|
||||
AppConfig appConfig,
|
||||
Clipboard clipboard,
|
||||
IFileSystemDialog fileSystemDialog,
|
||||
IHashAlgorithm hashAlgorithm,
|
||||
int id,
|
||||
bool isMainWindow,
|
||||
IKeyGenerator keyGenerator,
|
||||
IModuleLogger logger,
|
||||
IMessageBox messageBox,
|
||||
SessionMode sessionMode,
|
||||
BrowserSettings settings,
|
||||
string startUrl,
|
||||
IText text,
|
||||
IUserInterfaceFactory uiFactory)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.clipboard = clipboard;
|
||||
this.fileSystemDialog = fileSystemDialog;
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.httpClient = new HttpClient();
|
||||
this.Id = id;
|
||||
this.IsMainWindow = isMainWindow;
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.logger = logger;
|
||||
this.messageBox = messageBox;
|
||||
this.popups = new Dictionary<int, BrowserWindow>();
|
||||
this.sessionMode = sessionMode;
|
||||
this.settings = settings;
|
||||
this.startUrl = startUrl;
|
||||
this.text = text;
|
||||
this.uiFactory = uiFactory;
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
window.BringToForeground();
|
||||
}
|
||||
|
||||
internal void Close()
|
||||
{
|
||||
window.Close();
|
||||
Control.Destroy();
|
||||
}
|
||||
|
||||
internal void Focus(bool forward)
|
||||
{
|
||||
if (forward)
|
||||
{
|
||||
window.FocusToolbar(forward);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.FocusBrowser();
|
||||
Activate();
|
||||
}
|
||||
}
|
||||
|
||||
internal void InitializeControl()
|
||||
{
|
||||
var cefSharpControl = default(ICefSharpControl);
|
||||
var controlLogger = logger.CloneFor($"{nameof(BrowserControl)} #{Id}");
|
||||
var dialogHandler = new DialogHandler();
|
||||
var displayHandler = new DisplayHandler();
|
||||
var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} #{Id}");
|
||||
var downloadHandler = new DownloadHandler(appConfig, downloadLogger, settings, WindowSettings);
|
||||
var keyboardHandler = new KeyboardHandler();
|
||||
var renderHandler = new RenderProcessMessageHandler(appConfig, clipboard, keyGenerator, settings, text);
|
||||
var requestFilter = new RequestFilter();
|
||||
var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} #{Id}");
|
||||
var resourceHandler = new ResourceHandler(appConfig, requestFilter, keyGenerator, logger, sessionMode, settings, WindowSettings, text);
|
||||
var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, resourceHandler, settings, WindowSettings);
|
||||
|
||||
Icon = new BrowserIconResource();
|
||||
|
||||
if (IsMainWindow)
|
||||
{
|
||||
cefSharpControl = new CefSharpBrowserControl(CreateLifeSpanHandlerForMainWindow(), startUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
cefSharpControl = new CefSharpPopupControl();
|
||||
}
|
||||
|
||||
dialogHandler.DialogRequested += DialogHandler_DialogRequested;
|
||||
displayHandler.FaviconChanged += DisplayHandler_FaviconChanged;
|
||||
displayHandler.ProgressChanged += DisplayHandler_ProgressChanged;
|
||||
downloadHandler.ConfigurationDownloadRequested += DownloadHandler_ConfigurationDownloadRequested;
|
||||
downloadHandler.DownloadAborted += DownloadHandler_DownloadAborted;
|
||||
downloadHandler.DownloadUpdated += DownloadHandler_DownloadUpdated;
|
||||
keyboardHandler.FindRequested += KeyboardHandler_FindRequested;
|
||||
keyboardHandler.FocusAddressBarRequested += KeyboardHandler_FocusAddressBarRequested;
|
||||
keyboardHandler.HomeNavigationRequested += HomeNavigationRequested;
|
||||
keyboardHandler.ReloadRequested += ReloadRequested;
|
||||
keyboardHandler.TabPressed += KeyboardHandler_TabPressed;
|
||||
keyboardHandler.ZoomInRequested += ZoomInRequested;
|
||||
keyboardHandler.ZoomOutRequested += ZoomOutRequested;
|
||||
keyboardHandler.ZoomResetRequested += ZoomResetRequested;
|
||||
requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited;
|
||||
requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
|
||||
resourceHandler.UserIdentifierDetected += (id) => UserIdentifierDetected?.Invoke(id);
|
||||
|
||||
InitializeRequestFilter(requestFilter);
|
||||
|
||||
Control = new BrowserControl(clipboard, cefSharpControl, dialogHandler, displayHandler, downloadHandler, keyboardHandler, controlLogger, renderHandler, requestHandler);
|
||||
Control.AddressChanged += Control_AddressChanged;
|
||||
Control.LoadFailed += Control_LoadFailed;
|
||||
Control.LoadingStateChanged += Control_LoadingStateChanged;
|
||||
Control.TitleChanged += Control_TitleChanged;
|
||||
|
||||
Control.Initialize();
|
||||
logger.Debug("Initialized browser control.");
|
||||
}
|
||||
|
||||
internal void InitializeWindow()
|
||||
{
|
||||
window = uiFactory.CreateBrowserWindow(Control, settings, IsMainWindow, this.logger);
|
||||
window.AddressChanged += Window_AddressChanged;
|
||||
window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
|
||||
window.Closed += Window_Closed;
|
||||
window.Closing += Window_Closing;
|
||||
window.DeveloperConsoleRequested += Window_DeveloperConsoleRequested;
|
||||
window.FindRequested += Window_FindRequested;
|
||||
window.ForwardNavigationRequested += Window_ForwardNavigationRequested;
|
||||
window.HomeNavigationRequested += HomeNavigationRequested;
|
||||
window.LoseFocusRequested += Window_LoseFocusRequested;
|
||||
window.ReloadRequested += ReloadRequested;
|
||||
window.ZoomInRequested += ZoomInRequested;
|
||||
window.ZoomOutRequested += ZoomOutRequested;
|
||||
window.ZoomResetRequested += ZoomResetRequested;
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
window.Show();
|
||||
window.BringToForeground();
|
||||
|
||||
Handle = window.Handle;
|
||||
|
||||
logger.Debug("Initialized browser window.");
|
||||
}
|
||||
|
||||
private ILifeSpanHandler CreateLifeSpanHandlerForMainWindow()
|
||||
{
|
||||
return LifeSpanHandler
|
||||
.Create(() => LifeSpanHandler_CreatePopup())
|
||||
.OnBeforePopupCreated((wb, b, f, u, t, d, g, s) => LifeSpanHandler_PopupRequested(u))
|
||||
.OnPopupCreated((c, u) => LifeSpanHandler_PopupCreated(c))
|
||||
.OnPopupDestroyed((c, b) => LifeSpanHandler_PopupDestroyed(c))
|
||||
.Build();
|
||||
}
|
||||
|
||||
private void InitializeRequestFilter(IRequestFilter requestFilter)
|
||||
{
|
||||
if (settings.Filter.ProcessContentRequests || settings.Filter.ProcessMainRequests)
|
||||
{
|
||||
var factory = new RuleFactory();
|
||||
|
||||
foreach (var settings in settings.Filter.Rules)
|
||||
{
|
||||
var rule = factory.CreateRule(settings.Type);
|
||||
|
||||
rule.Initialize(settings);
|
||||
requestFilter.Load(rule);
|
||||
}
|
||||
|
||||
logger.Debug($"Initialized request filter with {settings.Filter.Rules.Count} rule(s).");
|
||||
|
||||
if (requestFilter.Process(new Request { Url = settings.StartUrl }) != FilterResult.Allow)
|
||||
{
|
||||
var rule = factory.CreateRule(FilterRuleType.Simplified);
|
||||
|
||||
rule.Initialize(new FilterRuleSettings { Expression = settings.StartUrl, Result = FilterResult.Allow });
|
||||
requestFilter.Load(rule);
|
||||
|
||||
logger.Debug($"Automatically created filter rule to allow start URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{settings.StartUrl}'" : "")}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Control_AddressChanged(string address)
|
||||
{
|
||||
logger.Info($"Navigated{(WindowSettings.UrlPolicy.CanLog() ? $" to '{address}'" : "")}.");
|
||||
|
||||
Url = address;
|
||||
window.UpdateAddress(address);
|
||||
|
||||
if (WindowSettings.UrlPolicy == UrlPolicy.Always || WindowSettings.UrlPolicy == UrlPolicy.BeforeTitle)
|
||||
{
|
||||
Title = address;
|
||||
window.UpdateTitle(address);
|
||||
TitleChanged?.Invoke(address);
|
||||
}
|
||||
|
||||
AutoFind();
|
||||
}
|
||||
|
||||
private void Control_LoadFailed(int errorCode, string errorText, bool isMainRequest, string url)
|
||||
{
|
||||
switch (errorCode)
|
||||
{
|
||||
case (int) CefErrorCode.Aborted:
|
||||
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was aborted.");
|
||||
break;
|
||||
case (int) CefErrorCode.InternetDisconnected:
|
||||
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} has failed due to loss of internet connection.");
|
||||
break;
|
||||
case (int) CefErrorCode.None:
|
||||
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was successful.");
|
||||
break;
|
||||
case (int) CefErrorCode.UnknownUrlScheme:
|
||||
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} has an unknown URL scheme and will be handled by the OS.");
|
||||
break;
|
||||
default:
|
||||
HandleUnknownLoadFailure(errorCode, errorText, isMainRequest, url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleUnknownLoadFailure(int errorCode, string errorText, bool isMainRequest, string url)
|
||||
{
|
||||
var requestInfo = $"{errorText} ({errorCode}, {(isMainRequest ? "main" : "resource")} request)";
|
||||
|
||||
logger.Warn($"Request{(WindowSettings.UrlPolicy.CanLogError() ? $" for '{url}'" : "")} failed: {requestInfo}.");
|
||||
|
||||
if (isMainRequest)
|
||||
{
|
||||
var title = text.Get(TextKey.Browser_LoadErrorTitle);
|
||||
var message = text.Get(TextKey.Browser_LoadErrorMessage).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "") + $" {requestInfo}";
|
||||
|
||||
Task.Run(() => messageBox.Show(message, title, icon: MessageBoxIcon.Error, parent: window)).ContinueWith(_ => Control.NavigateBackwards());
|
||||
}
|
||||
}
|
||||
|
||||
private void Control_LoadingStateChanged(bool isLoading)
|
||||
{
|
||||
window.CanNavigateBackwards = WindowSettings.AllowBackwardNavigation && Control.CanNavigateBackwards;
|
||||
window.CanNavigateForwards = WindowSettings.AllowForwardNavigation && Control.CanNavigateForwards;
|
||||
window.UpdateLoadingState(isLoading);
|
||||
}
|
||||
|
||||
private void Control_TitleChanged(string title)
|
||||
{
|
||||
if (WindowSettings.UrlPolicy != UrlPolicy.Always)
|
||||
{
|
||||
Title = title;
|
||||
window.UpdateTitle(Title);
|
||||
TitleChanged?.Invoke(Title);
|
||||
}
|
||||
}
|
||||
|
||||
private void DialogHandler_DialogRequested(DialogRequestedEventArgs args)
|
||||
{
|
||||
var isDownload = args.Operation == FileSystemOperation.Save;
|
||||
var isUpload = args.Operation == FileSystemOperation.Open;
|
||||
var isAllowed = (isDownload && settings.AllowDownloads) || (isUpload && settings.AllowUploads);
|
||||
var initialPath = default(string);
|
||||
|
||||
if (isDownload)
|
||||
{
|
||||
initialPath = args.InitialPath;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(settings.DownAndUploadDirectory))
|
||||
{
|
||||
initialPath = KnownFolders.Downloads.ExpandedPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
initialPath = Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory);
|
||||
}
|
||||
|
||||
if (isAllowed)
|
||||
{
|
||||
var result = fileSystemDialog.Show(
|
||||
args.Element,
|
||||
args.Operation,
|
||||
initialPath,
|
||||
title: args.Title,
|
||||
parent: window,
|
||||
restrictNavigation: !settings.AllowCustomDownAndUploadLocation,
|
||||
showElementPath: settings.ShowFileSystemElementPath);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
args.FullPath = result.FullPath;
|
||||
args.Success = result.Success;
|
||||
logger.Debug($"User selected path '{result.FullPath}' when asked to {args.Operation}->{args.Element}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"User aborted file system dialog to {args.Operation}->{args.Element}.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"Blocked file system dialog to {args.Operation}->{args.Element}, as {(isDownload ? "downloading" : "uploading")} is not allowed.");
|
||||
ShowDownUploadNotAllowedMessage(isDownload);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayHandler_FaviconChanged(string uri)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Head, uri);
|
||||
var response = httpClient.SendAsync(request).ContinueWith(task =>
|
||||
{
|
||||
if (task.IsCompleted && task.Result.IsSuccessStatusCode)
|
||||
{
|
||||
Icon = new BrowserIconResource(uri);
|
||||
|
||||
IconChanged?.Invoke(Icon);
|
||||
window.UpdateIcon(Icon);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void DisplayHandler_ProgressChanged(double value)
|
||||
{
|
||||
window.UpdateProgress(value);
|
||||
}
|
||||
|
||||
private void DownloadHandler_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args)
|
||||
{
|
||||
if (settings.AllowConfigurationDownloads)
|
||||
{
|
||||
logger.Debug($"Forwarding download request for configuration file '{fileName}'.");
|
||||
ConfigurationDownloadRequested?.Invoke(fileName, args);
|
||||
|
||||
if (args.AllowDownload)
|
||||
{
|
||||
logger.Debug($"Download request for configuration file '{fileName}' was granted.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Download request for configuration file '{fileName}' was denied.");
|
||||
messageBox.Show(TextKey.MessageBox_ReconfigurationDenied, TextKey.MessageBox_ReconfigurationDeniedTitle, parent: window);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Discarded download request for configuration file '{fileName}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private void DownloadHandler_DownloadAborted()
|
||||
{
|
||||
ShowDownUploadNotAllowedMessage();
|
||||
}
|
||||
|
||||
private void DownloadHandler_DownloadUpdated(DownloadItemState state)
|
||||
{
|
||||
window.UpdateDownloadState(state);
|
||||
}
|
||||
|
||||
private void HomeNavigationRequested()
|
||||
{
|
||||
if (IsMainWindow && (settings.UseStartUrlAsHomeUrl || !string.IsNullOrWhiteSpace(settings.HomeUrl)))
|
||||
{
|
||||
var navigate = false;
|
||||
var url = settings.UseStartUrlAsHomeUrl ? settings.StartUrl : settings.HomeUrl;
|
||||
|
||||
if (settings.HomeNavigationRequiresPassword && !string.IsNullOrWhiteSpace(settings.HomePasswordHash))
|
||||
{
|
||||
var message = text.Get(TextKey.PasswordDialog_BrowserHomePasswordRequired);
|
||||
var title = !string.IsNullOrWhiteSpace(settings.HomeNavigationMessage) ? settings.HomeNavigationMessage : text.Get(TextKey.PasswordDialog_BrowserHomePasswordRequiredTitle);
|
||||
var dialog = uiFactory.CreatePasswordDialog(message, title);
|
||||
var result = dialog.Show(window);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
var passwordHash = hashAlgorithm.GenerateHashFor(result.Password);
|
||||
|
||||
if (settings.HomePasswordHash.Equals(passwordHash, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
navigate = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
messageBox.Show(TextKey.MessageBox_InvalidHomePassword, TextKey.MessageBox_InvalidHomePasswordTitle, icon: MessageBoxIcon.Warning, parent: window);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var message = text.Get(TextKey.MessageBox_BrowserHomeQuestion);
|
||||
var title = !string.IsNullOrWhiteSpace(settings.HomeNavigationMessage) ? settings.HomeNavigationMessage : text.Get(TextKey.MessageBox_BrowserHomeQuestionTitle);
|
||||
var result = messageBox.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
|
||||
|
||||
navigate = result == MessageBoxResult.Yes;
|
||||
}
|
||||
|
||||
if (navigate)
|
||||
{
|
||||
Control.NavigateTo(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyboardHandler_FindRequested()
|
||||
{
|
||||
if (settings.AllowFind)
|
||||
{
|
||||
window.ShowFindbar();
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyboardHandler_FocusAddressBarRequested()
|
||||
{
|
||||
window.FocusAddressBar();
|
||||
}
|
||||
|
||||
private void KeyboardHandler_TabPressed(bool shiftPressed)
|
||||
{
|
||||
Control.ExecuteJavaScript("document.activeElement.tagName", result =>
|
||||
{
|
||||
if (result.Result is string tagName && tagName?.ToUpper() == "BODY")
|
||||
{
|
||||
// This means the user is now at the start of the focus / tabIndex chain in the website.
|
||||
if (shiftPressed)
|
||||
{
|
||||
window.FocusToolbar(!shiftPressed);
|
||||
}
|
||||
else
|
||||
{
|
||||
LoseFocusRequested?.Invoke(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ChromiumHostControl LifeSpanHandler_CreatePopup()
|
||||
{
|
||||
var args = new PopupRequestedEventArgs();
|
||||
|
||||
PopupRequested?.Invoke(args);
|
||||
|
||||
var control = args.Window.Control.EmbeddableControl as ChromiumHostControl;
|
||||
var id = control.GetHashCode();
|
||||
var window = args.Window;
|
||||
|
||||
popups[id] = window;
|
||||
window.Closed += (_) => popups.Remove(id);
|
||||
|
||||
return control;
|
||||
}
|
||||
|
||||
private void LifeSpanHandler_PopupCreated(ChromiumHostControl control)
|
||||
{
|
||||
var id = control.GetHashCode();
|
||||
var window = popups[id];
|
||||
|
||||
window.InitializeWindow();
|
||||
}
|
||||
|
||||
private void LifeSpanHandler_PopupDestroyed(ChromiumHostControl control)
|
||||
{
|
||||
var id = control.GetHashCode();
|
||||
var window = popups[id];
|
||||
|
||||
window.Close();
|
||||
}
|
||||
|
||||
private PopupCreation LifeSpanHandler_PopupRequested(string targetUrl)
|
||||
{
|
||||
var creation = PopupCreation.Cancel;
|
||||
var validCurrentUri = Uri.TryCreate(Control.Address, UriKind.Absolute, out var currentUri);
|
||||
var validNewUri = Uri.TryCreate(targetUrl, UriKind.Absolute, out var newUri);
|
||||
var sameHost = validCurrentUri && validNewUri && string.Equals(currentUri.Host, newUri.Host, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
switch (settings.PopupPolicy)
|
||||
{
|
||||
case PopupPolicy.Allow:
|
||||
case PopupPolicy.AllowSameHost when sameHost:
|
||||
logger.Debug($"Forwarding request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")}...");
|
||||
creation = PopupCreation.Continue;
|
||||
break;
|
||||
case PopupPolicy.AllowSameWindow:
|
||||
case PopupPolicy.AllowSameHostAndWindow when sameHost:
|
||||
logger.Info($"Discarding request to open new window and loading{(WindowSettings.UrlPolicy.CanLog() ? $" '{targetUrl}'" : "")} directly...");
|
||||
Control.NavigateTo(targetUrl);
|
||||
break;
|
||||
case PopupPolicy.AllowSameHost when !sameHost:
|
||||
case PopupPolicy.AllowSameHostAndWindow when !sameHost:
|
||||
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")} as it targets a different host.");
|
||||
break;
|
||||
default:
|
||||
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")}.");
|
||||
break;
|
||||
}
|
||||
|
||||
return creation;
|
||||
}
|
||||
|
||||
private void RequestHandler_QuitUrlVisited(string url)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (settings.ResetOnQuitUrl)
|
||||
{
|
||||
logger.Info("Forwarding request to reset browser...");
|
||||
ResetRequested?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (settings.ConfirmQuitUrl)
|
||||
{
|
||||
var message = text.Get(TextKey.MessageBox_BrowserQuitUrlConfirmation);
|
||||
var title = text.Get(TextKey.MessageBox_BrowserQuitUrlConfirmationTitle);
|
||||
var result = messageBox.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
|
||||
var terminate = result == MessageBoxResult.Yes;
|
||||
|
||||
if (terminate)
|
||||
{
|
||||
logger.Info($"User confirmed termination via quit URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{url}'" : "")}, forwarding request...");
|
||||
TerminationRequested?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"User aborted termination via quit URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{url}'" : "")}.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"Automatically requesting termination due to quit URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{url}'" : "")}...");
|
||||
TerminationRequested?.Invoke();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void RequestHandler_RequestBlocked(string url)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var message = text.Get(TextKey.MessageBox_BrowserNavigationBlocked).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "");
|
||||
var title = text.Get(TextKey.MessageBox_BrowserNavigationBlockedTitle);
|
||||
|
||||
Control.TitleChanged -= Control_TitleChanged;
|
||||
|
||||
if (url.Equals(startUrl, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
window.UpdateTitle($"*** {title} ***");
|
||||
TitleChanged?.Invoke($"*** {title} ***");
|
||||
}
|
||||
|
||||
messageBox.Show(message, title, parent: window);
|
||||
Control.TitleChanged += Control_TitleChanged;
|
||||
});
|
||||
}
|
||||
|
||||
private void ReloadRequested()
|
||||
{
|
||||
if (WindowSettings.AllowReloading && WindowSettings.ShowReloadWarning)
|
||||
{
|
||||
var result = messageBox.Show(TextKey.MessageBox_ReloadConfirmation, TextKey.MessageBox_ReloadConfirmationTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
logger.Debug("The user confirmed reloading the current page...");
|
||||
Control.Reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug("The user aborted reloading the current page.");
|
||||
}
|
||||
}
|
||||
else if (WindowSettings.AllowReloading)
|
||||
{
|
||||
logger.Debug("Reloading current page...");
|
||||
Control.Reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug("Blocked reload attempt, as the user is not allowed to reload web pages.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowDownUploadNotAllowedMessage(bool isDownload = true)
|
||||
{
|
||||
var message = isDownload ? TextKey.MessageBox_DownloadNotAllowed : TextKey.MessageBox_UploadNotAllowed;
|
||||
var title = isDownload ? TextKey.MessageBox_DownloadNotAllowedTitle : TextKey.MessageBox_UploadNotAllowedTitle;
|
||||
|
||||
messageBox.Show(message, title, icon: MessageBoxIcon.Warning, parent: window);
|
||||
}
|
||||
|
||||
private void Window_AddressChanged(string address)
|
||||
{
|
||||
var isValid = Uri.TryCreate(address, UriKind.Absolute, out _) || Uri.TryCreate($"https://{address}", UriKind.Absolute, out _);
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
logger.Debug($"The user requested to navigate to '{address}', the URI is valid.");
|
||||
Control.NavigateTo(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"The user requested to navigate to '{address}', but the URI is not valid.");
|
||||
window.UpdateAddress(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_BackwardNavigationRequested()
|
||||
{
|
||||
logger.Debug("Navigating backwards...");
|
||||
Control.NavigateBackwards();
|
||||
}
|
||||
|
||||
private void Window_Closing()
|
||||
{
|
||||
logger.Debug($"Window is closing...");
|
||||
}
|
||||
|
||||
private void Window_Closed()
|
||||
{
|
||||
logger.Debug($"Window has been closed.");
|
||||
Control.Destroy();
|
||||
Closed?.Invoke(Id);
|
||||
}
|
||||
|
||||
private void Window_DeveloperConsoleRequested()
|
||||
{
|
||||
logger.Debug("Showing developer console...");
|
||||
Control.ShowDeveloperConsole();
|
||||
}
|
||||
|
||||
private void Window_FindRequested(string term, bool isInitial, bool caseSensitive, bool forward = true)
|
||||
{
|
||||
if (settings.AllowFind)
|
||||
{
|
||||
findParameters.caseSensitive = caseSensitive;
|
||||
findParameters.forward = forward;
|
||||
findParameters.isInitial = isInitial;
|
||||
findParameters.term = term;
|
||||
|
||||
Control.Find(term, isInitial, caseSensitive, forward);
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_ForwardNavigationRequested()
|
||||
{
|
||||
logger.Debug("Navigating forwards...");
|
||||
Control.NavigateForwards();
|
||||
}
|
||||
|
||||
private void Window_LoseFocusRequested(bool forward)
|
||||
{
|
||||
LoseFocusRequested?.Invoke(forward);
|
||||
}
|
||||
|
||||
private void ZoomInRequested()
|
||||
{
|
||||
if (settings.AllowPageZoom && CalculateZoomPercentage() < 300)
|
||||
{
|
||||
zoomLevel += ZOOM_FACTOR;
|
||||
Control.Zoom(zoomLevel);
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
logger.Debug($"Increased page zoom to {CalculateZoomPercentage()}%.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ZoomOutRequested()
|
||||
{
|
||||
if (settings.AllowPageZoom && CalculateZoomPercentage() > 25)
|
||||
{
|
||||
zoomLevel -= ZOOM_FACTOR;
|
||||
Control.Zoom(zoomLevel);
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
logger.Debug($"Decreased page zoom to {CalculateZoomPercentage()}%.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ZoomResetRequested()
|
||||
{
|
||||
if (settings.AllowPageZoom)
|
||||
{
|
||||
zoomLevel = 0;
|
||||
Control.Zoom(0);
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
logger.Debug($"Reset page zoom to {CalculateZoomPercentage()}%.");
|
||||
}
|
||||
}
|
||||
|
||||
private void AutoFind()
|
||||
{
|
||||
if (settings.AllowFind && !string.IsNullOrEmpty(findParameters.term) && !CLEAR_FIND_TERM.Equals(findParameters.term, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Control.Find(findParameters.term, findParameters.isInitial, findParameters.caseSensitive, findParameters.forward);
|
||||
}
|
||||
}
|
||||
|
||||
private double CalculateZoomPercentage()
|
||||
{
|
||||
return (zoomLevel * 25.0) + 100.0;
|
||||
}
|
||||
}
|
||||
}
|
57
SafeExamBrowser.Browser/Clipboard.cs
Normal file
57
SafeExamBrowser.Browser/Clipboard.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
|
||||
namespace SafeExamBrowser.Browser
|
||||
{
|
||||
internal class Clipboard
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly BrowserSettings settings;
|
||||
|
||||
internal string Content { get; private set; }
|
||||
|
||||
internal event ClipboardChangedEventHandler Changed;
|
||||
|
||||
internal Clipboard(ILogger logger, BrowserSettings settings)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
internal void Process(JavascriptMessageReceivedEventArgs message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private bool TrySetContent(object value)
|
||||
{
|
||||
var text = value as string;
|
||||
|
||||
if (text != default)
|
||||
{
|
||||
Content = text;
|
||||
}
|
||||
|
||||
return text != default;
|
||||
}
|
||||
|
||||
private class Data
|
||||
{
|
||||
public string Content { get; set; }
|
||||
public long Id { get; set; }
|
||||
public string Type { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
16
SafeExamBrowser.Browser/Content/Api.js
Normal file
16
SafeExamBrowser.Browser/Content/Api.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
SafeExamBrowser = {
|
||||
version: 'SEB_Windows_%%_VERSION_%%',
|
||||
security: {
|
||||
browserExamKey: '%%_BEK_%%',
|
||||
configKey: '%%_CK_%%',
|
||||
updateKeys: (callback) => callback()
|
||||
}
|
||||
}
|
3
SafeExamBrowser.Browser/Content/BlockedContent.html
Normal file
3
SafeExamBrowser.Browser/Content/BlockedContent.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div style="background-color: lightgray; display: table; font-family: 'Segoe UI'; height: 100%; text-align: center; width: 100%">
|
||||
<p style="display: table-cell; font-weight: bold; vertical-align: middle">%%MESSAGE%%</p>
|
||||
</div>
|
13
SafeExamBrowser.Browser/Content/BlockedPage.html
Normal file
13
SafeExamBrowser.Browser/Content/BlockedPage.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="height: 100%; width: 100%">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>%%TITLE%%</title>
|
||||
</head>
|
||||
<body style="background-color: lightgray; display: table; font-family: 'Segoe UI'; height: 98%; text-align: center; width: 99%">
|
||||
<div style="display: table-cell; vertical-align: middle">
|
||||
<p style="font-weight: bold">%%MESSAGE%%</p>
|
||||
<button onclick="window.history.back()" style="cursor: pointer">⭠ %%BACK_BUTTON%%</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
195
SafeExamBrowser.Browser/Content/Clipboard.js
Normal file
195
SafeExamBrowser.Browser/Content/Clipboard.js
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Z<>rich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Original code taken and slightly adapted from https://github.com/eqsoft/seb2/blob/master/browser/app/modules/SebBrowser.jsm#L1215.
|
||||
*/
|
||||
|
||||
SafeExamBrowser.clipboard = {
|
||||
id: Math.round((Date.now() + Math.random()) * 1000),
|
||||
ranges: [],
|
||||
text: "",
|
||||
|
||||
clear: function () {
|
||||
this.ranges = [];
|
||||
this.text = "";
|
||||
},
|
||||
|
||||
getContentEncoded: function () {
|
||||
var bytes = new TextEncoder().encode(this.text);
|
||||
var base64 = btoa(String.fromCodePoint(...bytes));
|
||||
|
||||
return base64;
|
||||
},
|
||||
|
||||
update: function (id, base64) {
|
||||
if (this.id != id) {
|
||||
var bytes = Uint8Array.from(atob(base64), (m) => m.codePointAt(0));
|
||||
var content = new TextDecoder().decode(bytes);
|
||||
|
||||
this.ranges = [];
|
||||
this.text = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copySelectedData(e) {
|
||||
if (e.target.contentEditable && e.target.setRangeText) {
|
||||
SafeExamBrowser.clipboard.text = e.target.value.substring(e.target.selectionStart, e.target.selectionEnd);
|
||||
SafeExamBrowser.clipboard.ranges = [];
|
||||
} else {
|
||||
var selection = e.target.ownerDocument.defaultView.getSelection();
|
||||
var text = "";
|
||||
|
||||
for (var i = 0; i < selection.rangeCount; i++) {
|
||||
SafeExamBrowser.clipboard.ranges[i] = selection.getRangeAt(i).cloneContents();
|
||||
text += SafeExamBrowser.clipboard.ranges[i].textContent;
|
||||
}
|
||||
|
||||
SafeExamBrowser.clipboard.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
function cutSelectedData(e) {
|
||||
if (e.target.contentEditable && e.target.setRangeText) {
|
||||
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
|
||||
} else {
|
||||
var designMode = e.target.ownerDocument.designMode;
|
||||
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
|
||||
var selection = e.target.ownerDocument.defaultView.getSelection();
|
||||
|
||||
for (var i = 0; i < selection.rangeCount; i++) {
|
||||
var range = selection.getRangeAt(i);
|
||||
|
||||
if (designMode === 'on') {
|
||||
range.deleteContents();
|
||||
} else {
|
||||
if (contentEditables.length) {
|
||||
contentEditables.forEach(node => {
|
||||
if (node.contains(range.commonAncestorContainer)) {
|
||||
range.deleteContents();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pasteSelectedData(e) {
|
||||
if (e.target.contentEditable && e.target.setRangeText) {
|
||||
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
|
||||
e.target.setRangeText(SafeExamBrowser.clipboard.text, e.target.selectionStart, e.target.selectionStart + SafeExamBrowser.clipboard.text.length, 'end');
|
||||
} else {
|
||||
var w = e.target.ownerDocument.defaultView;
|
||||
var designMode = e.target.ownerDocument.designMode;
|
||||
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
|
||||
var selection = w.getSelection();
|
||||
|
||||
for (var i = 0; i < selection.rangeCount; i++) {
|
||||
var r = selection.getRangeAt(i);
|
||||
|
||||
if (designMode === 'on') {
|
||||
r.deleteContents();
|
||||
} else {
|
||||
if (contentEditables.length) {
|
||||
contentEditables.forEach(node => {
|
||||
if (node.contains(r.commonAncestorContainer)) {
|
||||
r.deleteContents();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (designMode === 'on') {
|
||||
var range = w.getSelection().getRangeAt(0);
|
||||
|
||||
if (SafeExamBrowser.clipboard.ranges.length > 0) {
|
||||
SafeExamBrowser.clipboard.ranges.map(r => {
|
||||
range = w.getSelection().getRangeAt(0);
|
||||
range.collapse();
|
||||
const newNode = r.cloneNode(true);
|
||||
range.insertNode(newNode);
|
||||
range.collapse();
|
||||
});
|
||||
} else {
|
||||
range.collapse();
|
||||
range.insertNode(w.document.createTextNode(SafeExamBrowser.clipboard.text));
|
||||
range.collapse();
|
||||
}
|
||||
} else {
|
||||
if (contentEditables.length) {
|
||||
contentEditables.forEach(node => {
|
||||
var range = w.getSelection().getRangeAt(0);
|
||||
|
||||
if (node.contains(range.commonAncestorContainer)) {
|
||||
if (SafeExamBrowser.clipboard.ranges.length > 0) {
|
||||
SafeExamBrowser.clipboard.ranges.map(r => {
|
||||
range = w.getSelection().getRangeAt(0);
|
||||
range.collapse();
|
||||
const newNode = r.cloneNode(true);
|
||||
range.insertNode(newNode);
|
||||
range.collapse();
|
||||
});
|
||||
} else {
|
||||
range = w.getSelection().getRangeAt(0);
|
||||
range.collapse();
|
||||
range.insertNode(w.document.createTextNode(SafeExamBrowser.clipboard.text));
|
||||
range.collapse();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onCopy(e) {
|
||||
SafeExamBrowser.clipboard.clear();
|
||||
|
||||
try {
|
||||
copySelectedData(e);
|
||||
|
||||
CefSharp.PostMessage({ Type: "Clipboard", Id: SafeExamBrowser.clipboard.id, Content: SafeExamBrowser.clipboard.getContentEncoded() });
|
||||
} finally {
|
||||
e.preventDefault();
|
||||
e.returnValue = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function onCut(e) {
|
||||
SafeExamBrowser.clipboard.clear();
|
||||
|
||||
try {
|
||||
copySelectedData(e);
|
||||
cutSelectedData(e);
|
||||
|
||||
CefSharp.PostMessage({ Type: "Clipboard", Id: SafeExamBrowser.clipboard.id, Content: SafeExamBrowser.clipboard.getContentEncoded() });
|
||||
} finally {
|
||||
e.preventDefault();
|
||||
e.returnValue = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function onPaste(e) {
|
||||
try {
|
||||
pasteSelectedData(e);
|
||||
} finally {
|
||||
e.preventDefault();
|
||||
e.returnValue = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
window.document.addEventListener("copy", onCopy, true);
|
||||
window.document.addEventListener("cut", onCut, true);
|
||||
window.document.addEventListener("paste", onPaste, true);
|
119
SafeExamBrowser.Browser/Content/ContentLoader.cs
Normal file
119
SafeExamBrowser.Browser/Content/ContentLoader.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Content
|
||||
{
|
||||
internal class ContentLoader
|
||||
{
|
||||
private readonly IText text;
|
||||
|
||||
private string api;
|
||||
private string clipboard;
|
||||
private string pageZoom;
|
||||
|
||||
internal ContentLoader(IText text)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
internal string LoadApi(string browserExamKey, string configurationKey, string version)
|
||||
{
|
||||
if (api == default)
|
||||
{
|
||||
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
|
||||
var path = $"{typeof(ContentLoader).Namespace}.Api.js";
|
||||
|
||||
using (var stream = assembly.GetManifestResourceStream(path))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
api = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
var js = api;
|
||||
|
||||
js = js.Replace("%%_BEK_%%", browserExamKey);
|
||||
js = js.Replace("%%_CK_%%", configurationKey);
|
||||
js = js.Replace("%%_VERSION_%%", version);
|
||||
|
||||
return js;
|
||||
}
|
||||
|
||||
internal string LoadBlockedContent()
|
||||
{
|
||||
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
|
||||
var path = $"{typeof(ContentLoader).Namespace}.BlockedContent.html";
|
||||
|
||||
using (var stream = assembly.GetManifestResourceStream(path))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
var html = reader.ReadToEnd();
|
||||
|
||||
html = html.Replace("%%MESSAGE%%", text.Get(TextKey.Browser_BlockedContentMessage));
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
internal string LoadBlockedPage()
|
||||
{
|
||||
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
|
||||
var path = $"{typeof(ContentLoader).Namespace}.BlockedPage.html";
|
||||
|
||||
using (var stream = assembly.GetManifestResourceStream(path))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
var html = reader.ReadToEnd();
|
||||
|
||||
html = html.Replace("%%BACK_BUTTON%%", text.Get(TextKey.Browser_BlockedPageButton));
|
||||
html = html.Replace("%%MESSAGE%%", text.Get(TextKey.Browser_BlockedPageMessage));
|
||||
html = html.Replace("%%TITLE%%", text.Get(TextKey.Browser_BlockedPageTitle));
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
internal string LoadClipboard()
|
||||
{
|
||||
if (clipboard == default)
|
||||
{
|
||||
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
|
||||
var path = $"{typeof(ContentLoader).Namespace}.Clipboard.js";
|
||||
|
||||
using (var stream = assembly.GetManifestResourceStream(path))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
clipboard = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
return clipboard;
|
||||
}
|
||||
|
||||
internal string LoadPageZoom()
|
||||
{
|
||||
if (pageZoom == default)
|
||||
{
|
||||
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
|
||||
var path = $"{typeof(ContentLoader).Namespace}.PageZoom.js";
|
||||
|
||||
using (var stream = assembly.GetManifestResourceStream(path))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
pageZoom = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
return pageZoom;
|
||||
}
|
||||
}
|
||||
}
|
16
SafeExamBrowser.Browser/Content/PageZoom.js
Normal file
16
SafeExamBrowser.Browser/Content/PageZoom.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Z<>rich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
function disableMouseWheelZoom(e) {
|
||||
if (e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('wheel', disableMouseWheelZoom, { passive: false });
|
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void ClipboardChangedEventHandler(long id);
|
||||
}
|
22
SafeExamBrowser.Browser/Events/DialogRequestedEventArgs.cs
Normal file
22
SafeExamBrowser.Browser/Events/DialogRequestedEventArgs.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal class DialogRequestedEventArgs
|
||||
{
|
||||
internal FileSystemElement Element { get; set; }
|
||||
internal string InitialPath { get; set; }
|
||||
internal FileSystemOperation Operation { get; set; }
|
||||
internal string FullPath { get; set; }
|
||||
internal bool Success { get; set; }
|
||||
internal string Title { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void DialogRequestedEventHandler(DialogRequestedEventArgs args);
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void DownloadAbortedEventHandler();
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void DownloadUpdatedEventHandler(DownloadItemState state);
|
||||
}
|
12
SafeExamBrowser.Browser/Events/FaviconChangedEventHandler.cs
Normal file
12
SafeExamBrowser.Browser/Events/FaviconChangedEventHandler.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void FaviconChangedEventHandler(string uri);
|
||||
}
|
15
SafeExamBrowser.Browser/Events/PopupRequestedEventArgs.cs
Normal file
15
SafeExamBrowser.Browser/Events/PopupRequestedEventArgs.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal class PopupRequestedEventArgs
|
||||
{
|
||||
public BrowserWindow Window { get; set; }
|
||||
}
|
||||
}
|
12
SafeExamBrowser.Browser/Events/PopupRequestedEventHandler.cs
Normal file
12
SafeExamBrowser.Browser/Events/PopupRequestedEventHandler.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void PopupRequestedEventHandler(PopupRequestedEventArgs args);
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void ProgressChangedEventHandler(double value);
|
||||
}
|
12
SafeExamBrowser.Browser/Events/ResetRequestedEventHandler.cs
Normal file
12
SafeExamBrowser.Browser/Events/ResetRequestedEventHandler.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void ResetRequestedEventHandler();
|
||||
}
|
12
SafeExamBrowser.Browser/Events/UrlEventHandler.cs
Normal file
12
SafeExamBrowser.Browser/Events/UrlEventHandler.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void UrlEventHandler(string url);
|
||||
}
|
12
SafeExamBrowser.Browser/Events/WindowClosedEventHandler.cs
Normal file
12
SafeExamBrowser.Browser/Events/WindowClosedEventHandler.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void WindowClosedEventHandler(int id);
|
||||
}
|
66
SafeExamBrowser.Browser/Filters/RequestFilter.cs
Normal file
66
SafeExamBrowser.Browser/Filters/RequestFilter.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Filters
|
||||
{
|
||||
internal class RequestFilter : IRequestFilter
|
||||
{
|
||||
private IList<IRule> allowRules;
|
||||
private IList<IRule> blockRules;
|
||||
|
||||
public FilterResult Default { get; set; }
|
||||
|
||||
internal RequestFilter()
|
||||
{
|
||||
allowRules = new List<IRule>();
|
||||
blockRules = new List<IRule>();
|
||||
Default = FilterResult.Block;
|
||||
}
|
||||
|
||||
public void Load(IRule rule)
|
||||
{
|
||||
switch (rule.Result)
|
||||
{
|
||||
case FilterResult.Allow:
|
||||
allowRules.Add(rule);
|
||||
break;
|
||||
case FilterResult.Block:
|
||||
blockRules.Add(rule);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Filter processing for result '{rule.Result}' is not yet implemented!");
|
||||
}
|
||||
}
|
||||
|
||||
public FilterResult Process(Request request)
|
||||
{
|
||||
foreach (var rule in blockRules)
|
||||
{
|
||||
if (rule.IsMatch(request))
|
||||
{
|
||||
return FilterResult.Block;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var rule in allowRules)
|
||||
{
|
||||
if (rule.IsMatch(request))
|
||||
{
|
||||
return FilterResult.Allow;
|
||||
}
|
||||
}
|
||||
|
||||
return Default;
|
||||
}
|
||||
}
|
||||
}
|
31
SafeExamBrowser.Browser/Filters/RuleFactory.cs
Normal file
31
SafeExamBrowser.Browser/Filters/RuleFactory.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Browser.Filters.Rules;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Filters
|
||||
{
|
||||
internal class RuleFactory : IRuleFactory
|
||||
{
|
||||
public IRule CreateRule(FilterRuleType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case FilterRuleType.Regex:
|
||||
return new RegexRule();
|
||||
case FilterRuleType.Simplified:
|
||||
return new SimplifiedRule();
|
||||
default:
|
||||
throw new NotImplementedException($"Filter rule of type '{type}' is not yet implemented!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
SafeExamBrowser.Browser/Filters/Rules/RegexRule.cs
Normal file
52
SafeExamBrowser.Browser/Filters/Rules/RegexRule.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Filters.Rules
|
||||
{
|
||||
internal class RegexRule : IRule
|
||||
{
|
||||
private string expression;
|
||||
|
||||
public FilterResult Result { get; private set; }
|
||||
|
||||
public void Initialize(FilterRuleSettings settings)
|
||||
{
|
||||
ValidateExpression(settings.Expression);
|
||||
|
||||
expression = settings.Expression;
|
||||
Result = settings.Result;
|
||||
}
|
||||
|
||||
public bool IsMatch(Request request)
|
||||
{
|
||||
return Regex.IsMatch(request.Url, expression, RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
private void ValidateExpression(string expression)
|
||||
{
|
||||
if (expression == default(string))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(expression));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Regex.Match("", expression);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ArgumentException($"Invalid regular expression!", nameof(expression), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
196
SafeExamBrowser.Browser/Filters/Rules/SimplifiedRule.cs
Normal file
196
SafeExamBrowser.Browser/Filters/Rules/SimplifiedRule.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Filters.Rules
|
||||
{
|
||||
internal class SimplifiedRule : IRule
|
||||
{
|
||||
private const string URL_DELIMITER_PATTERN = @"(?:([^\:]*)\://)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^/\:\?#]*))?(?:\:([0-9\*]*))?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
|
||||
|
||||
private Regex fragment;
|
||||
private Regex host;
|
||||
private Regex path;
|
||||
private int? port;
|
||||
private Regex query;
|
||||
private Regex scheme;
|
||||
private Regex userInfo;
|
||||
|
||||
public FilterResult Result { get; private set; }
|
||||
|
||||
public void Initialize(FilterRuleSettings settings)
|
||||
{
|
||||
ValidateExpression(settings.Expression);
|
||||
ParseExpression(settings.Expression);
|
||||
|
||||
Result = settings.Result;
|
||||
}
|
||||
|
||||
public bool IsMatch(Request request)
|
||||
{
|
||||
var url = new Uri(request.Url, UriKind.Absolute);
|
||||
var isMatch = true;
|
||||
|
||||
isMatch &= scheme == default(Regex) || scheme.IsMatch(url.Scheme);
|
||||
isMatch &= userInfo == default(Regex) || userInfo.IsMatch(url.UserInfo);
|
||||
isMatch &= host.IsMatch(url.Host);
|
||||
isMatch &= !port.HasValue || port == url.Port;
|
||||
isMatch &= path == default(Regex) || path.IsMatch(url.AbsolutePath);
|
||||
isMatch &= query == default(Regex) || query.IsMatch(url.Query);
|
||||
isMatch &= fragment == default(Regex) || fragment.IsMatch(url.Fragment);
|
||||
|
||||
return isMatch;
|
||||
}
|
||||
|
||||
private void ParseExpression(string expression)
|
||||
{
|
||||
var match = Regex.Match(expression, URL_DELIMITER_PATTERN);
|
||||
|
||||
ParseScheme(match.Groups[1].Value);
|
||||
ParseUserInfo(match.Groups[2].Value, match.Groups[3].Value);
|
||||
ParseHost(match.Groups[4].Value);
|
||||
ParsePort(match.Groups[5].Value);
|
||||
ParsePath(match.Groups[6].Value);
|
||||
ParseQuery(match.Groups[7].Value);
|
||||
ParseFragment(match.Groups[8].Value);
|
||||
}
|
||||
|
||||
private void ParseScheme(string expression)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(expression))
|
||||
{
|
||||
expression = Regex.Escape(expression);
|
||||
expression = ReplaceWildcard(expression);
|
||||
|
||||
scheme = Build(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseUserInfo(string username, string password)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(username))
|
||||
{
|
||||
var expression = default(string);
|
||||
|
||||
username = Regex.Escape(username);
|
||||
password = Regex.Escape(password);
|
||||
|
||||
expression = string.IsNullOrEmpty(password) ? $@"{username}(:.*)?" : $@"{username}:{password}";
|
||||
expression = ReplaceWildcard(expression);
|
||||
|
||||
userInfo = Build(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseHost(string expression)
|
||||
{
|
||||
var isAlphanumeric = Regex.IsMatch(expression, @"^[a-zA-Z0-9]+$");
|
||||
var matchExactSubdomain = expression.StartsWith(".");
|
||||
|
||||
expression = matchExactSubdomain ? expression.Substring(1) : expression;
|
||||
expression = Regex.Escape(expression);
|
||||
expression = ReplaceWildcard(expression);
|
||||
|
||||
if (!isAlphanumeric && !matchExactSubdomain)
|
||||
{
|
||||
expression = $@"(.+?\.)*{expression}";
|
||||
}
|
||||
|
||||
host = Build(expression);
|
||||
}
|
||||
|
||||
private void ParsePort(string expression)
|
||||
{
|
||||
if (int.TryParse(expression, out var port))
|
||||
{
|
||||
this.port = port;
|
||||
}
|
||||
}
|
||||
|
||||
private void ParsePath(string expression)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(expression) && !expression.Equals("/"))
|
||||
{
|
||||
expression = Regex.Escape(expression);
|
||||
expression = ReplaceWildcard(expression);
|
||||
expression = expression.EndsWith("/") ? $@"{expression}?" : $@"{expression}/?";
|
||||
|
||||
path = Build(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseQuery(string expression)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(expression))
|
||||
{
|
||||
var noQueryAllowed = expression == ".";
|
||||
|
||||
if (noQueryAllowed)
|
||||
{
|
||||
expression = @"\??";
|
||||
}
|
||||
else
|
||||
{
|
||||
expression = Regex.Escape(expression);
|
||||
expression = ReplaceWildcard(expression);
|
||||
expression = $@"\??{expression}";
|
||||
}
|
||||
|
||||
query = Build(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseFragment(string expression)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(expression))
|
||||
{
|
||||
expression = Regex.Escape(expression);
|
||||
expression = ReplaceWildcard(expression);
|
||||
expression = $"#?{expression}";
|
||||
|
||||
fragment = Build(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private Regex Build(string expression)
|
||||
{
|
||||
return new Regex($"^{expression}$", RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
private string ReplaceWildcard(string expression)
|
||||
{
|
||||
return expression.Replace(@"\*", ".*");
|
||||
}
|
||||
|
||||
private void ValidateExpression(string expression)
|
||||
{
|
||||
if (expression == default(string))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(expression));
|
||||
}
|
||||
|
||||
if (!Regex.IsMatch(expression, @"[a-zA-Z0-9\*]+"))
|
||||
{
|
||||
throw new ArgumentException("Expression must consist of at least one alphanumeric character or asterisk!", nameof(expression));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Regex.Match(expression, URL_DELIMITER_PATTERN);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ArgumentException("Expression is not a valid simplified filter expression!", nameof(expression), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
SafeExamBrowser.Browser/Handlers/ContextMenuHandler.cs
Normal file
34
SafeExamBrowser.Browser/Handlers/ContextMenuHandler.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class ContextMenuHandler : IContextMenuHandler
|
||||
{
|
||||
public void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model)
|
||||
{
|
||||
model.Clear();
|
||||
}
|
||||
|
||||
public bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame)
|
||||
{
|
||||
}
|
||||
|
||||
public bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
51
SafeExamBrowser.Browser/Handlers/DialogHandler.cs
Normal file
51
SafeExamBrowser.Browser/Handlers/DialogHandler.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Browser.Wrapper;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class DialogHandler : IDialogHandler
|
||||
{
|
||||
internal event DialogRequestedEventHandler DialogRequested;
|
||||
|
||||
public bool OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, IFileDialogCallback callback)
|
||||
{
|
||||
var args = new DialogRequestedEventArgs
|
||||
{
|
||||
Element = mode.ToElement(),
|
||||
InitialPath = defaultFilePath,
|
||||
Operation = mode.ToOperation(),
|
||||
Title = title
|
||||
};
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
DialogRequested?.Invoke(args);
|
||||
|
||||
using (callback)
|
||||
{
|
||||
if (args.Success)
|
||||
{
|
||||
callback.Continue(new List<string> { args.FullPath });
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.Cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
73
SafeExamBrowser.Browser/Handlers/DisplayHandler.cs
Normal file
73
SafeExamBrowser.Browser/Handlers/DisplayHandler.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CefSharp;
|
||||
using CefSharp.Enums;
|
||||
using CefSharp.Structs;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class DisplayHandler : IDisplayHandler
|
||||
{
|
||||
public event FaviconChangedEventHandler FaviconChanged;
|
||||
public event ProgressChangedEventHandler ProgressChanged;
|
||||
|
||||
public void OnAddressChanged(IWebBrowser chromiumWebBrowser, AddressChangedEventArgs addressChangedArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public bool OnAutoResize(IWebBrowser chromiumWebBrowser, IBrowser browser, Size newSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool OnConsoleMessage(IWebBrowser chromiumWebBrowser, ConsoleMessageEventArgs consoleMessageArgs)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool OnCursorChange(IWebBrowser chromiumWebBrowser, IBrowser browser, IntPtr cursor, CursorType type, CursorInfo customCursorInfo)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnFaviconUrlChange(IWebBrowser chromiumWebBrowser, IBrowser browser, IList<string> urls)
|
||||
{
|
||||
if (urls.Any())
|
||||
{
|
||||
FaviconChanged?.Invoke(urls.First());
|
||||
}
|
||||
}
|
||||
|
||||
public void OnFullscreenModeChange(IWebBrowser chromiumWebBrowser, IBrowser browser, bool fullscreen)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnLoadingProgressChange(IWebBrowser chromiumWebBrowser, IBrowser browser, double progress)
|
||||
{
|
||||
ProgressChanged?.Invoke(progress);
|
||||
}
|
||||
|
||||
public void OnStatusMessage(IWebBrowser chromiumWebBrowser, StatusMessageEventArgs statusMessageArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnTitleChanged(IWebBrowser chromiumWebBrowser, TitleChangedEventArgs titleChangedArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public bool OnTooltipChanged(IWebBrowser chromiumWebBrowser, ref string text)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
210
SafeExamBrowser.Browser/Handlers/DownloadHandler.cs
Normal file
210
SafeExamBrowser.Browser/Handlers/DownloadHandler.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
|
||||
using Syroot.Windows.IO;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class DownloadHandler : IDownloadHandler
|
||||
{
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly ConcurrentDictionary<int, DownloadFinishedCallback> callbacks;
|
||||
private readonly ConcurrentDictionary<int, Guid> downloads;
|
||||
private readonly ILogger logger;
|
||||
private readonly BrowserSettings settings;
|
||||
private readonly WindowSettings windowSettings;
|
||||
|
||||
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
internal event DownloadAbortedEventHandler DownloadAborted;
|
||||
internal event DownloadUpdatedEventHandler DownloadUpdated;
|
||||
|
||||
internal DownloadHandler(AppConfig appConfig, ILogger logger, BrowserSettings settings, WindowSettings windowSettings)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.callbacks = new ConcurrentDictionary<int, DownloadFinishedCallback>();
|
||||
this.downloads = new ConcurrentDictionary<int, Guid>();
|
||||
this.logger = logger;
|
||||
this.settings = settings;
|
||||
this.windowSettings = windowSettings;
|
||||
}
|
||||
|
||||
public bool CanDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, string url, string requestMethod)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnBeforeDownload(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback)
|
||||
{
|
||||
var fileExtension = Path.GetExtension(downloadItem.SuggestedFileName);
|
||||
var isConfigurationFile = false;
|
||||
var url = downloadItem.Url;
|
||||
var urlExtension = default(string);
|
||||
|
||||
if (downloadItem.Url.StartsWith("data:"))
|
||||
{
|
||||
url = downloadItem.Url.Length <= 100 ? downloadItem.Url : downloadItem.Url.Substring(0, 100) + "...";
|
||||
}
|
||||
|
||||
if (Uri.TryCreate(downloadItem.Url, UriKind.RelativeOrAbsolute, out var uri))
|
||||
{
|
||||
urlExtension = Path.GetExtension(uri.AbsolutePath);
|
||||
}
|
||||
|
||||
isConfigurationFile |= string.Equals(appConfig.ConfigurationFileExtension, fileExtension, StringComparison.OrdinalIgnoreCase);
|
||||
isConfigurationFile |= string.Equals(appConfig.ConfigurationFileExtension, urlExtension, StringComparison.OrdinalIgnoreCase);
|
||||
isConfigurationFile |= string.Equals(appConfig.ConfigurationFileMimeType, downloadItem.MimeType, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
logger.Debug($"Detected download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")}.");
|
||||
|
||||
if (isConfigurationFile)
|
||||
{
|
||||
Task.Run(() => RequestConfigurationFileDownload(downloadItem, callback));
|
||||
}
|
||||
else if (settings.AllowDownloads)
|
||||
{
|
||||
Task.Run(() => HandleFileDownload(downloadItem, callback));
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"Aborted download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")}, as downloading is not allowed.");
|
||||
Task.Run(() => DownloadAborted?.Invoke());
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDownloadUpdated(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback)
|
||||
{
|
||||
var hasId = downloads.TryGetValue(downloadItem.Id, out var id);
|
||||
|
||||
if (hasId)
|
||||
{
|
||||
var state = new DownloadItemState(id)
|
||||
{
|
||||
Completion = downloadItem.PercentComplete / 100.0,
|
||||
FullPath = downloadItem.FullPath,
|
||||
IsCancelled = downloadItem.IsCancelled,
|
||||
IsComplete = downloadItem.IsComplete,
|
||||
Url = downloadItem.Url
|
||||
};
|
||||
|
||||
Task.Run(() => DownloadUpdated?.Invoke(state));
|
||||
}
|
||||
|
||||
if (downloadItem.IsComplete || downloadItem.IsCancelled)
|
||||
{
|
||||
logger.Debug($"Download of '{downloadItem.FullPath}' {(downloadItem.IsComplete ? "is complete" : "was cancelled")}.");
|
||||
|
||||
if (callbacks.TryRemove(downloadItem.Id, out var finished) && finished != null)
|
||||
{
|
||||
Task.Run(() => finished.Invoke(downloadItem.IsComplete, downloadItem.Url, downloadItem.FullPath));
|
||||
}
|
||||
|
||||
if (hasId)
|
||||
{
|
||||
downloads.TryRemove(downloadItem.Id, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleFileDownload(DownloadItem downloadItem, IBeforeDownloadCallback callback)
|
||||
{
|
||||
var filePath = default(string);
|
||||
var showDialog = settings.AllowCustomDownAndUploadLocation;
|
||||
|
||||
logger.Debug($"Handling download of file '{downloadItem.SuggestedFileName}'.");
|
||||
|
||||
if (!string.IsNullOrEmpty(settings.DownAndUploadDirectory))
|
||||
{
|
||||
filePath = Path.Combine(Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory), downloadItem.SuggestedFileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
filePath = Path.Combine(KnownFolders.Downloads.ExpandedPath, downloadItem.SuggestedFileName);
|
||||
}
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
filePath = AppendIndexSuffixTo(filePath);
|
||||
}
|
||||
|
||||
if (showDialog)
|
||||
{
|
||||
logger.Debug($"Allowing user to select custom download location, with '{filePath}' as suggestion.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Automatically downloading file as '{filePath}'.");
|
||||
}
|
||||
|
||||
downloads[downloadItem.Id] = Guid.NewGuid();
|
||||
|
||||
using (callback)
|
||||
{
|
||||
callback.Continue(filePath, showDialog);
|
||||
}
|
||||
}
|
||||
|
||||
private string AppendIndexSuffixTo(string filePath)
|
||||
{
|
||||
var directory = Path.GetDirectoryName(filePath);
|
||||
var extension = Path.GetExtension(filePath);
|
||||
var name = Path.GetFileNameWithoutExtension(filePath);
|
||||
var path = default(string);
|
||||
|
||||
for (var suffix = 1; suffix < int.MaxValue; suffix++)
|
||||
{
|
||||
path = Path.Combine(directory, $"{name}({suffix}){extension}");
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private void RequestConfigurationFileDownload(DownloadItem downloadItem, IBeforeDownloadCallback callback)
|
||||
{
|
||||
var args = new DownloadEventArgs { Url = downloadItem.Url };
|
||||
|
||||
logger.Debug($"Handling download of configuration file '{downloadItem.SuggestedFileName}'.");
|
||||
ConfigurationDownloadRequested?.Invoke(downloadItem.SuggestedFileName, args);
|
||||
|
||||
if (args.AllowDownload)
|
||||
{
|
||||
if (args.Callback != null)
|
||||
{
|
||||
callbacks[downloadItem.Id] = args.Callback;
|
||||
}
|
||||
|
||||
logger.Debug($"Starting download of configuration file '{downloadItem.SuggestedFileName}'...");
|
||||
|
||||
using (callback)
|
||||
{
|
||||
callback.Continue(args.DownloadPath, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Download of configuration file '{downloadItem.SuggestedFileName}' was cancelled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
93
SafeExamBrowser.Browser/Handlers/KeyboardHandler.cs
Normal file
93
SafeExamBrowser.Browser/Handlers/KeyboardHandler.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class KeyboardHandler : IKeyboardHandler
|
||||
{
|
||||
internal event ActionRequestedEventHandler FindRequested;
|
||||
internal event ActionRequestedEventHandler HomeNavigationRequested;
|
||||
internal event ActionRequestedEventHandler ReloadRequested;
|
||||
internal event ActionRequestedEventHandler ZoomInRequested;
|
||||
internal event ActionRequestedEventHandler ZoomOutRequested;
|
||||
internal event ActionRequestedEventHandler ZoomResetRequested;
|
||||
internal event ActionRequestedEventHandler FocusAddressBarRequested;
|
||||
internal event TabPressedEventHandler TabPressed;
|
||||
|
||||
private int? currentKeyDown = null;
|
||||
|
||||
public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int keyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
|
||||
{
|
||||
var ctrl = modifiers.HasFlag(CefEventFlags.ControlDown);
|
||||
var shift = modifiers.HasFlag(CefEventFlags.ShiftDown);
|
||||
|
||||
if (type == KeyType.KeyUp)
|
||||
{
|
||||
if (ctrl && keyCode == (int) Keys.F)
|
||||
{
|
||||
FindRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (keyCode == (int) Keys.Home)
|
||||
{
|
||||
HomeNavigationRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (ctrl && keyCode == (int) Keys.L)
|
||||
{
|
||||
FocusAddressBarRequested?.Invoke();
|
||||
}
|
||||
|
||||
if ((ctrl && keyCode == (int) Keys.Add) || (ctrl && keyCode == (int) Keys.Oemplus) || (ctrl && shift && keyCode == (int) Keys.D1))
|
||||
{
|
||||
ZoomInRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (ctrl && (keyCode == (int) Keys.Subtract || keyCode == (int) Keys.OemMinus))
|
||||
{
|
||||
ZoomOutRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (ctrl && (keyCode == (int) Keys.D0 || keyCode == (int) Keys.NumPad0))
|
||||
{
|
||||
ZoomResetRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (keyCode == (int) Keys.Tab && keyCode == currentKeyDown)
|
||||
{
|
||||
TabPressed?.Invoke(shift);
|
||||
}
|
||||
}
|
||||
|
||||
currentKeyDown = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int keyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut)
|
||||
{
|
||||
if (type == KeyType.KeyUp && keyCode == (int) Keys.F5)
|
||||
{
|
||||
ReloadRequested?.Invoke();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type == KeyType.RawKeyDown || type == KeyType.KeyDown)
|
||||
{
|
||||
currentKeyDown = keyCode;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Content;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class RenderProcessMessageHandler : IRenderProcessMessageHandler
|
||||
{
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly Clipboard clipboard;
|
||||
private readonly ContentLoader contentLoader;
|
||||
private readonly IKeyGenerator keyGenerator;
|
||||
private readonly BrowserSettings settings;
|
||||
private readonly IText text;
|
||||
|
||||
internal RenderProcessMessageHandler(AppConfig appConfig, Clipboard clipboard, IKeyGenerator keyGenerator, BrowserSettings settings, IText text)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.clipboard = clipboard;
|
||||
this.contentLoader = new ContentLoader(text);
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public void OnContextCreated(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
var browserExamKey = keyGenerator.CalculateBrowserExamKeyHash(settings.ConfigurationKey, settings.BrowserExamKeySalt, frame.Url);
|
||||
var configurationKey = keyGenerator.CalculateConfigurationKeyHash(settings.ConfigurationKey, frame.Url);
|
||||
var api = contentLoader.LoadApi(browserExamKey, configurationKey, appConfig.ProgramBuildVersion);
|
||||
var clipboardScript = contentLoader.LoadClipboard();
|
||||
var pageZoomScript = contentLoader.LoadPageZoom();
|
||||
|
||||
frame.ExecuteJavaScriptAsync(api);
|
||||
|
||||
if (!settings.AllowPageZoom)
|
||||
{
|
||||
frame.ExecuteJavaScriptAsync(pageZoomScript);
|
||||
}
|
||||
|
||||
if (!settings.AllowPrint)
|
||||
{
|
||||
frame.ExecuteJavaScriptAsync($"window.print = function() {{ alert('{text.Get(TextKey.Browser_PrintNotAllowed)}') }}");
|
||||
}
|
||||
|
||||
if (settings.UseIsolatedClipboard)
|
||||
{
|
||||
frame.ExecuteJavaScriptAsync(clipboardScript);
|
||||
|
||||
if (clipboard.Content != default)
|
||||
{
|
||||
frame.ExecuteJavaScriptAsync($"SafeExamBrowser.clipboard.update('', '{clipboard.Content}');");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnContextReleased(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnFocusedNodeChanged(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IDomNode node)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnUncaughtException(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, JavascriptException exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
220
SafeExamBrowser.Browser/Handlers/RequestHandler.cs
Normal file
220
SafeExamBrowser.Browser/Handlers/RequestHandler.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
using Request = SafeExamBrowser.Browser.Contracts.Filters.Request;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class RequestHandler : CefSharp.Handler.RequestHandler
|
||||
{
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly IRequestFilter filter;
|
||||
private readonly ILogger logger;
|
||||
private readonly ResourceHandler resourceHandler;
|
||||
private readonly WindowSettings windowSettings;
|
||||
private readonly BrowserSettings settings;
|
||||
|
||||
private string quitUrlPattern;
|
||||
|
||||
internal event UrlEventHandler QuitUrlVisited;
|
||||
internal event UrlEventHandler RequestBlocked;
|
||||
|
||||
internal RequestHandler(
|
||||
AppConfig appConfig,
|
||||
IRequestFilter filter,
|
||||
ILogger logger,
|
||||
ResourceHandler resourceHandler,
|
||||
BrowserSettings settings,
|
||||
WindowSettings windowSettings)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.filter = filter;
|
||||
this.logger = logger;
|
||||
this.resourceHandler = resourceHandler;
|
||||
this.settings = settings;
|
||||
this.windowSettings = windowSettings;
|
||||
}
|
||||
|
||||
protected override bool GetAuthCredentials(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
|
||||
{
|
||||
if (isProxy)
|
||||
{
|
||||
foreach (var proxy in settings.Proxy.Proxies)
|
||||
{
|
||||
if (proxy.RequiresAuthentication && host?.Equals(proxy.Host, StringComparison.OrdinalIgnoreCase) == true && port == proxy.Port)
|
||||
{
|
||||
callback.Continue(proxy.Username, proxy.Password);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.GetAuthCredentials(webBrowser, browser, originUrl, isProxy, host, port, realm, scheme, callback);
|
||||
}
|
||||
|
||||
protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
|
||||
{
|
||||
return resourceHandler;
|
||||
}
|
||||
|
||||
protected override bool OnBeforeBrowse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
|
||||
{
|
||||
if (IsQuitUrl(request))
|
||||
{
|
||||
QuitUrlVisited?.Invoke(request.Url);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Block(request))
|
||||
{
|
||||
if (request.ResourceType == ResourceType.MainFrame)
|
||||
{
|
||||
RequestBlocked?.Invoke(request.Url);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsConfigurationFile(request, out var downloadUrl))
|
||||
{
|
||||
browser.GetHost().StartDownload(downloadUrl);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnBeforeBrowse(webBrowser, browser, frame, request, userGesture, isRedirect);
|
||||
}
|
||||
|
||||
protected override bool OnOpenUrlFromTab(IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
|
||||
{
|
||||
switch (targetDisposition)
|
||||
{
|
||||
case WindowOpenDisposition.NewBackgroundTab:
|
||||
case WindowOpenDisposition.NewPopup:
|
||||
case WindowOpenDisposition.NewWindow:
|
||||
case WindowOpenDisposition.SaveToDisk:
|
||||
return true;
|
||||
default:
|
||||
return base.OnOpenUrlFromTab(webBrowser, browser, frame, targetUrl, targetDisposition, userGesture);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsConfigurationFile(IRequest request, out string downloadUrl)
|
||||
{
|
||||
var isValidUri = Uri.TryCreate(request.Url, UriKind.RelativeOrAbsolute, out var uri);
|
||||
var hasFileExtension = string.Equals(appConfig.ConfigurationFileExtension, Path.GetExtension(uri.AbsolutePath), StringComparison.OrdinalIgnoreCase);
|
||||
var isDataUri = request.Url.Contains(appConfig.ConfigurationFileMimeType);
|
||||
var isConfigurationFile = isValidUri && (hasFileExtension || isDataUri);
|
||||
|
||||
downloadUrl = request.Url;
|
||||
|
||||
if (isConfigurationFile)
|
||||
{
|
||||
if (isDataUri)
|
||||
{
|
||||
if (uri.Scheme == appConfig.SebUriScheme)
|
||||
{
|
||||
downloadUrl = request.Url.Replace($"{appConfig.SebUriScheme}{Uri.SchemeDelimiter}", "data:");
|
||||
}
|
||||
else if (uri.Scheme == appConfig.SebUriSchemeSecure)
|
||||
{
|
||||
downloadUrl = request.Url.Replace($"{appConfig.SebUriSchemeSecure}{Uri.SchemeDelimiter}", "data:");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (uri.Scheme == appConfig.SebUriScheme)
|
||||
{
|
||||
downloadUrl = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri.AbsoluteUri;
|
||||
}
|
||||
else if (uri.Scheme == appConfig.SebUriSchemeSecure)
|
||||
{
|
||||
downloadUrl = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttps }.Uri.AbsoluteUri;
|
||||
}
|
||||
}
|
||||
|
||||
logger.Debug($"Detected configuration file {(windowSettings.UrlPolicy.CanLog() ? $"'{uri}'" : "")}.");
|
||||
}
|
||||
|
||||
return isConfigurationFile;
|
||||
}
|
||||
|
||||
private bool IsQuitUrl(IRequest request)
|
||||
{
|
||||
var isQuitUrl = false;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(settings.QuitUrl))
|
||||
{
|
||||
if (quitUrlPattern == default)
|
||||
{
|
||||
quitUrlPattern = $"^{Regex.Escape(settings.QuitUrl.TrimEnd('/'))}/?$";
|
||||
}
|
||||
|
||||
isQuitUrl = Regex.IsMatch(request.Url, quitUrlPattern, RegexOptions.IgnoreCase);
|
||||
|
||||
if (isQuitUrl)
|
||||
{
|
||||
logger.Debug($"Detected quit URL{(windowSettings.UrlPolicy.CanLog() ? $"'{request.Url}'" : "")}.");
|
||||
}
|
||||
}
|
||||
|
||||
return isQuitUrl;
|
||||
}
|
||||
|
||||
private bool Block(IRequest request)
|
||||
{
|
||||
var block = false;
|
||||
var url = WebUtility.UrlDecode(request.Url);
|
||||
var isValidUrl = Uri.TryCreate(url, UriKind.Absolute, out _);
|
||||
|
||||
if (settings.Filter.ProcessMainRequests && request.ResourceType == ResourceType.MainFrame && isValidUrl)
|
||||
{
|
||||
var result = filter.Process(new Request { Url = url });
|
||||
|
||||
// We apparently can't filter chrome extension requests, as this prevents the rendering of PDFs.
|
||||
if (result == FilterResult.Block && !url.StartsWith("chrome-extension://"))
|
||||
{
|
||||
block = true;
|
||||
logger.Info($"Blocked main request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.Filter.ProcessContentRequests && request.ResourceType != ResourceType.MainFrame && isValidUrl)
|
||||
{
|
||||
var result = filter.Process(new Request { Url = url });
|
||||
|
||||
if (result == FilterResult.Block)
|
||||
{
|
||||
block = true;
|
||||
logger.Info($"Blocked content request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValidUrl)
|
||||
{
|
||||
logger.Warn($"Filter could not process request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType})!");
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
}
|
||||
}
|
451
SafeExamBrowser.Browser/Handlers/ResourceHandler.cs
Normal file
451
SafeExamBrowser.Browser/Handlers/ResourceHandler.cs
Normal file
@@ -0,0 +1,451 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SafeExamBrowser.Browser.Content;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
using Request = SafeExamBrowser.Browser.Contracts.Filters.Request;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class ResourceHandler : CefSharp.Handler.ResourceRequestHandler
|
||||
{
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly ContentLoader contentLoader;
|
||||
private readonly IRequestFilter filter;
|
||||
private readonly IKeyGenerator keyGenerator;
|
||||
private readonly ILogger logger;
|
||||
private readonly SessionMode sessionMode;
|
||||
private readonly BrowserSettings settings;
|
||||
private readonly WindowSettings windowSettings;
|
||||
|
||||
private IResourceHandler contentHandler;
|
||||
private IResourceHandler pageHandler;
|
||||
private string userIdentifier;
|
||||
|
||||
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
|
||||
|
||||
internal ResourceHandler(
|
||||
AppConfig appConfig,
|
||||
IRequestFilter filter,
|
||||
IKeyGenerator keyGenerator,
|
||||
ILogger logger,
|
||||
SessionMode sessionMode,
|
||||
BrowserSettings settings,
|
||||
WindowSettings windowSettings,
|
||||
IText text)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.filter = filter;
|
||||
this.contentLoader = new ContentLoader(text);
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.logger = logger;
|
||||
this.sessionMode = sessionMode;
|
||||
this.settings = settings;
|
||||
this.windowSettings = windowSettings;
|
||||
}
|
||||
|
||||
protected override IResourceHandler GetResourceHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request)
|
||||
{
|
||||
if (Block(request))
|
||||
{
|
||||
return ResourceHandlerFor(request.ResourceType);
|
||||
}
|
||||
|
||||
return base.GetResourceHandler(webBrowser, browser, frame, request);
|
||||
}
|
||||
|
||||
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
|
||||
{
|
||||
if (IsMailtoUrl(request.Url))
|
||||
{
|
||||
return CefReturnValue.Cancel;
|
||||
}
|
||||
|
||||
AppendCustomHeaders(webBrowser, request);
|
||||
ReplaceSebScheme(request);
|
||||
|
||||
return base.OnBeforeResourceLoad(webBrowser, browser, frame, request, callback);
|
||||
}
|
||||
|
||||
protected override bool OnProtocolExecution(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnResourceRedirect(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl)
|
||||
{
|
||||
if (sessionMode == SessionMode.Server)
|
||||
{
|
||||
SearchUserIdentifier(request, response);
|
||||
}
|
||||
|
||||
base.OnResourceRedirect(chromiumWebBrowser, browser, frame, request, response, ref newUrl);
|
||||
}
|
||||
|
||||
protected override bool OnResourceResponse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response)
|
||||
{
|
||||
if (RedirectToDisablePdfReaderToolbar(request, response, out var url))
|
||||
{
|
||||
frame?.LoadUrl(url);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sessionMode == SessionMode.Server)
|
||||
{
|
||||
SearchUserIdentifier(request, response);
|
||||
}
|
||||
|
||||
return base.OnResourceResponse(webBrowser, browser, frame, request, response);
|
||||
}
|
||||
|
||||
private void AppendCustomHeaders(IWebBrowser webBrowser, IRequest request)
|
||||
{
|
||||
Uri.TryCreate(webBrowser.Address, UriKind.Absolute, out var pageUrl);
|
||||
Uri.TryCreate(request.Url, UriKind.Absolute, out var requestUrl);
|
||||
|
||||
if (request.ResourceType == ResourceType.MainFrame || pageUrl?.Host?.Equals(requestUrl?.Host) == true)
|
||||
{
|
||||
var headers = new NameValueCollection(request.Headers);
|
||||
|
||||
if (settings.SendConfigurationKey)
|
||||
{
|
||||
headers["X-SafeExamBrowser-ConfigKeyHash"] = keyGenerator.CalculateConfigurationKeyHash(settings.ConfigurationKey, request.Url);
|
||||
}
|
||||
|
||||
if (settings.SendBrowserExamKey)
|
||||
{
|
||||
headers["X-SafeExamBrowser-RequestHash"] = keyGenerator.CalculateBrowserExamKeyHash(settings.ConfigurationKey, settings.BrowserExamKeySalt, request.Url);
|
||||
}
|
||||
|
||||
request.Headers = headers;
|
||||
}
|
||||
}
|
||||
|
||||
private bool Block(IRequest request)
|
||||
{
|
||||
var block = false;
|
||||
var url = WebUtility.UrlDecode(request.Url);
|
||||
var isValidUri = Uri.TryCreate(url, UriKind.Absolute, out _);
|
||||
|
||||
if (settings.Filter.ProcessContentRequests && isValidUri)
|
||||
{
|
||||
var result = filter.Process(new Request { Url = url });
|
||||
|
||||
if (result == FilterResult.Block)
|
||||
{
|
||||
block = true;
|
||||
logger.Info($"Blocked content request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
|
||||
}
|
||||
}
|
||||
else if (!isValidUri)
|
||||
{
|
||||
logger.Warn($"Filter could not process request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType})!");
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
private bool IsMailtoUrl(string url)
|
||||
{
|
||||
return url.StartsWith(Uri.UriSchemeMailto);
|
||||
}
|
||||
|
||||
private bool RedirectToDisablePdfReaderToolbar(IRequest request, IResponse response, out string url)
|
||||
{
|
||||
const string DISABLE_PDF_READER_TOOLBAR = "#toolbar=0";
|
||||
|
||||
var isPdf = response.Headers["Content-Type"] == MediaTypeNames.Application.Pdf;
|
||||
var isMainFrame = request.ResourceType == ResourceType.MainFrame;
|
||||
var hasFragment = request.Url.Contains(DISABLE_PDF_READER_TOOLBAR);
|
||||
var redirect = settings.AllowPdfReader && !settings.AllowPdfReaderToolbar && isPdf && isMainFrame && !hasFragment;
|
||||
|
||||
url = request.Url + DISABLE_PDF_READER_TOOLBAR;
|
||||
|
||||
if (redirect)
|
||||
{
|
||||
logger.Info($"Redirecting{(windowSettings.UrlPolicy.CanLog() ? $" to '{url}'" : "")} to disable PDF reader toolbar.");
|
||||
}
|
||||
|
||||
return redirect;
|
||||
}
|
||||
|
||||
private void ReplaceSebScheme(IRequest request)
|
||||
{
|
||||
if (Uri.IsWellFormedUriString(request.Url, UriKind.RelativeOrAbsolute))
|
||||
{
|
||||
var uri = new Uri(request.Url);
|
||||
|
||||
if (uri.Scheme == appConfig.SebUriScheme)
|
||||
{
|
||||
request.Url = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri.AbsoluteUri;
|
||||
}
|
||||
else if (uri.Scheme == appConfig.SebUriSchemeSecure)
|
||||
{
|
||||
request.Url = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttps }.Uri.AbsoluteUri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IResourceHandler ResourceHandlerFor(ResourceType resourceType)
|
||||
{
|
||||
if (contentHandler == default(IResourceHandler))
|
||||
{
|
||||
contentHandler = CefSharp.ResourceHandler.FromString(contentLoader.LoadBlockedContent());
|
||||
}
|
||||
|
||||
if (pageHandler == default(IResourceHandler))
|
||||
{
|
||||
pageHandler = CefSharp.ResourceHandler.FromString(contentLoader.LoadBlockedPage());
|
||||
}
|
||||
|
||||
switch (resourceType)
|
||||
{
|
||||
case ResourceType.MainFrame:
|
||||
case ResourceType.SubFrame:
|
||||
return pageHandler;
|
||||
default:
|
||||
return contentHandler;
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchUserIdentifier(IRequest request, IResponse response)
|
||||
{
|
||||
var success = TrySearchGenericUserIdentifier(response);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
success = TrySearchEdxUserIdentifier(response);
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
TrySearchMoodleUserIdentifier(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TrySearchGenericUserIdentifier(IResponse response)
|
||||
{
|
||||
var ids = response.Headers.GetValues("X-LMS-USER-ID");
|
||||
var success = false;
|
||||
|
||||
if (ids != default(string[]))
|
||||
{
|
||||
var userId = ids.FirstOrDefault();
|
||||
|
||||
if (userId != default && userIdentifier != userId)
|
||||
{
|
||||
userIdentifier = userId;
|
||||
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||
logger.Info("Generic LMS user identifier detected.");
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TrySearchEdxUserIdentifier(IResponse response)
|
||||
{
|
||||
var cookies = response.Headers.GetValues("Set-Cookie");
|
||||
var success = false;
|
||||
|
||||
if (cookies != default(string[]))
|
||||
{
|
||||
try
|
||||
{
|
||||
var userInfo = cookies.FirstOrDefault(c => c.Contains("edx-user-info"));
|
||||
|
||||
if (userInfo != default)
|
||||
{
|
||||
var start = userInfo.IndexOf("=") + 1;
|
||||
var end = userInfo.IndexOf("; expires");
|
||||
var cookie = userInfo.Substring(start, end - start);
|
||||
var sanitized = cookie.Replace("\\\"", "\"").Replace("\\054", ",").Trim('"');
|
||||
var json = JsonConvert.DeserializeObject(sanitized) as JObject;
|
||||
var userName = json["username"].Value<string>();
|
||||
|
||||
if (userIdentifier != userName)
|
||||
{
|
||||
userIdentifier = userName;
|
||||
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||
logger.Info("EdX user identifier detected.");
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse edX user identifier!", e);
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TrySearchMoodleUserIdentifier(IRequest request, IResponse response)
|
||||
{
|
||||
var success = TrySearchMoodleUserIdentifierByLocation(response);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
success = TrySearchMoodleUserIdentifierByRequest(MoodleRequestType.Plugin, request, response);
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
success = TrySearchMoodleUserIdentifierByRequest(MoodleRequestType.Theme, request, response);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TrySearchMoodleUserIdentifierByLocation(IResponse response)
|
||||
{
|
||||
var locations = response.Headers.GetValues("Location");
|
||||
|
||||
if (locations != default(string[]))
|
||||
{
|
||||
try
|
||||
{
|
||||
var location = locations.FirstOrDefault(l => l.Contains("/login/index.php?testsession"));
|
||||
|
||||
if (location != default)
|
||||
{
|
||||
var userId = location.Substring(location.IndexOf("=") + 1);
|
||||
|
||||
if (userIdentifier != userId)
|
||||
{
|
||||
userIdentifier = userId;
|
||||
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||
logger.Info("Moodle user identifier detected by location.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse Moodle user identifier by location!", e);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TrySearchMoodleUserIdentifierByRequest(MoodleRequestType type, IRequest request, IResponse response)
|
||||
{
|
||||
var cookies = response.Headers.GetValues("Set-Cookie");
|
||||
var success = false;
|
||||
|
||||
if (cookies != default(string[]))
|
||||
{
|
||||
var session = cookies.FirstOrDefault(c => c.Contains("MoodleSession"));
|
||||
|
||||
if (session != default)
|
||||
{
|
||||
var userId = ExecuteMoodleUserIdentifierRequest(request.Url, session, type);
|
||||
|
||||
if (int.TryParse(userId, out var id) && id > 0 && userIdentifier != userId)
|
||||
{
|
||||
userIdentifier = userId;
|
||||
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||
logger.Info($"Moodle user identifier detected by request ({type}).");
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private string ExecuteMoodleUserIdentifierRequest(string requestUrl, string session, MoodleRequestType type)
|
||||
{
|
||||
var userId = default(string);
|
||||
|
||||
try
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var endpointUrl = default(string);
|
||||
var start = session.IndexOf("=") + 1;
|
||||
var end = session.IndexOf(";");
|
||||
var name = session.Substring(0, start - 1);
|
||||
var value = session.Substring(start, end - start);
|
||||
var uri = new Uri(requestUrl);
|
||||
|
||||
if (type == MoodleRequestType.Plugin)
|
||||
{
|
||||
endpointUrl = $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}/mod/quiz/accessrule/sebserver/classes/external/user.php";
|
||||
}
|
||||
else
|
||||
{
|
||||
endpointUrl = $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}/theme/boost_ethz/sebuser.php";
|
||||
}
|
||||
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, endpointUrl);
|
||||
|
||||
using (var handler = new HttpClientHandler { UseCookies = false })
|
||||
using (var client = new HttpClient(handler))
|
||||
{
|
||||
message.Headers.Add("Cookie", $"{name}={value}");
|
||||
|
||||
var result = await client.SendAsync(message);
|
||||
|
||||
if (result.IsSuccessStatusCode)
|
||||
{
|
||||
userId = await result.Content.ReadAsStringAsync();
|
||||
}
|
||||
else if (result.StatusCode != HttpStatusCode.NotFound)
|
||||
{
|
||||
logger.Error($"Failed to retrieve Moodle user identifier by request ({type})! Response: {(int) result.StatusCode} {result.ReasonPhrase}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to parse Moodle user identifier by request ({type})!", e);
|
||||
}
|
||||
}).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to execute Moodle user identifier request ({type})!", e);
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
|
||||
private enum MoodleRequestType
|
||||
{
|
||||
Plugin,
|
||||
Theme
|
||||
}
|
||||
}
|
||||
}
|
35
SafeExamBrowser.Browser/Properties/AssemblyInfo.cs
Normal file
35
SafeExamBrowser.Browser/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("SafeExamBrowser.Browser")]
|
||||
[assembly: AssemblyDescription("Safe Exam Browser")]
|
||||
[assembly: AssemblyCompany("ETH Zürich")]
|
||||
[assembly: AssemblyProduct("SafeExamBrowser.Browser")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: InternalsVisibleTo("SafeExamBrowser.Browser.UnitTests")]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("04e653f1-98e6-4e34-9dd7-7f2bc1a8b767")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
[assembly: AssemblyInformationalVersion("1.0.0.0")]
|
218
SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj
Normal file
218
SafeExamBrowser.Browser/SafeExamBrowser.Browser.csproj
Normal file
@@ -0,0 +1,218 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.props" Condition="Exists('..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.props')" />
|
||||
<Import Project="..\packages\chromiumembeddedframework.runtime.win-x86.121.3.13\build\chromiumembeddedframework.runtime.win-x86.props" Condition="Exists('..\packages\chromiumembeddedframework.runtime.win-x86.121.3.13\build\chromiumembeddedframework.runtime.win-x86.props')" />
|
||||
<Import Project="..\packages\chromiumembeddedframework.runtime.win-x64.121.3.13\build\chromiumembeddedframework.runtime.win-x64.props" Condition="Exists('..\packages\chromiumembeddedframework.runtime.win-x64.121.3.13\build\chromiumembeddedframework.runtime.win-x64.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SafeExamBrowser.Browser</RootNamespace>
|
||||
<AssemblyName>SafeExamBrowser.Browser</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Optimize>true</Optimize>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="CefSharp, Version=121.3.130.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CefSharp.Common.121.3.130\lib\net462\CefSharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="CefSharp.Core, Version=121.3.130.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CefSharp.Common.121.3.130\lib\net462\CefSharp.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="CefSharp.WinForms, Version=121.3.130.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CefSharp.WinForms.121.3.130\lib\net462\CefSharp.WinForms.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Syroot.KnownFolders, Version=1.3.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Syroot.Windows.IO.KnownFolders.1.3.0\lib\netstandard2.0\Syroot.KnownFolders.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Security.Principal.Windows, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Security.Principal.Windows.5.0.0\lib\net461\System.Security.Principal.Windows.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="BrowserApplication.cs" />
|
||||
<Compile Include="BrowserWindow.cs" />
|
||||
<Compile Include="Clipboard.cs" />
|
||||
<Compile Include="Events\ClipboardChangedEventHandler.cs" />
|
||||
<Compile Include="Events\DialogRequestedEventArgs.cs" />
|
||||
<Compile Include="Events\DialogRequestedEventHandler.cs" />
|
||||
<Compile Include="Events\DownloadAbortedEventHandler.cs" />
|
||||
<Compile Include="Events\DownloadUpdatedEventHandler.cs" />
|
||||
<Compile Include="Events\FaviconChangedEventHandler.cs" />
|
||||
<Compile Include="Events\WindowClosedEventHandler.cs" />
|
||||
<Compile Include="Events\PopupRequestedEventArgs.cs" />
|
||||
<Compile Include="Events\PopupRequestedEventHandler.cs" />
|
||||
<Compile Include="Events\ProgressChangedEventHandler.cs" />
|
||||
<Compile Include="Events\ResetRequestedEventHandler.cs" />
|
||||
<Compile Include="Events\UrlEventHandler.cs" />
|
||||
<Compile Include="Filters\RequestFilter.cs" />
|
||||
<Compile Include="Filters\Rules\RegexRule.cs" />
|
||||
<Compile Include="Filters\RuleFactory.cs" />
|
||||
<Compile Include="Filters\Rules\SimplifiedRule.cs" />
|
||||
<Compile Include="Handlers\ContextMenuHandler.cs" />
|
||||
<Compile Include="BrowserControl.cs" />
|
||||
<Compile Include="BrowserIconResource.cs" />
|
||||
<Compile Include="Handlers\DialogHandler.cs" />
|
||||
<Compile Include="Handlers\DisplayHandler.cs" />
|
||||
<Compile Include="Handlers\DownloadHandler.cs" />
|
||||
<Compile Include="Handlers\KeyboardHandler.cs" />
|
||||
<Compile Include="Handlers\RenderProcessMessageHandler.cs" />
|
||||
<Compile Include="Handlers\RequestHandler.cs" />
|
||||
<Compile Include="Handlers\ResourceHandler.cs" />
|
||||
<Compile Include="Content\ContentLoader.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Wrapper\CefSharpBrowserControl.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Wrapper\CefSharpPopupControl.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Wrapper\Events\AuthCredentialsEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\BeforeBrowseEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\BeforeDownloadEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\CanDownloadEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\ContextCreatedEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\ContextReleasedEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\DownloadUpdatedEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\FaviconUrlChangedEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\FileDialogRequestedEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\FocusedNodeChangedEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\KeyEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\LoadingProgressChangedEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\GenericEventArgs.cs" />
|
||||
<Compile Include="Wrapper\Events\OpenUrlFromTabEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\PreKeyEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\ResourceRequestEventArgs.cs" />
|
||||
<Compile Include="Wrapper\Events\ResourceRequestEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Events\UncaughtExceptionEventHandler.cs" />
|
||||
<Compile Include="Wrapper\Extensions.cs" />
|
||||
<Compile Include="Wrapper\Handlers\DialogHandlerSwitch.cs" />
|
||||
<Compile Include="Wrapper\Handlers\DisplayHandlerSwitch.cs" />
|
||||
<Compile Include="Wrapper\Handlers\DownloadHandlerSwitch.cs" />
|
||||
<Compile Include="Wrapper\Handlers\KeyboardHandlerSwitch.cs" />
|
||||
<Compile Include="Wrapper\Handlers\RenderProcessMessageHandlerSwitch.cs" />
|
||||
<Compile Include="Wrapper\Handlers\RequestHandlerSwitch.cs" />
|
||||
<Compile Include="Wrapper\ICefSharpControl.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Applications.Contracts\SafeExamBrowser.Applications.Contracts.csproj">
|
||||
<Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project>
|
||||
<Name>SafeExamBrowser.Applications.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Browser.Contracts\SafeExamBrowser.Browser.Contracts.csproj">
|
||||
<Project>{5fb5273d-277c-41dd-8593-a25ce1aff2e9}</Project>
|
||||
<Name>SafeExamBrowser.Browser.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Configuration.Contracts\SafeExamBrowser.Configuration.Contracts.csproj">
|
||||
<Project>{7d74555e-63e1-4c46-bd0a-8580552368c8}</Project>
|
||||
<Name>SafeExamBrowser.Configuration.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Core.Contracts\SafeExamBrowser.Core.Contracts.csproj">
|
||||
<Project>{fe0e1224-b447-4b14-81e7-ed7d84822aa0}</Project>
|
||||
<Name>SafeExamBrowser.Core.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.I18n.Contracts\SafeExamBrowser.I18n.Contracts.csproj">
|
||||
<Project>{1858ddf3-bc2a-4bff-b663-4ce2ffeb8b7d}</Project>
|
||||
<Name>SafeExamBrowser.I18n.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">
|
||||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
|
||||
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
|
||||
<Name>SafeExamBrowser.Settings</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.UserInterface.Contracts\SafeExamBrowser.UserInterface.Contracts.csproj">
|
||||
<Project>{c7889e97-6ff6-4a58-b7cb-521ed276b316}</Project>
|
||||
<Name>SafeExamBrowser.UserInterface.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj">
|
||||
<Project>{7016f080-9aa5-41b2-a225-385ad877c171}</Project>
|
||||
<Name>SafeExamBrowser.WindowsApi.Contracts</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Content\BlockedContent.html" />
|
||||
<EmbeddedResource Include="Content\BlockedPage.html" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Content\Api.js" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Content\Clipboard.js" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Content\PageZoom.js" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Target Name="AfterClean" AfterTargets="Clean">
|
||||
<RemoveDir Directories="$(TargetDir)" />
|
||||
</Target>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\chromiumembeddedframework.runtime.win-x64.121.3.13\build\chromiumembeddedframework.runtime.win-x64.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\chromiumembeddedframework.runtime.win-x64.121.3.13\build\chromiumembeddedframework.runtime.win-x64.props'))" />
|
||||
<Error Condition="!Exists('..\packages\chromiumembeddedframework.runtime.win-x86.121.3.13\build\chromiumembeddedframework.runtime.win-x86.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\chromiumembeddedframework.runtime.win-x86.121.3.13\build\chromiumembeddedframework.runtime.win-x86.props'))" />
|
||||
<Error Condition="!Exists('..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.props'))" />
|
||||
<Error Condition="!Exists('..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.targets" Condition="Exists('..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.targets')" />
|
||||
</Project>
|
134
SafeExamBrowser.Browser/Wrapper/CefSharpBrowserControl.cs
Normal file
134
SafeExamBrowser.Browser/Wrapper/CefSharpBrowserControl.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using SafeExamBrowser.Browser.Handlers;
|
||||
using SafeExamBrowser.Browser.Wrapper.Events;
|
||||
using SafeExamBrowser.Browser.Wrapper.Handlers;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper
|
||||
{
|
||||
internal class CefSharpBrowserControl : ChromiumWebBrowser, ICefSharpControl
|
||||
{
|
||||
public event AuthCredentialsEventHandler AuthCredentialsRequired;
|
||||
public event BeforeBrowseEventHandler BeforeBrowse;
|
||||
public event BeforeDownloadEventHandler BeforeDownload;
|
||||
public event CanDownloadEventHandler CanDownload;
|
||||
public event ContextCreatedEventHandler ContextCreated;
|
||||
public event ContextReleasedEventHandler ContextReleased;
|
||||
public event DownloadUpdatedEventHandler DownloadUpdated;
|
||||
public event FaviconUrlChangedEventHandler FaviconUrlChanged;
|
||||
public event FileDialogRequestedEventHandler FileDialogRequested;
|
||||
public event FocusedNodeChangedEventHandler FocusedNodeChanged;
|
||||
public event KeyEventHandler KeyEvent;
|
||||
public event LoadingProgressChangedEventHandler LoadingProgressChanged;
|
||||
public event OpenUrlFromTabEventHandler OpenUrlFromTab;
|
||||
public event PreKeyEventHandler PreKeyEvent;
|
||||
public event ResourceRequestEventHandler ResourceRequestHandlerRequired;
|
||||
public event UncaughtExceptionEventHandler UncaughtExceptionEvent;
|
||||
|
||||
public CefSharpBrowserControl(ILifeSpanHandler lifeSpanHandler, string url) : base(url)
|
||||
{
|
||||
DialogHandler = new DialogHandlerSwitch();
|
||||
DisplayHandler = new DisplayHandlerSwitch();
|
||||
DownloadHandler = new DownloadHandlerSwitch();
|
||||
KeyboardHandler = new KeyboardHandlerSwitch();
|
||||
LifeSpanHandler = lifeSpanHandler;
|
||||
MenuHandler = new ContextMenuHandler();
|
||||
RenderProcessMessageHandler = new RenderProcessMessageHandlerSwitch();
|
||||
RequestHandler = new RequestHandlerSwitch();
|
||||
}
|
||||
|
||||
void ICefSharpControl.Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public void GetAuthCredentials(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback, GenericEventArgs args)
|
||||
{
|
||||
AuthCredentialsRequired?.Invoke(webBrowser, browser, originUrl, isProxy, host, port, realm, scheme, callback, args);
|
||||
}
|
||||
|
||||
public void GetResourceRequestHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling, ResourceRequestEventArgs args)
|
||||
{
|
||||
ResourceRequestHandlerRequired?.Invoke(webBrowser, browser, frame, request, isNavigation, isDownload, requestInitiator, ref disableDefaultHandling, args);
|
||||
}
|
||||
|
||||
public void OnBeforeBrowse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect, GenericEventArgs args)
|
||||
{
|
||||
BeforeBrowse?.Invoke(webBrowser, browser, frame, request, userGesture, isRedirect, args);
|
||||
}
|
||||
|
||||
public void OnBeforeDownload(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback)
|
||||
{
|
||||
BeforeDownload?.Invoke(webBrowser, browser, downloadItem, callback);
|
||||
}
|
||||
|
||||
public void OnCanDownload(IWebBrowser webBrowser, IBrowser browser, string url, string requestMethod, GenericEventArgs args)
|
||||
{
|
||||
CanDownload?.Invoke(webBrowser, browser, url, requestMethod, args);
|
||||
}
|
||||
|
||||
public void OnContextCreated(IWebBrowser webBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
ContextCreated?.Invoke(webBrowser, browser, frame);
|
||||
}
|
||||
|
||||
public void OnContextReleased(IWebBrowser webBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
ContextReleased?.Invoke(webBrowser, browser, frame);
|
||||
}
|
||||
|
||||
public void OnDownloadUpdated(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback)
|
||||
{
|
||||
DownloadUpdated?.Invoke(webBrowser, browser, downloadItem, callback);
|
||||
}
|
||||
|
||||
public void OnFaviconUrlChange(IWebBrowser webBrowser, IBrowser browser, IList<string> urls)
|
||||
{
|
||||
FaviconUrlChanged?.Invoke(webBrowser, browser, urls);
|
||||
}
|
||||
|
||||
public void OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, IFileDialogCallback callback)
|
||||
{
|
||||
FileDialogRequested?.Invoke(webBrowser, browser, mode, title, defaultFilePath, acceptFilters, callback);
|
||||
}
|
||||
|
||||
public void OnFocusedNodeChanged(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IDomNode node)
|
||||
{
|
||||
FocusedNodeChanged?.Invoke(webBrowser, browser, frame, node);
|
||||
}
|
||||
|
||||
public void OnKeyEvent(IWebBrowser webBrowser, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
|
||||
{
|
||||
KeyEvent?.Invoke(webBrowser, browser, type, windowsKeyCode, nativeKeyCode, modifiers, isSystemKey);
|
||||
}
|
||||
|
||||
public void OnLoadingProgressChange(IWebBrowser webBrowser, IBrowser browser, double progress)
|
||||
{
|
||||
LoadingProgressChanged?.Invoke(webBrowser, browser, progress);
|
||||
}
|
||||
|
||||
public void OnOpenUrlFromTab(IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture, GenericEventArgs args)
|
||||
{
|
||||
OpenUrlFromTab?.Invoke(webBrowser, browser, frame, targetUrl, targetDisposition, userGesture, args);
|
||||
}
|
||||
|
||||
public void OnPreKeyEvent(IWebBrowser webBrowser, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut, GenericEventArgs args)
|
||||
{
|
||||
PreKeyEvent?.Invoke(webBrowser, browser, type, windowsKeyCode, nativeKeyCode, modifiers, isSystemKey, ref isKeyboardShortcut, args);
|
||||
}
|
||||
|
||||
public void OnUncaughtException(IWebBrowser webBrowser, IBrowser browser, IFrame frame, JavascriptException exception)
|
||||
{
|
||||
UncaughtExceptionEvent?.Invoke(webBrowser, browser, frame, exception);
|
||||
}
|
||||
}
|
||||
}
|
128
SafeExamBrowser.Browser/Wrapper/CefSharpPopupControl.cs
Normal file
128
SafeExamBrowser.Browser/Wrapper/CefSharpPopupControl.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms.Host;
|
||||
using SafeExamBrowser.Browser.Wrapper.Events;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper
|
||||
{
|
||||
internal class CefSharpPopupControl : ChromiumHostControl, ICefSharpControl
|
||||
{
|
||||
public event AuthCredentialsEventHandler AuthCredentialsRequired;
|
||||
public event BeforeBrowseEventHandler BeforeBrowse;
|
||||
public event BeforeDownloadEventHandler BeforeDownload;
|
||||
public event CanDownloadEventHandler CanDownload;
|
||||
public event ContextCreatedEventHandler ContextCreated;
|
||||
public event ContextReleasedEventHandler ContextReleased;
|
||||
public event DownloadUpdatedEventHandler DownloadUpdated;
|
||||
public event FaviconUrlChangedEventHandler FaviconUrlChanged;
|
||||
public event FileDialogRequestedEventHandler FileDialogRequested;
|
||||
public event FocusedNodeChangedEventHandler FocusedNodeChanged;
|
||||
public event KeyEventHandler KeyEvent;
|
||||
public event LoadingProgressChangedEventHandler LoadingProgressChanged;
|
||||
public event OpenUrlFromTabEventHandler OpenUrlFromTab;
|
||||
public event PreKeyEventHandler PreKeyEvent;
|
||||
public event ResourceRequestEventHandler ResourceRequestHandlerRequired;
|
||||
public event UncaughtExceptionEventHandler UncaughtExceptionEvent;
|
||||
|
||||
void ICefSharpControl.Dispose(bool disposing)
|
||||
{
|
||||
if (!IsDisposed && IsHandleCreated)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetAuthCredentials(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback, GenericEventArgs args)
|
||||
{
|
||||
AuthCredentialsRequired?.Invoke(webBrowser, browser, originUrl, isProxy, host, port, realm, scheme, callback, args);
|
||||
}
|
||||
|
||||
public void GetResourceRequestHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling, ResourceRequestEventArgs args)
|
||||
{
|
||||
ResourceRequestHandlerRequired?.Invoke(webBrowser, browser, frame, request, isNavigation, isDownload, requestInitiator, ref disableDefaultHandling, args);
|
||||
}
|
||||
|
||||
public void Load(string address)
|
||||
{
|
||||
LoadUrl(address);
|
||||
}
|
||||
|
||||
public void OnBeforeBrowse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect, GenericEventArgs args)
|
||||
{
|
||||
BeforeBrowse?.Invoke(webBrowser, browser, frame, request, userGesture, isRedirect, args);
|
||||
}
|
||||
|
||||
public void OnBeforeDownload(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback)
|
||||
{
|
||||
BeforeDownload?.Invoke(webBrowser, browser, downloadItem, callback);
|
||||
}
|
||||
|
||||
public void OnCanDownload(IWebBrowser webBrowser, IBrowser browser, string url, string requestMethod, GenericEventArgs args)
|
||||
{
|
||||
CanDownload?.Invoke(webBrowser, browser, url, requestMethod, args);
|
||||
}
|
||||
|
||||
public void OnContextCreated(IWebBrowser webBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
ContextCreated?.Invoke(webBrowser, browser, frame);
|
||||
}
|
||||
|
||||
public void OnContextReleased(IWebBrowser webBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
ContextReleased?.Invoke(webBrowser, browser, frame);
|
||||
}
|
||||
|
||||
public void OnDownloadUpdated(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback)
|
||||
{
|
||||
DownloadUpdated?.Invoke(webBrowser, browser, downloadItem, callback);
|
||||
}
|
||||
|
||||
public void OnFaviconUrlChange(IWebBrowser webBrowser, IBrowser browser, IList<string> urls)
|
||||
{
|
||||
FaviconUrlChanged?.Invoke(webBrowser, browser, urls);
|
||||
}
|
||||
|
||||
public void OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, IFileDialogCallback callback)
|
||||
{
|
||||
FileDialogRequested?.Invoke(webBrowser, browser, mode, title, defaultFilePath, acceptFilters, callback);
|
||||
}
|
||||
|
||||
public void OnFocusedNodeChanged(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IDomNode node)
|
||||
{
|
||||
FocusedNodeChanged?.Invoke(webBrowser, browser, frame, node);
|
||||
}
|
||||
|
||||
public void OnKeyEvent(IWebBrowser webBrowser, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
|
||||
{
|
||||
KeyEvent?.Invoke(webBrowser, browser, type, windowsKeyCode, nativeKeyCode, modifiers, isSystemKey);
|
||||
}
|
||||
|
||||
public void OnLoadingProgressChange(IWebBrowser webBrowser, IBrowser browser, double progress)
|
||||
{
|
||||
LoadingProgressChanged?.Invoke(webBrowser, browser, progress);
|
||||
}
|
||||
|
||||
public void OnOpenUrlFromTab(IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture, GenericEventArgs args)
|
||||
{
|
||||
OpenUrlFromTab?.Invoke(webBrowser, browser, frame, targetUrl, targetDisposition, userGesture, args);
|
||||
}
|
||||
|
||||
public void OnPreKeyEvent(IWebBrowser webBrowser, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut, GenericEventArgs args)
|
||||
{
|
||||
PreKeyEvent?.Invoke(webBrowser, browser, type, windowsKeyCode, nativeKeyCode, modifiers, isSystemKey, ref isKeyboardShortcut, args);
|
||||
}
|
||||
|
||||
public void OnUncaughtException(IWebBrowser webBrowser, IBrowser browser, IFrame frame, JavascriptException exception)
|
||||
{
|
||||
UncaughtExceptionEvent?.Invoke(webBrowser, browser, frame, exception);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void AuthCredentialsEventHandler(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback, GenericEventArgs args);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void BeforeBrowseEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect, GenericEventArgs args);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void BeforeDownloadEventHandler(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void CanDownloadEventHandler(IWebBrowser webBrowser, IBrowser browser, string url, string requestMethod, GenericEventArgs args);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void ContextCreatedEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void ContextReleasedEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void DownloadUpdatedEventHandler(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback);
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void FaviconUrlChangedEventHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IList<string> urls);
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void FileDialogRequestedEventHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, IFileDialogCallback callback);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void FocusedNodeChangedEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IDomNode node);
|
||||
}
|
15
SafeExamBrowser.Browser/Wrapper/Events/GenericEventArgs.cs
Normal file
15
SafeExamBrowser.Browser/Wrapper/Events/GenericEventArgs.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal class GenericEventArgs
|
||||
{
|
||||
public bool Value { get; set; }
|
||||
}
|
||||
}
|
14
SafeExamBrowser.Browser/Wrapper/Events/KeyEventHandler.cs
Normal file
14
SafeExamBrowser.Browser/Wrapper/Events/KeyEventHandler.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void KeyEventHandler(IWebBrowser webBrowser, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void LoadingProgressChangedEventHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, double progress);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void OpenUrlFromTabEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture, GenericEventArgs args);
|
||||
}
|
14
SafeExamBrowser.Browser/Wrapper/Events/PreKeyEventHandler.cs
Normal file
14
SafeExamBrowser.Browser/Wrapper/Events/PreKeyEventHandler.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void PreKeyEventHandler(IWebBrowser webBrowser, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut, GenericEventArgs args);
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal class ResourceRequestEventArgs
|
||||
{
|
||||
public IResourceRequestHandler Handler { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void ResourceRequestEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling, ResourceRequestEventArgs args);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Events
|
||||
{
|
||||
internal delegate void UncaughtExceptionEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, JavascriptException exception);
|
||||
}
|
38
SafeExamBrowser.Browser/Wrapper/Extensions.cs
Normal file
38
SafeExamBrowser.Browser/Wrapper/Extensions.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper
|
||||
{
|
||||
internal static class Extensions
|
||||
{
|
||||
internal static FileSystemElement ToElement(this CefFileDialogMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case CefFileDialogMode.OpenFolder:
|
||||
return FileSystemElement.Folder;
|
||||
default:
|
||||
return FileSystemElement.File;
|
||||
}
|
||||
}
|
||||
|
||||
internal static FileSystemOperation ToOperation(this CefFileDialogMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case CefFileDialogMode.Save:
|
||||
return FileSystemOperation.Save;
|
||||
default:
|
||||
return FileSystemOperation.Open;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using CefSharp.WinForms.Host;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Handlers
|
||||
{
|
||||
internal class DialogHandlerSwitch : IDialogHandler
|
||||
{
|
||||
public bool OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, IFileDialogCallback callback)
|
||||
{
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.OnFileDialog(webBrowser, browser, mode, title, defaultFilePath, acceptFilters, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.OnFileDialog(webBrowser, browser, mode, title, defaultFilePath, acceptFilters, callback);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using CefSharp.WinForms.Handler;
|
||||
using CefSharp.WinForms.Host;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Handlers
|
||||
{
|
||||
internal class DisplayHandlerSwitch : DisplayHandler
|
||||
{
|
||||
protected override void OnFaviconUrlChange(IWebBrowser chromiumWebBrowser, IBrowser browser, IList<string> urls)
|
||||
{
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.OnFaviconUrlChange(chromiumWebBrowser, browser, urls);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.OnFaviconUrlChange(chromiumWebBrowser, browser, urls);
|
||||
}
|
||||
|
||||
base.OnFaviconUrlChange(chromiumWebBrowser, browser, urls);
|
||||
}
|
||||
|
||||
protected override void OnLoadingProgressChange(IWebBrowser chromiumWebBrowser, IBrowser browser, double progress)
|
||||
{
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.OnLoadingProgressChange(chromiumWebBrowser, browser, progress);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.OnLoadingProgressChange(chromiumWebBrowser, browser, progress);
|
||||
}
|
||||
|
||||
base.OnLoadingProgressChange(chromiumWebBrowser, browser, progress);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using CefSharp.WinForms.Host;
|
||||
using SafeExamBrowser.Browser.Wrapper.Events;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Handlers
|
||||
{
|
||||
internal class DownloadHandlerSwitch : IDownloadHandler
|
||||
{
|
||||
public bool CanDownload(IWebBrowser webBrowser, IBrowser browser, string url, string requestMethod)
|
||||
{
|
||||
var args = new GenericEventArgs { Value = false };
|
||||
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.OnCanDownload(webBrowser, browser, url, requestMethod, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.OnCanDownload(webBrowser, browser, url, requestMethod, args);
|
||||
}
|
||||
|
||||
return args.Value;
|
||||
}
|
||||
|
||||
public void OnBeforeDownload(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback)
|
||||
{
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.OnBeforeDownload(webBrowser, browser, downloadItem, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.OnBeforeDownload(webBrowser, browser, downloadItem, callback);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDownloadUpdated(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback)
|
||||
{
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.OnDownloadUpdated(webBrowser, browser, downloadItem, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.OnDownloadUpdated(webBrowser, browser, downloadItem, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using CefSharp.WinForms.Host;
|
||||
using SafeExamBrowser.Browser.Wrapper.Events;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Handlers
|
||||
{
|
||||
internal class KeyboardHandlerSwitch : IKeyboardHandler
|
||||
{
|
||||
public bool OnKeyEvent(IWebBrowser webBrowser, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
|
||||
{
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.OnKeyEvent(webBrowser, browser, type, windowsKeyCode, nativeKeyCode, modifiers, isSystemKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.OnKeyEvent(webBrowser, browser, type, windowsKeyCode, nativeKeyCode, modifiers, isSystemKey);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool OnPreKeyEvent(IWebBrowser webBrowser, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut)
|
||||
{
|
||||
var args = new GenericEventArgs { Value = false };
|
||||
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.OnPreKeyEvent(webBrowser, browser, type, windowsKeyCode, nativeKeyCode, modifiers, isSystemKey, ref isKeyboardShortcut, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.OnPreKeyEvent(webBrowser, browser, type, windowsKeyCode, nativeKeyCode, modifiers, isSystemKey, ref isKeyboardShortcut, args);
|
||||
}
|
||||
|
||||
return args.Value;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using CefSharp.WinForms.Host;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Handlers
|
||||
{
|
||||
internal class RenderProcessMessageHandlerSwitch : IRenderProcessMessageHandler
|
||||
{
|
||||
public void OnContextCreated(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.OnContextCreated(chromiumWebBrowser, browser, frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.OnContextCreated(chromiumWebBrowser, browser, frame);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnContextReleased(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.OnContextReleased(chromiumWebBrowser, browser, frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.OnContextReleased(chromiumWebBrowser, browser, frame);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnFocusedNodeChanged(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IDomNode node)
|
||||
{
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.OnFocusedNodeChanged(chromiumWebBrowser, browser, frame, node);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.OnFocusedNodeChanged(chromiumWebBrowser, browser, frame, node);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnUncaughtException(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, JavascriptException exception)
|
||||
{
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.OnUncaughtException(chromiumWebBrowser, browser, frame, exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.OnUncaughtException(chromiumWebBrowser, browser, frame, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
using CefSharp.Handler;
|
||||
using CefSharp.WinForms;
|
||||
using CefSharp.WinForms.Host;
|
||||
using SafeExamBrowser.Browser.Wrapper.Events;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper.Handlers
|
||||
{
|
||||
internal class RequestHandlerSwitch : RequestHandler
|
||||
{
|
||||
protected override bool GetAuthCredentials(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
|
||||
{
|
||||
var args = new GenericEventArgs { Value = false };
|
||||
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.GetAuthCredentials(webBrowser, browser, originUrl, isProxy, host, port, realm, scheme, callback, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.GetAuthCredentials(webBrowser, browser, originUrl, isProxy, host, port, realm, scheme, callback, args);
|
||||
}
|
||||
|
||||
return args.Value;
|
||||
}
|
||||
|
||||
protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
|
||||
{
|
||||
var args = new ResourceRequestEventArgs();
|
||||
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.GetResourceRequestHandler(chromiumWebBrowser, browser, frame, request, isNavigation, isDownload, requestInitiator, ref disableDefaultHandling, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.GetResourceRequestHandler(chromiumWebBrowser, browser, frame, request, isNavigation, isDownload, requestInitiator, ref disableDefaultHandling, args);
|
||||
}
|
||||
|
||||
return args.Handler;
|
||||
}
|
||||
|
||||
protected override bool OnBeforeBrowse(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
|
||||
{
|
||||
var args = new GenericEventArgs { Value = false };
|
||||
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.OnBeforeBrowse(chromiumWebBrowser, browser, frame, request, userGesture, isRedirect, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.OnBeforeBrowse(chromiumWebBrowser, browser, frame, request, userGesture, isRedirect, args);
|
||||
}
|
||||
|
||||
return args.Value;
|
||||
}
|
||||
|
||||
protected override bool OnOpenUrlFromTab(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
|
||||
{
|
||||
var args = new GenericEventArgs { Value = false };
|
||||
|
||||
if (browser.IsPopup)
|
||||
{
|
||||
var control = ChromiumHostControl.FromBrowser(browser) as CefSharpPopupControl;
|
||||
|
||||
control?.OnOpenUrlFromTab(chromiumWebBrowser, browser, frame, targetUrl, targetDisposition, userGesture, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
var control = ChromiumWebBrowser.FromBrowser(browser) as CefSharpBrowserControl;
|
||||
|
||||
control?.OnOpenUrlFromTab(chromiumWebBrowser, browser, frame, targetUrl, targetDisposition, userGesture, args);
|
||||
}
|
||||
|
||||
return args.Value;
|
||||
}
|
||||
}
|
||||
}
|
57
SafeExamBrowser.Browser/Wrapper/ICefSharpControl.cs
Normal file
57
SafeExamBrowser.Browser/Wrapper/ICefSharpControl.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using SafeExamBrowser.Browser.Wrapper.Events;
|
||||
using KeyEventHandler = SafeExamBrowser.Browser.Wrapper.Events.KeyEventHandler;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Wrapper
|
||||
{
|
||||
internal interface ICefSharpControl : IChromiumWebBrowserBase, IWinFormsChromiumWebBrowser, IWin32Window, IComponent, ISynchronizeInvoke
|
||||
{
|
||||
event AuthCredentialsEventHandler AuthCredentialsRequired;
|
||||
event BeforeBrowseEventHandler BeforeBrowse;
|
||||
event BeforeDownloadEventHandler BeforeDownload;
|
||||
event CanDownloadEventHandler CanDownload;
|
||||
event ContextCreatedEventHandler ContextCreated;
|
||||
event ContextReleasedEventHandler ContextReleased;
|
||||
event DownloadUpdatedEventHandler DownloadUpdated;
|
||||
event FaviconUrlChangedEventHandler FaviconUrlChanged;
|
||||
event FileDialogRequestedEventHandler FileDialogRequested;
|
||||
event FocusedNodeChangedEventHandler FocusedNodeChanged;
|
||||
event KeyEventHandler KeyEvent;
|
||||
event LoadingProgressChangedEventHandler LoadingProgressChanged;
|
||||
event OpenUrlFromTabEventHandler OpenUrlFromTab;
|
||||
event PreKeyEventHandler PreKeyEvent;
|
||||
event ResourceRequestEventHandler ResourceRequestHandlerRequired;
|
||||
event UncaughtExceptionEventHandler UncaughtExceptionEvent;
|
||||
|
||||
void Dispose(bool disposing);
|
||||
void GetAuthCredentials(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback, GenericEventArgs args);
|
||||
void GetResourceRequestHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling, ResourceRequestEventArgs args);
|
||||
void Load(string address);
|
||||
void OnBeforeBrowse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect, GenericEventArgs args);
|
||||
void OnBeforeDownload(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback);
|
||||
void OnCanDownload(IWebBrowser webBrowser, IBrowser browser, string url, string requestMethod, GenericEventArgs args);
|
||||
void OnContextCreated(IWebBrowser webBrowser, IBrowser browser, IFrame frame);
|
||||
void OnContextReleased(IWebBrowser webBrowser, IBrowser browser, IFrame frame);
|
||||
void OnDownloadUpdated(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback);
|
||||
void OnFaviconUrlChange(IWebBrowser webBrowser, IBrowser browser, IList<string> urls);
|
||||
void OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, IFileDialogCallback callback);
|
||||
void OnFocusedNodeChanged(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IDomNode node);
|
||||
void OnKeyEvent(IWebBrowser webBrowser, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey);
|
||||
void OnLoadingProgressChange(IWebBrowser webBrowser, IBrowser browser, double progress);
|
||||
void OnOpenUrlFromTab(IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture, GenericEventArgs args);
|
||||
void OnPreKeyEvent(IWebBrowser webBrowser, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut, GenericEventArgs args);
|
||||
void OnUncaughtException(IWebBrowser webBrowser, IBrowser browser, IFrame frame, JavascriptException exception);
|
||||
}
|
||||
}
|
11
SafeExamBrowser.Browser/app.config
Normal file
11
SafeExamBrowser.Browser/app.config
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Security.Principal.Windows" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" /></startup></configuration>
|
10
SafeExamBrowser.Browser/packages.config
Normal file
10
SafeExamBrowser.Browser/packages.config
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="CefSharp.Common" version="121.3.130" targetFramework="net48" />
|
||||
<package id="CefSharp.WinForms" version="121.3.130" targetFramework="net48" />
|
||||
<package id="chromiumembeddedframework.runtime.win-x64" version="121.3.13" targetFramework="net48" />
|
||||
<package id="chromiumembeddedframework.runtime.win-x86" version="121.3.13" targetFramework="net48" />
|
||||
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
|
||||
<package id="Syroot.Windows.IO.KnownFolders" version="1.3.0" targetFramework="net48" />
|
||||
<package id="System.Security.Principal.Windows" version="5.0.0" targetFramework="net48" />
|
||||
</packages>
|
Reference in New Issue
Block a user