Restore SEBPatch

This commit is contained in:
2025-06-01 11:44:20 +02:00
commit 8c656e3137
1297 changed files with 142172 additions and 0 deletions

View 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.");
}
}
}

View 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);
}
}
}

View 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");
}
}
}

View 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;
}
}
}

View 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; }
}
}
}

View 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()
}
}

View 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>

View 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">&#x2B60; %%BACK_BUTTON%%</button>
</div>
</body>
</html>

View 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);

View 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;
}
}
}

View 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 });

View 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 ClipboardChangedEventHandler(long id);
}

View 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; }
}
}

View 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 DialogRequestedEventHandler(DialogRequestedEventArgs args);
}

View 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 DownloadAbortedEventHandler();
}

View 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 SafeExamBrowser.UserInterface.Contracts.Browser.Data;
namespace SafeExamBrowser.Browser.Events
{
internal delegate void DownloadUpdatedEventHandler(DownloadItemState state);
}

View 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);
}

View 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; }
}
}

View 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);
}

View 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 ProgressChangedEventHandler(double value);
}

View 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();
}

View 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);
}

View 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);
}

View 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;
}
}
}

View 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!");
}
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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.");
}
}
}
}

View 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;
}
}
}

View File

@@ -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)
{
}
}
}

View 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;
}
}
}

View 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
}
}
}

View 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")]

View 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>

View 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);
}
}
}

View 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);
}
}
}

View 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 AuthCredentialsEventHandler(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback, GenericEventArgs args);
}

View 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 BeforeBrowseEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect, GenericEventArgs args);
}

View 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 BeforeDownloadEventHandler(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback);
}

View 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 CanDownloadEventHandler(IWebBrowser webBrowser, IBrowser browser, string url, string requestMethod, GenericEventArgs args);
}

View 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 ContextCreatedEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame);
}

View 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 ContextReleasedEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame);
}

View 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 DownloadUpdatedEventHandler(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback);
}

View 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/.
*/
using System.Collections.Generic;
using CefSharp;
namespace SafeExamBrowser.Browser.Wrapper.Events
{
internal delegate void FaviconUrlChangedEventHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IList<string> urls);
}

View 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/.
*/
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);
}

View 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 FocusedNodeChangedEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IDomNode node);
}

View 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; }
}
}

View 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);
}

View 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 LoadingProgressChangedEventHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, double progress);
}

View 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 OpenUrlFromTabEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture, GenericEventArgs args);
}

View 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);
}

View File

@@ -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; }
}
}

View 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 ResourceRequestEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling, ResourceRequestEventArgs args);
}

View 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 UncaughtExceptionEventHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, JavascriptException exception);
}

View 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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}

View 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);
}
}

View 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>

View 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>