Compare commits

..

6 commits

1160 changed files with 8842 additions and 36288 deletions

View file

@ -5,131 +5,10 @@ root = true
[*]
end_of_line = crlf
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = false:none
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion
[*.cs]
dotnet_style_object_initializer = false:none
indent_style = tab
csharp_indent_labels = one_less_than_current
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_throw_expression = true:suggestion
csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_prefer_tuple_swap = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
csharp_style_unused_value_assignment_preference = discard_variable:silent
csharp_prefer_static_local_function = true:suggestion
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
csharp_style_conditional_delegate_call = true:suggestion
csharp_style_prefer_switch_expression = true:suggestion
csharp_style_prefer_pattern_matching = true:silent
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_extended_property_pattern = true:suggestion
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
csharp_space_after_cast = true
csharp_space_around_binary_operators = before_and_after
[*.xml]
indent_style = space
[*.{cs,vb}]
#### Naming styles ####
# Naming rules
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_readonly_field = true:suggestion
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_allow_multiple_blank_lines_experimental = true:silent
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
dotnet_code_quality_unused_parameters = all:suggestion
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent
indent_style = space

View file

@ -1,15 +1,15 @@
---
name: Bug Report
about: Create a bug report to help us improve Safe Exam Browser.
about: Create a report to help us improve Safe Exam Browser
title: ''
labels: ''
assignees: dbuechel
---
> [!IMPORTANT]
> - Please _always_ consult the documentation first before creating a bug report: https://safeexambrowser.org/windows/win_usermanual_en.html.
> - Please _always_ attach the log file(s) of the affected session(s)! They can be found under `%LocalAppData%\SafeExamBrowser\Logs`.
**IMPORTANT**
Please _always_ consult the documentation first before creating a bug report!
https://safeexambrowser.org/windows/win_usermanual_en.html
**Describe the Bug**
A clear and concise description of what the bug is.

View file

@ -1,6 +1,6 @@
---
name: Feature Request
about: Suggest an idea or new feature for Safe Exam Browser.
about: Suggest an idea for Safe Exam Browser
title: ''
labels: ''
assignees: dbuechel

View file

@ -1,54 +0,0 @@
name: "CodeQL"
on:
push:
branches: [ "master", "*" ]
pull_request:
branches: [ "master", "*" ]
schedule:
- cron: '0 0 * * 1'
jobs:
analyze:
name: Analyze
runs-on: "windows-latest"
timeout-minutes: 360
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'csharp', 'javascript-typescript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View file

@ -1,25 +0,0 @@
name: Issue Maintenance
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v8
with:
# https://github.com/marketplace/actions/close-stale-issues
days-before-issue-stale: 28
days-before-issue-close: 14
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 28 days with no activity. It will soon be closed automatically if there are no updates."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
exempt-issue-labels: "bug,enhancement,feature request,known issue"
repo-token: ${{ secrets.GITHUB_TOKEN }}

BIN
Libraries/SimpleWifi.dll Normal file

Binary file not shown.

View file

@ -2,25 +2,25 @@
Refactored version of Safe Exam Browser for Windows with Chromium as integrated browser engine.
## Requirements
### Requirements
SEB 3.x requires the prerequisites listed below in order to work correctly. These are automatically installed with the setup bundle and need only be manually installed when using the MSI packages.
* .NET Framework 4.8 Runtime: https://dotnet.microsoft.com/download/dotnet-framework/net48
* .NET Framework 4.7.2 Runtime: https://dotnet.microsoft.com/download/dotnet-framework/net472
* Visual C++ 2015-2019 Redistributable: https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads
## Project Status
### Project Status
> [!WARNING]
> **The builds linked below are for testing purposes only.** They may be unstable and should thus _never_ be used in a production environment! Always use the latest, official release version of SEB.
**_DISCLAIMER_**\
**The builds linked below are for testing purposes only.** They may be unstable and should thus _never_ be used in a production environment! Always use the latest, official release version of SEB.
| Aspect | Status | Details |
| ----------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| Development Build | ![Development Build Status](https://sebdev.ethz.ch/api/projects/status/kq78qrjtnpk82ti0?svg=true) | https://sebdev.ethz.ch/project/appveyor/seb-win-refactoring |
| Test Build | ![Test Build Status](https://ci.appveyor.com/api/projects/status/a56akt9r174570m7?svg=true) | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
| Test Run | ![AppVeyor Tests](https://img.shields.io/appveyor/tests/dbuechel/seb-win-refactoring?logo=appveyor&logoColor=%23ccc) | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
| Code Coverage | ![Code Coverage](https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring/branch/master/graph/badge.svg) | https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring |
| Issue Status | ![GitHub Issues](https://img.shields.io/github/issues/safeexambrowser/seb-win-refactoring?logo=github) | https://github.com/SafeExamBrowser/seb-win-refactoring/issues |
| Downloads | ![GitHub All Releases](https://img.shields.io/github/downloads/safeexambrowser/seb-win-refactoring/total?logo=github) | https://github.com/SafeExamBrowser/seb-win-refactoring/releases |
| Development | ![GitHub Last Commit](https://img.shields.io/github/last-commit/safeexambrowser/seb-win-refactoring?logo=github) | n/a |
| Repository Size | ![GitHub Repo Size](https://img.shields.io/github/repo-size/safeexambrowser/seb-win-refactoring?logo=github) | n/a |
| Aspect | Status | Details |
| --------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| Release Build | ![Release Build Status](https://sebdev-let.ethz.ch/api/projects/status/kq78qrjtnpk82ti0?svg=true) | https://sebdev-let.ethz.ch/project/appveyor/seb-win-refactoring |
| Test Build | ![Test Build Status](https://ci.appveyor.com/api/projects/status/a56akt9r174570m7?svg=true) | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
| Test Run | ![AppVeyor Tests](https://img.shields.io/appveyor/tests/dbuechel/seb-win-refactoring?logo=appveyor&logoColor=%23ccc) | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
| Code Coverage | ![Code Coverage](https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring/branch/master/graph/badge.svg) | https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring |
| Issue Status | ![GitHub Issues](https://img.shields.io/github/issues/safeexambrowser/seb-win-refactoring?logo=github) | https://github.com/SafeExamBrowser/seb-win-refactoring/issues |
| Downloads | ![GitHub All Releases](https://img.shields.io/github/downloads/safeexambrowser/seb-win-refactoring/total?logo=github) | https://github.com/SafeExamBrowser/seb-win-refactoring/releases |
| Development | ![GitHub Last Commit](https://img.shields.io/github/last-commit/safeexambrowser/seb-win-refactoring?logo=github) | n/a |
| Repository Size | ![GitHub Repo Size](https://img.shields.io/github/repo-size/safeexambrowser/seb-win-refactoring?logo=github) | n/a |

View file

@ -1,35 +0,0 @@
# Security Policy
We only support the latest official relese version with respect to security vulnerabilities. Thus, only the latest or then the upcoming next release version
will receive vulnerability fixes and security updates. A vulnerability may however be reported for any version, unless it already has been fixed with a later
release version.
## Reporting a Vulnerability
> [!IMPORTANT]
> - Please _always_ verify that no later release version exists which fixes the vulnerability.
> - Please _always_ consult the documentation first before creating a vulnerability report: https://safeexambrowser.org/windows/win_usermanual_en.html.
> - Please _always_ attach the log file(s) of the affected session(s)! They can be found under `%LocalAppData%\SafeExamBrowser\Logs`.
**Describe the Vulnerability**
A clear and concise description of what the vulnerability is.
**Steps to Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See ...
**Expected Behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Version Information**
- OS: [e.g. Windows 10 Professional, Version 1803]
- SEB-Version [e.g. SEB 3.0.1]
**Additional Context**
Add any other context about the vulnerability here.

View file

@ -1,12 +1,12 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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/.
*/
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
namespace SafeExamBrowser.Applications.Contracts.Events
{

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -9,14 +9,14 @@
using System;
using System.Collections.Generic;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
namespace SafeExamBrowser.Applications.Contracts
{
/// <summary>
/// Controls the lifetime and functionality of an application.
/// </summary>
public interface IApplication<out TWindow> where TWindow : IApplicationWindow
public interface IApplication
{
/// <summary>
/// Indicates whether the application should be automatically started.
@ -51,7 +51,7 @@ namespace SafeExamBrowser.Applications.Contracts
/// <summary>
/// Returns all windows of the application.
/// </summary>
IEnumerable<TWindow> GetWindows();
IEnumerable<IApplicationWindow> GetWindows();
/// <summary>
/// Performs any initialization work, if necessary.

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -18,6 +18,6 @@ namespace SafeExamBrowser.Applications.Contracts
/// <summary>
/// Attempts to create an application according to the given settings.
/// </summary>
FactoryResult TryCreate(WhitelistApplication settings, out IApplication<IApplicationWindow> application);
FactoryResult TryCreate(WhitelistApplication settings, out IApplication application);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -8,12 +8,12 @@
using System;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
namespace SafeExamBrowser.Applications.Contracts
{
/// <summary>
/// Defines a window of an <see cref="IApplication{TWindow}"/>.
/// Defines a window of an <see cref="IApplication"/>.
/// </summary>
public interface IApplicationWindow
{

View file

@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Applications.Contracts")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: AssemblyCopyright("Copyright © 2020 ETH Zürich, Educational Development and Technology (LET)")]
// 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -8,7 +8,7 @@
using System;
namespace SafeExamBrowser.Core.Contracts.Resources.Icons
namespace SafeExamBrowser.Applications.Contracts.Resources.Icons
{
/// <summary>
/// Defines an icon resource which is a bitmap image (i.e. raster graphics).

View file

@ -1,12 +1,12 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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.Core.Contracts.Resources.Icons
namespace SafeExamBrowser.Applications.Contracts.Resources.Icons
{
/// <summary>
/// Defines an icon resource which is a file with embedded icon data (e.g. an executable).

View file

@ -1,12 +1,12 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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.Core.Contracts.Resources.Icons
namespace SafeExamBrowser.Applications.Contracts.Resources.Icons
{
/// <summary>
/// Defines an icon resource.

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -8,7 +8,7 @@
using System;
namespace SafeExamBrowser.Core.Contracts.Resources.Icons
namespace SafeExamBrowser.Applications.Contracts.Resources.Icons
{
/// <summary>
/// Defines an icon resource which is managed by the operating system.

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -8,7 +8,7 @@
using System;
namespace SafeExamBrowser.Core.Contracts.Resources.Icons
namespace SafeExamBrowser.Applications.Contracts.Resources.Icons
{
/// <summary>
/// Defines an icon resource which consists of XAML markup (i.e. vector graphics).

View file

@ -9,10 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Applications.Contracts</RootNamespace>
<AssemblyName>SafeExamBrowser.Applications.Contracts</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
@ -62,13 +61,14 @@
<Compile Include="IApplication.cs" />
<Compile Include="IApplicationFactory.cs" />
<Compile Include="IApplicationWindow.cs" />
<Compile Include="Resources\Icons\BitmapIconResource.cs" />
<Compile Include="Resources\Icons\EmbeddedIconResource.cs" />
<Compile Include="Resources\Icons\IconResource.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Resources\Icons\NativeIconResource.cs" />
<Compile Include="Resources\Icons\XamlIconResource.cs" />
</ItemGroup>
<ItemGroup>
<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.Settings\SafeExamBrowser.Settings.csproj">
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name>

View file

@ -1,126 +0,0 @@
/*
* 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Settings.Applications;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications.UnitTests
{
[TestClass]
public class ApplicationFactoryTests
{
private Mock<IApplicationMonitor> applicationMonitor;
private Mock<IModuleLogger> logger;
private Mock<INativeMethods> nativeMethods;
private Mock<IProcessFactory> processFactory;
private Mock<IRegistry> registry;
private ApplicationFactory sut;
[TestInitialize]
public void Initialize()
{
applicationMonitor = new Mock<IApplicationMonitor>();
logger = new Mock<IModuleLogger>();
nativeMethods = new Mock<INativeMethods>();
processFactory = new Mock<IProcessFactory>();
registry = new Mock<IRegistry>();
sut = new ApplicationFactory(applicationMonitor.Object, logger.Object, nativeMethods.Object, processFactory.Object, registry.Object);
}
[TestMethod]
public void MustCorrectlyCreateApplication()
{
var settings = new WhitelistApplication
{
DisplayName = "Windows Command Prompt",
ExecutableName = "cmd.exe",
};
var result = sut.TryCreate(settings, out var application);
Assert.AreEqual(FactoryResult.Success, result);
Assert.IsNotNull(application);
Assert.IsInstanceOfType<ExternalApplication>(application);
}
[TestMethod]
public void MustCorrectlyReadPathFromRegistry()
{
object o = @"C:\Some\Registry\Path";
var settings = new WhitelistApplication
{
DisplayName = "Windows Command Prompt",
ExecutableName = "cmd.exe",
ExecutablePath = @"C:\Some\Path"
};
registry.Setup(r => r.TryRead(It.Is<string>(s => s.Contains(RegistryValue.MachineHive.AppPaths_Key)), It.Is<string>(s => s == "Path"), out o)).Returns(true);
var result = sut.TryCreate(settings, out var application);
registry.Verify(r => r.TryRead(It.Is<string>(s => s.Contains(RegistryValue.MachineHive.AppPaths_Key)), It.Is<string>(s => s == "Path"), out o), Times.Once);
Assert.AreEqual(FactoryResult.Success, result);
Assert.IsNotNull(application);
Assert.IsInstanceOfType<ExternalApplication>(application);
}
[TestMethod]
public void MustIndicateIfApplicationNotFound()
{
var settings = new WhitelistApplication
{
ExecutableName = "some_random_application_which_does_not_exist_on_a_normal_system.exe",
ExecutablePath = "Some/Path/Which/Does/Not/Exist"
};
var result = sut.TryCreate(settings, out var application);
Assert.AreEqual(FactoryResult.NotFound, result);
Assert.IsNull(application);
}
[TestMethod]
public void MustFailGracefullyWhenPathIsInvalid()
{
var settings = new WhitelistApplication
{
ExecutableName = "asdfg(/ç)&=%\"fsdg..exe..",
ExecutablePath = "[]#°§¬#°¢@tu03450'w89tz!$£äöüèé:"
};
var result = sut.TryCreate(settings, out _);
logger.Verify(l => l.Error(It.IsAny<string>(), It.IsAny<Exception>()), Times.AtLeastOnce);
Assert.AreEqual(FactoryResult.NotFound, result);
}
[TestMethod]
public void MustFailGracefullyAndIndicateThatErrorOccurred()
{
var o = default(object);
var settings = new WhitelistApplication();
registry.Setup(r => r.TryRead(It.IsAny<string>(), It.IsAny<string>(), out o)).Throws<Exception>();
var result = sut.TryCreate(settings, out var application);
Assert.AreEqual(FactoryResult.Error, result);
}
}
}

View file

@ -1,62 +0,0 @@
/*
* 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications.UnitTests
{
[TestClass]
public class ExternalApplicationInstanceTests
{
private NativeIconResource icon;
private Mock<ILogger> logger;
private Mock<INativeMethods> nativeMethods;
private Mock<IProcess> process;
private ExternalApplicationInstance sut;
[TestInitialize]
public void Initialize()
{
icon = new NativeIconResource();
logger = new Mock<ILogger>();
nativeMethods = new Mock<INativeMethods>();
process = new Mock<IProcess>();
sut = new ExternalApplicationInstance(icon, logger.Object, nativeMethods.Object, process.Object, 1);
}
[TestMethod]
public void Terminate_MustDoNothingIfAlreadyTerminated()
{
process.SetupGet(p => p.HasTerminated).Returns(true);
sut.Terminate();
process.Verify(p => p.TryClose(It.IsAny<int>()), Times.Never());
process.Verify(p => p.TryKill(It.IsAny<int>()), Times.Never());
}
[TestMethod]
public void Terminate_MustLogIfTerminationFailed()
{
process.Setup(p => p.TryClose(It.IsAny<int>())).Returns(false);
process.Setup(p => p.TryKill(It.IsAny<int>())).Returns(false);
process.SetupGet(p => p.HasTerminated).Returns(false);
sut.Terminate();
logger.Verify(l => l.Warn(It.IsAny<string>()), Times.AtLeastOnce);
process.Verify(p => p.TryClose(It.IsAny<int>()), Times.AtLeastOnce());
process.Verify(p => p.TryKill(It.IsAny<int>()), Times.AtLeastOnce());
}
}
}

View file

@ -1,216 +0,0 @@
/*
* 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 System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Monitoring.Contracts.Applications.Events;
using SafeExamBrowser.Settings.Applications;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications.UnitTests
{
[TestClass]
public class ExternalApplicationTests
{
private Mock<IApplicationMonitor> applicationMonitor;
private string executablePath;
private Mock<IModuleLogger> logger;
private Mock<INativeMethods> nativeMethods;
private Mock<IProcessFactory> processFactory;
private WhitelistApplication settings;
private ExternalApplication sut;
[TestInitialize]
public void Initialize()
{
applicationMonitor = new Mock<IApplicationMonitor>();
executablePath = @"C:\Some\Random\Path\Application.exe";
logger = new Mock<IModuleLogger>();
nativeMethods = new Mock<INativeMethods>();
processFactory = new Mock<IProcessFactory>();
settings = new WhitelistApplication();
logger.Setup(l => l.CloneFor(It.IsAny<string>())).Returns(new Mock<IModuleLogger>().Object);
sut = new ExternalApplication(applicationMonitor.Object, executablePath, logger.Object, nativeMethods.Object, processFactory.Object, settings, 1);
}
[TestMethod]
public void GetWindows_MustCorrectlyReturnOpenWindows()
{
var openWindows = new List<IntPtr> { new IntPtr(123), new IntPtr(234), new IntPtr(456), new IntPtr(345), new IntPtr(567), new IntPtr(789) };
var process1 = new Mock<IProcess>();
var process2 = new Mock<IProcess>();
var sync = new AutoResetEvent(false);
nativeMethods.Setup(n => n.GetOpenWindows()).Returns(openWindows);
nativeMethods.Setup(n => n.GetProcessIdFor(It.Is<IntPtr>(p => p == new IntPtr(234)))).Returns(1234);
nativeMethods.Setup(n => n.GetProcessIdFor(It.Is<IntPtr>(p => p == new IntPtr(345)))).Returns(1234);
nativeMethods.Setup(n => n.GetProcessIdFor(It.Is<IntPtr>(p => p == new IntPtr(567)))).Returns(5678);
process1.Setup(p => p.TryClose(It.IsAny<int>())).Returns(false);
process1.Setup(p => p.TryKill(It.IsAny<int>())).Returns(true);
process1.SetupGet(p => p.Id).Returns(1234);
process2.Setup(p => p.TryClose(It.IsAny<int>())).Returns(true);
process2.SetupGet(p => p.Id).Returns(5678);
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process1.Object);
sut.WindowsChanged += () => sync.Set();
sut.Initialize();
sut.Start();
applicationMonitor.Raise(m => m.InstanceStarted += null, sut.Id, process2.Object);
sync.WaitOne();
sync.WaitOne();
var windows = sut.GetWindows();
Assert.AreEqual(3, windows.Count());
Assert.IsTrue(windows.Any(w => w.Handle == new IntPtr(234)));
Assert.IsTrue(windows.Any(w => w.Handle == new IntPtr(345)));
Assert.IsTrue(windows.Any(w => w.Handle == new IntPtr(567)));
nativeMethods.Setup(n => n.GetOpenWindows()).Returns(openWindows.Skip(2));
Task.Run(() => process2.Raise(p => p.Terminated += null, default(int)));
sync.WaitOne();
sync.WaitOne();
windows = sut.GetWindows();
Assert.AreEqual(1, windows.Count());
Assert.IsTrue(windows.Any(w => w.Handle != new IntPtr(234)));
Assert.IsTrue(windows.Any(w => w.Handle == new IntPtr(345)));
Assert.IsTrue(windows.All(w => w.Handle != new IntPtr(567)));
}
[TestMethod]
public void Initialize_MustInitializeCorrectly()
{
settings.AutoStart = new Random().Next(2) == 1;
settings.Description = "Some Description";
sut.Initialize();
applicationMonitor.VerifyAdd(a => a.InstanceStarted += It.IsAny<InstanceStartedEventHandler>(), Times.Once);
Assert.AreEqual(settings.AutoStart, sut.AutoStart);
Assert.AreEqual(executablePath, (sut.Icon as EmbeddedIconResource).FilePath);
Assert.AreEqual(settings.Id, settings.Id);
Assert.AreEqual(settings.DisplayName, sut.Name);
Assert.AreEqual(settings.Description ?? settings.DisplayName, sut.Tooltip);
}
[TestMethod]
public void Start_MustCreateInstanceCorrectly()
{
settings.Arguments.Add("some_parameter");
settings.Arguments.Add("another_parameter");
settings.Arguments.Add("yet another parameter");
sut.Start();
processFactory.Verify(f => f.StartNew(executablePath, It.Is<string[]>(args => args.All(a => settings.Arguments.Contains(a)))), Times.Once);
}
[TestMethod]
public void Start_MustHandleFailureGracefully()
{
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Throws<Exception>();
sut.Start();
logger.Verify(l => l.Error(It.IsAny<string>(), It.IsAny<Exception>()), Times.AtLeastOnce);
processFactory.Verify(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>()), Times.Once);
}
[TestMethod]
public void Start_MustRemoveInstanceCorrectlyWhenTerminated()
{
var eventCount = 0;
var openWindows = new List<IntPtr> { new IntPtr(123), new IntPtr(234), new IntPtr(456), new IntPtr(345), new IntPtr(567), new IntPtr(789), };
var process = new Mock<IProcess>();
var sync = new AutoResetEvent(false);
nativeMethods.Setup(n => n.GetOpenWindows()).Returns(openWindows);
nativeMethods.Setup(n => n.GetProcessIdFor(It.Is<IntPtr>(p => p == new IntPtr(234)))).Returns(1234);
process.Setup(p => p.TryClose(It.IsAny<int>())).Returns(false);
process.Setup(p => p.TryKill(It.IsAny<int>())).Returns(true);
process.SetupGet(p => p.Id).Returns(1234);
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process.Object);
sut.WindowsChanged += () =>
{
eventCount++;
sync.Set();
};
sut.Initialize();
sut.Start();
sync.WaitOne();
Assert.AreEqual(1, sut.GetWindows().Count());
process.Raise(p => p.Terminated += null, default(int));
Assert.AreEqual(2, eventCount);
Assert.AreEqual(0, sut.GetWindows().Count());
}
[TestMethod]
public void Terminate_MustStopAllInstancesCorrectly()
{
var process1 = new Mock<IProcess>();
var process2 = new Mock<IProcess>();
process1.Setup(p => p.TryClose(It.IsAny<int>())).Returns(false);
process1.Setup(p => p.TryKill(It.IsAny<int>())).Returns(true);
process1.SetupGet(p => p.Id).Returns(1234);
process2.Setup(p => p.TryClose(It.IsAny<int>())).Returns(true);
process2.SetupGet(p => p.Id).Returns(5678);
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process1.Object);
sut.Initialize();
sut.Start();
applicationMonitor.Raise(m => m.InstanceStarted += null, sut.Id, process2.Object);
sut.Terminate();
process1.Verify(p => p.TryClose(It.IsAny<int>()), Times.AtLeastOnce);
process1.Verify(p => p.TryKill(It.IsAny<int>()), Times.Once);
process2.Verify(p => p.TryClose(It.IsAny<int>()), Times.Once);
process2.Verify(p => p.TryKill(It.IsAny<int>()), Times.Never);
}
[TestMethod]
public void Terminate_MustHandleFailureGracefully()
{
var process = new Mock<IProcess>();
process.Setup(p => p.TryClose(It.IsAny<int>())).Throws<Exception>();
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process.Object);
sut.Initialize();
sut.Start();
sut.Terminate();
process.Verify(p => p.TryClose(It.IsAny<int>()), Times.AtLeastOnce);
process.Verify(p => p.TryKill(It.IsAny<int>()), Times.Never);
}
}
}

View file

@ -1,64 +0,0 @@
/*
* 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications.UnitTests
{
[TestClass]
public class ExternalApplicationWindowTests
{
private IntPtr handle;
private NativeIconResource icon;
private Mock<INativeMethods> nativeMethods;
private ExternalApplicationWindow sut;
[TestInitialize]
public void Initialize()
{
handle = new IntPtr(123);
icon = new NativeIconResource();
nativeMethods = new Mock<INativeMethods>();
sut = new ExternalApplicationWindow(icon, nativeMethods.Object, handle);
}
[TestMethod]
public void Activate_MustCorrectlyActivateWindow()
{
sut.Activate();
nativeMethods.Verify(n => n.ActivateWindow(It.Is<IntPtr>(h => h == handle)));
}
[TestMethod]
public void Update_MustCorrectlyUpdateWindow()
{
var iconChanged = false;
var titleChanged = false;
nativeMethods.Setup(m => m.GetWindowIcon(It.IsAny<IntPtr>())).Returns(new IntPtr(456));
nativeMethods.Setup(m => m.GetWindowTitle((It.IsAny<IntPtr>()))).Returns("Some New Window Title");
sut.IconChanged += (_) => iconChanged = true;
sut.TitleChanged += (_) => titleChanged = true;
sut.Update();
nativeMethods.Verify(m => m.GetWindowIcon(handle), Times.Once);
nativeMethods.Verify(m => m.GetWindowTitle(handle), Times.Once);
Assert.IsTrue(iconChanged);
Assert.IsTrue(titleChanged);
}
}
}

View file

@ -1,16 +0,0 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("SafeExamBrowser.Applications.UnitTests")]
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Applications.UnitTests")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: ComVisible(false)]
[assembly: Guid("fc6d80ec-8611-4287-87e2-17c028a10858")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0.0")]

View file

@ -1,199 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props')" />
<Import Project="..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props" Condition="Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" />
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.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>{FC6D80EC-8611-4287-87E2-17C028A10858}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Applications.UnitTests</RootNamespace>
<AssemblyName>SafeExamBrowser.Applications.UnitTests</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
</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>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<Reference Include="Castle.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.5.1.1\lib\net462\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ApplicationInsights, Version=2.22.0.997, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.2.22.0\lib\net46\Microsoft.ApplicationInsights.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Testing.Extensions.Telemetry, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Extensions.TrxReport.Abstractions, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.TrxReport.Abstractions.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.TrxReport.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Extensions.VSTestBridge, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.VSTestBridge.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.VSTestBridge.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Platform, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\lib\netstandard2.0\Microsoft.Testing.Platform.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Platform.MSBuild, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\lib\netstandard2.0\Microsoft.Testing.Platform.MSBuild.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.TestPlatform.CoreUtilities.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TestPlatform.PlatformAbstractions, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.TestPlatform.PlatformAbstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.ObjectModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.3.2.2\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.3.2.2\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.20.70.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.20.70\lib\net462\Moq.dll</HintPath>
</Reference>
<Reference Include="NuGet.Frameworks, Version=6.9.1.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\NuGet.Frameworks.6.9.1\lib\net472\NuGet.Frameworks.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Immutable, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.8.0.0\lib\net462\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Diagnostics.DiagnosticSource, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Diagnostics.DiagnosticSource.8.0.0\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Metadata, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reflection.Metadata.8.0.0\lib\net462\System.Reflection.Metadata.dll</HintPath>
</Reference>
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ApplicationFactoryTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ExternalApplicationTests.cs" />
<Compile Include="ExternalApplicationWindowTests.cs" />
<Compile Include="ExternalApplicationInstanceTests.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</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.Applications\SafeExamBrowser.Applications.csproj">
<Project>{a113e68f-1209-4689-981a-15c554b2df4e}</Project>
<Name>SafeExamBrowser.Applications</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.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
<Name>SafeExamBrowser.Logging.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Monitoring.Contracts\SafeExamBrowser.Monitoring.Contracts.csproj">
<Project>{6d563a30-366d-4c35-815b-2c9e6872278b}</Project>
<Name>SafeExamBrowser.Monitoring.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.SystemComponents.Contracts\SafeExamBrowser.SystemComponents.Contracts.csproj">
<Project>{903129c6-e236-493b-9ad6-c6a57f647a3a}</Project>
<Name>SafeExamBrowser.SystemComponents.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>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<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\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets'))" />
</Target>
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets')" />
<Import Project="..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets')" />
</Project>

View file

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="NuGet.Frameworks" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.11.3.1" newVersion="5.11.3.1" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.ApplicationInsights" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.22.0.997" newVersion="2.22.0.997" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" /></startup></configuration>

View file

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Castle.Core" version="5.1.1" targetFramework="net48" />
<package id="Microsoft.ApplicationInsights" version="2.22.0" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.Telemetry" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.TrxReport.Abstractions" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.VSTestBridge" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Platform" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Platform.MSBuild" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.TestPlatform.ObjectModel" version="17.9.0" targetFramework="net48" />
<package id="Moq" version="4.20.70" targetFramework="net48" />
<package id="MSTest.TestAdapter" version="3.2.2" targetFramework="net48" />
<package id="MSTest.TestFramework" version="3.2.2" targetFramework="net48" />
<package id="NuGet.Frameworks" version="6.9.1" targetFramework="net48" />
<package id="System.Buffers" version="4.5.1" targetFramework="net48" />
<package id="System.Collections.Immutable" version="8.0.0" targetFramework="net48" />
<package id="System.Diagnostics.DiagnosticSource" version="8.0.0" targetFramework="net48" />
<package id="System.Memory" version="4.5.5" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net48" />
<package id="System.Reflection.Metadata" version="8.0.0" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net48" />
</packages>

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -9,42 +9,39 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Win32;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Settings.Applications;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications
{
public class ApplicationFactory : IApplicationFactory
{
private readonly IApplicationMonitor applicationMonitor;
private readonly IModuleLogger logger;
private readonly INativeMethods nativeMethods;
private readonly IProcessFactory processFactory;
private readonly IRegistry registry;
private IApplicationMonitor applicationMonitor;
private IModuleLogger logger;
private INativeMethods nativeMethods;
private IProcessFactory processFactory;
public ApplicationFactory(
IApplicationMonitor applicationMonitor,
IModuleLogger logger,
INativeMethods nativeMethods,
IProcessFactory processFactory,
IRegistry registry)
IProcessFactory processFactory)
{
this.applicationMonitor = applicationMonitor;
this.logger = logger;
this.nativeMethods = nativeMethods;
this.processFactory = processFactory;
this.registry = registry;
}
public FactoryResult TryCreate(WhitelistApplication settings, out IApplication<IApplicationWindow> application)
public FactoryResult TryCreate(WhitelistApplication settings, out IApplication application)
{
var name = $"'{settings.DisplayName}' ({settings.ExecutableName})";
var name = $"'{settings.DisplayName}' ({ settings.ExecutableName})";
application = default;
application = default(IApplication);
try
{
@ -72,12 +69,10 @@ namespace SafeExamBrowser.Applications
return FactoryResult.Error;
}
private IApplication<IApplicationWindow> BuildApplication(string executablePath, WhitelistApplication settings)
private IApplication BuildApplication(string executablePath, WhitelistApplication settings)
{
const int ONE_SECOND = 1000;
var applicationLogger = logger.CloneFor(settings.DisplayName);
var application = new ExternalApplication(applicationMonitor, executablePath, applicationLogger, nativeMethods, processFactory, settings, ONE_SECOND);
var application = new ExternalApplication(applicationMonitor, executablePath, applicationLogger, nativeMethods, processFactory, settings);
return application;
}
@ -87,14 +82,14 @@ namespace SafeExamBrowser.Applications
var paths = new List<string[]>();
var registryPath = QueryPathFromRegistry(settings);
mainExecutable = default;
mainExecutable = default(string);
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), settings.ExecutableName });
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), settings.ExecutableName });
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.System), settings.ExecutableName });
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), settings.ExecutableName });
if (settings.ExecutablePath != default)
if (settings.ExecutablePath != default(string))
{
paths.Add(new[] { settings.ExecutablePath, settings.ExecutableName });
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), settings.ExecutablePath, settings.ExecutableName });
@ -103,11 +98,11 @@ namespace SafeExamBrowser.Applications
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), settings.ExecutablePath, settings.ExecutableName });
}
if (registryPath != default)
if (registryPath != default(string))
{
paths.Add(new[] { registryPath, settings.ExecutableName });
if (settings.ExecutablePath != default)
if (settings.ExecutablePath != default(string))
{
paths.Add(new[] { registryPath, settings.ExecutablePath, settings.ExecutableName });
}
@ -136,12 +131,22 @@ namespace SafeExamBrowser.Applications
private string QueryPathFromRegistry(WhitelistApplication settings)
{
if (registry.TryRead($@"{RegistryValue.MachineHive.AppPaths_Key}\{settings.ExecutableName}", "Path", out var value))
try
{
return value as string;
using (var key = Registry.LocalMachine.OpenSubKey($@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{settings.ExecutableName}"))
{
if (key != null)
{
return key.GetValue("Path") as string;
}
}
}
catch (Exception e)
{
logger.Error($"Failed to query path in registry for '{settings.ExecutableName}'!", e);
}
return default;
return default(string);
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -11,7 +11,7 @@ using System.Collections.Generic;
using System.Linq;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Settings.Applications;
@ -19,18 +19,17 @@ using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications
{
internal class ExternalApplication : IApplication<IApplicationWindow>
internal class ExternalApplication : IApplication
{
private readonly object @lock = new object();
private readonly IApplicationMonitor applicationMonitor;
private readonly string executablePath;
private readonly IList<ExternalApplicationInstance> instances;
private readonly IModuleLogger logger;
private readonly INativeMethods nativeMethods;
private readonly IProcessFactory processFactory;
private readonly WhitelistApplication settings;
private readonly int windowMonitoringInterval;
private IApplicationMonitor applicationMonitor;
private string executablePath;
private IModuleLogger logger;
private INativeMethods nativeMethods;
private IList<ExternalApplicationInstance> instances;
private IProcessFactory processFactory;
private WhitelistApplication settings;
public bool AutoStart { get; private set; }
public IconResource Icon { get; private set; }
@ -46,8 +45,7 @@ namespace SafeExamBrowser.Applications
IModuleLogger logger,
INativeMethods nativeMethods,
IProcessFactory processFactory,
WhitelistApplication settings,
int windowMonitoringInterval_ms)
WhitelistApplication settings)
{
this.applicationMonitor = applicationMonitor;
this.executablePath = executablePath;
@ -56,7 +54,6 @@ namespace SafeExamBrowser.Applications
this.instances = new List<ExternalApplicationInstance>();
this.processFactory = processFactory;
this.settings = settings;
this.windowMonitoringInterval = windowMonitoringInterval_ms;
}
public IEnumerable<IApplicationWindow> GetWindows()
@ -92,6 +89,18 @@ namespace SafeExamBrowser.Applications
}
}
private string[] BuildArguments()
{
var arguments = new List<string>();
foreach (var argument in settings.Arguments)
{
arguments.Add(Environment.ExpandEnvironmentVariables(argument));
}
return arguments.ToArray();
}
public void Terminate()
{
applicationMonitor.InstanceStarted -= ApplicationMonitor_InstanceStarted;
@ -144,24 +153,12 @@ namespace SafeExamBrowser.Applications
WindowsChanged?.Invoke();
}
private string[] BuildArguments()
{
var arguments = new List<string>();
foreach (var argument in settings.Arguments)
{
arguments.Add(Environment.ExpandEnvironmentVariables(argument));
}
return arguments.ToArray();
}
private void InitializeInstance(IProcess process)
{
lock (@lock)
{
var instanceLogger = logger.CloneFor($"{Name} ({process.Id})");
var instance = new ExternalApplicationInstance(Icon, instanceLogger, nativeMethods, process, windowMonitoringInterval);
var instance = new ExternalApplicationInstance(Icon, instanceLogger, nativeMethods, process);
instance.Terminated += Instance_Terminated;
instance.WindowsChanged += () => WindowsChanged?.Invoke();

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -12,8 +12,8 @@ using System.Linq;
using System.Timers;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.Applications.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Contracts;
@ -23,32 +23,24 @@ namespace SafeExamBrowser.Applications
{
private readonly object @lock = new object();
private readonly IconResource icon;
private readonly ILogger logger;
private readonly INativeMethods nativeMethods;
private readonly IProcess process;
private readonly int windowMonitoringInterval;
private readonly IList<ExternalApplicationWindow> windows;
private IconResource icon;
private ILogger logger;
private INativeMethods nativeMethods;
private IProcess process;
private Timer timer;
private IList<ExternalApplicationWindow> windows;
internal int Id { get; private set; }
internal event InstanceTerminatedEventHandler Terminated;
internal event WindowsChangedEventHandler WindowsChanged;
internal ExternalApplicationInstance(
IconResource icon,
ILogger logger,
INativeMethods nativeMethods,
IProcess process,
int windowMonitoringInterval_ms)
internal ExternalApplicationInstance(IconResource icon, ILogger logger, INativeMethods nativeMethods, IProcess process)
{
this.icon = icon;
this.logger = logger;
this.nativeMethods = nativeMethods;
this.process = process;
this.windowMonitoringInterval = windowMonitoringInterval_ms;
this.windows = new List<ExternalApplicationWindow>();
}
@ -153,20 +145,19 @@ namespace SafeExamBrowser.Applications
private void InitializeEvents()
{
const int ONE_SECOND = 1000;
process.Terminated += Process_Terminated;
timer = new Timer(windowMonitoringInterval);
timer = new Timer(ONE_SECOND);
timer.Elapsed += Timer_Elapsed;
timer.Start();
}
private void FinalizeEvents()
{
if (timer != default)
{
timer.Elapsed -= Timer_Elapsed;
timer.Stop();
}
timer.Elapsed -= Timer_Elapsed;
timer.Stop();
process.Terminated -= Process_Terminated;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -9,14 +9,14 @@
using System;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Applications
{
internal class ExternalApplicationWindow : IApplicationWindow
{
private readonly INativeMethods nativeMethods;
private INativeMethods nativeMethods;
public IntPtr Handle { get; }
public IconResource Icon { get; private set; }

View file

@ -1,5 +1,4 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
@ -9,13 +8,12 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Applications")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: AssemblyCopyright("Copyright © 2020 ETH Zürich, Educational Development and Technology (LET)")]
// 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.Applications.UnitTests")]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("a113e68f-1209-4689-981a-15c554b2df4e")]

View file

@ -9,10 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Applications</RootNamespace>
<AssemblyName>SafeExamBrowser.Applications</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
@ -67,10 +66,6 @@
<Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project>
<Name>SafeExamBrowser.Applications.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.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
<Name>SafeExamBrowser.Logging.Contracts</Name>
@ -83,10 +78,6 @@
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.SystemComponents.Contracts\SafeExamBrowser.SystemComponents.Contracts.csproj">
<Project>{903129c6-e236-493b-9ad6-c6a57f647a3a}</Project>
<Name>SafeExamBrowser.SystemComponents.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj">
<Project>{7016f080-9aa5-41b2-a225-385ad877c171}</Project>
<Name>SafeExamBrowser.WindowsApi.Contracts</Name>

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,15 +0,0 @@
/*
* 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.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that the user wants to move the focus away from the item.
/// </summary>
public delegate void LoseFocusRequestedEventHandler(bool forward);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -9,7 +9,7 @@
namespace SafeExamBrowser.Browser.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that the browser has detected a user identifier of an LMS.
/// Event handler used to indicate that the browser has detected a session identifier of a LMS.
/// </summary>
public delegate void UserIdentifierDetectedEventHandler(string identifier);
public delegate void SessionIdentifierDetectedEventHandler(string identifier);
}

View file

@ -1,15 +0,0 @@
/*
* 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.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that the user pressed the tab key to move the focus forward or backward.
/// </summary>
public delegate void TabPressedEventHandler(bool forward);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -14,7 +14,7 @@ namespace SafeExamBrowser.Browser.Contracts
/// <summary>
/// Controls the lifetime and functionality of the browser application.
/// </summary>
public interface IBrowserApplication : IApplication<IBrowserWindow>
public interface IBrowserApplication : IApplication
{
/// <summary>
/// Event fired when the browser application detects a download request for an application configuration file.
@ -22,24 +22,13 @@ namespace SafeExamBrowser.Browser.Contracts
event DownloadRequestedEventHandler ConfigurationDownloadRequested;
/// <summary>
/// Event fired when the user tries to focus the taskbar.
/// Event fired when the browser application detects a session identifier of an LMS.
/// </summary>
event LoseFocusRequestedEventHandler LoseFocusRequested;
event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
/// <summary>
/// Event fired when the browser application detects a request to terminate SEB.
/// </summary>
event TerminationRequestedEventHandler TerminationRequested;
/// <summary>
/// Event fired when the browser application detects a user identifier of an LMS.
/// </summary>
event UserIdentifierDetectedEventHandler UserIdentifierDetected;
/// <summary>
/// Transfers the focus to the browser application. If the parameter is <c>true</c>, the first focusable element in the browser window
/// receives focus (passing forward of focus). Otherwise, the last element receives focus.
/// </summary>
void Focus(bool forward);
}
}

View file

@ -1,28 +0,0 @@
/*
* 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.Applications.Contracts;
namespace SafeExamBrowser.Browser.Contracts
{
/// <summary>
/// Defines a window of the <see cref="IBrowserApplication"/>.
/// </summary>
public interface IBrowserWindow : IApplicationWindow
{
/// <summary>
/// Indicates whether the window is the main browser window.
/// </summary>
bool IsMainWindow { get; }
/// <summary>
/// The currently loaded URL, or <c>default(string)</c> in case no navigation has happened yet.
/// </summary>
string Url { get; }
}
}

View file

@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Browser.Contracts")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: AssemblyCopyright("Copyright © 2020 ETH Zürich, Educational Development and Technology (LET)")]
// 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

View file

@ -9,10 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Browser.Contracts</RootNamespace>
<AssemblyName>SafeExamBrowser.Browser.Contracts</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
@ -58,16 +57,13 @@
<Compile Include="Events\DownloadEventArgs.cs" />
<Compile Include="Events\DownloadFinishedCallback.cs" />
<Compile Include="Events\DownloadRequestedEventHandler.cs" />
<Compile Include="Events\TabPressedEventHandler.cs" />
<Compile Include="Events\LoseFocusRequestedEventHandler.cs" />
<Compile Include="Events\UserIdentifierDetectedEventHandler.cs" />
<Compile Include="Events\SessionIdentifierDetectedEventHandler.cs" />
<Compile Include="Events\TerminationRequestedEventHandler.cs" />
<Compile Include="Filters\IRequestFilter.cs" />
<Compile Include="Filters\IRule.cs" />
<Compile Include="Filters\IRuleFactory.cs" />
<Compile Include="Filters\Request.cs" />
<Compile Include="IBrowserApplication.cs" />
<Compile Include="IBrowserWindow.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla internal
* License, v. 2.0. If a copy of the MPL was not distributed with this

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -31,7 +31,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
[TestMethod]
public void MustCorrectlyCancelDialog()
{
RequestDialog(default, false);
RequestDialog(default(CefFileDialogMode), false);
}
[TestMethod]
@ -71,7 +71,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
var threadId = default(int);
callback.Setup(c => c.Cancel()).Callback(() => sync.Set());
callback.Setup(c => c.Continue(It.IsAny<List<string>>())).Callback(() => sync.Set());
callback.Setup(c => c.Continue(It.IsAny<int>(), It.IsAny<List<string>>())).Callback(() => sync.Set());
sut.DialogRequested += (a) =>
{
args = a;
@ -80,18 +80,18 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
threadId = Thread.CurrentThread.ManagedThreadId;
};
var status = sut.OnFileDialog(default, default, mode, title, initialPath, default, callback.Object);
var status = sut.OnFileDialog(default(IWebBrowser), default(IBrowser), mode, default(CefFileDialogFlags), title, initialPath, default(List<string>), default(int), callback.Object);
sync.WaitOne();
if (confirm)
{
callback.Verify(c => c.Continue(It.IsAny<List<string>>()), Times.Once);
callback.Verify(c => c.Continue(It.IsAny<int>(), It.IsAny<List<string>>()), Times.Once);
callback.Verify(c => c.Cancel(), Times.Never);
}
else
{
callback.Verify(c => c.Continue(It.IsAny<List<string>>()), Times.Never);
callback.Verify(c => c.Continue(It.IsAny<int>(), It.IsAny<List<string>>()), Times.Never);
callback.Verify(c => c.Cancel(), Times.Once);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -7,6 +7,8 @@
*/
using System.Collections.Generic;
using CefSharp;
using CefSharp.Structs;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SafeExamBrowser.Browser.Handlers;
@ -28,10 +30,9 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
var text = default(string);
Assert.IsFalse(sut.OnAutoResize(default, default, default));
Assert.IsFalse(sut.OnConsoleMessage(default, default));
Assert.IsFalse(sut.OnCursorChange(default, default, default, default, default));
Assert.IsFalse(sut.OnTooltipChanged(default, ref text));
Assert.IsFalse(sut.OnAutoResize(default(IWebBrowser), default(IBrowser), default(Size)));
Assert.IsFalse(sut.OnConsoleMessage(default(IWebBrowser), default(ConsoleMessageEventArgs)));
Assert.IsFalse(sut.OnTooltipChanged(default(IWebBrowser), ref text));
}
[TestMethod]
@ -46,12 +47,12 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
called = true;
url = u;
};
sut.OnFaviconUrlChange(default, default, new List<string>());
sut.OnFaviconUrlChange(default(IWebBrowser), default(IBrowser), new List<string>());
Assert.AreEqual(default, url);
Assert.AreEqual(default(string), url);
Assert.IsFalse(called);
sut.OnFaviconUrlChange(default, default, new List<string> { newUrl });
sut.OnFaviconUrlChange(default(IWebBrowser), default(IBrowser), new List<string> { newUrl });
Assert.AreEqual(newUrl, url);
Assert.IsTrue(called);
@ -64,7 +65,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
var actual = default(double);
sut.ProgressChanged += (p) => actual = p;
sut.OnLoadingProgressChange(default, default, expected);
sut.OnLoadingProgressChange(default(IWebBrowser), default(IBrowser), expected);
Assert.AreEqual(expected, actual);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -25,52 +25,14 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
}
[TestMethod]
public void MustDetectFindCommand()
{
var findRequested = false;
sut.FindRequested += () => findRequested = true;
var handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.F, default(int), CefEventFlags.ControlDown, default(bool));
Assert.IsTrue(findRequested);
Assert.IsFalse(handled);
findRequested = false;
handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), default(KeyType), default(int), default(int), CefEventFlags.ControlDown, default(bool));
Assert.IsFalse(findRequested);
Assert.IsFalse(handled);
}
[TestMethod]
public void MustDetectHomeNavigationCommand()
{
var homeRequested = false;
sut.HomeNavigationRequested += () => homeRequested = true;
var handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.Home, default(int), default(CefEventFlags), default(bool));
Assert.IsTrue(homeRequested);
Assert.IsFalse(handled);
homeRequested = false;
handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), default(KeyType), default(int), default(int), default(CefEventFlags), default(bool));
Assert.IsFalse(homeRequested);
Assert.IsFalse(handled);
}
[TestMethod]
public void MustDetectReloadCommand()
public void MustDetectReload()
{
var isShortcut = default(bool);
var reloadRequested = false;
sut.ReloadRequested += () => reloadRequested = true;
var handled = sut.OnPreKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.F5, default(int), default(CefEventFlags), default(bool), ref isShortcut);
var handled = sut.OnPreKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int)Keys.F5, default(int), default(CefEventFlags), default(bool), ref isShortcut);
Assert.IsTrue(reloadRequested);
Assert.IsTrue(handled);

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2020 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/.
*/
using CefSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Handlers;
namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
[TestClass]
public class LifeSpanHandlerTests
{
private LifeSpanHandler sut;
[TestInitialize]
public void Initialize()
{
sut = new LifeSpanHandler();
}
[TestMethod]
public void MustUseDefaultBehavior()
{
Assert.IsFalse(sut.DoClose(default(IWebBrowser), default(IBrowser)));
}
[TestMethod]
public void MustHandlePopup()
{
var args = default(PopupRequestedEventArgs);
var jsAccess = false;
var url = "https://www.host.org/some-url";
sut.PopupRequested += (a) => args = a;
var result = sut.OnBeforePopup(default(IWebBrowser), default(IBrowser), default(IFrame), url, default(string), default(WindowOpenDisposition), default(bool), default(IPopupFeatures), default(IWindowInfo), default(IBrowserSettings), ref jsAccess, out var newBrowser);
Assert.IsTrue(result);
Assert.AreEqual(default(IWebBrowser), newBrowser);
Assert.AreEqual(url, args.Url);
}
}
}

View file

@ -1,19 +1,17 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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/.
*/
using System;
using CefSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
@ -30,7 +28,6 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
private AppConfig appConfig;
private Mock<IRequestFilter> filter;
private Mock<IKeyGenerator> keyGenerator;
private Mock<ILogger> logger;
private BrowserSettings settings;
private WindowSettings windowSettings;
@ -43,23 +40,13 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
appConfig = new AppConfig();
filter = new Mock<IRequestFilter>();
keyGenerator = new Mock<IKeyGenerator>();
logger = new Mock<ILogger>();
settings = new BrowserSettings();
windowSettings = new WindowSettings();
text = new Mock<IText>();
resourceHandler = new ResourceHandler(appConfig, filter.Object, keyGenerator.Object, logger.Object, default, settings, windowSettings, text.Object);
resourceHandler = new ResourceHandler(appConfig, filter.Object, logger.Object, settings, windowSettings, text.Object);
sut = new TestableRequestHandler(appConfig, filter.Object, logger.Object, resourceHandler, settings, windowSettings);
}
[TestMethod]
public void MustBlockSpecialWindowDispositions()
{
Assert.IsTrue(sut.OnOpenUrlFromTab(default, default, default, default, WindowOpenDisposition.NewBackgroundTab, default));
Assert.IsTrue(sut.OnOpenUrlFromTab(default, default, default, default, WindowOpenDisposition.NewPopup, default));
Assert.IsTrue(sut.OnOpenUrlFromTab(default, default, default, default, WindowOpenDisposition.NewWindow, default));
Assert.IsTrue(sut.OnOpenUrlFromTab(default, default, default, default, WindowOpenDisposition.SaveToDisk, default));
sut = new TestableRequestHandler(appConfig, filter.Object, logger.Object, resourceHandler, settings, windowSettings, text.Object);
}
[TestMethod]
@ -69,7 +56,6 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
var quitUrl = "http://www.byebye.com";
var request = new Mock<IRequest>();
appConfig.ConfigurationFileMimeType = "application/seb";
request.SetupGet(r => r.Url).Returns(quitUrl);
settings.QuitUrl = quitUrl;
sut.QuitUrlVisited += (url) => eventFired = true;
@ -121,9 +107,8 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
var eventFired = false;
var request = new Mock<IRequest>();
var url = "https://www.test.org";
var url = "www.test.org";
appConfig.ConfigurationFileMimeType = "application/seb";
filter.Setup(f => f.Process(It.Is<Request>(r => r.Url.Equals(url)))).Returns(FilterResult.Block);
request.SetupGet(r => r.ResourceType).Returns(ResourceType.MainFrame);
request.SetupGet(r => r.Url).Returns(url);
@ -155,9 +140,8 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
var eventFired = false;
var request = new Mock<IRequest>();
var url = "https://www.test.org";
var url = "www.test.org";
appConfig.ConfigurationFileMimeType = "application/seb";
filter.Setup(f => f.Process(It.Is<Request>(r => r.Url.Equals(url)))).Returns(FilterResult.Block);
request.SetupGet(r => r.ResourceType).Returns(ResourceType.SubFrame);
request.SetupGet(r => r.Url).Returns(url);
@ -184,75 +168,6 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
Assert.IsFalse(eventFired);
}
[TestMethod]
public void MustInitiateDataUriConfigurationFileDownload()
{
var browser = new Mock<IBrowser>();
var host = new Mock<IBrowserHost>();
var request = new Mock<IRequest>();
appConfig.ConfigurationFileExtension = ".xyz";
appConfig.ConfigurationFileMimeType = "application/seb";
appConfig.SebUriScheme = "abc";
appConfig.SebUriSchemeSecure = "abcd";
browser.Setup(b => b.GetHost()).Returns(host.Object);
request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriSchemeSecure}://{appConfig.ConfigurationFileMimeType};base64,H4sIAAAAAAAAE41WbXPaRhD...");
var handled = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), browser.Object, Mock.Of<IFrame>(), request.Object, false, false);
host.Verify(h => h.StartDownload(It.Is<string>(u => u == $"data:{appConfig.ConfigurationFileMimeType};base64,H4sIAAAAAAAAE41WbXPaRhD...")));
Assert.IsTrue(handled);
}
[TestMethod]
public void MustInitiateHttpConfigurationFileDownload()
{
var browser = new Mock<IBrowser>();
var host = new Mock<IBrowserHost>();
var request = new Mock<IRequest>();
appConfig.ConfigurationFileExtension = ".xyz";
appConfig.ConfigurationFileMimeType = "application/seb";
appConfig.SebUriScheme = "abc";
appConfig.SebUriSchemeSecure = "abcd";
browser.Setup(b => b.GetHost()).Returns(host.Object);
request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriScheme}://host.com/path/file{appConfig.ConfigurationFileExtension}");
var handled = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), browser.Object, Mock.Of<IFrame>(), request.Object, false, false);
host.Verify(h => h.StartDownload(It.Is<string>(u => u == $"{Uri.UriSchemeHttp}://host.com/path/file{appConfig.ConfigurationFileExtension}")));
Assert.IsTrue(handled);
}
[TestMethod]
public void MustInitiateHttpsConfigurationFileDownload()
{
var browser = new Mock<IBrowser>();
var host = new Mock<IBrowserHost>();
var request = new Mock<IRequest>();
appConfig.ConfigurationFileExtension = ".xyz";
appConfig.ConfigurationFileMimeType = "application/seb";
appConfig.SebUriScheme = "abc";
appConfig.SebUriSchemeSecure = "abcd";
browser.Setup(b => b.GetHost()).Returns(host.Object);
request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriSchemeSecure}://host.com/path/file{appConfig.ConfigurationFileExtension}");
var handled = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), browser.Object, Mock.Of<IFrame>(), request.Object, false, false);
host.Verify(h => h.StartDownload(It.Is<string>(u => u == $"{Uri.UriSchemeHttps}://host.com/path/file{appConfig.ConfigurationFileExtension}")));
Assert.IsTrue(handled);
}
[TestMethod]
public void MustReturnResourceHandler()
{
var disableDefaultHandling = default(bool);
var handler = sut.GetResourceRequestHandler(default, default, default, default, default, default, default, ref disableDefaultHandling);
Assert.AreSame(resourceHandler, handler);
}
[TestMethod]
public void MustUseProxyCredentials()
{
@ -263,7 +178,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
settings.Proxy.Proxies.Add(proxy1);
settings.Proxy.Proxies.Add(proxy2);
var result = sut.GetAuthCredentials(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), default, true, "WWW.tEst.Com", 10, default, default, callback.Object);
var result = sut.GetAuthCredentials(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), default(string), true, "WWW.tEst.Com", 10, default(string), default(string), callback.Object);
callback.Verify(c => c.Cancel(), Times.Never);
callback.Verify(c => c.Continue(It.Is<string>(u => u.Equals(proxy1.Username)), It.Is<string>(p => p.Equals(proxy1.Password))), Times.Once);
@ -277,7 +192,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
var callback = new Mock<IAuthCallback>();
sut.GetAuthCredentials(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), default, false, default, default, default, default, callback.Object);
sut.GetAuthCredentials(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), default(string), false, default(string), default(int), default(string), default(string), callback.Object);
callback.Verify(c => c.Cancel(), Times.Never);
callback.Verify(c => c.Continue(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
@ -285,7 +200,14 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
private class TestableRequestHandler : RequestHandler
{
internal TestableRequestHandler(AppConfig appConfig, IRequestFilter filter, ILogger logger, ResourceHandler resourceHandler, BrowserSettings settings, WindowSettings windowSettings) : base(appConfig, filter, logger, resourceHandler, settings, windowSettings)
internal TestableRequestHandler(
AppConfig appConfig,
IRequestFilter filter,
ILogger logger,
ResourceHandler resourceHandler,
BrowserSettings settings,
WindowSettings windowSettings,
IText text) : base(appConfig, filter, logger, resourceHandler, settings, windowSettings, text)
{
}
@ -294,20 +216,10 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
return base.GetAuthCredentials(webBrowser, browser, originUrl, isProxy, host, port, realm, scheme, callback);
}
public new IResourceRequestHandler GetResourceRequestHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
{
return base.GetResourceRequestHandler(webBrowser, browser, frame, request, isNavigation, isDownload, requestInitiator, ref disableDefaultHandling);
}
public new bool OnBeforeBrowse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
{
return base.OnBeforeBrowse(webBrowser, browser, frame, request, userGesture, isRedirect);
}
public new bool OnOpenUrlFromTab(IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
{
return base.OnOpenUrlFromTab(webBrowser, browser, frame, targetUrl, targetDisposition, userGesture);
}
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -9,16 +9,13 @@
using System;
using System.Collections.Specialized;
using System.Net.Mime;
using System.Threading;
using CefSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
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;
@ -32,7 +29,6 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
private AppConfig appConfig;
private Mock<IRequestFilter> filter;
private Mock<IKeyGenerator> keyGenerator;
private Mock<ILogger> logger;
private BrowserSettings settings;
private WindowSettings windowSettings;
@ -44,13 +40,12 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
appConfig = new AppConfig();
filter = new Mock<IRequestFilter>();
keyGenerator = new Mock<IKeyGenerator>();
logger = new Mock<ILogger>();
settings = new BrowserSettings();
windowSettings = new WindowSettings();
text = new Mock<IText>();
sut = new TestableResourceHandler(appConfig, filter.Object, keyGenerator.Object, logger.Object, SessionMode.Server, settings, windowSettings, text.Object);
sut = new TestableResourceHandler(appConfig, filter.Object, logger.Object, settings, windowSettings, text.Object);
}
[TestMethod]
@ -61,13 +56,11 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
var request = new Mock<IRequest>();
browser.SetupGet(b => b.Address).Returns("http://www.host.org");
keyGenerator.Setup(g => g.CalculateBrowserExamKeyHash(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<string>())).Returns(new Random().Next().ToString());
keyGenerator.Setup(g => g.CalculateConfigurationKeyHash(It.IsAny<string>(), It.IsAny<string>())).Returns(new Random().Next().ToString());
request.SetupGet(r => r.Headers).Returns(new NameValueCollection());
request.SetupGet(r => r.Url).Returns("http://www.host.org");
request.SetupSet(r => r.Headers = It.IsAny<NameValueCollection>()).Callback<NameValueCollection>((h) => headers = h);
settings.SendConfigurationKey = true;
settings.SendBrowserExamKey = true;
settings.SendExamKey = true;
var result = sut.OnBeforeResourceLoad(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
@ -80,46 +73,18 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
}
[TestMethod]
public void MustAppendCustomHeadersForCrossDomainResourceRequestAndMainFrame()
public void MustNotAppendCustomHeadersForCrossDomain()
{
var browser = new Mock<IWebBrowser>();
var headers = new NameValueCollection();
var request = new Mock<IRequest>();
browser.SetupGet(b => b.Address).Returns("http://www.otherhost.org");
keyGenerator.Setup(g => g.CalculateBrowserExamKeyHash(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<string>())).Returns(new Random().Next().ToString());
keyGenerator.Setup(g => g.CalculateConfigurationKeyHash(It.IsAny<string>(), It.IsAny<string>())).Returns(new Random().Next().ToString());
request.SetupGet(r => r.ResourceType).Returns(ResourceType.MainFrame);
request.SetupGet(r => r.Headers).Returns(new NameValueCollection());
request.SetupGet(r => r.Url).Returns("http://www.host.org");
request.SetupSet(r => r.Headers = It.IsAny<NameValueCollection>()).Callback<NameValueCollection>((h) => headers = h);
settings.SendConfigurationKey = true;
settings.SendBrowserExamKey = true;
var result = sut.OnBeforeResourceLoad(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
request.VerifyGet(r => r.Headers, Times.AtLeastOnce);
request.VerifySet(r => r.Headers = It.IsAny<NameValueCollection>(), Times.AtLeastOnce);
Assert.AreEqual(CefReturnValue.Continue, result);
Assert.IsNotNull(headers["X-SafeExamBrowser-ConfigKeyHash"]);
Assert.IsNotNull(headers["X-SafeExamBrowser-RequestHash"]);
}
[TestMethod]
public void MustNotAppendCustomHeadersForCrossDomainResourceRequestAndSubResource()
{
var browser = new Mock<IWebBrowser>();
var headers = new NameValueCollection();
var request = new Mock<IRequest>();
browser.SetupGet(b => b.Address).Returns("http://www.otherhost.org");
request.SetupGet(r => r.ResourceType).Returns(ResourceType.SubResource);
request.SetupGet(r => r.Headers).Returns(new NameValueCollection());
request.SetupGet(r => r.Url).Returns("http://www.host.org");
request.SetupSet(r => r.Headers = It.IsAny<NameValueCollection>()).Callback<NameValueCollection>((h) => headers = h);
settings.SendConfigurationKey = true;
settings.SendBrowserExamKey = true;
settings.SendExamKey = true;
var result = sut.OnBeforeResourceLoad(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
@ -148,7 +113,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
public void MustFilterContentRequests()
{
var request = new Mock<IRequest>();
var url = "http://www.test.org";
var url = "www.test.org";
filter.Setup(f => f.Process(It.Is<Request>(r => r.Url.Equals(url)))).Returns(FilterResult.Block);
request.SetupGet(r => r.ResourceType).Returns(ResourceType.SubFrame);
@ -163,16 +128,10 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
Assert.IsNotNull(resourceHandler);
}
[TestMethod]
public void MustLetOperatingSystemHandleUnknownProtocols()
{
Assert.IsTrue(sut.OnProtocolExecution(default, default, default, default));
}
[TestMethod]
public void MustRedirectToDisablePdfToolbar()
{
var frame = new Mock<IFrame>();
var browser = new Mock<IWebBrowser>();
var headers = new NameValueCollection { { "Content-Type", MediaTypeNames.Application.Pdf } };
var request = new Mock<IRequest>();
var response = new Mock<IResponse>();
@ -184,17 +143,17 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
settings.AllowPdfReader = true;
settings.AllowPdfReaderToolbar = false;
var result = sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), frame.Object, request.Object, response.Object);
var result = sut.OnResourceResponse(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
frame.Verify(b => b.LoadUrl(It.Is<string>(s => s.Equals($"{url}#toolbar=0"))), Times.Once);
browser.Verify(b => b.Load(It.Is<string>(s => s.Equals($"{url}#toolbar=0"))), Times.Once);
Assert.IsTrue(result);
frame.Reset();
browser.Reset();
request.SetupGet(r => r.Url).Returns($"{url}#toolbar=0");
result = sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), frame.Object, request.Object, response.Object);
result = sut.OnResourceResponse(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
frame.Verify(b => b.LoadUrl(It.IsAny<string>()), Times.Never);
browser.Verify(b => b.Load(It.IsAny<string>()), Times.Never);
Assert.IsFalse(result);
}
@ -224,113 +183,15 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
Assert.AreEqual("https://www.host.org/", url);
}
[TestMethod]
public void MustSearchGenericLmsSessionIdentifier()
{
var @event = new AutoResetEvent(false);
var headers = new NameValueCollection();
var newUrl = default(string);
var request = new Mock<IRequest>();
var response = new Mock<IResponse>();
var userId = default(string);
headers.Add("X-LMS-USER-ID", "some-session-id-123");
request.SetupGet(r => r.Url).Returns("https://www.somelms.org");
response.SetupGet(r => r.Headers).Returns(headers);
sut.UserIdentifierDetected += (id) =>
{
userId = id;
@event.Set();
};
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
@event.WaitOne();
Assert.AreEqual("some-session-id-123", userId);
headers.Clear();
headers.Add("X-LMS-USER-ID", "other-session-id-123");
userId = default;
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
@event.WaitOne();
Assert.AreEqual("other-session-id-123", userId);
}
[TestMethod]
public void MustSearchEdxSessionIdentifier()
{
var @event = new AutoResetEvent(false);
var headers = new NameValueCollection();
var newUrl = default(string);
var request = new Mock<IRequest>();
var response = new Mock<IResponse>();
var userId = default(string);
headers.Add("Set-Cookie", "edx-user-info=\"{\\\"username\\\": \\\"edx-123\\\"}\"; expires");
request.SetupGet(r => r.Url).Returns("https://www.somelms.org");
response.SetupGet(r => r.Headers).Returns(headers);
sut.UserIdentifierDetected += (id) =>
{
userId = id;
@event.Set();
};
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
@event.WaitOne();
Assert.AreEqual("edx-123", userId);
headers.Clear();
headers.Add("Set-Cookie", "edx-user-info=\"{\\\"username\\\": \\\"edx-345\\\"}\"; expires");
userId = default;
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
@event.WaitOne();
Assert.AreEqual("edx-345", userId);
}
[TestMethod]
public void MustSearchMoodleSessionIdentifier()
{
var @event = new AutoResetEvent(false);
var headers = new NameValueCollection();
var newUrl = default(string);
var request = new Mock<IRequest>();
var response = new Mock<IResponse>();
var userId = default(string);
headers.Add("Location", "https://www.some-moodle-instance.org/moodle/login/index.php?testsession=123");
request.SetupGet(r => r.Url).Returns("https://www.some-moodle-instance.org");
response.SetupGet(r => r.Headers).Returns(headers);
sut.UserIdentifierDetected += (id) =>
{
userId = id;
@event.Set();
};
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
@event.WaitOne();
Assert.AreEqual("123", userId);
headers.Clear();
headers.Add("Location", "https://www.some-moodle-instance.org/moodle/login/index.php?testsession=456");
userId = default;
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
@event.WaitOne();
Assert.AreEqual("456", userId);
}
private class TestableResourceHandler : ResourceHandler
{
internal TestableResourceHandler(
AppConfig appConfig,
IRequestFilter filter,
IKeyGenerator keyGenerator,
ILogger logger,
SessionMode sessionMode,
BrowserSettings settings,
WindowSettings windowSettings,
IText text) : base(appConfig, filter, keyGenerator, logger, sessionMode, settings, windowSettings, text)
IText text) : base(appConfig, filter, logger, settings, windowSettings, text)
{
}
@ -344,16 +205,6 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
return base.OnBeforeResourceLoad(webBrowser, browser, frame, request, callback);
}
public new bool OnProtocolExecution(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request)
{
return base.OnProtocolExecution(webBrowser, browser, frame, request);
}
public new void OnResourceRedirect(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl)
{
base.OnResourceRedirect(webBrowser, browser, frame, request, response, ref newUrl);
}
public new bool OnResourceResponse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response)
{
return base.OnResourceResponse(webBrowser, browser, frame, request, response);

View file

@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Browser.UnitTests")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: AssemblyCopyright("Copyright © 2020 ETH Zürich, Educational Development and Technology (LET)")]
[assembly: ComVisible(false)]

View file

@ -1,11 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props')" />
<Import Project="..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props" Condition="Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" />
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props')" />
<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="..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.props" Condition="Exists('..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.props')" />
<Import Project="..\packages\cef.redist.x86.86.0.24\build\cef.redist.x86.props" Condition="Exists('..\packages\cef.redist.x86.86.0.24\build\cef.redist.x86.props')" />
<Import Project="..\packages\cef.redist.x64.86.0.24\build\cef.redist.x64.props" Condition="Exists('..\packages\cef.redist.x64.86.0.24\build\cef.redist.x64.props')" />
<Import Project="..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@ -15,7 +13,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SafeExamBrowser.Browser.UnitTests</RootNamespace>
<AssemblyName>SafeExamBrowser.Browser.UnitTests</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
@ -25,7 +23,6 @@
<TestProjectType>UnitTest</TestProjectType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
@ -64,88 +61,28 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Castle.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.5.1.1\lib\net462\Castle.Core.dll</HintPath>
</Reference>
<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="Microsoft.ApplicationInsights, Version=2.22.0.997, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.2.22.0\lib\net46\Microsoft.ApplicationInsights.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Testing.Extensions.Telemetry, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Extensions.TrxReport.Abstractions, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.TrxReport.Abstractions.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.TrxReport.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Extensions.VSTestBridge, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.VSTestBridge.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.VSTestBridge.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Platform, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\lib\netstandard2.0\Microsoft.Testing.Platform.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Platform.MSBuild, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\lib\netstandard2.0\Microsoft.Testing.Platform.MSBuild.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.TestPlatform.CoreUtilities.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TestPlatform.PlatformAbstractions, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.TestPlatform.PlatformAbstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.ObjectModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll</HintPath>
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.4.4.1\lib\net45\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.3.2.2\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
<HintPath>..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.3.2.2\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
<HintPath>..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.20.70.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.20.70\lib\net462\Moq.dll</HintPath>
</Reference>
<Reference Include="NuGet.Frameworks, Version=6.9.1.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\NuGet.Frameworks.6.9.1\lib\net472\NuGet.Frameworks.dll</HintPath>
<Reference Include="Moq, Version=4.15.0.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.15.2\lib\net45\Moq.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Immutable, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.8.0.0\lib\net462\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Diagnostics.DiagnosticSource, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Diagnostics.DiagnosticSource.8.0.0\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Metadata, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reflection.Metadata.8.0.0\lib\net462\System.Reflection.Metadata.dll</HintPath>
</Reference>
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Filters\LegacyFilter.cs" />
@ -158,6 +95,7 @@
<Compile Include="Handlers\DisplayHandlerTests.cs" />
<Compile Include="Handlers\DownloadHandlerTests.cs" />
<Compile Include="Handlers\KeyboardHandlerTests.cs" />
<Compile Include="Handlers\LifeSpanHandlerTests.cs" />
<Compile Include="Handlers\RequestHandlerTests.cs" />
<Compile Include="Handlers\ResourceHandlerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@ -204,17 +142,13 @@
<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'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.targets'))" />
<Error Condition="!Exists('..\packages\cef.redist.x64.86.0.24\build\cef.redist.x64.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\cef.redist.x64.86.0.24\build\cef.redist.x64.props'))" />
<Error Condition="!Exists('..\packages\cef.redist.x86.86.0.24\build\cef.redist.x86.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\cef.redist.x86.86.0.24\build\cef.redist.x86.props'))" />
<Error Condition="!Exists('..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.props'))" />
<Error Condition="!Exists('..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.86.0.241\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')" />
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets')" />
<Import Project="..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets')" />
<Import Project="..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.targets')" />
<Import Project="..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.targets" Condition="Exists('..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.targets')" />
</Project>

View file

@ -8,44 +8,12 @@
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
</dependentAssembly>
<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>
<dependentAssembly>
<assemblyIdentity name="CefSharp" publicKeyToken="40c4b6fc221f4138" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-118.6.80.0" newVersion="118.6.80.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="CefSharp.Core" publicKeyToken="40c4b6fc221f4138" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-118.6.80.0" newVersion="118.6.80.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="NuGet.Frameworks" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.11.3.1" newVersion="5.11.3.1" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.ApplicationInsights" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.22.0.997" newVersion="2.22.0.997" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" /></startup></configuration>
</configuration>

View file

@ -1,26 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Castle.Core" version="5.1.1" targetFramework="net48" />
<package id="CefSharp.Common" 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="Microsoft.ApplicationInsights" version="2.22.0" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.Telemetry" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.TrxReport.Abstractions" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.VSTestBridge" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Platform" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Platform.MSBuild" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.TestPlatform.ObjectModel" version="17.9.0" targetFramework="net48" />
<package id="Moq" version="4.20.70" targetFramework="net48" />
<package id="MSTest.TestAdapter" version="3.2.2" targetFramework="net48" />
<package id="MSTest.TestFramework" version="3.2.2" targetFramework="net48" />
<package id="NuGet.Frameworks" version="6.9.1" targetFramework="net48" />
<package id="System.Buffers" version="4.5.1" targetFramework="net48" />
<package id="System.Collections.Immutable" version="8.0.0" targetFramework="net48" />
<package id="System.Diagnostics.DiagnosticSource" version="8.0.0" targetFramework="net48" />
<package id="System.Memory" version="4.5.5" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net48" />
<package id="System.Reflection.Metadata" version="8.0.0" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net48" />
<package id="Castle.Core" version="4.4.1" targetFramework="net472" />
<package id="cef.redist.x64" version="86.0.24" targetFramework="net472" />
<package id="cef.redist.x86" version="86.0.24" targetFramework="net472" />
<package id="CefSharp.Common" version="86.0.241" targetFramework="net472" />
<package id="Moq" version="4.15.2" targetFramework="net472" />
<package id="MSTest.TestAdapter" version="2.1.2" targetFramework="net472" />
<package id="MSTest.TestFramework" version="2.1.2" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="5.0.0" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
</packages>

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -8,22 +8,21 @@
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;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
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;
using SafeExamBrowser.Settings.Browser.Proxy;
using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.UserInterface.Contracts;
@ -36,21 +35,18 @@ namespace SafeExamBrowser.Browser
{
public class BrowserApplication : IBrowserApplication
{
private int windowIdCounter = default;
private int instanceIdCounter = default(int);
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;
private AppConfig appConfig;
private List<BrowserApplicationInstance> instances;
private IFileSystemDialog fileSystemDialog;
private IHashAlgorithm hashAlgorithm;
private INativeMethods nativeMethods;
private IMessageBox messageBox;
private IModuleLogger logger;
private BrowserSettings settings;
private IText text;
private IUserInterfaceFactory uiFactory;
public bool AutoStart { get; private set; }
public IconResource Icon { get; private set; }
@ -59,9 +55,8 @@ namespace SafeExamBrowser.Browser
public string Tooltip { get; private set; }
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event LoseFocusRequestedEventHandler LoseFocusRequested;
public event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
public event TerminationRequestedEventHandler TerminationRequested;
public event UserIdentifierDetectedEventHandler UserIdentifierDetected;
public event WindowsChangedEventHandler WindowsChanged;
public BrowserApplication(
@ -69,40 +64,27 @@ namespace SafeExamBrowser.Browser
BrowserSettings settings,
IFileSystemDialog fileSystemDialog,
IHashAlgorithm hashAlgorithm,
IKeyGenerator keyGenerator,
INativeMethods nativeMethods,
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.nativeMethods = nativeMethods;
this.instances = new List<BrowserApplicationInstance>();
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)
public IEnumerable<IApplicationWindow> GetWindows()
{
windows.ForEach(window =>
{
window.Focus(forward);
});
}
public IEnumerable<IBrowserWindow> GetWindows()
{
return new List<IBrowserWindow>(windows);
return new List<IApplicationWindow>(instances);
}
public void Initialize()
@ -116,18 +98,11 @@ namespace SafeExamBrowser.Browser
if (success)
{
InitializeIntegrityKeys();
if (settings.DeleteCookiesOnStartup)
{
DeleteCookies();
}
if (settings.UseTemporaryDownAndUploadDirectory)
{
CreateTemporaryDownAndUploadDirectory();
}
logger.Info("Initialized browser.");
}
else
@ -138,24 +113,18 @@ namespace SafeExamBrowser.Browser
public void Start()
{
CreateNewWindow();
CreateNewInstance();
}
public void Terminate()
{
logger.Info("Initiating termination...");
AwaitReady();
foreach (var window in windows)
foreach (var instance in instances)
{
window.Closed -= Window_Closed;
window.Close();
logger.Info($"Closed browser window #{window.Id}.");
}
if (settings.UseTemporaryDownAndUploadDirectory)
{
DeleteTemporaryDownAndUploadDirectory();
instance.Terminated -= Instance_Terminated;
instance.Terminate();
logger.Info($"Terminated browser instance {instance.Id}.");
}
if (settings.DeleteCookiesOnShutdown)
@ -176,88 +145,28 @@ namespace SafeExamBrowser.Browser
}
}
private void AwaitReady()
private void CreateNewInstance(string url = null)
{
// 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).
var id = ++instanceIdCounter;
var isMainInstance = instances.Count == 0;
var instanceLogger = logger.CloneFor($"Browser Instance #{id}");
var startUrl = url ?? GenerateStartUrl();
var instance = new BrowserApplicationInstance(appConfig, settings, id, isMainInstance, fileSystemDialog, hashAlgorithm, messageBox, instanceLogger, text, uiFactory, startUrl);
Thread.Sleep(500);
}
instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args);
instance.PopupRequested += Instance_PopupRequested;
instance.ResetRequested += Instance_ResetRequested;
instance.SessionIdentifierDetected += (i) => SessionIdentifierDetected?.Invoke(i);
instance.Terminated += Instance_Terminated;
instance.TerminationRequested += () => TerminationRequested?.Invoke();
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);
instance.Initialize();
instances.Add(instance);
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}.");
logger.Info($"Created browser instance {instance.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
@ -331,7 +240,6 @@ namespace SafeExamBrowser.Browser
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;
@ -339,11 +247,6 @@ namespace SafeExamBrowser.Browser
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");
@ -354,13 +257,8 @@ namespace SafeExamBrowser.Browser
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}");
@ -371,22 +269,6 @@ namespace SafeExamBrowser.Browser
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)
@ -460,32 +342,24 @@ namespace SafeExamBrowser.Browser
throw new NotImplementedException($"Mapping for proxy protocol '{protocol}' is not yet implemented!");
}
private void Window_Closed(int id)
private void Instance_PopupRequested(PopupRequestedEventArgs args)
{
windows.Remove(windows.First(i => i.Id == id));
WindowsChanged?.Invoke();
logger.Info($"Window #{id} has been closed.");
logger.Info($"Received request to create new instance{(settings.AdditionalWindow.UrlPolicy.CanLog() ? $" for '{args.Url}'" : "")}...");
CreateNewInstance(args.Url);
}
private void Window_PopupRequested(PopupRequestedEventArgs args)
{
logger.Info($"Received request to create new window...");
CreateNewWindow(args);
}
private void Window_ResetRequested()
private void Instance_ResetRequested()
{
logger.Info("Attempting to reset browser...");
AwaitReady();
foreach (var window in windows)
foreach (var instance in instances)
{
window.Closed -= Window_Closed;
window.Close();
logger.Info($"Closed browser window #{window.Id}.");
instance.Terminated -= Instance_Terminated;
instance.Terminate();
logger.Info($"Terminated browser instance {instance.Id}.");
}
windows.Clear();
instances.Clear();
WindowsChanged?.Invoke();
if (settings.DeleteCookiesOnStartup && settings.DeleteCookiesOnShutdown)
@ -494,8 +368,14 @@ namespace SafeExamBrowser.Browser
}
nativeMethods.EmptyClipboard();
CreateNewWindow();
CreateNewInstance();
logger.Info("Successfully reset browser.");
}
private void Instance_Terminated(int id)
{
instances.Remove(instances.First(i => i.Id == id));
WindowsChanged?.Invoke();
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -7,25 +7,21 @@
*/
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;
using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
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;
@ -35,94 +31,77 @@ 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
internal class BrowserApplicationInstance : IApplicationWindow
{
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 AppConfig appConfig;
private IBrowserControl control;
private IBrowserWindow window;
private HttpClient httpClient;
private bool isMainInstance;
private IFileSystemDialog fileSystemDialog;
private IHashAlgorithm hashAlgorithm;
private IMessageBox messageBox;
private IModuleLogger logger;
private BrowserSettings settings;
private string startUrl;
private IText text;
private IUserInterfaceFactory uiFactory;
private double zoomLevel;
private WindowSettings WindowSettings
{
get { return IsMainWindow ? settings.MainWindow : settings.AdditionalWindow; }
get { return isMainInstance ? 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 SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
internal event InstanceTerminatedEventHandler Terminated;
internal event TerminationRequestedEventHandler TerminationRequested;
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
public event IconChangedEventHandler IconChanged;
public event TitleChangedEventHandler TitleChanged;
public BrowserWindow(
public BrowserApplicationInstance(
AppConfig appConfig,
Clipboard clipboard,
BrowserSettings settings,
int id,
bool isMainInstance,
IFileSystemDialog fileSystemDialog,
IHashAlgorithm hashAlgorithm,
int id,
bool isMainWindow,
IKeyGenerator keyGenerator,
IModuleLogger logger,
IMessageBox messageBox,
SessionMode sessionMode,
BrowserSettings settings,
string startUrl,
IModuleLogger logger,
IText text,
IUserInterfaceFactory uiFactory)
IUserInterfaceFactory uiFactory,
string startUrl)
{
this.appConfig = appConfig;
this.clipboard = clipboard;
this.Id = id;
this.httpClient = new HttpClient();
this.isMainInstance = isMainInstance;
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.logger = logger;
this.settings = settings;
this.startUrl = startUrl;
this.text = text;
this.uiFactory = uiFactory;
this.startUrl = startUrl;
}
public void Activate()
@ -130,116 +109,70 @@ namespace SafeExamBrowser.Browser
window.BringToForeground();
}
internal void Close()
internal void Initialize()
{
InitializeControl();
InitializeWindow();
}
internal void Terminate()
{
window.Close();
Control.Destroy();
control.Destroy();
}
internal void Focus(bool forward)
private void InitializeControl()
{
if (forward)
{
window.FocusToolbar(forward);
}
else
{
window.FocusBrowser();
Activate();
}
}
internal void InitializeControl()
{
var cefSharpControl = default(ICefSharpControl);
var controlLogger = logger.CloneFor($"{nameof(BrowserControl)} #{Id}");
var contextMenuHandler = new ContextMenuHandler();
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 lifeSpanHandler = new LifeSpanHandler();
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);
var resourceHandler = new ResourceHandler(appConfig, requestFilter, logger, settings, WindowSettings, text);
var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, resourceHandler, settings, WindowSettings, text);
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;
lifeSpanHandler.PopupRequested += LifeSpanHandler_PopupRequested;
resourceHandler.SessionIdentifierDetected += (id) => SessionIdentifierDetected?.Invoke(id);
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 = new BrowserControl(
contextMenuHandler,
dialogHandler,
displayHandler,
downloadHandler,
keyboardHandler,
lifeSpanHandler,
requestHandler,
startUrl);
control.AddressChanged += Control_AddressChanged;
control.LoadFailed += Control_LoadFailed;
control.LoadingStateChanged += Control_LoadingStateChanged;
control.TitleChanged += Control_TitleChanged;
Control.Initialize();
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)
@ -268,11 +201,31 @@ namespace SafeExamBrowser.Browser
}
}
private void InitializeWindow()
{
window = uiFactory.CreateBrowserWindow(control, settings, isMainInstance);
window.Closing += Window_Closing;
window.AddressChanged += Window_AddressChanged;
window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
window.DeveloperConsoleRequested += Window_DeveloperConsoleRequested;
window.FindRequested += Window_FindRequested;
window.ForwardNavigationRequested += Window_ForwardNavigationRequested;
window.HomeNavigationRequested += HomeNavigationRequested;
window.ReloadRequested += ReloadRequested;
window.ZoomInRequested += ZoomInRequested;
window.ZoomOutRequested += ZoomOutRequested;
window.ZoomResetRequested += ZoomResetRequested;
window.UpdateZoomLevel(CalculateZoomPercentage());
window.Show();
Handle = window.Handle;
logger.Debug("Initialized browser window.");
}
private void Control_AddressChanged(string address)
{
logger.Info($"Navigated{(WindowSettings.UrlPolicy.CanLog() ? $" to '{address}'" : "")}.");
Url = address;
logger.Debug($"Navigated{(WindowSettings.UrlPolicy.CanLog() ? $" to '{address}'" : "")}.");
window.UpdateAddress(address);
if (WindowSettings.UrlPolicy == UrlPolicy.Always || WindowSettings.UrlPolicy == UrlPolicy.BeforeTitle)
@ -281,51 +234,37 @@ namespace SafeExamBrowser.Browser
window.UpdateTitle(address);
TitleChanged?.Invoke(address);
}
AutoFind();
}
private void Control_LoadFailed(int errorCode, string errorText, bool isMainRequest, string url)
private void Control_LoadFailed(int errorCode, string errorText, string url)
{
switch (errorCode)
if (errorCode == (int) CefErrorCode.None)
{
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;
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was successful.");
}
}
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)
else if (errorCode == (int) CefErrorCode.Aborted)
{
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was aborted.");
}
else if (errorCode == (int) CefErrorCode.UnknownUrlScheme)
{
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} contains unknown URL scheme and will be handled by the OS.");
}
else
{
var title = text.Get(TextKey.Browser_LoadErrorTitle);
var message = text.Get(TextKey.Browser_LoadErrorMessage).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "") + $" {requestInfo}";
var message = text.Get(TextKey.Browser_LoadErrorMessage).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "") + $" {errorText} ({errorCode})";
Task.Run(() => messageBox.Show(message, title, icon: MessageBoxIcon.Error, parent: window)).ContinueWith(_ => Control.NavigateBackwards());
logger.Warn($"Request{(WindowSettings.UrlPolicy.CanLogError() ? $" for '{url}'" : "")} failed: {errorText} ({errorCode}).");
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.CanNavigateBackwards = WindowSettings.AllowBackwardNavigation && control.CanNavigateBackwards;
window.CanNavigateForwards = WindowSettings.AllowForwardNavigation && control.CanNavigateForwards;
window.UpdateLoadingState(isLoading);
}
@ -350,13 +289,9 @@ namespace SafeExamBrowser.Browser
{
initialPath = args.InitialPath;
}
else if (string.IsNullOrEmpty(settings.DownAndUploadDirectory))
{
initialPath = KnownFolders.Downloads.ExpandedPath;
}
else
{
initialPath = Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory);
initialPath = string.IsNullOrEmpty(settings.DownAndUploadDirectory) ? KnownFolders.Downloads.ExpandedPath : Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory);
}
if (isAllowed)
@ -367,8 +302,7 @@ namespace SafeExamBrowser.Browser
initialPath,
title: args.Title,
parent: window,
restrictNavigation: !settings.AllowCustomDownAndUploadLocation,
showElementPath: settings.ShowFileSystemElementPath);
restrictNavigation: !settings.AllowCustomDownAndUploadLocation);
if (result.Success)
{
@ -384,7 +318,6 @@ namespace SafeExamBrowser.Browser
else
{
logger.Info($"Blocked file system dialog to {args.Operation}->{args.Element}, as {(isDownload ? "downloading" : "uploading")} is not allowed.");
ShowDownUploadNotAllowedMessage(isDownload);
}
}
@ -434,11 +367,6 @@ namespace SafeExamBrowser.Browser
}
}
private void DownloadHandler_DownloadAborted()
{
ShowDownUploadNotAllowedMessage();
}
private void DownloadHandler_DownloadUpdated(DownloadItemState state)
{
window.UpdateDownloadState(state);
@ -446,7 +374,7 @@ namespace SafeExamBrowser.Browser
private void HomeNavigationRequested()
{
if (IsMainWindow && (settings.UseStartUrlAsHomeUrl || !string.IsNullOrWhiteSpace(settings.HomeUrl)))
if (isMainInstance && (settings.UseStartUrlAsHomeUrl || !string.IsNullOrWhiteSpace(settings.HomeUrl)))
{
var navigate = false;
var url = settings.UseStartUrlAsHomeUrl ? settings.StartUrl : settings.HomeUrl;
@ -483,7 +411,7 @@ namespace SafeExamBrowser.Browser
if (navigate)
{
Control.NavigateTo(url);
control.NavigateTo(url);
}
}
}
@ -496,91 +424,32 @@ namespace SafeExamBrowser.Browser
}
}
private void KeyboardHandler_FocusAddressBarRequested()
private void LifeSpanHandler_PopupRequested(PopupRequestedEventArgs args)
{
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 validCurrentUri = Uri.TryCreate(control.Address, UriKind.Absolute, out var currentUri);
var validNewUri = Uri.TryCreate(args.Url, 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;
logger.Debug($"Forwarding request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{args.Url}'" : "")}...");
PopupRequested?.Invoke(args);
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);
logger.Info($"Discarding request to open new window and loading{(WindowSettings.UrlPolicy.CanLog() ? $" '{args.Url}'" : "")} directly...");
control.NavigateTo(args.Url);
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.");
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{args.Url}'" : "")} as it targets a different host.");
break;
default:
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")}.");
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{args.Url}'" : "")}.");
break;
}
return creation;
}
private void RequestHandler_QuitUrlVisited(string url)
@ -627,7 +496,7 @@ namespace SafeExamBrowser.Browser
var message = text.Get(TextKey.MessageBox_BrowserNavigationBlocked).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "");
var title = text.Get(TextKey.MessageBox_BrowserNavigationBlockedTitle);
Control.TitleChanged -= Control_TitleChanged;
control.TitleChanged -= Control_TitleChanged;
if (url.Equals(startUrl, StringComparison.OrdinalIgnoreCase))
{
@ -636,7 +505,7 @@ namespace SafeExamBrowser.Browser
}
messageBox.Show(message, title, parent: window);
Control.TitleChanged += Control_TitleChanged;
control.TitleChanged += Control_TitleChanged;
});
}
@ -649,7 +518,7 @@ namespace SafeExamBrowser.Browser
if (result == MessageBoxResult.Yes)
{
logger.Debug("The user confirmed reloading the current page...");
Control.Reload();
control.Reload();
}
else
{
@ -659,7 +528,7 @@ namespace SafeExamBrowser.Browser
else if (WindowSettings.AllowReloading)
{
logger.Debug("Reloading current page...");
Control.Reload();
control.Reload();
}
else
{
@ -667,14 +536,6 @@ namespace SafeExamBrowser.Browser
}
}
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 _);
@ -682,7 +543,7 @@ namespace SafeExamBrowser.Browser
if (isValid)
{
logger.Debug($"The user requested to navigate to '{address}', the URI is valid.");
Control.NavigateTo(address);
control.NavigateTo(address);
}
else
{
@ -694,49 +555,34 @@ namespace SafeExamBrowser.Browser
private void Window_BackwardNavigationRequested()
{
logger.Debug("Navigating backwards...");
Control.NavigateBackwards();
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);
logger.Info($"Instance has terminated.");
control.Destroy();
Terminated?.Invoke(Id);
}
private void Window_DeveloperConsoleRequested()
{
logger.Debug("Showing developer console...");
Control.ShowDeveloperConsole();
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);
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);
control.NavigateForwards();
}
private void ZoomInRequested()
@ -744,7 +590,7 @@ namespace SafeExamBrowser.Browser
if (settings.AllowPageZoom && CalculateZoomPercentage() < 300)
{
zoomLevel += ZOOM_FACTOR;
Control.Zoom(zoomLevel);
control.Zoom(zoomLevel);
window.UpdateZoomLevel(CalculateZoomPercentage());
logger.Debug($"Increased page zoom to {CalculateZoomPercentage()}%.");
}
@ -755,7 +601,7 @@ namespace SafeExamBrowser.Browser
if (settings.AllowPageZoom && CalculateZoomPercentage() > 25)
{
zoomLevel -= ZOOM_FACTOR;
Control.Zoom(zoomLevel);
control.Zoom(zoomLevel);
window.UpdateZoomLevel(CalculateZoomPercentage());
logger.Debug($"Decreased page zoom to {CalculateZoomPercentage()}%.");
}
@ -766,20 +612,12 @@ namespace SafeExamBrowser.Browser
if (settings.AllowPageZoom)
{
zoomLevel = 0;
Control.Zoom(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

@ -1,195 +1,141 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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/.
*/
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 CefSharp.WinForms;
using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
namespace SafeExamBrowser.Browser
{
internal class BrowserControl : IBrowserControl
internal class BrowserControl : ChromiumWebBrowser, 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;
private IContextMenuHandler contextMenuHandler;
private IDialogHandler dialogHandler;
private IDisplayHandler displayHandler;
private IDownloadHandler downloadHandler;
private IKeyboardHandler keyboardHandler;
private ILifeSpanHandler lifeSpanHandler;
private 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;
private AddressChangedEventHandler addressChanged;
private LoadFailedEventHandler loadFailed;
private LoadingStateChangedEventHandler loadingStateChanged;
private TitleChangedEventHandler titleChanged;
public event AddressChangedEventHandler AddressChanged;
public event LoadFailedEventHandler LoadFailed;
public event LoadingStateChangedEventHandler LoadingStateChanged;
public event TitleChangedEventHandler TitleChanged;
public bool CanNavigateBackwards => GetBrowser().CanGoBack;
public bool CanNavigateForwards => GetBrowser().CanGoForward;
event AddressChangedEventHandler IBrowserControl.AddressChanged
{
add { addressChanged += value; }
remove { addressChanged -= value; }
}
event LoadFailedEventHandler IBrowserControl.LoadFailed
{
add { loadFailed += value; }
remove { loadFailed -= value; }
}
event LoadingStateChangedEventHandler IBrowserControl.LoadingStateChanged
{
add { loadingStateChanged += value; }
remove { loadingStateChanged -= value; }
}
event TitleChangedEventHandler IBrowserControl.TitleChanged
{
add { titleChanged += value; }
remove { titleChanged -= value; }
}
public BrowserControl(
Clipboard clipboard,
ICefSharpControl control,
IContextMenuHandler contextMenuHandler,
IDialogHandler dialogHandler,
IDisplayHandler displayHandler,
IDownloadHandler downloadHandler,
IKeyboardHandler keyboardHandler,
ILogger logger,
IRenderProcessMessageHandler renderProcessMessageHandler,
IRequestHandler requestHandler)
ILifeSpanHandler lifeSpanHandler,
IRequestHandler requestHandler,
string url) : base(url)
{
this.control = control;
this.clipboard = clipboard;
this.contextMenuHandler = contextMenuHandler;
this.dialogHandler = dialogHandler;
this.displayHandler = displayHandler;
this.downloadHandler = downloadHandler;
this.keyboardHandler = keyboardHandler;
this.logger = logger;
this.renderProcessMessageHandler = renderProcessMessageHandler;
this.lifeSpanHandler = lifeSpanHandler;
this.requestHandler = requestHandler;
}
public void Destroy()
{
if (!control.IsDisposed)
if (!IsDisposed)
{
control.Dispose(true);
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;
AddressChanged += (o, args) => addressChanged?.Invoke(args.Address);
LoadError += BrowserControl_LoadError;
LoadingStateChanged += (o, args) => loadingStateChanged?.Invoke(args.IsLoading);
TitleChanged += (o, args) => titleChanged?.Invoke(args.Title);
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);
DialogHandler = dialogHandler;
DisplayHandler = displayHandler;
DownloadHandler = downloadHandler;
KeyboardHandler = keyboardHandler;
LifeSpanHandler = lifeSpanHandler;
MenuHandler = contextMenuHandler;
RequestHandler = requestHandler;
}
if (control is IWebBrowser webBrowser)
{
webBrowser.JavascriptMessageReceived += WebBrowser_JavascriptMessageReceived;
}
public void Find(string term, bool isInitial, bool caseSensitive, bool forward = true)
{
this.Find(0, term, forward, caseSensitive, !isInitial);
}
public void NavigateBackwards()
{
control.BrowserCore.GoBack();
GetBrowser().GoBack();
}
public void NavigateForwards()
{
control.BrowserCore.GoForward();
GetBrowser().GoForward();
}
public void NavigateTo(string address)
{
control.Load(address);
Load(address);
}
public void ShowDeveloperConsole()
{
control.BrowserCore.ShowDevTools();
GetBrowser().ShowDevTools();
}
public void Reload()
{
control.BrowserCore.Reload();
GetBrowser().Reload();
}
public void Zoom(double level)
{
control.BrowserCore.SetZoomLevel(level);
GetBrowser().SetZoomLevel(level);
}
private void Clipboard_Changed(long id)
private void BrowserControl_LoadError(object sender, LoadErrorEventArgs e)
{
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);
loadFailed?.Invoke((int) e.ErrorCode, e.ErrorText, e.FailedUrl);
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -7,7 +7,7 @@
*/
using System;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
namespace SafeExamBrowser.Browser
{

View file

@ -1,72 +0,0 @@
/*
* 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)
{
if (settings.UseIsolatedClipboard)
{
try
{
var data = message.ConvertMessageTo<Data>();
if (data != default && data.Type == "Clipboard" && TrySetContent(data.Content))
{
Task.Run(() => Changed?.Invoke(data.Id));
}
}
catch (Exception e)
{
logger.Error($"Failed to process browser message '{message}'!", e);
}
}
}
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

@ -1,16 +0,0 @@
/*
* 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

@ -1,195 +0,0 @@
/*
* 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

@ -1,119 +0,0 @@
/*
* 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

@ -1,16 +0,0 @@
/*
* 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

@ -1,12 +0,0 @@
/*
* 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

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,12 +0,0 @@
/*
* 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

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -8,5 +8,5 @@
namespace SafeExamBrowser.Browser.Events
{
internal delegate void WindowClosedEventHandler(int id);
internal delegate void InstanceTerminatedEventHandler(int id);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -10,6 +10,6 @@ namespace SafeExamBrowser.Browser.Events
{
internal class PopupRequestedEventArgs
{
public BrowserWindow Window { get; set; }
public string Url { get; set; }
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -10,7 +10,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using CefSharp;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Wrapper;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
namespace SafeExamBrowser.Browser.Handlers
{
@ -18,13 +18,13 @@ namespace SafeExamBrowser.Browser.Handlers
{
internal event DialogRequestedEventHandler DialogRequested;
public bool OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, IFileDialogCallback callback)
public bool OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback)
{
var args = new DialogRequestedEventArgs
{
Element = mode.ToElement(),
Element = ToElement(mode),
InitialPath = defaultFilePath,
Operation = mode.ToOperation(),
Operation = ToOperation(mode),
Title = title
};
@ -36,7 +36,7 @@ namespace SafeExamBrowser.Browser.Handlers
{
if (args.Success)
{
callback.Continue(new List<string> { args.FullPath });
callback.Continue(selectedAcceptFilter, new List<string> { args.FullPath });
}
else
{
@ -47,5 +47,27 @@ namespace SafeExamBrowser.Browser.Handlers
return true;
}
private FileSystemElement ToElement(CefFileDialogMode mode)
{
switch (mode)
{
case CefFileDialogMode.OpenFolder:
return FileSystemElement.Folder;
default:
return FileSystemElement.File;
}
}
private FileSystemOperation ToOperation(CefFileDialogMode mode)
{
switch (mode)
{
case CefFileDialogMode.Save:
return FileSystemOperation.Save;
default:
return FileSystemOperation.Open;
}
}
}
}

View file

@ -1,16 +1,14 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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/.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using CefSharp;
using CefSharp.Enums;
using CefSharp.Structs;
using SafeExamBrowser.Browser.Events;
@ -35,11 +33,6 @@ namespace SafeExamBrowser.Browser.Handlers
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())

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -24,15 +24,14 @@ 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;
private AppConfig appConfig;
private BrowserSettings settings;
private WindowSettings windowSettings;
private ConcurrentDictionary<int, DownloadFinishedCallback> callbacks;
private ConcurrentDictionary<int, Guid> downloads;
private ILogger logger;
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
internal event DownloadAbortedEventHandler DownloadAborted;
internal event DownloadUpdatedEventHandler DownloadUpdated;
internal DownloadHandler(AppConfig appConfig, ILogger logger, BrowserSettings settings, WindowSettings windowSettings)
@ -45,33 +44,18 @@ namespace SafeExamBrowser.Browser.Handlers
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 uri = new Uri(downloadItem.Url);
var uriExtension = Path.GetExtension(uri.AbsolutePath);
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.ConfigurationFileExtension, uriExtension, StringComparison.OrdinalIgnoreCase);
isConfigurationFile |= string.Equals(appConfig.ConfigurationFileMimeType, downloadItem.MimeType, StringComparison.OrdinalIgnoreCase);
logger.Debug($"Detected download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")}.");
logger.Debug($"Detected download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{uri}'" : "")}.");
if (isConfigurationFile)
{
@ -83,8 +67,7 @@ namespace SafeExamBrowser.Browser.Handlers
}
else
{
logger.Info($"Aborted download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")}, as downloading is not allowed.");
Task.Run(() => DownloadAborted?.Invoke());
logger.Info($"Aborted download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{uri}'" : "")}, as downloading is not allowed.");
}
}
@ -102,7 +85,7 @@ namespace SafeExamBrowser.Browser.Handlers
IsComplete = downloadItem.IsComplete,
Url = downloadItem.Url
};
Task.Run(() => DownloadUpdated?.Invoke(state));
}
@ -110,7 +93,7 @@ namespace SafeExamBrowser.Browser.Handlers
{
logger.Debug($"Download of '{downloadItem.FullPath}' {(downloadItem.IsComplete ? "is complete" : "was cancelled")}.");
if (callbacks.TryRemove(downloadItem.Id, out var finished) && finished != null)
if (callbacks.TryRemove(downloadItem.Id, out DownloadFinishedCallback finished) && finished != null)
{
Task.Run(() => finished.Invoke(downloadItem.IsComplete, downloadItem.Url, downloadItem.FullPath));
}
@ -138,11 +121,6 @@ namespace SafeExamBrowser.Browser.Handlers
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.");
@ -160,26 +138,6 @@ namespace SafeExamBrowser.Browser.Handlers
}
}
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 };

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2020 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
@ -8,7 +8,6 @@
using System.Windows.Forms;
using CefSharp;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.UserInterface.Contracts;
namespace SafeExamBrowser.Browser.Handlers
@ -21,10 +20,6 @@ namespace SafeExamBrowser.Browser.Handlers
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)
{
@ -43,12 +38,7 @@ namespace SafeExamBrowser.Browser.Handlers
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))
if ((ctrl && keyCode == (int) Keys.Add) || (ctrl && shift && keyCode == (int) Keys.D1))
{
ZoomInRequested?.Invoke();
}
@ -62,14 +52,8 @@ namespace SafeExamBrowser.Browser.Handlers
{
ZoomResetRequested?.Invoke();
}
if (keyCode == (int) Keys.Tab && keyCode == currentKeyDown)
{
TabPressed?.Invoke(shift);
}
}
currentKeyDown = null;
return false;
}
@ -82,11 +66,6 @@ namespace SafeExamBrowser.Browser.Handlers
return true;
}
if (type == KeyType.RawKeyDown || type == KeyType.KeyDown)
{
currentKeyDown = keyCode;
}
return false;
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2020 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/.
*/
using CefSharp;
using SafeExamBrowser.Browser.Events;
namespace SafeExamBrowser.Browser.Handlers
{
internal class LifeSpanHandler : ILifeSpanHandler
{
public event PopupRequestedEventHandler PopupRequested;
public bool DoClose(IWebBrowser chromiumWebBrowser, IBrowser browser)
{
return false;
}
public void OnAfterCreated(IWebBrowser chromiumWebBrowser, IBrowser browser)
{
}
public void OnBeforeClose(IWebBrowser chromiumWebBrowser, IBrowser browser)
{
}
public bool OnBeforePopup(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser)
{
var args = new PopupRequestedEventArgs { Url = targetUrl };
newBrowser = default(IWebBrowser);
PopupRequested?.Invoke(args);
return true;
}
}
}

Some files were not shown because too many files have changed in this diff Show more