SEBWIN-314: Main requests are now entirely prevented (before any loading happens) and only content requests are intercepted with custom resource handling.

This commit is contained in:
dbuechel 2019-09-12 12:31:17 +02:00
parent ceba0e8a2f
commit d51422e188
10 changed files with 123 additions and 54 deletions

View file

@ -57,7 +57,6 @@
<Compile Include="Events\DownloadFinishedCallback.cs" /> <Compile Include="Events\DownloadFinishedCallback.cs" />
<Compile Include="Events\DownloadRequestedEventHandler.cs" /> <Compile Include="Events\DownloadRequestedEventHandler.cs" />
<Compile Include="IBrowserApplication.cs" /> <Compile Include="IBrowserApplication.cs" />
<Compile Include="Events\ProgressChangedEventHandler.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -8,6 +8,7 @@
using System; using System;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks;
using SafeExamBrowser.Applications.Contracts; using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events; using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Browser.Contracts.Events; using SafeExamBrowser.Browser.Contracts.Events;
@ -102,7 +103,7 @@ namespace SafeExamBrowser.Browser
var keyboardHandler = new KeyboardHandler(); var keyboardHandler = new KeyboardHandler();
var lifeSpanHandler = new LifeSpanHandler(); var lifeSpanHandler = new LifeSpanHandler();
var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} {Id}"); var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} {Id}");
var requestHandler = new RequestHandler(appConfig, settings.Filter, logger, text); var requestHandler = new RequestHandler(appConfig, settings.Filter, requestLogger, text);
displayHandler.FaviconChanged += DisplayHandler_FaviconChanged; displayHandler.FaviconChanged += DisplayHandler_FaviconChanged;
displayHandler.ProgressChanged += DisplayHandler_ProgressChanged; displayHandler.ProgressChanged += DisplayHandler_ProgressChanged;
@ -112,6 +113,7 @@ namespace SafeExamBrowser.Browser
keyboardHandler.ZoomOutRequested += ZoomOutRequested; keyboardHandler.ZoomOutRequested += ZoomOutRequested;
keyboardHandler.ZoomResetRequested += ZoomResetRequested; keyboardHandler.ZoomResetRequested += ZoomResetRequested;
lifeSpanHandler.PopupRequested += LifeSpanHandler_PopupRequested; lifeSpanHandler.PopupRequested += LifeSpanHandler_PopupRequested;
requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
control = new BrowserControl(contextMenuHandler, displayHandler, downloadHandler, keyboardHandler, lifeSpanHandler, requestHandler, url); control = new BrowserControl(contextMenuHandler, displayHandler, downloadHandler, keyboardHandler, lifeSpanHandler, requestHandler, url);
control.AddressChanged += Control_AddressChanged; control.AddressChanged += Control_AddressChanged;
@ -224,6 +226,17 @@ namespace SafeExamBrowser.Browser
} }
} }
private void RequestHandler_RequestBlocked(string url)
{
Task.Run(() =>
{
var message = text.Get(TextKey.MessageBox_BrowserNavigationBlocked).Replace("%%URL%%", url);
var title = text.Get(TextKey.MessageBox_BrowserNavigationBlockedTitle);
messageBox.Show(message, title, parent: window);
});
}
private void ReloadRequested() private void ReloadRequested()
{ {
if (WindowSettings.AllowReloading && WindowSettings.ShowReloadWarning) if (WindowSettings.AllowReloading && WindowSettings.ShowReloadWarning)

View file

@ -6,10 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
namespace SafeExamBrowser.Browser.Contracts.Events namespace SafeExamBrowser.Browser.Events
{ {
/// <summary> internal delegate void ProgressChangedEventHandler(double value);
/// Event handler used to indicate the current progress value of the page load process (from <c>0.0</c> to <c>1.0</c>).
/// </summary>
public delegate void ProgressChangedEventHandler(double value);
} }

View file

@ -0,0 +1,12 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* 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 RequestBlockedEventHandler(string url);
}

View file

@ -10,7 +10,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using CefSharp; using CefSharp;
using CefSharp.Structs; using CefSharp.Structs;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Browser.Events; using SafeExamBrowser.Browser.Events;
namespace SafeExamBrowser.Browser.Handlers namespace SafeExamBrowser.Browser.Handlers

View file

@ -7,6 +7,8 @@
*/ */
using CefSharp; using CefSharp;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Filters;
using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
@ -16,15 +18,28 @@ namespace SafeExamBrowser.Browser.Handlers
{ {
internal class RequestHandler : CefSharp.Handler.RequestHandler internal class RequestHandler : CefSharp.Handler.RequestHandler
{ {
private RequestFilter filter;
private ILogger logger;
private ResourceHandler resourceHandler; private ResourceHandler resourceHandler;
private BrowserFilterSettings settings;
internal event RequestBlockedEventHandler RequestBlocked;
internal RequestHandler(AppConfig appConfig, BrowserFilterSettings settings, ILogger logger, IText text) internal RequestHandler(AppConfig appConfig, BrowserFilterSettings settings, ILogger logger, IText text)
{ {
this.resourceHandler = new ResourceHandler(appConfig, settings, logger, text); this.filter = new RequestFilter();
this.logger = logger;
this.resourceHandler = new ResourceHandler(appConfig, settings, filter, logger, text);
this.settings = settings;
} }
internal void Initiailize() internal void Initiailize()
{ {
if (settings.FilterMainRequests || settings.FilterContentRequests)
{
InitializeFilter();
}
resourceHandler.Initialize(); resourceHandler.Initialize();
} }
@ -32,5 +47,45 @@ namespace SafeExamBrowser.Browser.Handlers
{ {
return resourceHandler; return resourceHandler;
} }
protected override bool OnBeforeBrowse(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
{
if (Block(request))
{
RequestBlocked?.Invoke(request.Url);
return true;
}
return base.OnBeforeBrowse(chromiumWebBrowser, browser, frame, request, userGesture, isRedirect);
}
private bool Block(IRequest request)
{
if (settings.FilterMainRequests)
{
var result = filter.Process(request.Url);
var block = result == FilterResult.Block;
if (block)
{
logger.Info($"Blocked main request for '{request.Url}'.");
}
return block;
}
return false;
}
private void InitializeFilter()
{
foreach (var rule in settings.Rules)
{
filter.Load(rule);
}
logger.Debug($"Initialized request filter with {settings.Rules.Count} rules.");
}
} }
} }

View file

@ -25,14 +25,14 @@ namespace SafeExamBrowser.Browser.Handlers
private BrowserFilterSettings settings; private BrowserFilterSettings settings;
private ILogger logger; private ILogger logger;
private RequestFilter filter; private RequestFilter filter;
private IResourceHandler contentBlockedHandler; private IResourceHandler contentHandler;
private IResourceHandler pageBlockedHandler; private IResourceHandler pageHandler;
private IText text; private IText text;
internal ResourceHandler(AppConfig appConfig, BrowserFilterSettings settings, ILogger logger, IText text) internal ResourceHandler(AppConfig appConfig, BrowserFilterSettings settings, RequestFilter filter, ILogger logger, IText text)
{ {
this.appConfig = appConfig; this.appConfig = appConfig;
this.filter = new RequestFilter(); this.filter = filter;
this.logger = logger; this.logger = logger;
this.settings = settings; this.settings = settings;
this.text = text; this.text = text;
@ -40,22 +40,17 @@ namespace SafeExamBrowser.Browser.Handlers
internal void Initialize() internal void Initialize()
{ {
if (settings.FilterMainRequests || settings.FilterContentRequests) if (settings.FilterContentRequests)
{ {
InitializeFilter(); InitializeResourceHandlers();
} }
} }
protected override IResourceHandler GetResourceHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request) protected override IResourceHandler GetResourceHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request)
{ {
if (BlockMainRequest(request)) if (Block(request))
{ {
return pageBlockedHandler; return ResourceHandlerFor(request.ResourceType);
}
if (BlockContentRequest(request))
{
return contentBlockedHandler;
} }
return base.GetResourceHandler(chromiumWebBrowser, browser, frame, request); return base.GetResourceHandler(chromiumWebBrowser, browser, frame, request);
@ -73,7 +68,7 @@ namespace SafeExamBrowser.Browser.Handlers
return CefReturnValue.Cancel; return CefReturnValue.Cancel;
} }
ReplaceCustomScheme(request); ReplaceSebScheme(request);
return base.OnBeforeResourceLoad(webBrowser, browser, frame, request, callback); return base.OnBeforeResourceLoad(webBrowser, browser, frame, request, callback);
} }
@ -87,9 +82,9 @@ namespace SafeExamBrowser.Browser.Handlers
request.Headers = headers; request.Headers = headers;
} }
private bool BlockContentRequest(IRequest request) private bool Block(IRequest request)
{ {
if (settings.FilterContentRequests && request.ResourceType != ResourceType.MainFrame) if (settings.FilterContentRequests)
{ {
var result = filter.Process(request.Url); var result = filter.Process(request.Url);
var block = result == FilterResult.Block; var block = result == FilterResult.Block;
@ -105,25 +100,7 @@ namespace SafeExamBrowser.Browser.Handlers
return false; return false;
} }
private bool BlockMainRequest(IRequest request) private void InitializeResourceHandlers()
{
if (settings.FilterMainRequests && request.ResourceType == ResourceType.MainFrame)
{
var result = filter.Process(request.Url);
var block = result == FilterResult.Block;
if (block)
{
logger.Info($"Blocked main request for '{request.Url}'.");
}
return block;
}
return false;
}
private void InitializeFilter()
{ {
var assembly = Assembly.GetAssembly(typeof(RequestFilter)); var assembly = Assembly.GetAssembly(typeof(RequestFilter));
var contentMessage = text.Get(TextKey.Browser_BlockedContentMessage); var contentMessage = text.Get(TextKey.Browser_BlockedContentMessage);
@ -136,17 +113,12 @@ namespace SafeExamBrowser.Browser.Handlers
var pageHtml = new StreamReader(pageStream).ReadToEnd(); var pageHtml = new StreamReader(pageStream).ReadToEnd();
contentHtml = contentHtml.Replace("%%MESSAGE%%", contentMessage); contentHtml = contentHtml.Replace("%%MESSAGE%%", contentMessage);
contentHandler = CefSharp.ResourceHandler.FromString(contentHtml);
pageHtml = pageHtml.Replace("%%MESSAGE%%", pageMessage).Replace("%%TITLE%%", pageTitle).Replace("%%BACK_BUTTON%%", pageButton); pageHtml = pageHtml.Replace("%%MESSAGE%%", pageMessage).Replace("%%TITLE%%", pageTitle).Replace("%%BACK_BUTTON%%", pageButton);
pageHandler = CefSharp.ResourceHandler.FromString(pageHtml);
contentBlockedHandler = CefSharp.ResourceHandler.FromString(contentHtml); logger.Debug("Initialized resource handlers for blocked requests.");
pageBlockedHandler = CefSharp.ResourceHandler.FromString(pageHtml);
foreach (var rule in settings.Rules)
{
filter.Load(rule);
}
logger.Debug($"Initialized request filter with {settings.Rules.Count} rules.");
} }
private bool IsMailtoUrl(string url) private bool IsMailtoUrl(string url)
@ -154,7 +126,7 @@ namespace SafeExamBrowser.Browser.Handlers
return url.StartsWith(Uri.UriSchemeMailto); return url.StartsWith(Uri.UriSchemeMailto);
} }
private void ReplaceCustomScheme(IRequest request) private void ReplaceSebScheme(IRequest request)
{ {
if (Uri.IsWellFormedUriString(request.Url, UriKind.RelativeOrAbsolute)) if (Uri.IsWellFormedUriString(request.Url, UriKind.RelativeOrAbsolute))
{ {
@ -170,5 +142,17 @@ namespace SafeExamBrowser.Browser.Handlers
} }
} }
} }
private IResourceHandler ResourceHandlerFor(ResourceType resourceType)
{
switch (resourceType)
{
case ResourceType.MainFrame:
case ResourceType.SubFrame:
return pageHandler;
default:
return contentHandler;
}
}
} }
} }

View file

@ -72,6 +72,8 @@
<Compile Include="Events\FaviconChangedEventHandler.cs" /> <Compile Include="Events\FaviconChangedEventHandler.cs" />
<Compile Include="Events\PopupRequestedEventArgs.cs" /> <Compile Include="Events\PopupRequestedEventArgs.cs" />
<Compile Include="Events\PopupRequestedEventHandler.cs" /> <Compile Include="Events\PopupRequestedEventHandler.cs" />
<Compile Include="Events\ProgressChangedEventHandler.cs" />
<Compile Include="Events\RequestBlockedEventHandler.cs" />
<Compile Include="Filters\RequestFilter.cs" /> <Compile Include="Filters\RequestFilter.cs" />
<Compile Include="Filters\Rules\Rule.cs" /> <Compile Include="Filters\Rules\Rule.cs" />
<Compile Include="Filters\Rules\RegexRule.cs" /> <Compile Include="Filters\Rules\RegexRule.cs" />

View file

@ -24,6 +24,8 @@ namespace SafeExamBrowser.I18n.Contracts
LogWindow_Title, LogWindow_Title,
MessageBox_ApplicationError, MessageBox_ApplicationError,
MessageBox_ApplicationErrorTitle, MessageBox_ApplicationErrorTitle,
MessageBox_BrowserNavigationBlocked,
MessageBox_BrowserNavigationBlockedTitle,
MessageBox_CancelButton, MessageBox_CancelButton,
MessageBox_ClientConfigurationError, MessageBox_ClientConfigurationError,
MessageBox_ClientConfigurationErrorTitle, MessageBox_ClientConfigurationErrorTitle,

View file

@ -30,6 +30,12 @@
<Entry key="MessageBox_ApplicationErrorTitle"> <Entry key="MessageBox_ApplicationErrorTitle">
Application Error Application Error
</Entry> </Entry>
<Entry key="MessageBox_BrowserNavigationBlocked">
Access to "%%URL%%" is not allowed according to the application configuration.
</Entry>
<Entry key="MessageBox_BrowserNavigationBlockedTitle">
Page Blocked
</Entry>
<Entry key="MessageBox_CancelButton"> <Entry key="MessageBox_CancelButton">
Cancel Cancel
</Entry> </Entry>