Compare commits
2 commits
master
...
3.0.0-alph
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cab1b3b049 | ||
![]() |
c38e57e081 |
1382 changed files with 14902 additions and 104081 deletions
123
.editorconfig
123
.editorconfig
|
@ -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
|
35
.github/ISSUE_TEMPLATE/bug-report.md
vendored
35
.github/ISSUE_TEMPLATE/bug-report.md
vendored
|
@ -1,35 +0,0 @@
|
|||
---
|
||||
name: Bug Report
|
||||
about: Create a bug 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`.
|
||||
|
||||
**Describe the Bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Steps to Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**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 problem here.
|
20
.github/ISSUE_TEMPLATE/feature-request.md
vendored
20
.github/ISSUE_TEMPLATE/feature-request.md
vendored
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea or new feature for Safe Exam Browser.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: dbuechel
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
54
.github/workflows/codeql.yml
vendored
54
.github/workflows/codeql.yml
vendored
|
@ -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}}"
|
25
.github/workflows/issues.yml
vendored
25
.github/workflows/issues.yml
vendored
|
@ -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
BIN
Libraries/SimpleWifi.dll
Normal file
Binary file not shown.
35
README.md
35
README.md
|
@ -1,26 +1,21 @@
|
|||
# Safe Exam Browser, Version 3.x
|
||||
|
||||
# seb-win-refactoring
|
||||
Refactored version of Safe Exam Browser for Windows with Chromium as integrated browser engine.
|
||||
|
||||
## Requirements
|
||||
**PLEASE NOTE**\
|
||||
This version is work in progress and not yet meant to be used by the public. See https://github.com/SafeExamBrowser/seb-win/releases for the current, official release of SEB.
|
||||
|
||||
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.
|
||||
## Status & Current Build
|
||||
[](https://ci.appveyor.com/project/dbuechel/seb-win-refactoring)
|
||||
[](https://ci.appveyor.com/project/dbuechel/seb-win-refactoring/build/tests)
|
||||
[](https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring)
|
||||
|
||||
* .NET Framework 4.8 Runtime: https://dotnet.microsoft.com/download/dotnet-framework/net48
|
||||
* Visual C++ 2015-2019 Redistributable: https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads
|
||||
**Disclaimer**\
|
||||
Development builds may be unstable and should thus _never_ be used in a production environment!
|
||||
|
||||
## Project Status
|
||||
**Requirements**\
|
||||
SEB 3.x requires these prerequisites in order to work correctly:
|
||||
* .NET Framework 4.7.2 Runtime: https://dotnet.microsoft.com/download/dotnet-framework/net472
|
||||
* Visual C++ Runtime Redistributable (x86, 2015): https://www.microsoft.com/en-us/download/details.aspx?id=53840
|
||||
|
||||
> [!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.
|
||||
|
||||
| Aspect | Status | Details |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| Development Build |  | https://sebdev.ethz.ch/project/appveyor/seb-win-refactoring |
|
||||
| Test Build |  | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
|
||||
| Test Run |  | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
|
||||
| Code Coverage |  | https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring |
|
||||
| Issue Status |  | https://github.com/SafeExamBrowser/seb-win-refactoring/issues |
|
||||
| Downloads |  | https://github.com/SafeExamBrowser/seb-win-refactoring/releases |
|
||||
| Development |  | n/a |
|
||||
| Repository Size |  | n/a |
|
||||
**Download**\
|
||||
Download the latest build here: https://ci.appveyor.com/project/dbuechel/seb-win-refactoring/build/artifacts
|
||||
|
|
35
SECURITY.md
35
SECURITY.md
|
@ -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.
|
|
@ -1,17 +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.Core.Contracts.Resources.Icons;
|
||||
|
||||
namespace SafeExamBrowser.Applications.Contracts.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Event handler used to indicate that an icon has changed.
|
||||
/// </summary>
|
||||
public delegate void IconChangedEventHandler(IconResource icon);
|
||||
}
|
|
@ -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.Applications.Contracts.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Event handler used to indicate that a title has changed to a new value.
|
||||
/// </summary>
|
||||
public delegate void TitleChangedEventHandler(string title);
|
||||
}
|
|
@ -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.Applications.Contracts.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Event handler used to indicate that the windows of an application have changed.
|
||||
/// </summary>
|
||||
public delegate void WindowsChangedEventHandler();
|
||||
}
|
|
@ -1,31 +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.Applications.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines all possible results of an attempt to create an application.
|
||||
/// </summary>
|
||||
public enum FactoryResult
|
||||
{
|
||||
/// <summary>
|
||||
/// An error occurred while trying to create the application.
|
||||
/// </summary>
|
||||
Error,
|
||||
|
||||
/// <summary>
|
||||
/// The application could not be found on the system.
|
||||
/// </summary>
|
||||
NotFound,
|
||||
|
||||
/// <summary>
|
||||
/// The application has been created successfully.
|
||||
/// </summary>
|
||||
Success
|
||||
}
|
||||
}
|
|
@ -1,71 +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 SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Core.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
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether the application should be automatically started.
|
||||
/// </summary>
|
||||
bool AutoStart { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The resource providing the application icon.
|
||||
/// </summary>
|
||||
IconResource Icon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique identifier of the application.
|
||||
/// </summary>
|
||||
Guid Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the application.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The tooltip for the application.
|
||||
/// </summary>
|
||||
string Tooltip { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the windows of the application have changed.
|
||||
/// </summary>
|
||||
event WindowsChangedEventHandler WindowsChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Returns all windows of the application.
|
||||
/// </summary>
|
||||
IEnumerable<TWindow> GetWindows();
|
||||
|
||||
/// <summary>
|
||||
/// Performs any initialization work, if necessary.
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Starts the execution of the application.
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// Performs any termination work, e.g. releasing of used resources.
|
||||
/// </summary>
|
||||
void Terminate();
|
||||
}
|
||||
}
|
|
@ -1,23 +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.Settings.Applications;
|
||||
|
||||
namespace SafeExamBrowser.Applications.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides functionality to create external applications.
|
||||
/// </summary>
|
||||
public interface IApplicationFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to create an application according to the given settings.
|
||||
/// </summary>
|
||||
FactoryResult TryCreate(WhitelistApplication settings, out IApplication<IApplicationWindow> application);
|
||||
}
|
||||
}
|
|
@ -1,50 +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 SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
|
||||
namespace SafeExamBrowser.Applications.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a window of an <see cref="IApplication{TWindow}"/>.
|
||||
/// </summary>
|
||||
public interface IApplicationWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// The native handle of the window.
|
||||
/// </summary>
|
||||
IntPtr Handle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The icon of the window.
|
||||
/// </summary>
|
||||
IconResource Icon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The title of the window.
|
||||
/// </summary>
|
||||
string Title { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the icon of the window has changed.
|
||||
/// </summary>
|
||||
event IconChangedEventHandler IconChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the title of the window has changed.
|
||||
/// </summary>
|
||||
event TitleChangedEventHandler TitleChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Brings the window to the foreground and activates it.
|
||||
/// </summary>
|
||||
void Activate();
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("SafeExamBrowser.Applications.Contracts")]
|
||||
[assembly: AssemblyDescription("Safe Exam Browser")]
|
||||
[assembly: AssemblyCompany("ETH Zürich")]
|
||||
[assembly: AssemblyProduct("SafeExamBrowser.Applications.Contracts")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("ac77745d-3b41-43e2-8e84-d40e5a4ee77f")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
[assembly: AssemblyInformationalVersion("1.0.0.0")]
|
|
@ -1,78 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<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>{AC77745D-3B41-43E2-8E84-D40E5A4EE77F}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SafeExamBrowser.Applications.Contracts</RootNamespace>
|
||||
<AssemblyName>SafeExamBrowser.Applications.Contracts</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</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>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Events\IconChangedEventHandler.cs" />
|
||||
<Compile Include="Events\TitleChangedEventHandler.cs" />
|
||||
<Compile Include="Events\WindowsChangedEventHandler.cs" />
|
||||
<Compile Include="FactoryResult.cs" />
|
||||
<Compile Include="IApplication.cs" />
|
||||
<Compile Include="IApplicationFactory.cs" />
|
||||
<Compile Include="IApplicationWindow.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.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>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")]
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,147 +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.IO;
|
||||
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;
|
||||
|
||||
public ApplicationFactory(
|
||||
IApplicationMonitor applicationMonitor,
|
||||
IModuleLogger logger,
|
||||
INativeMethods nativeMethods,
|
||||
IProcessFactory processFactory,
|
||||
IRegistry registry)
|
||||
{
|
||||
this.applicationMonitor = applicationMonitor;
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.processFactory = processFactory;
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
public FactoryResult TryCreate(WhitelistApplication settings, out IApplication<IApplicationWindow> application)
|
||||
{
|
||||
var name = $"'{settings.DisplayName}' ({settings.ExecutableName})";
|
||||
|
||||
application = default;
|
||||
|
||||
try
|
||||
{
|
||||
var success = TryFindApplication(settings, out var executablePath);
|
||||
|
||||
if (success)
|
||||
{
|
||||
application = BuildApplication(executablePath, settings);
|
||||
application.Initialize();
|
||||
|
||||
logger.Debug($"Successfully initialized application {name}.");
|
||||
|
||||
return FactoryResult.Success;
|
||||
}
|
||||
|
||||
logger.Error($"Could not find application {name}!");
|
||||
|
||||
return FactoryResult.NotFound;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Unexpected error while trying to initialize application {name}!", e);
|
||||
}
|
||||
|
||||
return FactoryResult.Error;
|
||||
}
|
||||
|
||||
private IApplication<IApplicationWindow> 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);
|
||||
|
||||
return application;
|
||||
}
|
||||
|
||||
private bool TryFindApplication(WhitelistApplication settings, out string mainExecutable)
|
||||
{
|
||||
var paths = new List<string[]>();
|
||||
var registryPath = QueryPathFromRegistry(settings);
|
||||
|
||||
mainExecutable = default;
|
||||
|
||||
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)
|
||||
{
|
||||
paths.Add(new[] { settings.ExecutablePath, settings.ExecutableName });
|
||||
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), settings.ExecutablePath, settings.ExecutableName });
|
||||
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), settings.ExecutablePath, settings.ExecutableName });
|
||||
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.System), settings.ExecutablePath, settings.ExecutableName });
|
||||
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), settings.ExecutablePath, settings.ExecutableName });
|
||||
}
|
||||
|
||||
if (registryPath != default)
|
||||
{
|
||||
paths.Add(new[] { registryPath, settings.ExecutableName });
|
||||
|
||||
if (settings.ExecutablePath != default)
|
||||
{
|
||||
paths.Add(new[] { registryPath, settings.ExecutablePath, settings.ExecutableName });
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var path in paths)
|
||||
{
|
||||
try
|
||||
{
|
||||
mainExecutable = Path.Combine(path);
|
||||
mainExecutable = Environment.ExpandEnvironmentVariables(mainExecutable);
|
||||
|
||||
if (File.Exists(mainExecutable))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to test path {string.Join(@"\", path)}!", e);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string QueryPathFromRegistry(WhitelistApplication settings)
|
||||
{
|
||||
if (registry.TryRead($@"{RegistryValue.MachineHive.AppPaths_Key}\{settings.ExecutableName}", "Path", out var value))
|
||||
{
|
||||
return value as string;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.Applications.Events
|
||||
{
|
||||
internal delegate void InstanceTerminatedEventHandler(int id);
|
||||
}
|
|
@ -1,174 +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 SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||
using SafeExamBrowser.Settings.Applications;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Applications
|
||||
{
|
||||
internal class ExternalApplication : IApplication<IApplicationWindow>
|
||||
{
|
||||
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;
|
||||
|
||||
public bool AutoStart { get; private set; }
|
||||
public IconResource Icon { get; private set; }
|
||||
public Guid Id { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public string Tooltip { get; private set; }
|
||||
|
||||
public event WindowsChangedEventHandler WindowsChanged;
|
||||
|
||||
internal ExternalApplication(
|
||||
IApplicationMonitor applicationMonitor,
|
||||
string executablePath,
|
||||
IModuleLogger logger,
|
||||
INativeMethods nativeMethods,
|
||||
IProcessFactory processFactory,
|
||||
WhitelistApplication settings,
|
||||
int windowMonitoringInterval_ms)
|
||||
{
|
||||
this.applicationMonitor = applicationMonitor;
|
||||
this.executablePath = executablePath;
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.instances = new List<ExternalApplicationInstance>();
|
||||
this.processFactory = processFactory;
|
||||
this.settings = settings;
|
||||
this.windowMonitoringInterval = windowMonitoringInterval_ms;
|
||||
}
|
||||
|
||||
public IEnumerable<IApplicationWindow> GetWindows()
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
return instances.SelectMany(i => i.GetWindows());
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
AutoStart = settings.AutoStart;
|
||||
Icon = new EmbeddedIconResource { FilePath = executablePath };
|
||||
Id = settings.Id;
|
||||
Name = settings.DisplayName;
|
||||
Tooltip = settings.Description ?? settings.DisplayName;
|
||||
|
||||
applicationMonitor.InstanceStarted += ApplicationMonitor_InstanceStarted;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.Info("Starting application...");
|
||||
InitializeInstance(processFactory.StartNew(executablePath, BuildArguments()));
|
||||
logger.Info("Successfully started application.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to start application!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
applicationMonitor.InstanceStarted -= ApplicationMonitor_InstanceStarted;
|
||||
|
||||
try
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (instances.Any() && !settings.AllowRunning)
|
||||
{
|
||||
logger.Info($"Terminating application with {instances.Count} instance(s)...");
|
||||
|
||||
foreach (var instance in instances)
|
||||
{
|
||||
instance.Terminated -= Instance_Terminated;
|
||||
instance.Terminate();
|
||||
}
|
||||
|
||||
logger.Info("Successfully terminated application.");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to terminate application!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplicationMonitor_InstanceStarted(Guid applicationId, IProcess process)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
var isNewInstance = instances.All(i => i.Id != process.Id);
|
||||
|
||||
if (applicationId == Id && isNewInstance)
|
||||
{
|
||||
logger.Info("New application instance was started.");
|
||||
InitializeInstance(process);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Instance_Terminated(int id)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
instances.Remove(instances.First(i => i.Id == id));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
instance.Terminated += Instance_Terminated;
|
||||
instance.WindowsChanged += () => WindowsChanged?.Invoke();
|
||||
instance.Initialize();
|
||||
|
||||
instances.Add(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,174 +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.Timers;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Applications.Events;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Applications
|
||||
{
|
||||
internal class ExternalApplicationInstance
|
||||
{
|
||||
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 Timer timer;
|
||||
|
||||
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)
|
||||
{
|
||||
this.icon = icon;
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.process = process;
|
||||
this.windowMonitoringInterval = windowMonitoringInterval_ms;
|
||||
this.windows = new List<ExternalApplicationWindow>();
|
||||
}
|
||||
|
||||
internal IEnumerable<IApplicationWindow> GetWindows()
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
return new List<IApplicationWindow>(windows);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
Id = process.Id;
|
||||
InitializeEvents();
|
||||
logger.Info("Initialized application instance.");
|
||||
}
|
||||
|
||||
internal void Terminate()
|
||||
{
|
||||
const int MAX_ATTEMPTS = 5;
|
||||
const int TIMEOUT_MS = 500;
|
||||
|
||||
var terminated = process.HasTerminated;
|
||||
|
||||
if (terminated)
|
||||
{
|
||||
logger.Info("Application instance is already terminated.");
|
||||
}
|
||||
else
|
||||
{
|
||||
FinalizeEvents();
|
||||
|
||||
for (var attempt = 0; attempt < MAX_ATTEMPTS && !terminated; attempt++)
|
||||
{
|
||||
terminated = process.TryClose(TIMEOUT_MS);
|
||||
}
|
||||
|
||||
for (var attempt = 0; attempt < MAX_ATTEMPTS && !terminated; attempt++)
|
||||
{
|
||||
terminated = process.TryKill(TIMEOUT_MS);
|
||||
}
|
||||
|
||||
if (terminated)
|
||||
{
|
||||
logger.Info("Successfully terminated application instance.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("Failed to terminate application instance!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Process_Terminated(int exitCode)
|
||||
{
|
||||
logger.Info($"Application instance has terminated with exit code {exitCode}.");
|
||||
FinalizeEvents();
|
||||
Terminated?.Invoke(Id);
|
||||
}
|
||||
|
||||
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
var changed = false;
|
||||
var openWindows = nativeMethods.GetOpenWindows();
|
||||
|
||||
lock (@lock)
|
||||
{
|
||||
var closedWindows = windows.Where(w => openWindows.All(ow => ow != w.Handle)).ToList();
|
||||
var openedWindows = openWindows.Where(ow => windows.All(w => w.Handle != ow) && BelongsToInstance(ow)).ToList();
|
||||
|
||||
foreach (var window in closedWindows)
|
||||
{
|
||||
changed = true;
|
||||
windows.Remove(window);
|
||||
}
|
||||
|
||||
foreach (var window in openedWindows)
|
||||
{
|
||||
changed = true;
|
||||
windows.Add(new ExternalApplicationWindow(icon, nativeMethods, window));
|
||||
}
|
||||
|
||||
foreach (var window in windows)
|
||||
{
|
||||
window.Update();
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
WindowsChanged?.Invoke();
|
||||
}
|
||||
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
private bool BelongsToInstance(IntPtr window)
|
||||
{
|
||||
return nativeMethods.GetProcessIdFor(window) == process.Id;
|
||||
}
|
||||
|
||||
private void InitializeEvents()
|
||||
{
|
||||
process.Terminated += Process_Terminated;
|
||||
|
||||
timer = new Timer(windowMonitoringInterval);
|
||||
timer.Elapsed += Timer_Elapsed;
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
private void FinalizeEvents()
|
||||
{
|
||||
if (timer != default)
|
||||
{
|
||||
timer.Elapsed -= Timer_Elapsed;
|
||||
timer.Stop();
|
||||
}
|
||||
|
||||
process.Terminated -= Process_Terminated;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,60 +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 SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Applications
|
||||
{
|
||||
internal class ExternalApplicationWindow : IApplicationWindow
|
||||
{
|
||||
private readonly INativeMethods nativeMethods;
|
||||
|
||||
public IntPtr Handle { get; }
|
||||
public IconResource Icon { get; private set; }
|
||||
public string Title { get; private set; }
|
||||
|
||||
public event IconChangedEventHandler IconChanged;
|
||||
public event TitleChangedEventHandler TitleChanged;
|
||||
|
||||
internal ExternalApplicationWindow(IconResource icon, INativeMethods nativeMethods, IntPtr handle)
|
||||
{
|
||||
this.Handle = handle;
|
||||
this.Icon = icon;
|
||||
this.nativeMethods = nativeMethods;
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
nativeMethods.ActivateWindow(Handle);
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
var icon = nativeMethods.GetWindowIcon(Handle);
|
||||
var iconChanged = icon != IntPtr.Zero && (!(Icon is NativeIconResource) || Icon is NativeIconResource r && r.Handle != icon);
|
||||
var title = nativeMethods.GetWindowTitle(Handle);
|
||||
var titleChanged = Title?.Equals(title, StringComparison.Ordinal) != true;
|
||||
|
||||
if (iconChanged)
|
||||
{
|
||||
Icon = new NativeIconResource { Handle = icon };
|
||||
IconChanged?.Invoke(Icon);
|
||||
}
|
||||
|
||||
if (titleChanged)
|
||||
{
|
||||
Title = title;
|
||||
TitleChanged?.Invoke(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("SafeExamBrowser.Applications")]
|
||||
[assembly: AssemblyDescription("Safe Exam Browser")]
|
||||
[assembly: AssemblyCompany("ETH Zürich")]
|
||||
[assembly: AssemblyProduct("SafeExamBrowser.Applications")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: InternalsVisibleTo("SafeExamBrowser.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")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
[assembly: AssemblyInformationalVersion("1.0.0.0")]
|
|
@ -1,96 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<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>{A113E68F-1209-4689-981A-15C554B2DF4E}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SafeExamBrowser.Applications</RootNamespace>
|
||||
<AssemblyName>SafeExamBrowser.Applications</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</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>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ApplicationFactory.cs" />
|
||||
<Compile Include="Events\InstanceTerminatedEventHandler.cs" />
|
||||
<Compile Include="ExternalApplication.cs" />
|
||||
<Compile Include="ExternalApplicationInstance.cs" />
|
||||
<Compile Include="ExternalApplicationWindow.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Applications.Contracts\SafeExamBrowser.Applications.Contracts.csproj">
|
||||
<Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project>
|
||||
<Name>SafeExamBrowser.Applications.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.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="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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 a termination request has been detected.
|
||||
/// </summary>
|
||||
public delegate void TerminationRequestedEventHandler();
|
||||
}
|
|
@ -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 browser has detected a user identifier of an LMS.
|
||||
/// </summary>
|
||||
public delegate void UserIdentifierDetectedEventHandler(string identifier);
|
||||
}
|
|
@ -1,33 +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.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Contracts.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the filter for browser requests.
|
||||
/// </summary>
|
||||
public interface IRequestFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// The default result to be returned by <see cref="Process(Request)"/> if no rule matches.
|
||||
/// </summary>
|
||||
FilterResult Default { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads the given filter rule to be used when processing requests.
|
||||
/// </summary>
|
||||
void Load(IRule rule);
|
||||
|
||||
/// <summary>
|
||||
/// Filters the given request according to the loaded rules.
|
||||
/// </summary>
|
||||
FilterResult Process(Request request);
|
||||
}
|
||||
}
|
|
@ -1,33 +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.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Contracts.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a request filter rule.
|
||||
/// </summary>
|
||||
public interface IRule
|
||||
{
|
||||
/// <summary>
|
||||
/// The filter result to be used if the rule matches a request.
|
||||
/// </summary>
|
||||
FilterResult Result { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the rule for processing requests.
|
||||
/// </summary>
|
||||
void Initialize(FilterRuleSettings settings);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the rule applies for the given request.
|
||||
/// </summary>
|
||||
bool IsMatch(Request request);
|
||||
}
|
||||
}
|
|
@ -1,23 +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.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Contracts.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds request filter rules.
|
||||
/// </summary>
|
||||
public interface IRuleFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a filter rule for the given type.
|
||||
/// </summary>
|
||||
IRule CreateRule(FilterRuleType type);
|
||||
}
|
||||
}
|
|
@ -1,21 +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.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds data relevant for filtering requests.
|
||||
/// </summary>
|
||||
public class Request
|
||||
{
|
||||
/// <summary>
|
||||
/// The full URL of the request.
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,45 +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;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls the lifetime and functionality of the browser application.
|
||||
/// </summary>
|
||||
public interface IBrowserApplication : IApplication<IBrowserWindow>
|
||||
{
|
||||
/// <summary>
|
||||
/// Event fired when the browser application detects a download request for an application configuration file.
|
||||
/// </summary>
|
||||
event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the user tries to focus the taskbar.
|
||||
/// </summary>
|
||||
event LoseFocusRequestedEventHandler LoseFocusRequested;
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("SafeExamBrowser.Browser.Contracts")]
|
||||
[assembly: AssemblyDescription("Safe Exam Browser")]
|
||||
[assembly: AssemblyCompany("ETH Zürich")]
|
||||
[assembly: AssemblyProduct("SafeExamBrowser.Browser.Contracts")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("5fb5273d-277c-41dd-8593-a25ce1aff2e9")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
[assembly: AssemblyInformationalVersion("1.0.0.0")]
|
|
@ -1,84 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<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>{5FB5273D-277C-41DD-8593-A25CE1AFF2E9}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SafeExamBrowser.Browser.Contracts</RootNamespace>
|
||||
<AssemblyName>SafeExamBrowser.Browser.Contracts</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</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>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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\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>
|
||||
<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.Settings\SafeExamBrowser.Settings.csproj">
|
||||
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
|
||||
<Name>SafeExamBrowser.Settings</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -1,553 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* 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
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SafeExamBrowser.Browser.UnitTests.Filters
|
||||
{
|
||||
internal class LegacyFilter
|
||||
{
|
||||
internal Regex scheme;
|
||||
internal Regex user;
|
||||
internal Regex password;
|
||||
internal Regex host;
|
||||
internal int? port;
|
||||
internal Regex path;
|
||||
internal Regex query;
|
||||
internal Regex fragment;
|
||||
|
||||
internal LegacyFilter(string filterExpressionString)
|
||||
{
|
||||
SEBURLFilterExpression URLFromString = new SEBURLFilterExpression(filterExpressionString);
|
||||
try
|
||||
{
|
||||
this.scheme = RegexForFilterString(URLFromString.scheme);
|
||||
this.user = RegexForFilterString(URLFromString.user);
|
||||
this.password = RegexForFilterString(URLFromString.password);
|
||||
this.host = RegexForHostFilterString(URLFromString.host);
|
||||
this.port = URLFromString.port;
|
||||
this.path = RegexForPathFilterString(URLFromString.path);
|
||||
this.query = RegexForQueryFilterString(URLFromString.query);
|
||||
this.fragment = RegexForFilterString(URLFromString.fragment);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// Method comparing all components of a passed URL with the filter expression
|
||||
// and returning YES (= allow or block) if it matches
|
||||
internal bool IsMatch(Uri URLToFilter)
|
||||
{
|
||||
Regex filterComponent;
|
||||
|
||||
// If a scheme is indicated in the filter expression, it has to match
|
||||
filterComponent = scheme;
|
||||
UriBuilder urlToFilterParts = new UriBuilder(URLToFilter);
|
||||
|
||||
if (filterComponent != null &&
|
||||
!Regex.IsMatch(URLToFilter.Scheme, filterComponent.ToString(), RegexOptions.IgnoreCase))
|
||||
{
|
||||
// Scheme of the URL to filter doesn't match the one from the filter expression: Exit with matching = NO
|
||||
return false;
|
||||
}
|
||||
|
||||
string userInfo = URLToFilter.UserInfo;
|
||||
filterComponent = user;
|
||||
if (filterComponent != null &&
|
||||
!Regex.IsMatch(urlToFilterParts.UserName, filterComponent.ToString(), RegexOptions.IgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
filterComponent = password;
|
||||
if (filterComponent != null &&
|
||||
!Regex.IsMatch(urlToFilterParts.Password, filterComponent.ToString(), RegexOptions.IgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
filterComponent = host;
|
||||
if (filterComponent != null &&
|
||||
!Regex.IsMatch(URLToFilter.Host, filterComponent.ToString(), RegexOptions.IgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (port != null && URLToFilter.Port != port)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
filterComponent = path;
|
||||
if (filterComponent != null &&
|
||||
!Regex.IsMatch(URLToFilter.AbsolutePath.TrimEnd(new char[] { '/' }), filterComponent.ToString(), RegexOptions.IgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string urlQuery = URLToFilter.GetComponents(UriComponents.Query, UriFormat.Unescaped);
|
||||
filterComponent = query;
|
||||
if (filterComponent != null)
|
||||
{
|
||||
// If there's a query filter component, then we need to even filter empty URL query strings
|
||||
// as the filter might either allow some specific queries or no query at all ("?." query filter)
|
||||
if (urlQuery == null)
|
||||
{
|
||||
urlQuery = "";
|
||||
}
|
||||
if (!Regex.IsMatch(urlQuery, filterComponent.ToString(), RegexOptions.IgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
string urlFragment = URLToFilter.GetComponents(UriComponents.Fragment, UriFormat.Unescaped);
|
||||
filterComponent = fragment;
|
||||
if (filterComponent != null &&
|
||||
!Regex.IsMatch(urlFragment, filterComponent.ToString(), RegexOptions.IgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// URL matches the filter expression
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static Regex RegexForFilterString(string filterString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filterString))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
string regexString = Regex.Escape(filterString);
|
||||
regexString = regexString.Replace("\\*", ".*?");
|
||||
// Add regex command characters for matching at start and end of a line (part)
|
||||
regexString = string.Format("^{0}$", regexString);
|
||||
|
||||
try
|
||||
{
|
||||
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
|
||||
return regex;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static Regex RegexForHostFilterString(string filterString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filterString))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if host string has a dot "." prefix to disable subdomain matching
|
||||
if (filterString.Length > 1 && filterString.StartsWith("."))
|
||||
{
|
||||
// Get host string without the "." prefix
|
||||
filterString = filterString.Substring(1);
|
||||
// Get regex for host <*://example.com> (without possible subdomains)
|
||||
return RegexForFilterString(filterString);
|
||||
}
|
||||
// Allow subdomain matching: Create combined regex for <example.com> and <*.example.com>
|
||||
string regexString = Regex.Escape(filterString);
|
||||
regexString = regexString.Replace("\\*", ".*?");
|
||||
// Add regex command characters for matching at start and end of a line (part)
|
||||
regexString = string.Format("^(({0})|(.*?\\.{0}))$", regexString);
|
||||
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
|
||||
return regex;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static Regex RegexForPathFilterString(string filterString)
|
||||
{
|
||||
// Trim a possible trailing slash "/", we will instead add a rule to also match paths to directories without trailing slash
|
||||
filterString = filterString.TrimEnd(new char[] { '/' });
|
||||
;
|
||||
|
||||
if (string.IsNullOrEmpty(filterString))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if path string ends with a "/*" for matching contents of a directory
|
||||
if (filterString.EndsWith("/*"))
|
||||
{
|
||||
// As the path filter string matches for a directory, we need to add a string to match directories without trailing slash
|
||||
|
||||
// Get path string without the "/*" suffix
|
||||
string filterStringDirectory = filterString.Substring(0, filterString.Length - 2);
|
||||
|
||||
string regexString = Regex.Escape(filterString);
|
||||
regexString = regexString.Replace("\\*", ".*?");
|
||||
|
||||
string regexStringDir = Regex.Escape(filterString);
|
||||
regexStringDir = regexStringDir.Replace("\\*", ".*?");
|
||||
|
||||
// Add regex command characters for matching at start and end of a line (part)
|
||||
regexString = string.Format("^(({0})|({1}))$", regexString, regexStringDir);
|
||||
|
||||
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
|
||||
return regex;
|
||||
}
|
||||
else
|
||||
{
|
||||
return RegexForFilterString(filterString);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static Regex RegexForQueryFilterString(string filterString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filterString))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (filterString.Equals("."))
|
||||
{
|
||||
// Add regex command characters for matching at start and end of a line (part)
|
||||
// and regex for no string allowed
|
||||
string regexString = @"^$";
|
||||
try
|
||||
{
|
||||
Regex regex = new Regex(regexString, RegexOptions.IgnoreCase);
|
||||
return regex;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return RegexForFilterString(filterString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder expressionString = new StringBuilder();
|
||||
string part;
|
||||
expressionString.Append("^");
|
||||
|
||||
/// Scheme
|
||||
if (this.scheme != null)
|
||||
{
|
||||
// If there is a regex filter for scheme
|
||||
// get stripped regex pattern
|
||||
part = StringForRegexFilter(this.scheme);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise use the regex wildcard pattern for scheme
|
||||
part = @".*?";
|
||||
}
|
||||
|
||||
expressionString.AppendFormat("{0}:\\/\\/", part);
|
||||
|
||||
/// User/Password
|
||||
if (this.user != null)
|
||||
{
|
||||
part = StringForRegexFilter(this.user);
|
||||
|
||||
expressionString.Append(part);
|
||||
|
||||
if (this.password != null)
|
||||
{
|
||||
expressionString.AppendFormat(":{0}@", StringForRegexFilter(this.password));
|
||||
}
|
||||
else
|
||||
{
|
||||
expressionString.Append("@");
|
||||
}
|
||||
}
|
||||
|
||||
/// Host
|
||||
string hostPort = "";
|
||||
if (this.host != null)
|
||||
{
|
||||
hostPort = StringForRegexFilter(this.host);
|
||||
}
|
||||
else
|
||||
{
|
||||
hostPort = ".*?";
|
||||
}
|
||||
|
||||
/// Port
|
||||
if (this.port != null && this.port > 0 && this.port <= 65535)
|
||||
{
|
||||
hostPort = string.Format("{0}:{1}", hostPort, this.port);
|
||||
}
|
||||
|
||||
// When there is a host, but no path
|
||||
if (this.host != null && this.path == null)
|
||||
{
|
||||
hostPort = string.Format("(({0})|({0}\\/.*?))", hostPort);
|
||||
}
|
||||
|
||||
expressionString.Append(hostPort);
|
||||
|
||||
/// Path
|
||||
if (this.path != null)
|
||||
{
|
||||
string path = StringForRegexFilter(this.path);
|
||||
if (path.StartsWith("\\/"))
|
||||
{
|
||||
expressionString.Append(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
expressionString.AppendFormat("\\/{0}", path);
|
||||
}
|
||||
}
|
||||
|
||||
/// Query
|
||||
if (this.query != null)
|
||||
{
|
||||
// Check for special case Query = "?." which means no query string is allowed
|
||||
if (StringForRegexFilter(this.query).Equals("."))
|
||||
{
|
||||
expressionString.AppendFormat("[^\\?]");
|
||||
}
|
||||
else
|
||||
{
|
||||
expressionString.AppendFormat("\\?{0}", StringForRegexFilter(this.query));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
expressionString.AppendFormat("(()|(\\?.*?))");
|
||||
}
|
||||
|
||||
/// Fragment
|
||||
if (this.fragment != null)
|
||||
{
|
||||
expressionString.AppendFormat("#{0}", StringForRegexFilter(this.fragment));
|
||||
}
|
||||
|
||||
expressionString.Append("$");
|
||||
|
||||
return expressionString.ToString();
|
||||
}
|
||||
|
||||
internal string StringForRegexFilter(Regex regexFilter)
|
||||
{
|
||||
// Get pattern string from regular expression
|
||||
string regexPattern = regexFilter.ToString();
|
||||
if (regexPattern.Length <= 2)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
// Remove the regex command characters for matching at start and end of a line
|
||||
regexPattern = regexPattern.Substring(1, regexPattern.Length - 2);
|
||||
return regexPattern;
|
||||
}
|
||||
|
||||
private class SEBURLFilterExpression
|
||||
{
|
||||
internal string scheme;
|
||||
internal string user;
|
||||
internal string password;
|
||||
internal string host;
|
||||
internal int? port;
|
||||
internal string path;
|
||||
internal string query;
|
||||
internal string fragment;
|
||||
|
||||
internal SEBURLFilterExpression(string filterExpressionString)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(filterExpressionString))
|
||||
{
|
||||
/// Convert Uri to a SEBURLFilterExpression
|
||||
string splitURLRegexPattern = @"(?:([^\:]*)\:\/\/)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^\/\:]*))?(?:\:([0-9\*]*))?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
|
||||
Regex splitURLRegex = new Regex(splitURLRegexPattern);
|
||||
Match regexMatch = splitURLRegex.Match(filterExpressionString);
|
||||
if (regexMatch.Success == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.scheme = regexMatch.Groups[1].Value;
|
||||
this.user = regexMatch.Groups[2].Value;
|
||||
this.password = regexMatch.Groups[3].Value;
|
||||
this.host = regexMatch.Groups[4].Value;
|
||||
|
||||
// Treat a special case when a query or fragment is interpreted as part of the host address
|
||||
if (this.host.Contains("?") || this.host.Contains("#"))
|
||||
{
|
||||
string splitURLRegexPattern2 = @"([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
|
||||
Regex splitURLRegex2 = new Regex(splitURLRegexPattern2);
|
||||
Match regexMatch2 = splitURLRegex2.Match(this.host);
|
||||
if (regexMatch.Success == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.host = regexMatch2.Groups[1].Value;
|
||||
this.port = null;
|
||||
this.path = "";
|
||||
this.query = regexMatch2.Groups[2].Value;
|
||||
this.fragment = regexMatch2.Groups[3].Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
string portNumber = regexMatch.Groups[5].Value;
|
||||
|
||||
// We only want a port if the filter expression string explicitely defines one!
|
||||
if (portNumber.Length == 0 || portNumber == "*")
|
||||
{
|
||||
this.port = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.port = UInt16.Parse(portNumber);
|
||||
}
|
||||
|
||||
this.path = regexMatch.Groups[6].Value.TrimEnd(new char[] { '/' });
|
||||
this.query = regexMatch.Groups[7].Value;
|
||||
this.fragment = regexMatch.Groups[8].Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static string User(string userInfo)
|
||||
{
|
||||
string user = "";
|
||||
if (!string.IsNullOrEmpty(userInfo))
|
||||
{
|
||||
int userPasswordSeparator = userInfo.IndexOf(":");
|
||||
if (userPasswordSeparator == -1)
|
||||
{
|
||||
user = userInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (userPasswordSeparator != 0)
|
||||
{
|
||||
user = userInfo.Substring(0, userPasswordSeparator);
|
||||
}
|
||||
}
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
internal static string Password(string userInfo)
|
||||
{
|
||||
string password = "";
|
||||
if (!string.IsNullOrEmpty(userInfo))
|
||||
{
|
||||
int userPasswordSeparator = userInfo.IndexOf(":");
|
||||
if (userPasswordSeparator != -1)
|
||||
{
|
||||
if (userPasswordSeparator < userInfo.Length - 1)
|
||||
{
|
||||
password = userInfo.Substring(userPasswordSeparator + 1, userInfo.Length - 1 - userPasswordSeparator);
|
||||
}
|
||||
}
|
||||
}
|
||||
return password;
|
||||
}
|
||||
|
||||
internal SEBURLFilterExpression(string scheme, string user, string password, string host, int port, string path, string query, string fragment)
|
||||
{
|
||||
this.scheme = scheme;
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.path = path;
|
||||
this.query = query;
|
||||
this.fragment = fragment;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder expressionString = new StringBuilder();
|
||||
if (!string.IsNullOrEmpty(this.scheme))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(this.host))
|
||||
{
|
||||
expressionString.AppendFormat("{0}://", this.scheme);
|
||||
}
|
||||
else
|
||||
{
|
||||
expressionString.AppendFormat("{0}:", this.scheme);
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(this.user))
|
||||
{
|
||||
expressionString.Append(this.user);
|
||||
|
||||
if (!string.IsNullOrEmpty(this.password))
|
||||
{
|
||||
expressionString.AppendFormat(":{0}@", this.password);
|
||||
}
|
||||
else
|
||||
{
|
||||
expressionString.Append("@");
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(this.host))
|
||||
{
|
||||
expressionString.Append(this.host);
|
||||
}
|
||||
if (this.port != null && this.port > 0 && this.port <= 65535)
|
||||
{
|
||||
expressionString.AppendFormat(":{0}", this.port);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(this.path))
|
||||
{
|
||||
if (this.path.StartsWith("/"))
|
||||
{
|
||||
expressionString.Append(this.path);
|
||||
}
|
||||
else
|
||||
{
|
||||
expressionString.AppendFormat("/{0}", this.path);
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(this.query))
|
||||
{
|
||||
expressionString.AppendFormat("?{0}", this.query);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(this.fragment))
|
||||
{
|
||||
expressionString.AppendFormat("#{0}", this.fragment);
|
||||
}
|
||||
|
||||
return expressionString.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,114 +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.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Browser.Filters;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.UnitTests.Filters
|
||||
{
|
||||
[TestClass]
|
||||
public class RequestFilterTests
|
||||
{
|
||||
private RequestFilter sut;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
sut = new RequestFilter();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustProcessBlockRulesFirst()
|
||||
{
|
||||
var allow = new Mock<IRule>();
|
||||
var block = new Mock<IRule>();
|
||||
|
||||
allow.SetupGet(r => r.Result).Returns(FilterResult.Allow);
|
||||
block.SetupGet(r => r.Result).Returns(FilterResult.Block);
|
||||
block.Setup(r => r.IsMatch(It.IsAny<Request>())).Returns(true);
|
||||
|
||||
sut.Load(allow.Object);
|
||||
sut.Load(block.Object);
|
||||
|
||||
var result = sut.Process(new Request());
|
||||
|
||||
allow.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Never);
|
||||
block.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
|
||||
|
||||
Assert.AreEqual(FilterResult.Block, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustProcessAllowRulesSecond()
|
||||
{
|
||||
var allow = new Mock<IRule>();
|
||||
var block = new Mock<IRule>();
|
||||
|
||||
allow.SetupGet(r => r.Result).Returns(FilterResult.Allow);
|
||||
allow.Setup(r => r.IsMatch(It.IsAny<Request>())).Returns(true);
|
||||
block.SetupGet(r => r.Result).Returns(FilterResult.Block);
|
||||
|
||||
sut.Load(allow.Object);
|
||||
sut.Load(block.Object);
|
||||
|
||||
var result = sut.Process(new Request());
|
||||
|
||||
allow.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
|
||||
block.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
|
||||
|
||||
Assert.AreEqual(FilterResult.Allow, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustReturnDefaultWithoutMatch()
|
||||
{
|
||||
var allow = new Mock<IRule>();
|
||||
var block = new Mock<IRule>();
|
||||
|
||||
allow.SetupGet(r => r.Result).Returns(FilterResult.Allow);
|
||||
block.SetupGet(r => r.Result).Returns(FilterResult.Block);
|
||||
|
||||
sut.Default = (FilterResult) (-1);
|
||||
sut.Load(allow.Object);
|
||||
sut.Load(block.Object);
|
||||
|
||||
var result = sut.Process(new Request());
|
||||
|
||||
allow.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
|
||||
block.Verify(r => r.IsMatch(It.IsAny<Request>()), Times.Once);
|
||||
|
||||
Assert.AreEqual((FilterResult) (-1), result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustReturnDefaultWithoutRules()
|
||||
{
|
||||
sut.Default = FilterResult.Allow;
|
||||
var result = sut.Process(new Request());
|
||||
Assert.AreEqual(FilterResult.Allow, result);
|
||||
|
||||
sut.Default = FilterResult.Block;
|
||||
result = sut.Process(new Request());
|
||||
Assert.AreEqual(FilterResult.Block, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotImplementedException))]
|
||||
public void MustNotAllowUnsupportedResult()
|
||||
{
|
||||
var rule = new Mock<IRule>();
|
||||
|
||||
rule.SetupGet(r => r.Result).Returns((FilterResult) (-1));
|
||||
sut.Load(rule.Object);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +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 SafeExamBrowser.Browser.Filters;
|
||||
using SafeExamBrowser.Browser.Filters.Rules;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.UnitTests.Filters
|
||||
{
|
||||
[TestClass]
|
||||
public class RuleFactoryTests
|
||||
{
|
||||
private RuleFactory sut;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
sut = new RuleFactory();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustCreateCorrectRules()
|
||||
{
|
||||
Assert.IsInstanceOfType(sut.CreateRule(FilterRuleType.Regex), typeof(RegexRule));
|
||||
Assert.IsInstanceOfType(sut.CreateRule(FilterRuleType.Simplified), typeof(SimplifiedRule));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotImplementedException))]
|
||||
public void MustNotAllowUnsupportedFilterType()
|
||||
{
|
||||
sut.CreateRule((FilterRuleType) (-1));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,67 +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.Text.RegularExpressions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Browser.Filters.Rules;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
|
||||
{
|
||||
[TestClass]
|
||||
public class RegexRuleTests
|
||||
{
|
||||
private RegexRule sut;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
sut = new RegexRule();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustIgnoreCase()
|
||||
{
|
||||
sut.Initialize(new FilterRuleSettings { Expression = Regex.Escape("http://www.test.org/path/file.txt?param=123") });
|
||||
|
||||
Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
|
||||
Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
|
||||
|
||||
sut.Initialize(new FilterRuleSettings { Expression = Regex.Escape("HTTP://WWW.TEST.ORG/PATH/FILE.TXT?PARAM=123") });
|
||||
|
||||
Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
|
||||
Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustInitializeResult()
|
||||
{
|
||||
foreach (var result in Enum.GetValues(typeof(FilterResult)))
|
||||
{
|
||||
sut.Initialize(new FilterRuleSettings { Expression = "", Result = (FilterResult) result });
|
||||
Assert.AreEqual(result, sut.Result);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void MustNotAllowUndefinedExpression()
|
||||
{
|
||||
sut.Initialize(new FilterRuleSettings());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void MustValidateExpression()
|
||||
{
|
||||
sut.Initialize(new FilterRuleSettings { Expression = "ç+\"}%&*/(+)=?{=*+¦]@#°§]`?´^¨'°[¬|¢" });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,962 +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 Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Browser.Filters.Rules;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
|
||||
{
|
||||
[TestClass]
|
||||
public class SimplifiedRuleTests
|
||||
{
|
||||
private SimplifiedRule sut;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
sut = new SimplifiedRule();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustIgnoreCase()
|
||||
{
|
||||
sut.Initialize(new FilterRuleSettings { Expression = "http://www.test.org/path/file.txt?param=123" });
|
||||
|
||||
Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
|
||||
Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
|
||||
|
||||
sut.Initialize(new FilterRuleSettings { Expression = "HTTP://WWW.TEST.ORG/PATH/FILE.TXT?PARAM=123" });
|
||||
|
||||
Assert.IsTrue(sut.IsMatch(new Request { Url = "hTtP://wWw.TeSt.OrG/pAtH/fIlE.tXt?PaRaM=123" }));
|
||||
Assert.IsTrue(sut.IsMatch(new Request { Url = "HtTp://WwW.tEst.oRg/PaTh/FiLe.TxT?pArAm=123" }));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustInitializeResult()
|
||||
{
|
||||
foreach (var result in Enum.GetValues(typeof(FilterResult)))
|
||||
{
|
||||
sut.Initialize(new FilterRuleSettings { Expression = "*", Result = (FilterResult) result });
|
||||
Assert.AreEqual(result, sut.Result);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void MustNotAllowUndefinedExpression()
|
||||
{
|
||||
sut.Initialize(new FilterRuleSettings());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustValidateExpression()
|
||||
{
|
||||
var invalid = new[]
|
||||
{
|
||||
".", "+", "\"", "ç", "%", "&", "/", "(", ")", "=", "?", "^", "!", "[", "]", "{", "}", "¦", "@", "#", "°", "§", "¬", "|", "¢", "´", "'", "`", "~", "<", ">", "\\"
|
||||
};
|
||||
|
||||
sut.Initialize(new FilterRuleSettings { Expression = "*" });
|
||||
sut.Initialize(new FilterRuleSettings { Expression = "a" });
|
||||
sut.Initialize(new FilterRuleSettings { Expression = "A" });
|
||||
sut.Initialize(new FilterRuleSettings { Expression = "0" });
|
||||
sut.Initialize(new FilterRuleSettings { Expression = "abcdeFGHIJK-12345" });
|
||||
|
||||
foreach (var expression in invalid)
|
||||
{
|
||||
Assert.ThrowsException<ArgumentException>(() => sut.Initialize(new FilterRuleSettings { Expression = expression }));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestAlphanumericExpression()
|
||||
{
|
||||
var expression = "hostname123";
|
||||
var positive = new[]
|
||||
{
|
||||
$"scheme://{expression}"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
$"scheme://hostname",
|
||||
$"scheme://hostname1",
|
||||
$"scheme://hostname12",
|
||||
$"scheme://hostname1234",
|
||||
$"scheme://{expression}.org",
|
||||
$"scheme://www.{expression}.org",
|
||||
$"scheme://subdomain.{expression}.com",
|
||||
$"scheme://www.realhost.{expression}",
|
||||
$"scheme://subdomain-1.subdomain-2.{expression}.org",
|
||||
$"scheme://user:password@www.{expression}.org/path/file.txt?param=123#fragment",
|
||||
$"scheme://{expression}4",
|
||||
$"scheme://hostname.org",
|
||||
$"scheme://hostname12.org",
|
||||
$"scheme://{expression}4.org",
|
||||
$"scheme://{expression}.realhost.org",
|
||||
$"scheme://subdomain.{expression}.realhost.org",
|
||||
$"{expression}://www.host.org",
|
||||
$"scheme://www.host.org/{expression}/path",
|
||||
$"scheme://www.host.org/path?param={expression}",
|
||||
$"scheme://{expression}:password@www.host.org",
|
||||
$"scheme://user:{expression}@www.host.org",
|
||||
$"scheme://user:password@www.host.org/path?param=123#{expression}"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative, false);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestAlphanumericExpressionWithWildcard()
|
||||
{
|
||||
var expression = "hostname*";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://hostname.org",
|
||||
"scheme://hostnameabc.org",
|
||||
"scheme://hostname-12.org",
|
||||
"scheme://hostname-abc-def-123-456.org",
|
||||
"scheme://www.hostname-abc.org",
|
||||
"scheme://www.realhost.hostname",
|
||||
"scheme://subdomain.hostname-xyz.com",
|
||||
"scheme://hostname.realhost.org",
|
||||
"scheme://subdomain.hostname.realhost.org",
|
||||
"scheme://subdomain-1.subdomain-2.hostname-abc-123.org",
|
||||
"scheme://user:password@www.hostname-abc.org/path/file.txt?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://hostnam.org",
|
||||
"hostname://www.host.org",
|
||||
"scheme://www.host.org/hostname/path",
|
||||
"scheme://www.host.org/path?param=hostname",
|
||||
"scheme://hostname:password@www.host.org",
|
||||
"scheme://user:hostname@www.host.org",
|
||||
"scheme://user:password@www.host.org/path?param=123#hostname"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative, false);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestHostWithDomain()
|
||||
{
|
||||
var expression = "123-hostname.org";
|
||||
var positive = new[]
|
||||
{
|
||||
$"scheme://{expression}",
|
||||
$"scheme://www.{expression}",
|
||||
$"scheme://subdomain.{expression}",
|
||||
$"scheme://subdomain-1.subdomain-2.{expression}",
|
||||
$"scheme://user:password@www.{expression}/path/file.txt?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
$"scheme://123.org",
|
||||
$"scheme://123-host.org",
|
||||
$"scheme://{expression}.com",
|
||||
$"scheme://{expression}s.org",
|
||||
$"scheme://{expression}.realhost.org",
|
||||
$"scheme://subdomain.{expression}.realhost.org",
|
||||
$"scheme{expression}://www.host.org",
|
||||
$"scheme://www.host.org/{expression}/path",
|
||||
$"scheme://www.host.org/path?param={expression}",
|
||||
$"scheme://{expression}:password@www.host.org",
|
||||
$"scheme://user:{expression}@www.host.org",
|
||||
$"scheme://user:password@www.host.org/path?param=123#{expression}"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestHostWithWildcard()
|
||||
{
|
||||
var expression = "test.*.org";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://test.host.org",
|
||||
"scheme://test.host.domain.org",
|
||||
"scheme://subdomain.test.host.org",
|
||||
"scheme://user:password@test.domain.org/path/file.txt?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://test.org",
|
||||
"scheme://host.com/test.host.org",
|
||||
"scheme://www.host.org/test.host.org/path",
|
||||
"scheme://www.host.org/path?param=test.host.org",
|
||||
"scheme://test.host.org:password@www.host.org",
|
||||
"scheme://user:test.host.org@www.host.org",
|
||||
"scheme://user:password@www.host.org/path?param=123#test.host.org"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestHostWithWildcardAsSuffix()
|
||||
{
|
||||
var expression = "test.host.*";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://test.host.org",
|
||||
"scheme://test.host.domain.org",
|
||||
"scheme://subdomain.test.host.org",
|
||||
"scheme://user:password@test.host.org/path/file.txt?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.com",
|
||||
"scheme://test.host",
|
||||
"scheme://host.com/test.host.txt"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestHostWithWildcardAsPrefix()
|
||||
{
|
||||
var expression = "*.org";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://domain.org",
|
||||
"scheme://test.host.org",
|
||||
"scheme://test.host.domain.org",
|
||||
"scheme://user:password@www.host.org/path/file.txt?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://org",
|
||||
"scheme://host.com",
|
||||
"scheme://test.net",
|
||||
"scheme://test.ch",
|
||||
"scheme://host.com/test.org"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestHostWithExactSubdomain()
|
||||
{
|
||||
var expression = ".www.host.org";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://www.host.org",
|
||||
"scheme://user:password@www.host.org/path/file.txt?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://test.www.host.org",
|
||||
"scheme://www.host.org.com"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestHostWithTrailingSlash()
|
||||
{
|
||||
var expression = "host.org/";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://host.org/",
|
||||
"scheme://host.org/url",
|
||||
"scheme://host.org/url/",
|
||||
"scheme://host.org/url/path",
|
||||
"scheme://host.org/url/path/",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment",
|
||||
"scheme://user:password@www.host.org/url/path/?param=123#fragment"
|
||||
};
|
||||
|
||||
Test(expression, positive, Array.Empty<string>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestHostWithoutTrailingSlash()
|
||||
{
|
||||
var expression = "host.org";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://host.org/",
|
||||
"scheme://host.org/url",
|
||||
"scheme://host.org/url/",
|
||||
"scheme://host.org/url/path",
|
||||
"scheme://host.org/url/path/",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment",
|
||||
"scheme://user:password@www.host.org/url/path/?param=123#fragment"
|
||||
};
|
||||
|
||||
Test(expression, positive, Array.Empty<string>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestPortNumber()
|
||||
{
|
||||
var expression = "host.org:2020";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org:2020",
|
||||
"scheme://www.host.org:2020",
|
||||
"scheme://user:password@www.host.org:2020/path/file.txt?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://www.host.org",
|
||||
"scheme://www.host.org:2",
|
||||
"scheme://www.host.org:20",
|
||||
"scheme://www.host.org:202",
|
||||
"scheme://www.host.org:20202"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestPortWildcard()
|
||||
{
|
||||
var expression = "host.org:*";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://host.org:0",
|
||||
"scheme://host.org:1",
|
||||
"scheme://host.org:2020",
|
||||
"scheme://host.org:65535",
|
||||
"scheme://www.host.org",
|
||||
"scheme://www.host.org:2",
|
||||
"scheme://www.host.org:20",
|
||||
"scheme://www.host.org:202",
|
||||
"scheme://www.host.org:2020",
|
||||
"scheme://www.host.org:20202",
|
||||
"scheme://user:password@www.host.org:2020/path/file.txt?param=123#fragment"
|
||||
};
|
||||
|
||||
Test(expression, positive, Array.Empty<string>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestPortNumberWithHostWildcard()
|
||||
{
|
||||
var expression = "*:2020";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org:2020",
|
||||
"scheme://domain.com:2020",
|
||||
"scheme://user:password@www.server.net:2020/path/file.txt?param=123#fragment"
|
||||
};
|
||||
var negative = new List<string>
|
||||
{
|
||||
"scheme://host.org"
|
||||
};
|
||||
|
||||
for (var port = 0; port < 65536; port++)
|
||||
{
|
||||
if (port != 2020)
|
||||
{
|
||||
negative.Add($"{negative[0]}:{port}");
|
||||
}
|
||||
}
|
||||
|
||||
Test(expression, positive, negative.ToArray());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestPath()
|
||||
{
|
||||
var expression = "host.org/url/path";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org/url/path",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://host.org//",
|
||||
"scheme://host.org///",
|
||||
"scheme://host.org/url",
|
||||
"scheme://host.org/path",
|
||||
"scheme://host.org/url/path.txt",
|
||||
"scheme://host.org/another/url/path"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestPathWithFile()
|
||||
{
|
||||
var expression = "host.org/url/path/to/file.txt";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org/url/path/to/file.txt",
|
||||
"scheme://subdomain.host.org/url/path/to/file.txt",
|
||||
"scheme://user:password@www.host.org/url/path/to/file.txt?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://host.org/url",
|
||||
"scheme://host.org/path",
|
||||
"scheme://host.org/file.txt",
|
||||
"scheme://host.org/url/path.txt",
|
||||
"scheme://host.org/url/path/to.txt",
|
||||
"scheme://host.org/url/path/to/file",
|
||||
"scheme://host.org/url/path/to/file.",
|
||||
"scheme://host.org/url/path/to/file.t",
|
||||
"scheme://host.org/url/path/to/file.tx",
|
||||
"scheme://host.org/url/path/to/file.txt/segment",
|
||||
"scheme://host.org/url/path/to/file.txtt",
|
||||
"scheme://host.org/another/url/path/to/file.txt"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestPathWithWildcard()
|
||||
{
|
||||
var expression = "host.org/*/path";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org//path",
|
||||
"scheme://host.org/url/path",
|
||||
"scheme://host.org/another/url/path",
|
||||
"scheme://user:password@www.host.org/yet/another/url/path?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://host.org/url",
|
||||
"scheme://host.org/path",
|
||||
"scheme://host.org/url/path.txt",
|
||||
"scheme://host.org/url/path/2"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestPathWithHostWildcard()
|
||||
{
|
||||
var expression = "*/url/path";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://local/url/path",
|
||||
"scheme://host.org/url/path",
|
||||
"scheme://www.host.org/url/path",
|
||||
"scheme://another.server.org/url/path",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://host.org/url",
|
||||
"scheme://host.org/path",
|
||||
"scheme://host.org/url/path.txt",
|
||||
"scheme://host.org/url/path/2"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestPathWithTrailingSlash()
|
||||
{
|
||||
var expression = "host.org/url/path/";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org/url/path",
|
||||
"scheme://host.org/url/path/",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment",
|
||||
"scheme://user:password@www.host.org/url/path/?param=123#fragment"
|
||||
};
|
||||
|
||||
Test(expression, positive, Array.Empty<string>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestPathWithoutTrailingSlash()
|
||||
{
|
||||
var expression = "host.org/url/path";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org/url/path",
|
||||
"scheme://host.org/url/path/",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment",
|
||||
"scheme://user:password@www.host.org/url/path/?param=123#fragment"
|
||||
};
|
||||
|
||||
Test(expression, positive, Array.Empty<string>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestScheme()
|
||||
{
|
||||
var expression = "scheme://host.org";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://www.host.org",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"//host.org",
|
||||
"https://host.org",
|
||||
"ftp://host.org",
|
||||
"ftps://host.org",
|
||||
"schemes://host.org"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestSchemeWithWildcard()
|
||||
{
|
||||
var expression = "*tp://host.org";
|
||||
var positive = new[]
|
||||
{
|
||||
"tp://host.org",
|
||||
"ftp://www.host.org",
|
||||
"http://user:password@www.host.org/url/path?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"//host.org",
|
||||
"p://host.org",
|
||||
"https://host.org",
|
||||
"ftps://host.org"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestSchemeWithHostWildcard()
|
||||
{
|
||||
var expression = "scheme://*";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host",
|
||||
"scheme://www.server.org",
|
||||
"scheme://subdomain.domain.org",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"//host.org",
|
||||
"http://host.org",
|
||||
"https://host.org",
|
||||
"ftp://host.org",
|
||||
"ftps://host.org",
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestUserInfoWithName()
|
||||
{
|
||||
var expression = "user@host.org";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://user@host.org",
|
||||
"scheme://user@www.host.org",
|
||||
"scheme://user:password@host.org",
|
||||
"scheme://user:password-123@host.org",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://u@host.org",
|
||||
"scheme://us@host.org",
|
||||
"scheme://use@host.org",
|
||||
"scheme://usera@host.org",
|
||||
"scheme://user@server.net",
|
||||
"scheme://usertwo@www.host.org"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestUserInfoWithNameWildcard()
|
||||
{
|
||||
var expression = "user*@host.org";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://user@host.org",
|
||||
"scheme://userabc@host.org",
|
||||
"scheme://user:abc@host.org",
|
||||
"scheme://user-123@www.host.org",
|
||||
"scheme://user-123:password@host.org",
|
||||
"scheme://user-123:password-123@host.org",
|
||||
"scheme://user-abc-123:password@www.host.org/url/path?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://u@host.org",
|
||||
"scheme://us@host.org",
|
||||
"scheme://use@host.org",
|
||||
"scheme://user@server.net",
|
||||
"scheme://usertwo@server.org"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestUserInfoWithPassword()
|
||||
{
|
||||
var expression = "user:password@host.org";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://user:password@host.org",
|
||||
"scheme://user:password@www.host.org",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://u@host.org",
|
||||
"scheme://us@host.org",
|
||||
"scheme://use@host.org",
|
||||
"scheme://user@server.net",
|
||||
"scheme://usertwo@server.org",
|
||||
"scheme://user@host.org",
|
||||
"scheme://userabc@host.org",
|
||||
"scheme://user:abc@host.org",
|
||||
"scheme://user-123@www.host.org",
|
||||
"scheme://user-123:password@host.org",
|
||||
"scheme://user-123:password@www.host.org/url/path?param=123#fragment",
|
||||
"scheme://user:password-123@host.org",
|
||||
"scheme://user:password-123@www.host.org/url/path?param=123#fragment"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestUserInfoWithWildcard()
|
||||
{
|
||||
var expression = "*@host.org";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://user@host.org",
|
||||
"scheme://user:password@host.org",
|
||||
"scheme://www.host.org/url/path?param=123#fragment",
|
||||
"scheme://user@www.host.org/url/path?param=123#fragment",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://server.org",
|
||||
"scheme://user@server.org",
|
||||
"scheme://www.server.org/url/path?param=123#fragment",
|
||||
"scheme://user:password@www.server.org/url/path?param=123#fragment"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestUserInfoWithHostWildcard()
|
||||
{
|
||||
var expression = "user:password@*";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://user:password@host.org",
|
||||
"scheme://user:password@server.net",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://server.org",
|
||||
"scheme://user@host.org",
|
||||
"scheme://user@server.org",
|
||||
"scheme://password@host.org",
|
||||
"scheme://www.host.org/url/path?param=123#fragment",
|
||||
"scheme://www.server.org/url/path?param=123#fragment",
|
||||
"scheme://user@www.host.org/url/path?param=123#fragment",
|
||||
"scheme://user@www.server.org/url/path?param=123#fragment",
|
||||
"scheme://password@www.server.org/url/path?param=123#fragment"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestQuery()
|
||||
{
|
||||
var expression = "host.org?param=123";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org?param=123",
|
||||
"scheme://www.host.org/?param=123",
|
||||
"scheme://www.host.org/path/?param=123",
|
||||
"scheme://www.host.org/some/other/random/path?param=123",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org?",
|
||||
"scheme://host.org?=",
|
||||
"scheme://host.org?=123",
|
||||
"scheme://host.org?param=",
|
||||
"scheme://host.org?param=1",
|
||||
"scheme://host.org?param=12",
|
||||
"scheme://host.org?param=1234"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestQueryWithWildcardAsPrefix()
|
||||
{
|
||||
var expression = "host.org?*param=123";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org?param=123",
|
||||
"scheme://www.host.org?param=123",
|
||||
"scheme://www.host.org/path/?param=123",
|
||||
"scheme://www.host.org/some/other/random/path?param=123",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment",
|
||||
"scheme://host.org?other_param=456¶m=123",
|
||||
"scheme://host.org?param=123&another_param=123",
|
||||
"scheme://www.host.org?other_param=456¶m=123",
|
||||
"scheme://www.host.org/path/?other_param=456¶m=123",
|
||||
"scheme://www.host.org/some/other/random/path?other_param=456¶m=123",
|
||||
"scheme://user:password@www.host.org/url/path?other_param=456¶m=123#fragment",
|
||||
"scheme://host.org?some_param=123469yvuiopwo&another_param=some%20whitespaces%26special%20characters%2B%22%2A%25%C3%A7%2F%28¶m=123"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org?",
|
||||
"scheme://host.org?=",
|
||||
"scheme://host.org?=123",
|
||||
"scheme://host.org?aram=123",
|
||||
"scheme://host.org?param=",
|
||||
"scheme://host.org?param=1",
|
||||
"scheme://host.org?param=12",
|
||||
"scheme://host.org?param=1234",
|
||||
"scheme://host.org?param=123&another_param=456"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestQueryWithWildcardAsSuffix()
|
||||
{
|
||||
var expression = "host.org?param=123*";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org?param=123",
|
||||
"scheme://host.org?param=1234",
|
||||
"scheme://www.host.org?param=123",
|
||||
"scheme://www.host.org/path/?param=123",
|
||||
"scheme://host.org?param=123&another_param=456",
|
||||
"scheme://www.host.org/some/other/random/path?param=123",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment",
|
||||
"scheme://host.org?param=123&other_param=456",
|
||||
"scheme://www.host.org/path/?param=123&other_param=456",
|
||||
"scheme://www.host.org/some/other/random/path?param=123&other_param=456",
|
||||
"scheme://user:password@www.host.org/url/path?param=123&other_param=456#fragment",
|
||||
"scheme://host.org?param=123&some_param=123469yvuiopwo&another_param=some%20whitespaces%26special%20characters%2B%22%2A%25%C3%A7%2F%28"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org?",
|
||||
"scheme://host.org?=",
|
||||
"scheme://host.org?=123",
|
||||
"scheme://host.org?aram=123",
|
||||
"scheme://host.org?param=",
|
||||
"scheme://host.org?param=1",
|
||||
"scheme://host.org?param=12",
|
||||
"scheme://host.org?aparam=123",
|
||||
"scheme://www.host.org?param=456¶m=123",
|
||||
"scheme://host.org?some_param=123469yvuiopwo&another_param=some%20whitespaces%26special%20characters%2B%22%2A%25%C3%A7%2F%28¶m=123"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestQueryNotAllowed()
|
||||
{
|
||||
var expression = "host.org?.";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://host.org?",
|
||||
"scheme://user:password@www.host.org/url/path#fragment",
|
||||
"scheme://user:password@www.host.org/url/path?#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org?a",
|
||||
"scheme://host.org?%20",
|
||||
"scheme://host.org?=",
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestQueryWithHostWildcard()
|
||||
{
|
||||
var expression = "*?param=123";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org?param=123",
|
||||
"scheme://server.net?param=123",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment",
|
||||
"scheme://user:password@www.server.net/url/path?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org?param=1234",
|
||||
"scheme://host.org?param=12",
|
||||
"scheme://host.org?",
|
||||
"scheme://host.org?param",
|
||||
"scheme://host.org?123",
|
||||
"scheme://host.org?="
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestFragment()
|
||||
{
|
||||
var expression = "host.org#fragment";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org#fragment",
|
||||
"scheme://www.host.org#fragment",
|
||||
"scheme://user:password@www.host.org/url/path/file.txt?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://host.org#",
|
||||
"scheme://host.org#fragmen",
|
||||
"scheme://host.org#fragment123",
|
||||
"scheme://host.org#otherfragment"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestFragmentWithWildcardAsPrefix()
|
||||
{
|
||||
var expression = "host.org#*fragment";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org#fragment",
|
||||
"scheme://host.org#somefragment",
|
||||
"scheme://www.host.org#another_fragment",
|
||||
"scheme://user:password@www.host.org/url/path/file.txt?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://host.org#",
|
||||
"scheme://host.org#fragmen",
|
||||
"scheme://host.org#fragment123"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestFragmentWithWildcardAsSuffix()
|
||||
{
|
||||
var expression = "host.org#fragment*";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org#fragment",
|
||||
"scheme://host.org#fragment-123",
|
||||
"scheme://user:password@www.host.org/url/path/file.txt?param=123#fragment_abc"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://host.org#",
|
||||
"scheme://host.org#fragmen",
|
||||
"scheme://www.host.org#another_fragment"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestFragmentWithHostWildcard()
|
||||
{
|
||||
var expression = "*#fragment";
|
||||
var positive = new[]
|
||||
{
|
||||
"scheme://host.org#fragment",
|
||||
"scheme://server.net#fragment",
|
||||
"scheme://user:password@www.host.org/url/path?param=123#fragment",
|
||||
"scheme://user:password@www.server.net/url/path?param=123#fragment"
|
||||
};
|
||||
var negative = new[]
|
||||
{
|
||||
"scheme://host.org",
|
||||
"scheme://host.org#",
|
||||
"scheme://host.org#fragmen",
|
||||
"scheme://host.org#fragment123"
|
||||
};
|
||||
|
||||
Test(expression, positive, negative);
|
||||
}
|
||||
|
||||
private void Test(string expression, string[] positive, string[] negative, bool testLegacy = true)
|
||||
{
|
||||
var legacy = new LegacyFilter(expression);
|
||||
|
||||
sut.Initialize(new FilterRuleSettings { Expression = expression });
|
||||
|
||||
foreach (var url in positive)
|
||||
{
|
||||
Assert.IsTrue(sut.IsMatch(new Request { Url = url }), url);
|
||||
|
||||
if (testLegacy)
|
||||
{
|
||||
Assert.IsTrue(legacy.IsMatch(new Uri(url)), url);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var url in negative)
|
||||
{
|
||||
Assert.IsFalse(sut.IsMatch(new Request { Url = url }), url);
|
||||
|
||||
if (testLegacy)
|
||||
{
|
||||
Assert.IsFalse(legacy.IsMatch(new Uri(url)), url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +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 CefSharp;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using SafeExamBrowser.Browser.Handlers;
|
||||
|
||||
namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
||||
{
|
||||
[TestClass]
|
||||
public class ContextMenuHandlerTests
|
||||
{
|
||||
private ContextMenuHandler sut;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
sut = new ContextMenuHandler();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustClearContextMenu()
|
||||
{
|
||||
var menu = new Mock<IMenuModel>();
|
||||
|
||||
sut.OnBeforeContextMenu(default(IWebBrowser), default(IBrowser), default(IFrame), default(IContextMenuParams), menu.Object);
|
||||
menu.Verify(m => m.Clear(), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustBlockContextMenu()
|
||||
{
|
||||
var command = sut.OnContextMenuCommand(default(IWebBrowser), default(IBrowser), default(IFrame), default(IContextMenuParams), default(CefMenuCommand), default(CefEventFlags));
|
||||
var run = sut.RunContextMenu(default(IWebBrowser), default(IBrowser), default(IFrame), default(IContextMenuParams), default(IMenuModel), default(IRunContextMenuCallback));
|
||||
|
||||
Assert.IsFalse(command);
|
||||
Assert.IsFalse(run);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,106 +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.Collections.Generic;
|
||||
using System.Threading;
|
||||
using CefSharp;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Browser.Handlers;
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
|
||||
namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
||||
{
|
||||
[TestClass]
|
||||
public class DialogHandlerTests
|
||||
{
|
||||
private DialogHandler sut;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
sut = new DialogHandler();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustCorrectlyCancelDialog()
|
||||
{
|
||||
RequestDialog(default, false);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustCorrectlyRequestOpenFileDialog()
|
||||
{
|
||||
var args = RequestDialog(CefFileDialogMode.Open);
|
||||
|
||||
Assert.AreEqual(FileSystemElement.File, args.Element);
|
||||
Assert.AreEqual(FileSystemOperation.Open, args.Operation);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustCorrectlyRequestOpenFolderDialog()
|
||||
{
|
||||
var args = RequestDialog(CefFileDialogMode.OpenFolder);
|
||||
|
||||
Assert.AreEqual(FileSystemElement.Folder, args.Element);
|
||||
Assert.AreEqual(FileSystemOperation.Open, args.Operation);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustCorrectlyRequestSaveFileDialog()
|
||||
{
|
||||
var args = RequestDialog(CefFileDialogMode.Save);
|
||||
|
||||
Assert.AreEqual(FileSystemElement.File, args.Element);
|
||||
Assert.AreEqual(FileSystemOperation.Save, args.Operation);
|
||||
}
|
||||
|
||||
private DialogRequestedEventArgs RequestDialog(CefFileDialogMode mode, bool confirm = true)
|
||||
{
|
||||
var args = default(DialogRequestedEventArgs);
|
||||
var callback = new Mock<IFileDialogCallback>();
|
||||
var title = "Some random dialog title";
|
||||
var initialPath = @"C:\Some\Random\Path";
|
||||
var sync = new AutoResetEvent(false);
|
||||
var threadId = default(int);
|
||||
|
||||
callback.Setup(c => c.Cancel()).Callback(() => sync.Set());
|
||||
callback.Setup(c => c.Continue(It.IsAny<List<string>>())).Callback(() => sync.Set());
|
||||
sut.DialogRequested += (a) =>
|
||||
{
|
||||
args = a;
|
||||
args.Success = confirm;
|
||||
args.FullPath = @"D:\Some\Other\File\Path.txt";
|
||||
threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
};
|
||||
|
||||
var status = sut.OnFileDialog(default, default, mode, title, initialPath, default, callback.Object);
|
||||
|
||||
sync.WaitOne();
|
||||
|
||||
if (confirm)
|
||||
{
|
||||
callback.Verify(c => c.Continue(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.Cancel(), Times.Once);
|
||||
}
|
||||
|
||||
Assert.IsTrue(status);
|
||||
Assert.AreEqual(initialPath, args.InitialPath);
|
||||
Assert.AreEqual(title, args.Title);
|
||||
Assert.AreNotEqual(threadId, Thread.CurrentThread.ManagedThreadId);
|
||||
|
||||
return args;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using SafeExamBrowser.Browser.Handlers;
|
||||
|
||||
namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
||||
{
|
||||
[TestClass]
|
||||
public class DisplayHandlerTests
|
||||
{
|
||||
private DisplayHandler sut;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
sut = new DisplayHandler();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustUseDefaultHandling()
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustHandleFaviconChange()
|
||||
{
|
||||
var newUrl = "www.someurl.org/favicon.ico";
|
||||
var url = default(string);
|
||||
var called = false;
|
||||
|
||||
sut.FaviconChanged += (u) =>
|
||||
{
|
||||
called = true;
|
||||
url = u;
|
||||
};
|
||||
sut.OnFaviconUrlChange(default, default, new List<string>());
|
||||
|
||||
Assert.AreEqual(default, url);
|
||||
Assert.IsFalse(called);
|
||||
|
||||
sut.OnFaviconUrlChange(default, default, new List<string> { newUrl });
|
||||
|
||||
Assert.AreEqual(newUrl, url);
|
||||
Assert.IsTrue(called);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustHandleProgressChange()
|
||||
{
|
||||
var expected = 0.123456;
|
||||
var actual = default(double);
|
||||
|
||||
sut.ProgressChanged += (p) => actual = p;
|
||||
sut.OnLoadingProgressChange(default, default, expected);
|
||||
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,311 +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.IO;
|
||||
using System.Threading;
|
||||
using CefSharp;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Handlers;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
|
||||
namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
||||
{
|
||||
[TestClass]
|
||||
public class DownloadHandlerTests
|
||||
{
|
||||
private AppConfig appConfig;
|
||||
private Mock<ILogger> logger;
|
||||
private BrowserSettings settings;
|
||||
private WindowSettings windowSettings;
|
||||
private DownloadHandler sut;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
appConfig = new AppConfig();
|
||||
logger = new Mock<ILogger>();
|
||||
settings = new BrowserSettings();
|
||||
windowSettings = new WindowSettings();
|
||||
|
||||
sut = new DownloadHandler(appConfig, logger.Object, settings, windowSettings);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustCorrectlyHandleConfigurationByFileExtension()
|
||||
{
|
||||
var item = new DownloadItem
|
||||
{
|
||||
SuggestedFileName = "File.seb",
|
||||
Url = "https://somehost.org/some-path"
|
||||
};
|
||||
|
||||
RequestConfigurationDownload(item);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustCorrectlyHandleConfigurationByUrlExtension()
|
||||
{
|
||||
var item = new DownloadItem
|
||||
{
|
||||
SuggestedFileName = "Abc.xyz",
|
||||
Url = "https://somehost.org/some-path-to/file.seb"
|
||||
};
|
||||
|
||||
RequestConfigurationDownload(item);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustCorrectlyHandleConfigurationByMimeType()
|
||||
{
|
||||
appConfig.ConfigurationFileMimeType = "some/mime-type";
|
||||
|
||||
var item = new DownloadItem
|
||||
{
|
||||
MimeType = appConfig.ConfigurationFileMimeType,
|
||||
SuggestedFileName = "Abc.xyz",
|
||||
Url = "https://somehost.org/some-path"
|
||||
};
|
||||
|
||||
RequestConfigurationDownload(item);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustCorrectlyHandleDeniedConfigurationFileDownload()
|
||||
{
|
||||
var args = default(DownloadEventArgs);
|
||||
var callback = new Mock<IBeforeDownloadCallback>();
|
||||
var failed = false;
|
||||
var fileName = default(string);
|
||||
var item = new DownloadItem
|
||||
{
|
||||
SuggestedFileName = "File.seb",
|
||||
Url = "https://somehost.org/some-path"
|
||||
};
|
||||
var sync = new AutoResetEvent(false);
|
||||
var threadId = default(int);
|
||||
|
||||
settings.AllowDownloads = false;
|
||||
settings.AllowConfigurationDownloads = true;
|
||||
sut.ConfigurationDownloadRequested += (f, a) =>
|
||||
{
|
||||
args = a;
|
||||
args.AllowDownload = false;
|
||||
fileName = f;
|
||||
threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
sync.Set();
|
||||
};
|
||||
sut.DownloadUpdated += (state) => failed = true;
|
||||
|
||||
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
|
||||
sync.WaitOne();
|
||||
|
||||
callback.VerifyNoOtherCalls();
|
||||
|
||||
Assert.IsFalse(failed);
|
||||
Assert.IsFalse(args.AllowDownload);
|
||||
Assert.AreEqual(item.SuggestedFileName, fileName);
|
||||
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustCorrectlyHandleFileDownload()
|
||||
{
|
||||
var callback = new Mock<IBeforeDownloadCallback>();
|
||||
var downloadPath = default(string);
|
||||
var failed = false;
|
||||
var item = new DownloadItem
|
||||
{
|
||||
MimeType = "application/something",
|
||||
SuggestedFileName = "File.txt",
|
||||
Url = "https://somehost.org/somefile.abc"
|
||||
};
|
||||
var sync = new AutoResetEvent(false);
|
||||
var threadId = default(int);
|
||||
|
||||
callback.Setup(c => c.Continue(It.IsAny<string>(), It.IsAny<bool>())).Callback<string, bool>((f, s) =>
|
||||
{
|
||||
downloadPath = f;
|
||||
threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
sync.Set();
|
||||
});
|
||||
settings.AllowDownloads = true;
|
||||
settings.AllowConfigurationDownloads = false;
|
||||
sut.ConfigurationDownloadRequested += (f, a) => failed = true;
|
||||
sut.DownloadUpdated += (state) => failed = true;
|
||||
|
||||
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
|
||||
sync.WaitOne();
|
||||
|
||||
callback.Verify(c => c.Continue(It.Is<string>(p => p.Equals(downloadPath)), false), Times.Once);
|
||||
|
||||
Assert.IsFalse(failed);
|
||||
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustCorrectlyHandleFileDownloadWithCustomDirectory()
|
||||
{
|
||||
var callback = new Mock<IBeforeDownloadCallback>();
|
||||
var failed = false;
|
||||
var item = new DownloadItem
|
||||
{
|
||||
MimeType = "application/something",
|
||||
SuggestedFileName = "File.txt",
|
||||
Url = "https://somehost.org/somefile.abc"
|
||||
};
|
||||
var sync = new AutoResetEvent(false);
|
||||
var threadId = default(int);
|
||||
|
||||
callback.Setup(c => c.Continue(It.IsAny<string>(), It.IsAny<bool>())).Callback(() =>
|
||||
{
|
||||
threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
sync.Set();
|
||||
});
|
||||
settings.AllowDownloads = true;
|
||||
settings.AllowConfigurationDownloads = false;
|
||||
settings.AllowCustomDownAndUploadLocation = true;
|
||||
settings.DownAndUploadDirectory = @"%APPDATA%\Downloads";
|
||||
sut.ConfigurationDownloadRequested += (f, a) => failed = true;
|
||||
sut.DownloadUpdated += (state) => failed = true;
|
||||
|
||||
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
|
||||
sync.WaitOne();
|
||||
|
||||
var downloadPath = Path.Combine(Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory), item.SuggestedFileName);
|
||||
|
||||
callback.Verify(c => c.Continue(It.Is<string>(p => p.Equals(downloadPath)), true), Times.Once);
|
||||
|
||||
Assert.IsFalse(failed);
|
||||
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustDoNothingIfDownloadsNotAllowed()
|
||||
{
|
||||
var callback = new Mock<IBeforeDownloadCallback>();
|
||||
var fail = false;
|
||||
var item = new DownloadItem
|
||||
{
|
||||
SuggestedFileName = "File.txt",
|
||||
Url = "https://somehost.org/somefile.abc"
|
||||
};
|
||||
|
||||
settings.AllowDownloads = false;
|
||||
settings.AllowConfigurationDownloads = false;
|
||||
sut.ConfigurationDownloadRequested += (file, args) => fail = true;
|
||||
sut.DownloadUpdated += (state) => fail = true;
|
||||
|
||||
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
|
||||
|
||||
callback.VerifyNoOtherCalls();
|
||||
Assert.IsFalse(fail);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustUpdateDownloadProgress()
|
||||
{
|
||||
var callback = new Mock<IBeforeDownloadCallback>();
|
||||
var failed = false;
|
||||
var item = new DownloadItem
|
||||
{
|
||||
MimeType = "application/something",
|
||||
SuggestedFileName = "File.txt",
|
||||
Url = "https://somehost.org/somefile.abc"
|
||||
};
|
||||
var state = default(DownloadItemState);
|
||||
var sync = new AutoResetEvent(false);
|
||||
var threadId = default(int);
|
||||
|
||||
callback.Setup(c => c.Continue(It.IsAny<string>(), It.IsAny<bool>())).Callback(() => sync.Set());
|
||||
settings.AllowDownloads = true;
|
||||
settings.AllowConfigurationDownloads = false;
|
||||
sut.ConfigurationDownloadRequested += (f, a) => failed = true;
|
||||
|
||||
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
|
||||
sync.WaitOne();
|
||||
|
||||
Assert.IsFalse(failed);
|
||||
|
||||
sut.DownloadUpdated += (s) =>
|
||||
{
|
||||
state = s;
|
||||
threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
sync.Set();
|
||||
};
|
||||
|
||||
item.PercentComplete = 10;
|
||||
sut.OnDownloadUpdated(default(IWebBrowser), default(IBrowser), item, default(IDownloadItemCallback));
|
||||
sync.WaitOne();
|
||||
|
||||
Assert.IsFalse(state.IsCancelled);
|
||||
Assert.IsFalse(state.IsComplete);
|
||||
Assert.AreEqual(0.1, state.Completion);
|
||||
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
|
||||
|
||||
item.PercentComplete = 20;
|
||||
sut.OnDownloadUpdated(default(IWebBrowser), default(IBrowser), item, default(IDownloadItemCallback));
|
||||
sync.WaitOne();
|
||||
|
||||
Assert.IsFalse(state.IsCancelled);
|
||||
Assert.IsFalse(state.IsComplete);
|
||||
Assert.AreEqual(0.2, state.Completion);
|
||||
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
|
||||
|
||||
item.PercentComplete = 50;
|
||||
item.IsCancelled = true;
|
||||
sut.OnDownloadUpdated(default(IWebBrowser), default(IBrowser), item, default(IDownloadItemCallback));
|
||||
sync.WaitOne();
|
||||
|
||||
Assert.IsFalse(failed);
|
||||
Assert.IsTrue(state.IsCancelled);
|
||||
Assert.IsFalse(state.IsComplete);
|
||||
Assert.AreEqual(0.5, state.Completion);
|
||||
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
|
||||
}
|
||||
|
||||
private void RequestConfigurationDownload(DownloadItem item)
|
||||
{
|
||||
var args = default(DownloadEventArgs);
|
||||
var callback = new Mock<IBeforeDownloadCallback>();
|
||||
var failed = false;
|
||||
var fileName = default(string);
|
||||
var sync = new AutoResetEvent(false);
|
||||
var threadId = default(int);
|
||||
|
||||
callback.Setup(c => c.Continue(It.IsAny<string>(), It.IsAny<bool>())).Callback(() => sync.Set());
|
||||
settings.AllowDownloads = false;
|
||||
settings.AllowConfigurationDownloads = true;
|
||||
sut.ConfigurationDownloadRequested += (f, a) =>
|
||||
{
|
||||
args = a;
|
||||
args.AllowDownload = true;
|
||||
args.DownloadPath = @"C:\Downloads\File.seb";
|
||||
fileName = f;
|
||||
threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
};
|
||||
sut.DownloadUpdated += (state) => failed = true;
|
||||
|
||||
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
|
||||
sync.WaitOne();
|
||||
|
||||
callback.Verify(c => c.Continue(It.Is<string>(p => p.Equals(args.DownloadPath)), false), Times.Once);
|
||||
|
||||
Assert.IsFalse(failed);
|
||||
Assert.IsTrue(args.AllowDownload);
|
||||
Assert.AreEqual(item.SuggestedFileName, fileName);
|
||||
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,166 +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.Windows.Forms;
|
||||
using CefSharp;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using SafeExamBrowser.Browser.Handlers;
|
||||
|
||||
namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
||||
{
|
||||
[TestClass]
|
||||
public class KeyboardHandlerTests
|
||||
{
|
||||
private KeyboardHandler sut;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
sut = new KeyboardHandler();
|
||||
}
|
||||
|
||||
[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()
|
||||
{
|
||||
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);
|
||||
|
||||
Assert.IsTrue(reloadRequested);
|
||||
Assert.IsTrue(handled);
|
||||
|
||||
reloadRequested = false;
|
||||
handled = sut.OnPreKeyEvent(default(IWebBrowser), default(IBrowser), default(KeyType), default(int), default(int), default(CefEventFlags), default(bool), ref isShortcut);
|
||||
|
||||
Assert.IsFalse(reloadRequested);
|
||||
Assert.IsFalse(handled);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustDetectZoomInCommand()
|
||||
{
|
||||
var zoomIn = false;
|
||||
var zoomOut = false;
|
||||
var zoomReset = false;
|
||||
|
||||
sut.ZoomInRequested += () => zoomIn = true;
|
||||
sut.ZoomOutRequested += () => zoomOut = true;
|
||||
sut.ZoomResetRequested += () => zoomReset = true;
|
||||
|
||||
var handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.Add, default(int), CefEventFlags.ControlDown, false);
|
||||
|
||||
Assert.IsFalse(handled);
|
||||
Assert.IsTrue(zoomIn);
|
||||
Assert.IsFalse(zoomOut);
|
||||
Assert.IsFalse(zoomReset);
|
||||
|
||||
zoomIn = false;
|
||||
handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.D1, default(int), CefEventFlags.ControlDown | CefEventFlags.ShiftDown, false);
|
||||
|
||||
Assert.IsFalse(handled);
|
||||
Assert.IsTrue(zoomIn);
|
||||
Assert.IsFalse(zoomOut);
|
||||
Assert.IsFalse(zoomReset);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustDetectZoomOutCommand()
|
||||
{
|
||||
var zoomIn = false;
|
||||
var zoomOut = false;
|
||||
var zoomReset = false;
|
||||
|
||||
sut.ZoomInRequested += () => zoomIn = true;
|
||||
sut.ZoomOutRequested += () => zoomOut = true;
|
||||
sut.ZoomResetRequested += () => zoomReset = true;
|
||||
|
||||
var handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.Subtract, default(int), CefEventFlags.ControlDown, false);
|
||||
|
||||
Assert.IsFalse(handled);
|
||||
Assert.IsFalse(zoomIn);
|
||||
Assert.IsTrue(zoomOut);
|
||||
Assert.IsFalse(zoomReset);
|
||||
|
||||
zoomOut = false;
|
||||
handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.OemMinus, default(int), CefEventFlags.ControlDown, false);
|
||||
|
||||
Assert.IsFalse(handled);
|
||||
Assert.IsFalse(zoomIn);
|
||||
Assert.IsTrue(zoomOut);
|
||||
Assert.IsFalse(zoomReset);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustDetectZoomResetCommand()
|
||||
{
|
||||
var zoomIn = false;
|
||||
var zoomOut = false;
|
||||
var zoomReset = false;
|
||||
|
||||
sut.ZoomInRequested += () => zoomIn = true;
|
||||
sut.ZoomOutRequested += () => zoomOut = true;
|
||||
sut.ZoomResetRequested += () => zoomReset = true;
|
||||
|
||||
var handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.D0, default(int), CefEventFlags.ControlDown, false);
|
||||
|
||||
Assert.IsFalse(handled);
|
||||
Assert.IsFalse(zoomIn);
|
||||
Assert.IsFalse(zoomOut);
|
||||
Assert.IsTrue(zoomReset);
|
||||
|
||||
zoomReset = false;
|
||||
handled = sut.OnKeyEvent(default(IWebBrowser), default(IBrowser), KeyType.KeyUp, (int) Keys.NumPad0, default(int), CefEventFlags.ControlDown, false);
|
||||
|
||||
Assert.IsFalse(handled);
|
||||
Assert.IsFalse(zoomIn);
|
||||
Assert.IsFalse(zoomOut);
|
||||
Assert.IsTrue(zoomReset);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,313 +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 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;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
using SafeExamBrowser.Settings.Browser.Proxy;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
using Request = SafeExamBrowser.Browser.Contracts.Filters.Request;
|
||||
using ResourceHandler = SafeExamBrowser.Browser.Handlers.ResourceHandler;
|
||||
|
||||
namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
||||
{
|
||||
[TestClass]
|
||||
public class RequestHandlerTests
|
||||
{
|
||||
private AppConfig appConfig;
|
||||
private Mock<IRequestFilter> filter;
|
||||
private Mock<IKeyGenerator> keyGenerator;
|
||||
private Mock<ILogger> logger;
|
||||
private BrowserSettings settings;
|
||||
private WindowSettings windowSettings;
|
||||
private ResourceHandler resourceHandler;
|
||||
private Mock<IText> text;
|
||||
private TestableRequestHandler sut;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
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);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustDetectQuitUrl()
|
||||
{
|
||||
var eventFired = false;
|
||||
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;
|
||||
|
||||
var blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
|
||||
|
||||
Assert.IsTrue(blocked);
|
||||
Assert.IsTrue(eventFired);
|
||||
|
||||
blocked = false;
|
||||
eventFired = false;
|
||||
request.SetupGet(r => r.Url).Returns("http://www.bye.com");
|
||||
|
||||
blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
|
||||
|
||||
Assert.IsFalse(blocked);
|
||||
Assert.IsFalse(eventFired);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustIgnoreTrailingSlashForQuitUrl()
|
||||
{
|
||||
var eventFired = false;
|
||||
var quitUrl = "http://www.byebye.com";
|
||||
var request = new Mock<IRequest>();
|
||||
|
||||
request.SetupGet(r => r.Url).Returns($"{quitUrl}/");
|
||||
settings.QuitUrl = quitUrl;
|
||||
sut.QuitUrlVisited += (url) => eventFired = true;
|
||||
|
||||
var blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
|
||||
|
||||
Assert.IsTrue(blocked);
|
||||
Assert.IsTrue(eventFired);
|
||||
|
||||
blocked = false;
|
||||
eventFired = false;
|
||||
request.SetupGet(r => r.Url).Returns(quitUrl);
|
||||
settings.QuitUrl = $"{quitUrl}/";
|
||||
|
||||
blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
|
||||
|
||||
Assert.IsTrue(blocked);
|
||||
Assert.IsTrue(eventFired);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustFilterMainRequests()
|
||||
{
|
||||
var eventFired = false;
|
||||
var request = new Mock<IRequest>();
|
||||
var url = "https://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);
|
||||
settings.Filter.ProcessContentRequests = false;
|
||||
settings.Filter.ProcessMainRequests = true;
|
||||
sut.RequestBlocked += (u) => eventFired = true;
|
||||
|
||||
var blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
|
||||
|
||||
filter.Verify(f => f.Process(It.Is<Request>(r => r.Url.Equals(url))), Times.Once);
|
||||
|
||||
Assert.IsTrue(blocked);
|
||||
Assert.IsTrue(eventFired);
|
||||
|
||||
blocked = false;
|
||||
eventFired = false;
|
||||
request.SetupGet(r => r.ResourceType).Returns(ResourceType.SubFrame);
|
||||
|
||||
blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
|
||||
|
||||
filter.Verify(f => f.Process(It.Is<Request>(r => r.Url.Equals(url))), Times.Once);
|
||||
|
||||
Assert.IsFalse(blocked);
|
||||
Assert.IsFalse(eventFired);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustFilterContentRequests()
|
||||
{
|
||||
var eventFired = false;
|
||||
var request = new Mock<IRequest>();
|
||||
var url = "https://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);
|
||||
settings.Filter.ProcessContentRequests = true;
|
||||
settings.Filter.ProcessMainRequests = false;
|
||||
sut.RequestBlocked += (u) => eventFired = true;
|
||||
|
||||
var blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
|
||||
|
||||
filter.Verify(f => f.Process(It.Is<Request>(r => r.Url.Equals(url))), Times.Once);
|
||||
|
||||
Assert.IsTrue(blocked);
|
||||
Assert.IsFalse(eventFired);
|
||||
|
||||
blocked = false;
|
||||
eventFired = false;
|
||||
request.SetupGet(r => r.ResourceType).Returns(ResourceType.MainFrame);
|
||||
|
||||
blocked = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, false, false);
|
||||
|
||||
filter.Verify(f => f.Process(It.Is<Request>(r => r.Url.Equals(url))), Times.Once);
|
||||
|
||||
Assert.IsFalse(blocked);
|
||||
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()
|
||||
{
|
||||
var callback = new Mock<IAuthCallback>();
|
||||
var proxy1 = new ProxyConfiguration { Host = "www.test.com", Username = "Sepp", Password = "1234", Port = 10, RequiresAuthentication = true };
|
||||
var proxy2 = new ProxyConfiguration { Host = "www.nope.com", Username = "Peter", Password = "4321", Port = 10, RequiresAuthentication = false };
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
callback.Verify(c => c.Continue(It.Is<string>(u => u.Equals(proxy2.Username)), It.Is<string>(p => p.Equals(proxy2.Password))), Times.Never);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustNotUseProxyCredentialsIfNoProxy()
|
||||
{
|
||||
var callback = new Mock<IAuthCallback>();
|
||||
|
||||
sut.GetAuthCredentials(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), default, false, default, default, default, default, callback.Object);
|
||||
|
||||
callback.Verify(c => c.Cancel(), Times.Never);
|
||||
callback.Verify(c => c.Continue(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
public new bool GetAuthCredentials(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,363 +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.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;
|
||||
using Request = SafeExamBrowser.Browser.Contracts.Filters.Request;
|
||||
using ResourceHandler = SafeExamBrowser.Browser.Handlers.ResourceHandler;
|
||||
|
||||
namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
||||
{
|
||||
[TestClass]
|
||||
public class ResourceHandlerTests
|
||||
{
|
||||
private AppConfig appConfig;
|
||||
private Mock<IRequestFilter> filter;
|
||||
private Mock<IKeyGenerator> keyGenerator;
|
||||
private Mock<ILogger> logger;
|
||||
private BrowserSettings settings;
|
||||
private WindowSettings windowSettings;
|
||||
private Mock<IText> text;
|
||||
private TestableResourceHandler sut;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustAppendCustomHeadersForSameDomain()
|
||||
{
|
||||
var browser = new Mock<IWebBrowser>();
|
||||
var headers = default(NameValueCollection);
|
||||
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;
|
||||
|
||||
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 MustAppendCustomHeadersForCrossDomainResourceRequestAndMainFrame()
|
||||
{
|
||||
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;
|
||||
|
||||
var result = sut.OnBeforeResourceLoad(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
|
||||
|
||||
request.VerifyGet(r => r.Headers, Times.Never);
|
||||
request.VerifySet(r => r.Headers = It.IsAny<NameValueCollection>(), Times.Never);
|
||||
|
||||
Assert.AreEqual(CefReturnValue.Continue, result);
|
||||
Assert.IsNull(headers["X-SafeExamBrowser-ConfigKeyHash"]);
|
||||
Assert.IsNull(headers["X-SafeExamBrowser-RequestHash"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustBlockMailToUrls()
|
||||
{
|
||||
var request = new Mock<IRequest>();
|
||||
var url = $"{Uri.UriSchemeMailto}:someone@somewhere.org";
|
||||
|
||||
request.SetupGet(r => r.Url).Returns(url);
|
||||
|
||||
var result = sut.OnBeforeResourceLoad(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
|
||||
|
||||
Assert.AreEqual(CefReturnValue.Cancel, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustFilterContentRequests()
|
||||
{
|
||||
var request = new Mock<IRequest>();
|
||||
var url = "http://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);
|
||||
request.SetupGet(r => r.Url).Returns(url);
|
||||
settings.Filter.ProcessContentRequests = true;
|
||||
settings.Filter.ProcessMainRequests = true;
|
||||
|
||||
var resourceHandler = sut.GetResourceHandler(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object);
|
||||
|
||||
filter.Verify(f => f.Process(It.Is<Request>(r => r.Url.Equals(url))), Times.Once);
|
||||
|
||||
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 headers = new NameValueCollection { { "Content-Type", MediaTypeNames.Application.Pdf } };
|
||||
var request = new Mock<IRequest>();
|
||||
var response = new Mock<IResponse>();
|
||||
var url = "http://www.host.org/some-document";
|
||||
|
||||
request.SetupGet(r => r.ResourceType).Returns(ResourceType.MainFrame);
|
||||
request.SetupGet(r => r.Url).Returns(url);
|
||||
response.SetupGet(r => r.Headers).Returns(headers);
|
||||
settings.AllowPdfReader = true;
|
||||
settings.AllowPdfReaderToolbar = false;
|
||||
|
||||
var result = sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), frame.Object, request.Object, response.Object);
|
||||
|
||||
frame.Verify(b => b.LoadUrl(It.Is<string>(s => s.Equals($"{url}#toolbar=0"))), Times.Once);
|
||||
Assert.IsTrue(result);
|
||||
|
||||
frame.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);
|
||||
|
||||
frame.Verify(b => b.LoadUrl(It.IsAny<string>()), Times.Never);
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustReplaceSebScheme()
|
||||
{
|
||||
var request = new Mock<IRequest>();
|
||||
var url = default(string);
|
||||
|
||||
appConfig.SebUriScheme = "abc";
|
||||
appConfig.SebUriSchemeSecure = "abcs";
|
||||
request.SetupGet(r => r.Headers).Returns(new NameValueCollection());
|
||||
request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriScheme}://www.host.org");
|
||||
request.SetupSet(r => r.Url = It.IsAny<string>()).Callback<string>(u => url = u);
|
||||
|
||||
var result = sut.OnBeforeResourceLoad(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
|
||||
|
||||
Assert.AreEqual(CefReturnValue.Continue, result);
|
||||
Assert.AreEqual("http://www.host.org/", url);
|
||||
|
||||
request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriSchemeSecure}://www.host.org");
|
||||
request.SetupSet(r => r.Url = It.IsAny<string>()).Callback<string>(u => url = u);
|
||||
|
||||
result = sut.OnBeforeResourceLoad(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
|
||||
|
||||
Assert.AreEqual(CefReturnValue.Continue, result);
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
public new IResourceHandler GetResourceHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request)
|
||||
{
|
||||
return base.GetResourceHandler(webBrowser, browser, frame, request);
|
||||
}
|
||||
|
||||
public new CefReturnValue OnBeforeResourceLoad(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("SafeExamBrowser.Browser.UnitTests")]
|
||||
[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: ComVisible(false)]
|
||||
|
||||
[assembly: Guid("f54c4c0e-4c72-4f88-a389-7f6de3ccb745")]
|
||||
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
[assembly: AssemblyInformationalVersion("1.0.0.0")]
|
|
@ -1,220 +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="..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.props" Condition="Exists('..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.props')" />
|
||||
<Import Project="..\packages\chromiumembeddedframework.runtime.win-x86.121.3.13\build\chromiumembeddedframework.runtime.win-x86.props" Condition="Exists('..\packages\chromiumembeddedframework.runtime.win-x86.121.3.13\build\chromiumembeddedframework.runtime.win-x86.props')" />
|
||||
<Import Project="..\packages\chromiumembeddedframework.runtime.win-x64.121.3.13\build\chromiumembeddedframework.runtime.win-x64.props" Condition="Exists('..\packages\chromiumembeddedframework.runtime.win-x64.121.3.13\build\chromiumembeddedframework.runtime.win-x64.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{F54C4C0E-4C72-4F88-A389-7F6DE3CCB745}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SafeExamBrowser.Browser.UnitTests</RootNamespace>
|
||||
<AssemblyName>SafeExamBrowser.Browser.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|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</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>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<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>
|
||||
<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.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Filters\LegacyFilter.cs" />
|
||||
<Compile Include="Filters\RequestFilterTests.cs" />
|
||||
<Compile Include="Filters\RuleFactoryTests.cs" />
|
||||
<Compile Include="Filters\Rules\RegexRuleTests.cs" />
|
||||
<Compile Include="Filters\Rules\SimplifiedRuleTests.cs" />
|
||||
<Compile Include="Handlers\ContextMenuHandlerTests.cs" />
|
||||
<Compile Include="Handlers\DialogHandlerTests.cs" />
|
||||
<Compile Include="Handlers\DisplayHandlerTests.cs" />
|
||||
<Compile Include="Handlers\DownloadHandlerTests.cs" />
|
||||
<Compile Include="Handlers\KeyboardHandlerTests.cs" />
|
||||
<Compile Include="Handlers\RequestHandlerTests.cs" />
|
||||
<Compile Include="Handlers\ResourceHandlerTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Browser.Contracts\SafeExamBrowser.Browser.Contracts.csproj">
|
||||
<Project>{5fb5273d-277c-41dd-8593-a25ce1aff2e9}</Project>
|
||||
<Name>SafeExamBrowser.Browser.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Browser\SafeExamBrowser.Browser.csproj">
|
||||
<Project>{04e653f1-98e6-4e34-9dd7-7f2bc1a8b767}</Project>
|
||||
<Name>SafeExamBrowser.Browser</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Configuration.Contracts\SafeExamBrowser.Configuration.Contracts.csproj">
|
||||
<Project>{7d74555e-63e1-4c46-bd0a-8580552368c8}</Project>
|
||||
<Name>SafeExamBrowser.Configuration.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.I18n.Contracts\SafeExamBrowser.I18n.Contracts.csproj">
|
||||
<Project>{1858ddf3-bc2a-4bff-b663-4ce2ffeb8b7d}</Project>
|
||||
<Name>SafeExamBrowser.I18n.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">
|
||||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
|
||||
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
|
||||
<Name>SafeExamBrowser.Settings</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.UserInterface.Contracts\SafeExamBrowser.UserInterface.Contracts.csproj">
|
||||
<Project>{c7889e97-6ff6-4a58-b7cb-521ed276b316}</Project>
|
||||
<Name>SafeExamBrowser.UserInterface.Contracts</Name>
|
||||
</ProjectReference>
|
||||
</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\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'))" />
|
||||
</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')" />
|
||||
</Project>
|
|
@ -1,51 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
|
||||
</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" />
|
||||
</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>
|
|
@ -1,26 +0,0 @@
|
|||
<?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" />
|
||||
</packages>
|
|
@ -1,501 +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.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Contracts;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Browser.Proxy;
|
||||
using SafeExamBrowser.Settings.Logging;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
|
||||
namespace SafeExamBrowser.Browser
|
||||
{
|
||||
public class BrowserApplication : IBrowserApplication
|
||||
{
|
||||
private int windowIdCounter = default;
|
||||
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly Clipboard clipboard;
|
||||
private readonly IFileSystemDialog fileSystemDialog;
|
||||
private readonly IHashAlgorithm hashAlgorithm;
|
||||
private readonly IKeyGenerator keyGenerator;
|
||||
private readonly IModuleLogger logger;
|
||||
private readonly IMessageBox messageBox;
|
||||
private readonly INativeMethods nativeMethods;
|
||||
private readonly SessionMode sessionMode;
|
||||
private readonly BrowserSettings settings;
|
||||
private readonly IText text;
|
||||
private readonly IUserInterfaceFactory uiFactory;
|
||||
private readonly List<BrowserWindow> windows;
|
||||
|
||||
public bool AutoStart { get; private set; }
|
||||
public IconResource Icon { get; private set; }
|
||||
public Guid Id { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public string Tooltip { get; private set; }
|
||||
|
||||
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
public event LoseFocusRequestedEventHandler LoseFocusRequested;
|
||||
public event TerminationRequestedEventHandler TerminationRequested;
|
||||
public event UserIdentifierDetectedEventHandler UserIdentifierDetected;
|
||||
public event WindowsChangedEventHandler WindowsChanged;
|
||||
|
||||
public BrowserApplication(
|
||||
AppConfig appConfig,
|
||||
BrowserSettings settings,
|
||||
IFileSystemDialog fileSystemDialog,
|
||||
IHashAlgorithm hashAlgorithm,
|
||||
IKeyGenerator keyGenerator,
|
||||
IMessageBox messageBox,
|
||||
IModuleLogger logger,
|
||||
INativeMethods nativeMethods,
|
||||
SessionMode sessionMode,
|
||||
IText text,
|
||||
IUserInterfaceFactory uiFactory)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.clipboard = new Clipboard(logger.CloneFor(nameof(Clipboard)), settings);
|
||||
this.fileSystemDialog = fileSystemDialog;
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.logger = logger;
|
||||
this.messageBox = messageBox;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.sessionMode = sessionMode;
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
this.uiFactory = uiFactory;
|
||||
this.windows = new List<BrowserWindow>();
|
||||
}
|
||||
|
||||
public void Focus(bool forward)
|
||||
{
|
||||
windows.ForEach(window =>
|
||||
{
|
||||
window.Focus(forward);
|
||||
});
|
||||
}
|
||||
|
||||
public IEnumerable<IBrowserWindow> GetWindows()
|
||||
{
|
||||
return new List<IBrowserWindow>(windows);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
logger.Info("Starting initialization...");
|
||||
|
||||
var cefSettings = InitializeCefSettings();
|
||||
var success = Cef.Initialize(cefSettings, true, default(IApp));
|
||||
|
||||
InitializeApplicationInfo();
|
||||
|
||||
if (success)
|
||||
{
|
||||
InitializeIntegrityKeys();
|
||||
|
||||
if (settings.DeleteCookiesOnStartup)
|
||||
{
|
||||
DeleteCookies();
|
||||
}
|
||||
|
||||
if (settings.UseTemporaryDownAndUploadDirectory)
|
||||
{
|
||||
CreateTemporaryDownAndUploadDirectory();
|
||||
}
|
||||
|
||||
logger.Info("Initialized browser.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Failed to initialize browser!");
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
CreateNewWindow();
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
logger.Info("Initiating termination...");
|
||||
AwaitReady();
|
||||
|
||||
foreach (var window in windows)
|
||||
{
|
||||
window.Closed -= Window_Closed;
|
||||
window.Close();
|
||||
logger.Info($"Closed browser window #{window.Id}.");
|
||||
}
|
||||
|
||||
if (settings.UseTemporaryDownAndUploadDirectory)
|
||||
{
|
||||
DeleteTemporaryDownAndUploadDirectory();
|
||||
}
|
||||
|
||||
if (settings.DeleteCookiesOnShutdown)
|
||||
{
|
||||
DeleteCookies();
|
||||
}
|
||||
|
||||
Cef.Shutdown();
|
||||
logger.Info("Terminated browser.");
|
||||
|
||||
if (settings.DeleteCacheOnShutdown && settings.DeleteCookiesOnShutdown)
|
||||
{
|
||||
DeleteCache();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info("Retained browser cache.");
|
||||
}
|
||||
}
|
||||
|
||||
private void AwaitReady()
|
||||
{
|
||||
// We apparently need to let the browser finish any pending work before attempting to reset or terminate it, especially if the
|
||||
// reset or termination is initiated automatically (e.g. by a quit URL). Otherwise, the engine will crash on some occasions, seemingly
|
||||
// when it can't finish handling its events (like ChromiumWebBrowser.LoadError).
|
||||
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
|
||||
private void CreateNewWindow(PopupRequestedEventArgs args = default)
|
||||
{
|
||||
var id = ++windowIdCounter;
|
||||
var isMainWindow = windows.Count == 0;
|
||||
var startUrl = GenerateStartUrl();
|
||||
var windowLogger = logger.CloneFor($"Browser Window #{id}");
|
||||
var window = new BrowserWindow(
|
||||
appConfig,
|
||||
clipboard,
|
||||
fileSystemDialog,
|
||||
hashAlgorithm,
|
||||
id,
|
||||
isMainWindow,
|
||||
keyGenerator,
|
||||
windowLogger,
|
||||
messageBox,
|
||||
sessionMode,
|
||||
settings,
|
||||
startUrl,
|
||||
text,
|
||||
uiFactory);
|
||||
|
||||
window.Closed += Window_Closed;
|
||||
window.ConfigurationDownloadRequested += (f, a) => ConfigurationDownloadRequested?.Invoke(f, a);
|
||||
window.PopupRequested += Window_PopupRequested;
|
||||
window.ResetRequested += Window_ResetRequested;
|
||||
window.UserIdentifierDetected += (i) => UserIdentifierDetected?.Invoke(i);
|
||||
window.TerminationRequested += () => TerminationRequested?.Invoke();
|
||||
window.LoseFocusRequested += (forward) => LoseFocusRequested?.Invoke(forward);
|
||||
|
||||
window.InitializeControl();
|
||||
windows.Add(window);
|
||||
|
||||
if (args != default(PopupRequestedEventArgs))
|
||||
{
|
||||
args.Window = window;
|
||||
}
|
||||
else
|
||||
{
|
||||
window.InitializeWindow();
|
||||
}
|
||||
|
||||
logger.Info($"Created browser window #{window.Id}.");
|
||||
WindowsChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void CreateTemporaryDownAndUploadDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
settings.DownAndUploadDirectory = Path.Combine(appConfig.TemporaryDirectory, Path.GetRandomFileName());
|
||||
Directory.CreateDirectory(settings.DownAndUploadDirectory);
|
||||
logger.Info($"Created temporary down- and upload directory.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to create temporary down- and upload directory!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteTemporaryDownAndUploadDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(settings.DownAndUploadDirectory, true);
|
||||
logger.Info("Deleted temporary down- and upload directory.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to delete temporary down- and upload directory!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteCache()
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(appConfig.BrowserCachePath, true);
|
||||
logger.Info("Deleted browser cache.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to delete browser cache!", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteCookies()
|
||||
{
|
||||
var callback = new TaskDeleteCookiesCallback();
|
||||
|
||||
callback.Task.ContinueWith(task =>
|
||||
{
|
||||
if (!task.IsCompleted || task.Result == TaskDeleteCookiesCallback.InvalidNoOfCookiesDeleted)
|
||||
{
|
||||
logger.Warn("Failed to delete cookies!");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Deleted {task.Result} cookies.");
|
||||
}
|
||||
});
|
||||
|
||||
if (Cef.GetGlobalCookieManager().DeleteCookies(callback: callback))
|
||||
{
|
||||
logger.Debug("Successfully initiated cookie deletion.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("Failed to initiate cookie deletion!");
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateStartUrl()
|
||||
{
|
||||
var url = settings.StartUrl;
|
||||
|
||||
if (settings.UseQueryParameter)
|
||||
{
|
||||
if (url.Contains("?") && settings.StartUrlQuery?.Length > 1 && Uri.TryCreate(url, UriKind.Absolute, out var uri))
|
||||
{
|
||||
url = url.Replace(uri.Query, $"{uri.Query}&{settings.StartUrlQuery.Substring(1)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
url = $"{url}{settings.StartUrlQuery}";
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
private void InitializeApplicationInfo()
|
||||
{
|
||||
AutoStart = true;
|
||||
Icon = new BrowserIconResource();
|
||||
Id = Guid.NewGuid();
|
||||
Name = text.Get(TextKey.Browser_Name);
|
||||
Tooltip = text.Get(TextKey.Browser_Tooltip);
|
||||
}
|
||||
|
||||
private CefSettings InitializeCefSettings()
|
||||
{
|
||||
var warning = logger.LogLevel == LogLevel.Warning;
|
||||
var error = logger.LogLevel == LogLevel.Error;
|
||||
var cefSettings = new CefSettings();
|
||||
|
||||
cefSettings.AcceptLanguageList = CultureInfo.CurrentUICulture.Name;
|
||||
cefSettings.CachePath = appConfig.BrowserCachePath;
|
||||
cefSettings.CefCommandLineArgs.Add("touch-events", "enabled");
|
||||
cefSettings.LogFile = appConfig.BrowserLogFilePath;
|
||||
cefSettings.LogSeverity = error ? LogSeverity.Error : (warning ? LogSeverity.Warning : LogSeverity.Info);
|
||||
cefSettings.PersistSessionCookies = !settings.DeleteCookiesOnStartup || !settings.DeleteCookiesOnShutdown;
|
||||
cefSettings.UserAgent = InitializeUserAgent();
|
||||
|
||||
if (!settings.AllowPageZoom)
|
||||
{
|
||||
cefSettings.CefCommandLineArgs.Add("disable-pinch");
|
||||
}
|
||||
|
||||
if (!settings.AllowPdfReader)
|
||||
{
|
||||
cefSettings.CefCommandLineArgs.Add("disable-pdf-extension");
|
||||
}
|
||||
|
||||
if (!settings.AllowSpellChecking)
|
||||
{
|
||||
cefSettings.CefCommandLineArgs.Add("disable-spell-checking");
|
||||
}
|
||||
|
||||
cefSettings.CefCommandLineArgs.Add("enable-media-stream");
|
||||
cefSettings.CefCommandLineArgs.Add("enable-usermedia-screen-capturing");
|
||||
cefSettings.CefCommandLineArgs.Add("use-fake-ui-for-media-stream");
|
||||
|
||||
InitializeProxySettings(cefSettings);
|
||||
|
||||
logger.Debug($"Accept Language: {cefSettings.AcceptLanguageList}");
|
||||
logger.Debug($"Cache Path: {cefSettings.CachePath}");
|
||||
logger.Debug($"Engine Version: Chromium {Cef.ChromiumVersion}, CEF {Cef.CefVersion}, CefSharp {Cef.CefSharpVersion}");
|
||||
logger.Debug($"Log File: {cefSettings.LogFile}");
|
||||
logger.Debug($"Log Severity: {cefSettings.LogSeverity}.");
|
||||
logger.Debug($"PDF Reader: {(settings.AllowPdfReader ? "Enabled" : "Disabled")}.");
|
||||
logger.Debug($"Session Persistence: {(cefSettings.PersistSessionCookies ? "Enabled" : "Disabled")}.");
|
||||
|
||||
return cefSettings;
|
||||
}
|
||||
|
||||
private void InitializeIntegrityKeys()
|
||||
{
|
||||
logger.Debug($"Browser Exam Key (BEK) transmission is {(settings.SendBrowserExamKey ? "enabled" : "disabled")}.");
|
||||
logger.Debug($"Configuration Key (CK) transmission is {(settings.SendConfigurationKey ? "enabled" : "disabled")}.");
|
||||
|
||||
if (settings.CustomBrowserExamKey != default)
|
||||
{
|
||||
keyGenerator.UseCustomBrowserExamKey(settings.CustomBrowserExamKey);
|
||||
logger.Debug($"The browser application will be using a custom browser exam key.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"The browser application will be using the default browser exam key.");
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeProxySettings(CefSettings cefSettings)
|
||||
{
|
||||
if (settings.Proxy.Policy == ProxyPolicy.Custom)
|
||||
{
|
||||
if (settings.Proxy.AutoConfigure)
|
||||
{
|
||||
cefSettings.CefCommandLineArgs.Add("proxy-pac-url", settings.Proxy.AutoConfigureUrl);
|
||||
}
|
||||
|
||||
if (settings.Proxy.AutoDetect)
|
||||
{
|
||||
cefSettings.CefCommandLineArgs.Add("proxy-auto-detect", "");
|
||||
}
|
||||
|
||||
if (settings.Proxy.BypassList.Any())
|
||||
{
|
||||
cefSettings.CefCommandLineArgs.Add("proxy-bypass-list", string.Join(";", settings.Proxy.BypassList));
|
||||
}
|
||||
|
||||
if (settings.Proxy.Proxies.Any())
|
||||
{
|
||||
var proxies = new List<string>();
|
||||
|
||||
foreach (var proxy in settings.Proxy.Proxies)
|
||||
{
|
||||
proxies.Add($"{ToScheme(proxy.Protocol)}={proxy.Host}:{proxy.Port}");
|
||||
}
|
||||
|
||||
cefSettings.CefCommandLineArgs.Add("proxy-server", string.Join(";", proxies));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string InitializeUserAgent()
|
||||
{
|
||||
var osVersion = $"{Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}";
|
||||
var sebVersion = $"SEB/{appConfig.ProgramInformationalVersion}";
|
||||
var userAgent = default(string);
|
||||
|
||||
if (settings.UseCustomUserAgent)
|
||||
{
|
||||
userAgent = $"{settings.CustomUserAgent} {sebVersion}";
|
||||
}
|
||||
else
|
||||
{
|
||||
userAgent = $"Mozilla/5.0 (Windows NT {osVersion}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{Cef.ChromiumVersion} {sebVersion}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(settings.UserAgentSuffix))
|
||||
{
|
||||
userAgent = $"{userAgent} {settings.UserAgentSuffix}";
|
||||
}
|
||||
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
private string ToScheme(ProxyProtocol protocol)
|
||||
{
|
||||
switch (protocol)
|
||||
{
|
||||
case ProxyProtocol.Ftp:
|
||||
return Uri.UriSchemeFtp;
|
||||
case ProxyProtocol.Http:
|
||||
return Uri.UriSchemeHttp;
|
||||
case ProxyProtocol.Https:
|
||||
return Uri.UriSchemeHttps;
|
||||
case ProxyProtocol.Socks:
|
||||
return "socks";
|
||||
}
|
||||
|
||||
throw new NotImplementedException($"Mapping for proxy protocol '{protocol}' is not yet implemented!");
|
||||
}
|
||||
|
||||
private void Window_Closed(int id)
|
||||
{
|
||||
windows.Remove(windows.First(i => i.Id == id));
|
||||
WindowsChanged?.Invoke();
|
||||
logger.Info($"Window #{id} has been closed.");
|
||||
}
|
||||
|
||||
private void Window_PopupRequested(PopupRequestedEventArgs args)
|
||||
{
|
||||
logger.Info($"Received request to create new window...");
|
||||
CreateNewWindow(args);
|
||||
}
|
||||
|
||||
private void Window_ResetRequested()
|
||||
{
|
||||
logger.Info("Attempting to reset browser...");
|
||||
AwaitReady();
|
||||
|
||||
foreach (var window in windows)
|
||||
{
|
||||
window.Closed -= Window_Closed;
|
||||
window.Close();
|
||||
logger.Info($"Closed browser window #{window.Id}.");
|
||||
}
|
||||
|
||||
windows.Clear();
|
||||
WindowsChanged?.Invoke();
|
||||
|
||||
if (settings.DeleteCookiesOnStartup && settings.DeleteCookiesOnShutdown)
|
||||
{
|
||||
DeleteCookies();
|
||||
}
|
||||
|
||||
nativeMethods.EmptyClipboard();
|
||||
CreateNewWindow();
|
||||
logger.Info("Successfully reset browser.");
|
||||
}
|
||||
}
|
||||
}
|
188
SafeExamBrowser.Browser/BrowserApplicationController.cs
Normal file
188
SafeExamBrowser.Browser/BrowserApplicationController.cs
Normal file
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Contracts.Applications;
|
||||
using SafeExamBrowser.Contracts.Browser;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.I18n;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
using SafeExamBrowser.Contracts.UserInterface;
|
||||
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
|
||||
using SafeExamBrowser.Contracts.UserInterface.Shell;
|
||||
using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.BrowserSettings;
|
||||
|
||||
namespace SafeExamBrowser.Browser
|
||||
{
|
||||
public class BrowserApplicationController : IBrowserApplicationController
|
||||
{
|
||||
private int instanceIdCounter = default(int);
|
||||
|
||||
private AppConfig appConfig;
|
||||
private IList<IApplicationControl> controls;
|
||||
private IList<IApplicationInstance> instances;
|
||||
private IMessageBox messageBox;
|
||||
private IModuleLogger logger;
|
||||
private BrowserSettings settings;
|
||||
private IText text;
|
||||
private IUserInterfaceFactory uiFactory;
|
||||
|
||||
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
|
||||
public BrowserApplicationController(
|
||||
AppConfig appConfig,
|
||||
BrowserSettings settings,
|
||||
IMessageBox messageBox,
|
||||
IModuleLogger logger,
|
||||
IText text,
|
||||
IUserInterfaceFactory uiFactory)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.controls = new List<IApplicationControl>();
|
||||
this.instances = new List<IApplicationInstance>();
|
||||
this.logger = logger;
|
||||
this.messageBox = messageBox;
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
this.uiFactory = uiFactory;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
var cefSettings = InitializeCefSettings();
|
||||
var success = Cef.Initialize(cefSettings, true, default(IApp));
|
||||
|
||||
logger.Info("Initialized browser.");
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw new Exception("Failed to initialize browser!");
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterApplicationControl(IApplicationControl control)
|
||||
{
|
||||
control.Clicked += ApplicationControl_Clicked;
|
||||
controls.Add(control);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
CreateNewInstance();
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
foreach (var instance in instances)
|
||||
{
|
||||
instance.Terminated -= Instance_Terminated;
|
||||
instance.Window.Close();
|
||||
|
||||
logger.Info($"Terminated browser instance {instance.Id}.");
|
||||
}
|
||||
|
||||
Cef.Shutdown();
|
||||
|
||||
logger.Info("Terminated browser.");
|
||||
}
|
||||
|
||||
private void CreateNewInstance(string url = null)
|
||||
{
|
||||
var id = new BrowserInstanceIdentifier(++instanceIdCounter);
|
||||
var isMainInstance = instances.Count == 0;
|
||||
var instanceLogger = logger.CloneFor($"BrowserInstance {id}");
|
||||
var startUrl = url ?? settings.StartUrl;
|
||||
var instance = new BrowserApplicationInstance(appConfig, settings, id, isMainInstance, messageBox, instanceLogger, text, uiFactory, startUrl);
|
||||
|
||||
instance.Initialize();
|
||||
instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args);
|
||||
instance.PopupRequested += Instance_PopupRequested;
|
||||
instance.Terminated += Instance_Terminated;
|
||||
|
||||
foreach (var control in controls)
|
||||
{
|
||||
control.RegisterInstance(instance);
|
||||
}
|
||||
|
||||
instances.Add(instance);
|
||||
instance.Window.Show();
|
||||
|
||||
logger.Info($"Created browser instance {instance.Id}.");
|
||||
}
|
||||
|
||||
private CefSettings InitializeCefSettings()
|
||||
{
|
||||
var warning = logger.LogLevel == LogLevel.Warning;
|
||||
var error = logger.LogLevel == LogLevel.Error;
|
||||
var cefSettings = new CefSettings
|
||||
{
|
||||
CachePath = appConfig.BrowserCachePath,
|
||||
LogFile = appConfig.BrowserLogFile,
|
||||
LogSeverity = error ? LogSeverity.Error : (warning ? LogSeverity.Warning : LogSeverity.Info),
|
||||
UserAgent = InitializeUserAgent()
|
||||
};
|
||||
|
||||
cefSettings.CefCommandLineArgs.Add("touch-events", "enabled");
|
||||
|
||||
logger.Debug($"Cache path: {cefSettings.CachePath}");
|
||||
logger.Debug($"Engine version: Chromium {Cef.ChromiumVersion}, CEF {Cef.CefVersion}, CefSharp {Cef.CefSharpVersion}");
|
||||
logger.Debug($"Log file: {cefSettings.LogFile}");
|
||||
logger.Debug($"Log severity: {cefSettings.LogSeverity}");
|
||||
|
||||
return cefSettings;
|
||||
}
|
||||
|
||||
private void ApplicationControl_Clicked(InstanceIdentifier id = null)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
CreateNewInstance();
|
||||
}
|
||||
else
|
||||
{
|
||||
instances.FirstOrDefault(i => i.Id == id)?.Window?.BringToForeground();
|
||||
}
|
||||
}
|
||||
|
||||
private void Instance_PopupRequested(PopupRequestedEventArgs args)
|
||||
{
|
||||
logger.Info($"Received request to create new instance for '{args.Url}'...");
|
||||
CreateNewInstance(args.Url);
|
||||
}
|
||||
|
||||
private void Instance_Terminated(InstanceIdentifier id)
|
||||
{
|
||||
instances.Remove(instances.FirstOrDefault(i => i.Id == id));
|
||||
logger.Info($"Browser instance {id} was terminated.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO: Workaround to correctly set the user agent due to missing support for request interception for requests made by service workers.
|
||||
/// Remove once CEF fully supports service workers and reactivate the functionality in <see cref="Handlers.RequestHandler"/>!
|
||||
/// </summary>
|
||||
private string InitializeUserAgent()
|
||||
{
|
||||
var osVersion = $"{Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}";
|
||||
var sebVersion = $"SEB/{appConfig.ProgramVersion}";
|
||||
|
||||
if (settings.UseCustomUserAgent)
|
||||
{
|
||||
return $"{settings.CustomUserAgent} {sebVersion}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"Mozilla/5.0 (Windows NT {osVersion}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{Cef.ChromiumVersion} {sebVersion}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
SafeExamBrowser.Browser/BrowserApplicationInfo.cs
Normal file
20
SafeExamBrowser.Browser/BrowserApplicationInfo.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Contracts.Applications;
|
||||
using SafeExamBrowser.Contracts.Core;
|
||||
|
||||
namespace SafeExamBrowser.Browser
|
||||
{
|
||||
public class BrowserApplicationInfo : IApplicationInfo
|
||||
{
|
||||
public string Name => "Safe Exam Browser";
|
||||
public string Tooltip => Name;
|
||||
public IIconResource IconResource { get; } = new BrowserIconResource();
|
||||
}
|
||||
}
|
289
SafeExamBrowser.Browser/BrowserApplicationInstance.cs
Normal file
289
SafeExamBrowser.Browser/BrowserApplicationInstance.cs
Normal file
|
@ -0,0 +1,289 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Browser.Handlers;
|
||||
using SafeExamBrowser.Contracts.Applications;
|
||||
using SafeExamBrowser.Contracts.Applications.Events;
|
||||
using SafeExamBrowser.Contracts.Browser;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
||||
using SafeExamBrowser.Contracts.I18n;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
using SafeExamBrowser.Contracts.UserInterface;
|
||||
using SafeExamBrowser.Contracts.UserInterface.Browser;
|
||||
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
|
||||
using SafeExamBrowser.Contracts.UserInterface.Windows;
|
||||
|
||||
namespace SafeExamBrowser.Browser
|
||||
{
|
||||
internal class BrowserApplicationInstance : IApplicationInstance
|
||||
{
|
||||
private const double ZOOM_FACTOR = 0.2;
|
||||
|
||||
private AppConfig appConfig;
|
||||
private IBrowserControl control;
|
||||
private IBrowserWindow window;
|
||||
private HttpClient httpClient;
|
||||
private bool isMainInstance;
|
||||
private IMessageBox messageBox;
|
||||
private IModuleLogger logger;
|
||||
private BrowserSettings settings;
|
||||
private IText text;
|
||||
private IUserInterfaceFactory uiFactory;
|
||||
private string url;
|
||||
private double zoomLevel;
|
||||
|
||||
private BrowserWindowSettings WindowSettings
|
||||
{
|
||||
get { return isMainInstance ? settings.MainWindowSettings : settings.AdditionalWindowSettings; }
|
||||
}
|
||||
|
||||
public InstanceIdentifier Id { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public IWindow Window { get { return window; } }
|
||||
|
||||
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
public event IconChangedEventHandler IconChanged;
|
||||
public event InstanceTerminatedEventHandler Terminated;
|
||||
public event NameChangedEventHandler NameChanged;
|
||||
public event PopupRequestedEventHandler PopupRequested;
|
||||
|
||||
public BrowserApplicationInstance(
|
||||
AppConfig appConfig,
|
||||
BrowserSettings settings,
|
||||
InstanceIdentifier id,
|
||||
bool isMainInstance,
|
||||
IMessageBox messageBox,
|
||||
IModuleLogger logger,
|
||||
IText text,
|
||||
IUserInterfaceFactory uiFactory,
|
||||
string url)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.Id = id;
|
||||
this.httpClient = new HttpClient();
|
||||
this.isMainInstance = isMainInstance;
|
||||
this.messageBox = messageBox;
|
||||
this.logger = logger;
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
this.uiFactory = uiFactory;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
var contextMenuHandler = new ContextMenuHandler();
|
||||
var displayHandler = new DisplayHandler();
|
||||
var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} {Id}");
|
||||
var downloadHandler = new DownloadHandler(appConfig, settings, downloadLogger);
|
||||
var keyboardHandler = new KeyboardHandler();
|
||||
var lifeSpanHandler = new LifeSpanHandler();
|
||||
var requestHandler = new RequestHandler(appConfig);
|
||||
|
||||
displayHandler.FaviconChanged += DisplayHandler_FaviconChanged;
|
||||
displayHandler.ProgressChanged += DisplayHandler_ProgressChanged;
|
||||
downloadHandler.ConfigurationDownloadRequested += DownloadHandler_ConfigurationDownloadRequested;
|
||||
keyboardHandler.ReloadRequested += ReloadRequested;
|
||||
keyboardHandler.ZoomInRequested += ZoomInRequested;
|
||||
keyboardHandler.ZoomOutRequested += ZoomOutRequested;
|
||||
keyboardHandler.ZoomResetRequested += ZoomResetRequested;
|
||||
lifeSpanHandler.PopupRequested += LifeSpanHandler_PopupRequested;
|
||||
|
||||
control = new BrowserControl(contextMenuHandler, displayHandler, downloadHandler, keyboardHandler, lifeSpanHandler, requestHandler, url);
|
||||
control.AddressChanged += Control_AddressChanged;
|
||||
control.LoadingStateChanged += Control_LoadingStateChanged;
|
||||
control.TitleChanged += Control_TitleChanged;
|
||||
control.Initialize();
|
||||
|
||||
logger.Debug("Initialized browser control.");
|
||||
|
||||
window = uiFactory.CreateBrowserWindow(control, settings, isMainInstance);
|
||||
window.Closing += () => Terminated?.Invoke(Id);
|
||||
window.AddressChanged += Window_AddressChanged;
|
||||
window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
|
||||
window.DeveloperConsoleRequested += Window_DeveloperConsoleRequested;
|
||||
window.ForwardNavigationRequested += Window_ForwardNavigationRequested;
|
||||
window.ReloadRequested += ReloadRequested;
|
||||
window.ZoomInRequested += ZoomInRequested;
|
||||
window.ZoomOutRequested += ZoomOutRequested;
|
||||
window.ZoomResetRequested += ZoomResetRequested;
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
|
||||
logger.Debug("Initialized browser window.");
|
||||
}
|
||||
|
||||
private void Control_AddressChanged(string address)
|
||||
{
|
||||
logger.Debug($"Navigated to '{address}'.");
|
||||
window.UpdateAddress(address);
|
||||
}
|
||||
|
||||
private void Control_LoadingStateChanged(bool isLoading)
|
||||
{
|
||||
window.CanNavigateBackwards = WindowSettings.AllowBackwardNavigation && control.CanNavigateBackwards;
|
||||
window.CanNavigateForwards = WindowSettings.AllowForwardNavigation && control.CanNavigateForwards;
|
||||
window.UpdateLoadingState(isLoading);
|
||||
}
|
||||
|
||||
private void Control_TitleChanged(string title)
|
||||
{
|
||||
window.UpdateTitle(title);
|
||||
NameChanged?.Invoke(title);
|
||||
}
|
||||
|
||||
private void DisplayHandler_FaviconChanged(string uri)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Head, uri);
|
||||
var response = httpClient.SendAsync(request).ContinueWith(task =>
|
||||
{
|
||||
if (task.IsCompleted && task.Result.IsSuccessStatusCode)
|
||||
{
|
||||
var icon = new BrowserIconResource(uri);
|
||||
|
||||
IconChanged?.Invoke(icon);
|
||||
window.UpdateIcon(icon);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void DisplayHandler_ProgressChanged(double value)
|
||||
{
|
||||
window.UpdateProgress(value);
|
||||
}
|
||||
|
||||
private void DownloadHandler_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args)
|
||||
{
|
||||
if (settings.AllowConfigurationDownloads)
|
||||
{
|
||||
args.BrowserWindow = window;
|
||||
logger.Debug($"Forwarding download request for configuration file '{fileName}'.");
|
||||
ConfigurationDownloadRequested?.Invoke(fileName, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Discarded download request for configuration file '{fileName}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private void LifeSpanHandler_PopupRequested(PopupRequestedEventArgs args)
|
||||
{
|
||||
if (settings.AllowPopups)
|
||||
{
|
||||
logger.Debug($"Forwarding request to open new window for '{args.Url}'...");
|
||||
PopupRequested?.Invoke(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Blocked attempt to open new window for '{args.Url}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ReloadRequested()
|
||||
{
|
||||
if (WindowSettings.AllowReloading && WindowSettings.ShowReloadWarning)
|
||||
{
|
||||
var result = messageBox.Show(TextKey.MessageBox_ReloadConfirmation, TextKey.MessageBox_ReloadConfirmationTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
logger.Debug("The user confirmed reloading the current page...");
|
||||
control.Reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug("The user aborted reloading the current page.");
|
||||
}
|
||||
}
|
||||
else if (WindowSettings.AllowReloading)
|
||||
{
|
||||
logger.Debug("Reloading current page...");
|
||||
control.Reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug("Blocked reload attempt, as the user is not allowed to reload web pages.");
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_AddressChanged(string address)
|
||||
{
|
||||
var isValid = Uri.TryCreate(address, UriKind.Absolute, out _) || Uri.TryCreate($"https://{address}", UriKind.Absolute, out _);
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
logger.Debug($"The user requested to navigate to '{address}', the URI is valid.");
|
||||
control.NavigateTo(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"The user requested to navigate to '{address}', but the URI is not valid.");
|
||||
window.UpdateAddress(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_BackwardNavigationRequested()
|
||||
{
|
||||
logger.Debug("Navigating backwards...");
|
||||
control.NavigateBackwards();
|
||||
}
|
||||
|
||||
private void Window_DeveloperConsoleRequested()
|
||||
{
|
||||
logger.Debug("Showing developer console...");
|
||||
control.ShowDeveloperConsole();
|
||||
}
|
||||
|
||||
private void Window_ForwardNavigationRequested()
|
||||
{
|
||||
logger.Debug("Navigating forwards...");
|
||||
control.NavigateForwards();
|
||||
}
|
||||
|
||||
private void ZoomInRequested()
|
||||
{
|
||||
if (settings.AllowPageZoom && CalculateZoomPercentage() < 300)
|
||||
{
|
||||
zoomLevel += ZOOM_FACTOR;
|
||||
control.Zoom(zoomLevel);
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
logger.Debug($"Increased page zoom to {CalculateZoomPercentage()}%.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ZoomOutRequested()
|
||||
{
|
||||
if (settings.AllowPageZoom && CalculateZoomPercentage() > 25)
|
||||
{
|
||||
zoomLevel -= ZOOM_FACTOR;
|
||||
control.Zoom(zoomLevel);
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
logger.Debug($"Decreased page zoom to {CalculateZoomPercentage()}%.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ZoomResetRequested()
|
||||
{
|
||||
if (settings.AllowPageZoom)
|
||||
{
|
||||
zoomLevel = 0;
|
||||
control.Zoom(0);
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
logger.Debug($"Reset page zoom to {CalculateZoomPercentage()}%.");
|
||||
}
|
||||
}
|
||||
|
||||
private double CalculateZoomPercentage()
|
||||
{
|
||||
return (zoomLevel * 25.0) + 100.0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -7,189 +7,121 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Wrapper;
|
||||
using SafeExamBrowser.Browser.Wrapper.Events;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
|
||||
using CefSharp.WinForms;
|
||||
using SafeExamBrowser.Contracts.UserInterface.Browser;
|
||||
using SafeExamBrowser.Contracts.UserInterface.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 const uint WS_EX_NOACTIVATE = 0x08000000;
|
||||
|
||||
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 IContextMenuHandler contextMenuHandler;
|
||||
private IDisplayHandler displayHandler;
|
||||
private IDownloadHandler downloadHandler;
|
||||
private IKeyboardHandler keyboardHandler;
|
||||
private ILifeSpanHandler lifeSpanHandler;
|
||||
private IRequestHandler requestHandler;
|
||||
|
||||
public event AddressChangedEventHandler AddressChanged;
|
||||
public event LoadFailedEventHandler LoadFailed;
|
||||
public event LoadingStateChangedEventHandler LoadingStateChanged;
|
||||
public event TitleChangedEventHandler TitleChanged;
|
||||
private AddressChangedEventHandler addressChanged;
|
||||
private LoadingStateChangedEventHandler loadingStateChanged;
|
||||
private TitleChangedEventHandler titleChanged;
|
||||
|
||||
public bool CanNavigateBackwards => GetBrowser().CanGoBack;
|
||||
public bool CanNavigateForwards => GetBrowser().CanGoForward;
|
||||
|
||||
event AddressChangedEventHandler IBrowserControl.AddressChanged
|
||||
{
|
||||
add { addressChanged += value; }
|
||||
remove { addressChanged -= 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,
|
||||
IDialogHandler dialogHandler,
|
||||
IContextMenuHandler contextMenuHandler,
|
||||
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.dialogHandler = dialogHandler;
|
||||
this.contextMenuHandler = contextMenuHandler;
|
||||
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)
|
||||
{
|
||||
control.Dispose(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void ExecuteJavaScript(string code, Action<JavaScriptResult> callback = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (control.BrowserCore != default && control.BrowserCore.MainFrame != default)
|
||||
{
|
||||
control.BrowserCore.EvaluateScriptAsync(code).ContinueWith(t =>
|
||||
{
|
||||
callback?.Invoke(new JavaScriptResult
|
||||
{
|
||||
Message = t.Result.Message,
|
||||
Result = t.Result.Result,
|
||||
Success = t.Result.Success
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Task.Run(() => callback?.Invoke(new JavaScriptResult
|
||||
{
|
||||
Message = "JavaScript can't be executed in main frame!",
|
||||
Success = false
|
||||
}));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to execute JavaScript '{(code.Length > 50 ? code.Take(50) : code)}'!", e);
|
||||
Task.Run(() => callback?.Invoke(new JavaScriptResult
|
||||
{
|
||||
Message = $"Failed to execute JavaScript '{(code.Length > 50 ? code.Take(50) : code)}'! Reason: {e.Message}",
|
||||
Success = false
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public void Find(string term, bool isInitial, bool caseSensitive, bool forward = true)
|
||||
{
|
||||
control.Find(term, forward, caseSensitive, !isInitial);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
clipboard.Changed += Clipboard_Changed;
|
||||
AddressChanged += (o, args) => addressChanged?.Invoke(args.Address);
|
||||
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);
|
||||
|
||||
if (control is IWebBrowser webBrowser)
|
||||
{
|
||||
webBrowser.JavascriptMessageReceived += WebBrowser_JavascriptMessageReceived;
|
||||
}
|
||||
DisplayHandler = displayHandler;
|
||||
DownloadHandler = downloadHandler;
|
||||
KeyboardHandler = keyboardHandler;
|
||||
LifeSpanHandler = lifeSpanHandler;
|
||||
MenuHandler = contextMenuHandler;
|
||||
RequestHandler = requestHandler;
|
||||
}
|
||||
|
||||
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)
|
||||
/// <summary>
|
||||
/// TODO: This is a workaround due to the broken initial touch activation in version 73.1.130, it must be removed once fixed in CefSharp.
|
||||
/// See https://github.com/cefsharp/CefSharp/issues/2776 for more information.
|
||||
/// </summary>
|
||||
protected override IWindowInfo CreateBrowserWindowInfo(IntPtr handle)
|
||||
{
|
||||
ExecuteJavaScript($"SafeExamBrowser.clipboard.update({id}, '{clipboard.Content}');");
|
||||
}
|
||||
var windowInfo = base.CreateBrowserWindowInfo(handle);
|
||||
|
||||
private void Control_IsBrowserInitializedChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (control.IsBrowserInitialized)
|
||||
{
|
||||
control.BrowserCore.GetHost().SetFocus(true);
|
||||
}
|
||||
}
|
||||
windowInfo.ExStyle &= ~WS_EX_NOACTIVATE;
|
||||
|
||||
private void WebBrowser_JavascriptMessageReceived(object sender, JavascriptMessageReceivedEventArgs e)
|
||||
{
|
||||
clipboard.Process(e);
|
||||
return windowInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -7,12 +7,16 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.Contracts.Core;
|
||||
|
||||
namespace SafeExamBrowser.Browser
|
||||
{
|
||||
public class BrowserIconResource : BitmapIconResource
|
||||
public class BrowserIconResource : IIconResource
|
||||
{
|
||||
public Uri Uri { get; private set; }
|
||||
public bool IsBitmapResource => true;
|
||||
public bool IsXamlResource => false;
|
||||
|
||||
public BrowserIconResource(string uri = null)
|
||||
{
|
||||
Uri = new Uri(uri ?? "pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/SafeExamBrowser.ico");
|
||||
|
|
42
SafeExamBrowser.Browser/BrowserInstanceIdentifier.cs
Normal file
42
SafeExamBrowser.Browser/BrowserInstanceIdentifier.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Contracts.Applications;
|
||||
|
||||
namespace SafeExamBrowser.Browser
|
||||
{
|
||||
internal class BrowserInstanceIdentifier : InstanceIdentifier
|
||||
{
|
||||
public int Value { get; private set; }
|
||||
|
||||
public BrowserInstanceIdentifier(int id)
|
||||
{
|
||||
Value = id;
|
||||
}
|
||||
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
if (other is BrowserInstanceIdentifier id)
|
||||
{
|
||||
return Value == id.Value;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"#{Value}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,788 +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.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms.Handler;
|
||||
using CefSharp.WinForms.Host;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Browser.Filters;
|
||||
using SafeExamBrowser.Browser.Handlers;
|
||||
using SafeExamBrowser.Browser.Wrapper;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
using Syroot.Windows.IO;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
using DisplayHandler = SafeExamBrowser.Browser.Handlers.DisplayHandler;
|
||||
using Request = SafeExamBrowser.Browser.Contracts.Filters.Request;
|
||||
using ResourceHandler = SafeExamBrowser.Browser.Handlers.ResourceHandler;
|
||||
using TitleChangedEventHandler = SafeExamBrowser.Applications.Contracts.Events.TitleChangedEventHandler;
|
||||
|
||||
namespace SafeExamBrowser.Browser
|
||||
{
|
||||
internal class BrowserWindow : Contracts.IBrowserWindow
|
||||
{
|
||||
private const string CLEAR_FIND_TERM = "thisisahacktoclearthesearchresultsasitappearsthatthereisnosuchfunctionalityincef";
|
||||
private const double ZOOM_FACTOR = 0.2;
|
||||
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly Clipboard clipboard;
|
||||
private readonly IFileSystemDialog fileSystemDialog;
|
||||
private readonly IHashAlgorithm hashAlgorithm;
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly IKeyGenerator keyGenerator;
|
||||
private readonly IModuleLogger logger;
|
||||
private readonly IMessageBox messageBox;
|
||||
private readonly SessionMode sessionMode;
|
||||
private readonly Dictionary<int, BrowserWindow> popups;
|
||||
private readonly BrowserSettings settings;
|
||||
private readonly string startUrl;
|
||||
private readonly IText text;
|
||||
private readonly IUserInterfaceFactory uiFactory;
|
||||
|
||||
private (string term, bool isInitial, bool caseSensitive, bool forward) findParameters;
|
||||
private IBrowserWindow window;
|
||||
private double zoomLevel;
|
||||
|
||||
private WindowSettings WindowSettings
|
||||
{
|
||||
get { return IsMainWindow ? settings.MainWindow : settings.AdditionalWindow; }
|
||||
}
|
||||
|
||||
internal IBrowserControl Control { get; private set; }
|
||||
internal int Id { get; }
|
||||
|
||||
public IntPtr Handle { get; private set; }
|
||||
public IconResource Icon { get; private set; }
|
||||
public bool IsMainWindow { get; private set; }
|
||||
public string Title { get; private set; }
|
||||
public string Url { get; private set; }
|
||||
|
||||
internal event WindowClosedEventHandler Closed;
|
||||
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
internal event LoseFocusRequestedEventHandler LoseFocusRequested;
|
||||
internal event PopupRequestedEventHandler PopupRequested;
|
||||
internal event ResetRequestedEventHandler ResetRequested;
|
||||
internal event TerminationRequestedEventHandler TerminationRequested;
|
||||
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
|
||||
|
||||
public event IconChangedEventHandler IconChanged;
|
||||
public event TitleChangedEventHandler TitleChanged;
|
||||
|
||||
public BrowserWindow(
|
||||
AppConfig appConfig,
|
||||
Clipboard clipboard,
|
||||
IFileSystemDialog fileSystemDialog,
|
||||
IHashAlgorithm hashAlgorithm,
|
||||
int id,
|
||||
bool isMainWindow,
|
||||
IKeyGenerator keyGenerator,
|
||||
IModuleLogger logger,
|
||||
IMessageBox messageBox,
|
||||
SessionMode sessionMode,
|
||||
BrowserSettings settings,
|
||||
string startUrl,
|
||||
IText text,
|
||||
IUserInterfaceFactory uiFactory)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.clipboard = clipboard;
|
||||
this.fileSystemDialog = fileSystemDialog;
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.httpClient = new HttpClient();
|
||||
this.Id = id;
|
||||
this.IsMainWindow = isMainWindow;
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.logger = logger;
|
||||
this.messageBox = messageBox;
|
||||
this.popups = new Dictionary<int, BrowserWindow>();
|
||||
this.sessionMode = sessionMode;
|
||||
this.settings = settings;
|
||||
this.startUrl = startUrl;
|
||||
this.text = text;
|
||||
this.uiFactory = uiFactory;
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
window.BringToForeground();
|
||||
}
|
||||
|
||||
internal void Close()
|
||||
{
|
||||
window.Close();
|
||||
Control.Destroy();
|
||||
}
|
||||
|
||||
internal void Focus(bool forward)
|
||||
{
|
||||
if (forward)
|
||||
{
|
||||
window.FocusToolbar(forward);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.FocusBrowser();
|
||||
Activate();
|
||||
}
|
||||
}
|
||||
|
||||
internal void InitializeControl()
|
||||
{
|
||||
var cefSharpControl = default(ICefSharpControl);
|
||||
var controlLogger = logger.CloneFor($"{nameof(BrowserControl)} #{Id}");
|
||||
var dialogHandler = new DialogHandler();
|
||||
var displayHandler = new DisplayHandler();
|
||||
var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} #{Id}");
|
||||
var downloadHandler = new DownloadHandler(appConfig, downloadLogger, settings, WindowSettings);
|
||||
var keyboardHandler = new KeyboardHandler();
|
||||
var renderHandler = new RenderProcessMessageHandler(appConfig, clipboard, keyGenerator, settings, text);
|
||||
var requestFilter = new RequestFilter();
|
||||
var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} #{Id}");
|
||||
var resourceHandler = new ResourceHandler(appConfig, requestFilter, keyGenerator, logger, sessionMode, settings, WindowSettings, text);
|
||||
var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, resourceHandler, settings, WindowSettings);
|
||||
|
||||
Icon = new BrowserIconResource();
|
||||
|
||||
if (IsMainWindow)
|
||||
{
|
||||
cefSharpControl = new CefSharpBrowserControl(CreateLifeSpanHandlerForMainWindow(), startUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
cefSharpControl = new CefSharpPopupControl();
|
||||
}
|
||||
|
||||
dialogHandler.DialogRequested += DialogHandler_DialogRequested;
|
||||
displayHandler.FaviconChanged += DisplayHandler_FaviconChanged;
|
||||
displayHandler.ProgressChanged += DisplayHandler_ProgressChanged;
|
||||
downloadHandler.ConfigurationDownloadRequested += DownloadHandler_ConfigurationDownloadRequested;
|
||||
downloadHandler.DownloadAborted += DownloadHandler_DownloadAborted;
|
||||
downloadHandler.DownloadUpdated += DownloadHandler_DownloadUpdated;
|
||||
keyboardHandler.FindRequested += KeyboardHandler_FindRequested;
|
||||
keyboardHandler.FocusAddressBarRequested += KeyboardHandler_FocusAddressBarRequested;
|
||||
keyboardHandler.HomeNavigationRequested += HomeNavigationRequested;
|
||||
keyboardHandler.ReloadRequested += ReloadRequested;
|
||||
keyboardHandler.TabPressed += KeyboardHandler_TabPressed;
|
||||
keyboardHandler.ZoomInRequested += ZoomInRequested;
|
||||
keyboardHandler.ZoomOutRequested += ZoomOutRequested;
|
||||
keyboardHandler.ZoomResetRequested += ZoomResetRequested;
|
||||
requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited;
|
||||
requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
|
||||
resourceHandler.UserIdentifierDetected += (id) => UserIdentifierDetected?.Invoke(id);
|
||||
|
||||
InitializeRequestFilter(requestFilter);
|
||||
|
||||
Control = new BrowserControl(clipboard, cefSharpControl, dialogHandler, displayHandler, downloadHandler, keyboardHandler, controlLogger, renderHandler, requestHandler);
|
||||
Control.AddressChanged += Control_AddressChanged;
|
||||
Control.LoadFailed += Control_LoadFailed;
|
||||
Control.LoadingStateChanged += Control_LoadingStateChanged;
|
||||
Control.TitleChanged += Control_TitleChanged;
|
||||
|
||||
Control.Initialize();
|
||||
logger.Debug("Initialized browser control.");
|
||||
}
|
||||
|
||||
internal void InitializeWindow()
|
||||
{
|
||||
window = uiFactory.CreateBrowserWindow(Control, settings, IsMainWindow, this.logger);
|
||||
window.AddressChanged += Window_AddressChanged;
|
||||
window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
|
||||
window.Closed += Window_Closed;
|
||||
window.Closing += Window_Closing;
|
||||
window.DeveloperConsoleRequested += Window_DeveloperConsoleRequested;
|
||||
window.FindRequested += Window_FindRequested;
|
||||
window.ForwardNavigationRequested += Window_ForwardNavigationRequested;
|
||||
window.HomeNavigationRequested += HomeNavigationRequested;
|
||||
window.LoseFocusRequested += Window_LoseFocusRequested;
|
||||
window.ReloadRequested += ReloadRequested;
|
||||
window.ZoomInRequested += ZoomInRequested;
|
||||
window.ZoomOutRequested += ZoomOutRequested;
|
||||
window.ZoomResetRequested += ZoomResetRequested;
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
window.Show();
|
||||
window.BringToForeground();
|
||||
|
||||
Handle = window.Handle;
|
||||
|
||||
logger.Debug("Initialized browser window.");
|
||||
}
|
||||
|
||||
private ILifeSpanHandler CreateLifeSpanHandlerForMainWindow()
|
||||
{
|
||||
return LifeSpanHandler
|
||||
.Create(() => LifeSpanHandler_CreatePopup())
|
||||
.OnBeforePopupCreated((wb, b, f, u, t, d, g, s) => LifeSpanHandler_PopupRequested(u))
|
||||
.OnPopupCreated((c, u) => LifeSpanHandler_PopupCreated(c))
|
||||
.OnPopupDestroyed((c, b) => LifeSpanHandler_PopupDestroyed(c))
|
||||
.Build();
|
||||
}
|
||||
|
||||
private void InitializeRequestFilter(IRequestFilter requestFilter)
|
||||
{
|
||||
if (settings.Filter.ProcessContentRequests || settings.Filter.ProcessMainRequests)
|
||||
{
|
||||
var factory = new RuleFactory();
|
||||
|
||||
foreach (var settings in settings.Filter.Rules)
|
||||
{
|
||||
var rule = factory.CreateRule(settings.Type);
|
||||
|
||||
rule.Initialize(settings);
|
||||
requestFilter.Load(rule);
|
||||
}
|
||||
|
||||
logger.Debug($"Initialized request filter with {settings.Filter.Rules.Count} rule(s).");
|
||||
|
||||
if (requestFilter.Process(new Request { Url = settings.StartUrl }) != FilterResult.Allow)
|
||||
{
|
||||
var rule = factory.CreateRule(FilterRuleType.Simplified);
|
||||
|
||||
rule.Initialize(new FilterRuleSettings { Expression = settings.StartUrl, Result = FilterResult.Allow });
|
||||
requestFilter.Load(rule);
|
||||
|
||||
logger.Debug($"Automatically created filter rule to allow start URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{settings.StartUrl}'" : "")}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Control_AddressChanged(string address)
|
||||
{
|
||||
logger.Info($"Navigated{(WindowSettings.UrlPolicy.CanLog() ? $" to '{address}'" : "")}.");
|
||||
|
||||
Url = address;
|
||||
window.UpdateAddress(address);
|
||||
|
||||
if (WindowSettings.UrlPolicy == UrlPolicy.Always || WindowSettings.UrlPolicy == UrlPolicy.BeforeTitle)
|
||||
{
|
||||
Title = address;
|
||||
window.UpdateTitle(address);
|
||||
TitleChanged?.Invoke(address);
|
||||
}
|
||||
|
||||
AutoFind();
|
||||
}
|
||||
|
||||
private void Control_LoadFailed(int errorCode, string errorText, bool isMainRequest, string url)
|
||||
{
|
||||
switch (errorCode)
|
||||
{
|
||||
case (int) CefErrorCode.Aborted:
|
||||
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was aborted.");
|
||||
break;
|
||||
case (int) CefErrorCode.InternetDisconnected:
|
||||
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} has failed due to loss of internet connection.");
|
||||
break;
|
||||
case (int) CefErrorCode.None:
|
||||
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was successful.");
|
||||
break;
|
||||
case (int) CefErrorCode.UnknownUrlScheme:
|
||||
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} has an unknown URL scheme and will be handled by the OS.");
|
||||
break;
|
||||
default:
|
||||
HandleUnknownLoadFailure(errorCode, errorText, isMainRequest, url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleUnknownLoadFailure(int errorCode, string errorText, bool isMainRequest, string url)
|
||||
{
|
||||
var requestInfo = $"{errorText} ({errorCode}, {(isMainRequest ? "main" : "resource")} request)";
|
||||
|
||||
logger.Warn($"Request{(WindowSettings.UrlPolicy.CanLogError() ? $" for '{url}'" : "")} failed: {requestInfo}.");
|
||||
|
||||
if (isMainRequest)
|
||||
{
|
||||
var title = text.Get(TextKey.Browser_LoadErrorTitle);
|
||||
var message = text.Get(TextKey.Browser_LoadErrorMessage).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "") + $" {requestInfo}";
|
||||
|
||||
Task.Run(() => messageBox.Show(message, title, icon: MessageBoxIcon.Error, parent: window)).ContinueWith(_ => Control.NavigateBackwards());
|
||||
}
|
||||
}
|
||||
|
||||
private void Control_LoadingStateChanged(bool isLoading)
|
||||
{
|
||||
window.CanNavigateBackwards = WindowSettings.AllowBackwardNavigation && Control.CanNavigateBackwards;
|
||||
window.CanNavigateForwards = WindowSettings.AllowForwardNavigation && Control.CanNavigateForwards;
|
||||
window.UpdateLoadingState(isLoading);
|
||||
}
|
||||
|
||||
private void Control_TitleChanged(string title)
|
||||
{
|
||||
if (WindowSettings.UrlPolicy != UrlPolicy.Always)
|
||||
{
|
||||
Title = title;
|
||||
window.UpdateTitle(Title);
|
||||
TitleChanged?.Invoke(Title);
|
||||
}
|
||||
}
|
||||
|
||||
private void DialogHandler_DialogRequested(DialogRequestedEventArgs args)
|
||||
{
|
||||
var isDownload = args.Operation == FileSystemOperation.Save;
|
||||
var isUpload = args.Operation == FileSystemOperation.Open;
|
||||
var isAllowed = (isDownload && settings.AllowDownloads) || (isUpload && settings.AllowUploads);
|
||||
var initialPath = default(string);
|
||||
|
||||
if (isDownload)
|
||||
{
|
||||
initialPath = args.InitialPath;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(settings.DownAndUploadDirectory))
|
||||
{
|
||||
initialPath = KnownFolders.Downloads.ExpandedPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
initialPath = Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory);
|
||||
}
|
||||
|
||||
if (isAllowed)
|
||||
{
|
||||
var result = fileSystemDialog.Show(
|
||||
args.Element,
|
||||
args.Operation,
|
||||
initialPath,
|
||||
title: args.Title,
|
||||
parent: window,
|
||||
restrictNavigation: !settings.AllowCustomDownAndUploadLocation,
|
||||
showElementPath: settings.ShowFileSystemElementPath);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
args.FullPath = result.FullPath;
|
||||
args.Success = result.Success;
|
||||
logger.Debug($"User selected path '{result.FullPath}' when asked to {args.Operation}->{args.Element}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"User aborted file system dialog to {args.Operation}->{args.Element}.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"Blocked file system dialog to {args.Operation}->{args.Element}, as {(isDownload ? "downloading" : "uploading")} is not allowed.");
|
||||
ShowDownUploadNotAllowedMessage(isDownload);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayHandler_FaviconChanged(string uri)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Head, uri);
|
||||
var response = httpClient.SendAsync(request).ContinueWith(task =>
|
||||
{
|
||||
if (task.IsCompleted && task.Result.IsSuccessStatusCode)
|
||||
{
|
||||
Icon = new BrowserIconResource(uri);
|
||||
|
||||
IconChanged?.Invoke(Icon);
|
||||
window.UpdateIcon(Icon);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void DisplayHandler_ProgressChanged(double value)
|
||||
{
|
||||
window.UpdateProgress(value);
|
||||
}
|
||||
|
||||
private void DownloadHandler_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args)
|
||||
{
|
||||
if (settings.AllowConfigurationDownloads)
|
||||
{
|
||||
logger.Debug($"Forwarding download request for configuration file '{fileName}'.");
|
||||
ConfigurationDownloadRequested?.Invoke(fileName, args);
|
||||
|
||||
if (args.AllowDownload)
|
||||
{
|
||||
logger.Debug($"Download request for configuration file '{fileName}' was granted.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Download request for configuration file '{fileName}' was denied.");
|
||||
messageBox.Show(TextKey.MessageBox_ReconfigurationDenied, TextKey.MessageBox_ReconfigurationDeniedTitle, parent: window);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Discarded download request for configuration file '{fileName}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private void DownloadHandler_DownloadAborted()
|
||||
{
|
||||
ShowDownUploadNotAllowedMessage();
|
||||
}
|
||||
|
||||
private void DownloadHandler_DownloadUpdated(DownloadItemState state)
|
||||
{
|
||||
window.UpdateDownloadState(state);
|
||||
}
|
||||
|
||||
private void HomeNavigationRequested()
|
||||
{
|
||||
if (IsMainWindow && (settings.UseStartUrlAsHomeUrl || !string.IsNullOrWhiteSpace(settings.HomeUrl)))
|
||||
{
|
||||
var navigate = false;
|
||||
var url = settings.UseStartUrlAsHomeUrl ? settings.StartUrl : settings.HomeUrl;
|
||||
|
||||
if (settings.HomeNavigationRequiresPassword && !string.IsNullOrWhiteSpace(settings.HomePasswordHash))
|
||||
{
|
||||
var message = text.Get(TextKey.PasswordDialog_BrowserHomePasswordRequired);
|
||||
var title = !string.IsNullOrWhiteSpace(settings.HomeNavigationMessage) ? settings.HomeNavigationMessage : text.Get(TextKey.PasswordDialog_BrowserHomePasswordRequiredTitle);
|
||||
var dialog = uiFactory.CreatePasswordDialog(message, title);
|
||||
var result = dialog.Show(window);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
var passwordHash = hashAlgorithm.GenerateHashFor(result.Password);
|
||||
|
||||
if (settings.HomePasswordHash.Equals(passwordHash, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
navigate = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
messageBox.Show(TextKey.MessageBox_InvalidHomePassword, TextKey.MessageBox_InvalidHomePasswordTitle, icon: MessageBoxIcon.Warning, parent: window);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var message = text.Get(TextKey.MessageBox_BrowserHomeQuestion);
|
||||
var title = !string.IsNullOrWhiteSpace(settings.HomeNavigationMessage) ? settings.HomeNavigationMessage : text.Get(TextKey.MessageBox_BrowserHomeQuestionTitle);
|
||||
var result = messageBox.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
|
||||
|
||||
navigate = result == MessageBoxResult.Yes;
|
||||
}
|
||||
|
||||
if (navigate)
|
||||
{
|
||||
Control.NavigateTo(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyboardHandler_FindRequested()
|
||||
{
|
||||
if (settings.AllowFind)
|
||||
{
|
||||
window.ShowFindbar();
|
||||
}
|
||||
}
|
||||
|
||||
private void KeyboardHandler_FocusAddressBarRequested()
|
||||
{
|
||||
window.FocusAddressBar();
|
||||
}
|
||||
|
||||
private void KeyboardHandler_TabPressed(bool shiftPressed)
|
||||
{
|
||||
Control.ExecuteJavaScript("document.activeElement.tagName", result =>
|
||||
{
|
||||
if (result.Result is string tagName && tagName?.ToUpper() == "BODY")
|
||||
{
|
||||
// This means the user is now at the start of the focus / tabIndex chain in the website.
|
||||
if (shiftPressed)
|
||||
{
|
||||
window.FocusToolbar(!shiftPressed);
|
||||
}
|
||||
else
|
||||
{
|
||||
LoseFocusRequested?.Invoke(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ChromiumHostControl LifeSpanHandler_CreatePopup()
|
||||
{
|
||||
var args = new PopupRequestedEventArgs();
|
||||
|
||||
PopupRequested?.Invoke(args);
|
||||
|
||||
var control = args.Window.Control.EmbeddableControl as ChromiumHostControl;
|
||||
var id = control.GetHashCode();
|
||||
var window = args.Window;
|
||||
|
||||
popups[id] = window;
|
||||
window.Closed += (_) => popups.Remove(id);
|
||||
|
||||
return control;
|
||||
}
|
||||
|
||||
private void LifeSpanHandler_PopupCreated(ChromiumHostControl control)
|
||||
{
|
||||
var id = control.GetHashCode();
|
||||
var window = popups[id];
|
||||
|
||||
window.InitializeWindow();
|
||||
}
|
||||
|
||||
private void LifeSpanHandler_PopupDestroyed(ChromiumHostControl control)
|
||||
{
|
||||
var id = control.GetHashCode();
|
||||
var window = popups[id];
|
||||
|
||||
window.Close();
|
||||
}
|
||||
|
||||
private PopupCreation LifeSpanHandler_PopupRequested(string targetUrl)
|
||||
{
|
||||
var creation = PopupCreation.Cancel;
|
||||
var validCurrentUri = Uri.TryCreate(Control.Address, UriKind.Absolute, out var currentUri);
|
||||
var validNewUri = Uri.TryCreate(targetUrl, UriKind.Absolute, out var newUri);
|
||||
var sameHost = validCurrentUri && validNewUri && string.Equals(currentUri.Host, newUri.Host, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
switch (settings.PopupPolicy)
|
||||
{
|
||||
case PopupPolicy.Allow:
|
||||
case PopupPolicy.AllowSameHost when sameHost:
|
||||
logger.Debug($"Forwarding request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")}...");
|
||||
creation = PopupCreation.Continue;
|
||||
break;
|
||||
case PopupPolicy.AllowSameWindow:
|
||||
case PopupPolicy.AllowSameHostAndWindow when sameHost:
|
||||
logger.Info($"Discarding request to open new window and loading{(WindowSettings.UrlPolicy.CanLog() ? $" '{targetUrl}'" : "")} directly...");
|
||||
Control.NavigateTo(targetUrl);
|
||||
break;
|
||||
case PopupPolicy.AllowSameHost when !sameHost:
|
||||
case PopupPolicy.AllowSameHostAndWindow when !sameHost:
|
||||
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")} as it targets a different host.");
|
||||
break;
|
||||
default:
|
||||
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")}.");
|
||||
break;
|
||||
}
|
||||
|
||||
return creation;
|
||||
}
|
||||
|
||||
private void RequestHandler_QuitUrlVisited(string url)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (settings.ResetOnQuitUrl)
|
||||
{
|
||||
logger.Info("Forwarding request to reset browser...");
|
||||
ResetRequested?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (settings.ConfirmQuitUrl)
|
||||
{
|
||||
var message = text.Get(TextKey.MessageBox_BrowserQuitUrlConfirmation);
|
||||
var title = text.Get(TextKey.MessageBox_BrowserQuitUrlConfirmationTitle);
|
||||
var result = messageBox.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
|
||||
var terminate = result == MessageBoxResult.Yes;
|
||||
|
||||
if (terminate)
|
||||
{
|
||||
logger.Info($"User confirmed termination via quit URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{url}'" : "")}, forwarding request...");
|
||||
TerminationRequested?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"User aborted termination via quit URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{url}'" : "")}.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"Automatically requesting termination due to quit URL{(WindowSettings.UrlPolicy.CanLog() ? $" '{url}'" : "")}...");
|
||||
TerminationRequested?.Invoke();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void RequestHandler_RequestBlocked(string url)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var message = text.Get(TextKey.MessageBox_BrowserNavigationBlocked).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "");
|
||||
var title = text.Get(TextKey.MessageBox_BrowserNavigationBlockedTitle);
|
||||
|
||||
Control.TitleChanged -= Control_TitleChanged;
|
||||
|
||||
if (url.Equals(startUrl, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
window.UpdateTitle($"*** {title} ***");
|
||||
TitleChanged?.Invoke($"*** {title} ***");
|
||||
}
|
||||
|
||||
messageBox.Show(message, title, parent: window);
|
||||
Control.TitleChanged += Control_TitleChanged;
|
||||
});
|
||||
}
|
||||
|
||||
private void ReloadRequested()
|
||||
{
|
||||
if (WindowSettings.AllowReloading && WindowSettings.ShowReloadWarning)
|
||||
{
|
||||
var result = messageBox.Show(TextKey.MessageBox_ReloadConfirmation, TextKey.MessageBox_ReloadConfirmationTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
logger.Debug("The user confirmed reloading the current page...");
|
||||
Control.Reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug("The user aborted reloading the current page.");
|
||||
}
|
||||
}
|
||||
else if (WindowSettings.AllowReloading)
|
||||
{
|
||||
logger.Debug("Reloading current page...");
|
||||
Control.Reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug("Blocked reload attempt, as the user is not allowed to reload web pages.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowDownUploadNotAllowedMessage(bool isDownload = true)
|
||||
{
|
||||
var message = isDownload ? TextKey.MessageBox_DownloadNotAllowed : TextKey.MessageBox_UploadNotAllowed;
|
||||
var title = isDownload ? TextKey.MessageBox_DownloadNotAllowedTitle : TextKey.MessageBox_UploadNotAllowedTitle;
|
||||
|
||||
messageBox.Show(message, title, icon: MessageBoxIcon.Warning, parent: window);
|
||||
}
|
||||
|
||||
private void Window_AddressChanged(string address)
|
||||
{
|
||||
var isValid = Uri.TryCreate(address, UriKind.Absolute, out _) || Uri.TryCreate($"https://{address}", UriKind.Absolute, out _);
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
logger.Debug($"The user requested to navigate to '{address}', the URI is valid.");
|
||||
Control.NavigateTo(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"The user requested to navigate to '{address}', but the URI is not valid.");
|
||||
window.UpdateAddress(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_BackwardNavigationRequested()
|
||||
{
|
||||
logger.Debug("Navigating backwards...");
|
||||
Control.NavigateBackwards();
|
||||
}
|
||||
|
||||
private void Window_Closing()
|
||||
{
|
||||
logger.Debug($"Window is closing...");
|
||||
}
|
||||
|
||||
private void Window_Closed()
|
||||
{
|
||||
logger.Debug($"Window has been closed.");
|
||||
Control.Destroy();
|
||||
Closed?.Invoke(Id);
|
||||
}
|
||||
|
||||
private void Window_DeveloperConsoleRequested()
|
||||
{
|
||||
logger.Debug("Showing developer console...");
|
||||
Control.ShowDeveloperConsole();
|
||||
}
|
||||
|
||||
private void Window_FindRequested(string term, bool isInitial, bool caseSensitive, bool forward = true)
|
||||
{
|
||||
if (settings.AllowFind)
|
||||
{
|
||||
findParameters.caseSensitive = caseSensitive;
|
||||
findParameters.forward = forward;
|
||||
findParameters.isInitial = isInitial;
|
||||
findParameters.term = term;
|
||||
|
||||
Control.Find(term, isInitial, caseSensitive, forward);
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_ForwardNavigationRequested()
|
||||
{
|
||||
logger.Debug("Navigating forwards...");
|
||||
Control.NavigateForwards();
|
||||
}
|
||||
|
||||
private void Window_LoseFocusRequested(bool forward)
|
||||
{
|
||||
LoseFocusRequested?.Invoke(forward);
|
||||
}
|
||||
|
||||
private void ZoomInRequested()
|
||||
{
|
||||
if (settings.AllowPageZoom && CalculateZoomPercentage() < 300)
|
||||
{
|
||||
zoomLevel += ZOOM_FACTOR;
|
||||
Control.Zoom(zoomLevel);
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
logger.Debug($"Increased page zoom to {CalculateZoomPercentage()}%.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ZoomOutRequested()
|
||||
{
|
||||
if (settings.AllowPageZoom && CalculateZoomPercentage() > 25)
|
||||
{
|
||||
zoomLevel -= ZOOM_FACTOR;
|
||||
Control.Zoom(zoomLevel);
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
logger.Debug($"Decreased page zoom to {CalculateZoomPercentage()}%.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ZoomResetRequested()
|
||||
{
|
||||
if (settings.AllowPageZoom)
|
||||
{
|
||||
zoomLevel = 0;
|
||||
Control.Zoom(0);
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
logger.Debug($"Reset page zoom to {CalculateZoomPercentage()}%.");
|
||||
}
|
||||
}
|
||||
|
||||
private void AutoFind()
|
||||
{
|
||||
if (settings.AllowFind && !string.IsNullOrEmpty(findParameters.term) && !CLEAR_FIND_TERM.Equals(findParameters.term, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Control.Find(findParameters.term, findParameters.isInitial, findParameters.caseSensitive, findParameters.forward);
|
||||
}
|
||||
}
|
||||
|
||||
private double CalculateZoomPercentage()
|
||||
{
|
||||
return (zoomLevel * 25.0) + 100.0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
<div style="background-color: lightgray; display: table; font-family: 'Segoe UI'; height: 100%; text-align: center; width: 100%">
|
||||
<p style="display: table-cell; font-weight: bold; vertical-align: middle">%%MESSAGE%%</p>
|
||||
</div>
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html style="height: 100%; width: 100%">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>%%TITLE%%</title>
|
||||
</head>
|
||||
<body style="background-color: lightgray; display: table; font-family: 'Segoe UI'; height: 98%; text-align: center; width: 99%">
|
||||
<div style="display: table-cell; vertical-align: middle">
|
||||
<p style="font-weight: bold">%%MESSAGE%%</p>
|
||||
<button onclick="window.history.back()" style="cursor: pointer">⭠ %%BACK_BUTTON%%</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 });
|
|
@ -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);
|
||||
}
|
|
@ -1,22 +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.UserInterface.Contracts.FileSystemDialog;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal class DialogRequestedEventArgs
|
||||
{
|
||||
internal FileSystemElement Element { get; set; }
|
||||
internal string InitialPath { get; set; }
|
||||
internal FileSystemOperation Operation { get; set; }
|
||||
internal string FullPath { get; set; }
|
||||
internal bool Success { get; set; }
|
||||
internal string Title { get; set; }
|
||||
}
|
||||
}
|
|
@ -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 DialogRequestedEventHandler(DialogRequestedEventArgs args);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -1,14 +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.UserInterface.Contracts.Browser.Data;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void DownloadUpdatedEventHandler(DownloadItemState state);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -10,6 +10,6 @@ namespace SafeExamBrowser.Browser.Events
|
|||
{
|
||||
internal class PopupRequestedEventArgs
|
||||
{
|
||||
public BrowserWindow Window { get; set; }
|
||||
public string Url { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
|
|
@ -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 ProgressChangedEventHandler(double value);
|
||||
}
|
|
@ -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 ResetRequestedEventHandler();
|
||||
}
|
|
@ -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 UrlEventHandler(string url);
|
||||
}
|
|
@ -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 WindowClosedEventHandler(int id);
|
||||
}
|
|
@ -1,66 +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 SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Filters
|
||||
{
|
||||
internal class RequestFilter : IRequestFilter
|
||||
{
|
||||
private IList<IRule> allowRules;
|
||||
private IList<IRule> blockRules;
|
||||
|
||||
public FilterResult Default { get; set; }
|
||||
|
||||
internal RequestFilter()
|
||||
{
|
||||
allowRules = new List<IRule>();
|
||||
blockRules = new List<IRule>();
|
||||
Default = FilterResult.Block;
|
||||
}
|
||||
|
||||
public void Load(IRule rule)
|
||||
{
|
||||
switch (rule.Result)
|
||||
{
|
||||
case FilterResult.Allow:
|
||||
allowRules.Add(rule);
|
||||
break;
|
||||
case FilterResult.Block:
|
||||
blockRules.Add(rule);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Filter processing for result '{rule.Result}' is not yet implemented!");
|
||||
}
|
||||
}
|
||||
|
||||
public FilterResult Process(Request request)
|
||||
{
|
||||
foreach (var rule in blockRules)
|
||||
{
|
||||
if (rule.IsMatch(request))
|
||||
{
|
||||
return FilterResult.Block;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var rule in allowRules)
|
||||
{
|
||||
if (rule.IsMatch(request))
|
||||
{
|
||||
return FilterResult.Allow;
|
||||
}
|
||||
}
|
||||
|
||||
return Default;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +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 SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Browser.Filters.Rules;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Filters
|
||||
{
|
||||
internal class RuleFactory : IRuleFactory
|
||||
{
|
||||
public IRule CreateRule(FilterRuleType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case FilterRuleType.Regex:
|
||||
return new RegexRule();
|
||||
case FilterRuleType.Simplified:
|
||||
return new SimplifiedRule();
|
||||
default:
|
||||
throw new NotImplementedException($"Filter rule of type '{type}' is not yet implemented!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,52 +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.Text.RegularExpressions;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Filters.Rules
|
||||
{
|
||||
internal class RegexRule : IRule
|
||||
{
|
||||
private string expression;
|
||||
|
||||
public FilterResult Result { get; private set; }
|
||||
|
||||
public void Initialize(FilterRuleSettings settings)
|
||||
{
|
||||
ValidateExpression(settings.Expression);
|
||||
|
||||
expression = settings.Expression;
|
||||
Result = settings.Result;
|
||||
}
|
||||
|
||||
public bool IsMatch(Request request)
|
||||
{
|
||||
return Regex.IsMatch(request.Url, expression, RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
private void ValidateExpression(string expression)
|
||||
{
|
||||
if (expression == default(string))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(expression));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Regex.Match("", expression);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ArgumentException($"Invalid regular expression!", nameof(expression), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,196 +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.Text.RegularExpressions;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Filters.Rules
|
||||
{
|
||||
internal class SimplifiedRule : IRule
|
||||
{
|
||||
private const string URL_DELIMITER_PATTERN = @"(?:([^\:]*)\://)?(?:([^\:\@]*)(?:\:([^\@]*))?\@)?(?:([^/\:\?#]*))?(?:\:([0-9\*]*))?([^\?#]*)?(?:\?([^#]*))?(?:#(.*))?";
|
||||
|
||||
private Regex fragment;
|
||||
private Regex host;
|
||||
private Regex path;
|
||||
private int? port;
|
||||
private Regex query;
|
||||
private Regex scheme;
|
||||
private Regex userInfo;
|
||||
|
||||
public FilterResult Result { get; private set; }
|
||||
|
||||
public void Initialize(FilterRuleSettings settings)
|
||||
{
|
||||
ValidateExpression(settings.Expression);
|
||||
ParseExpression(settings.Expression);
|
||||
|
||||
Result = settings.Result;
|
||||
}
|
||||
|
||||
public bool IsMatch(Request request)
|
||||
{
|
||||
var url = new Uri(request.Url, UriKind.Absolute);
|
||||
var isMatch = true;
|
||||
|
||||
isMatch &= scheme == default(Regex) || scheme.IsMatch(url.Scheme);
|
||||
isMatch &= userInfo == default(Regex) || userInfo.IsMatch(url.UserInfo);
|
||||
isMatch &= host.IsMatch(url.Host);
|
||||
isMatch &= !port.HasValue || port == url.Port;
|
||||
isMatch &= path == default(Regex) || path.IsMatch(url.AbsolutePath);
|
||||
isMatch &= query == default(Regex) || query.IsMatch(url.Query);
|
||||
isMatch &= fragment == default(Regex) || fragment.IsMatch(url.Fragment);
|
||||
|
||||
return isMatch;
|
||||
}
|
||||
|
||||
private void ParseExpression(string expression)
|
||||
{
|
||||
var match = Regex.Match(expression, URL_DELIMITER_PATTERN);
|
||||
|
||||
ParseScheme(match.Groups[1].Value);
|
||||
ParseUserInfo(match.Groups[2].Value, match.Groups[3].Value);
|
||||
ParseHost(match.Groups[4].Value);
|
||||
ParsePort(match.Groups[5].Value);
|
||||
ParsePath(match.Groups[6].Value);
|
||||
ParseQuery(match.Groups[7].Value);
|
||||
ParseFragment(match.Groups[8].Value);
|
||||
}
|
||||
|
||||
private void ParseScheme(string expression)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(expression))
|
||||
{
|
||||
expression = Regex.Escape(expression);
|
||||
expression = ReplaceWildcard(expression);
|
||||
|
||||
scheme = Build(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseUserInfo(string username, string password)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(username))
|
||||
{
|
||||
var expression = default(string);
|
||||
|
||||
username = Regex.Escape(username);
|
||||
password = Regex.Escape(password);
|
||||
|
||||
expression = string.IsNullOrEmpty(password) ? $@"{username}(:.*)?" : $@"{username}:{password}";
|
||||
expression = ReplaceWildcard(expression);
|
||||
|
||||
userInfo = Build(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseHost(string expression)
|
||||
{
|
||||
var isAlphanumeric = Regex.IsMatch(expression, @"^[a-zA-Z0-9]+$");
|
||||
var matchExactSubdomain = expression.StartsWith(".");
|
||||
|
||||
expression = matchExactSubdomain ? expression.Substring(1) : expression;
|
||||
expression = Regex.Escape(expression);
|
||||
expression = ReplaceWildcard(expression);
|
||||
|
||||
if (!isAlphanumeric && !matchExactSubdomain)
|
||||
{
|
||||
expression = $@"(.+?\.)*{expression}";
|
||||
}
|
||||
|
||||
host = Build(expression);
|
||||
}
|
||||
|
||||
private void ParsePort(string expression)
|
||||
{
|
||||
if (int.TryParse(expression, out var port))
|
||||
{
|
||||
this.port = port;
|
||||
}
|
||||
}
|
||||
|
||||
private void ParsePath(string expression)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(expression) && !expression.Equals("/"))
|
||||
{
|
||||
expression = Regex.Escape(expression);
|
||||
expression = ReplaceWildcard(expression);
|
||||
expression = expression.EndsWith("/") ? $@"{expression}?" : $@"{expression}/?";
|
||||
|
||||
path = Build(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseQuery(string expression)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(expression))
|
||||
{
|
||||
var noQueryAllowed = expression == ".";
|
||||
|
||||
if (noQueryAllowed)
|
||||
{
|
||||
expression = @"\??";
|
||||
}
|
||||
else
|
||||
{
|
||||
expression = Regex.Escape(expression);
|
||||
expression = ReplaceWildcard(expression);
|
||||
expression = $@"\??{expression}";
|
||||
}
|
||||
|
||||
query = Build(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseFragment(string expression)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(expression))
|
||||
{
|
||||
expression = Regex.Escape(expression);
|
||||
expression = ReplaceWildcard(expression);
|
||||
expression = $"#?{expression}";
|
||||
|
||||
fragment = Build(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private Regex Build(string expression)
|
||||
{
|
||||
return new Regex($"^{expression}$", RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
private string ReplaceWildcard(string expression)
|
||||
{
|
||||
return expression.Replace(@"\*", ".*");
|
||||
}
|
||||
|
||||
private void ValidateExpression(string expression)
|
||||
{
|
||||
if (expression == default(string))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(expression));
|
||||
}
|
||||
|
||||
if (!Regex.IsMatch(expression, @"[a-zA-Z0-9\*]+"))
|
||||
{
|
||||
throw new ArgumentException("Expression must consist of at least one alphanumeric character or asterisk!", nameof(expression));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Regex.Match(expression, URL_DELIMITER_PATTERN);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ArgumentException("Expression is not a valid simplified filter expression!", nameof(expression), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -10,6 +10,9 @@ using CefSharp;
|
|||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
/// <remarks>
|
||||
/// See https://cefsharp.github.io/api/73.1.x/html/T_CefSharp_IContextMenuHandler.htm.
|
||||
/// </remarks>
|
||||
internal class ContextMenuHandler : IContextMenuHandler
|
||||
{
|
||||
public void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model)
|
||||
|
|
|
@ -1,51 +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.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Browser.Wrapper;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class DialogHandler : IDialogHandler
|
||||
{
|
||||
internal event DialogRequestedEventHandler DialogRequested;
|
||||
|
||||
public bool OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, IFileDialogCallback callback)
|
||||
{
|
||||
var args = new DialogRequestedEventArgs
|
||||
{
|
||||
Element = mode.ToElement(),
|
||||
InitialPath = defaultFilePath,
|
||||
Operation = mode.ToOperation(),
|
||||
Title = title
|
||||
};
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
DialogRequested?.Invoke(args);
|
||||
|
||||
using (callback)
|
||||
{
|
||||
if (args.Success)
|
||||
{
|
||||
callback.Continue(new List<string> { args.FullPath });
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.Cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CefSharp;
|
||||
using CefSharp.Enums;
|
||||
using CefSharp.Structs;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Contracts.Browser;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
/// <remarks>
|
||||
/// See https://cefsharp.github.io/api/73.1.x/html/T_CefSharp_IDisplayHandler.htm.
|
||||
/// </remarks>
|
||||
internal class DisplayHandler : IDisplayHandler
|
||||
{
|
||||
public event FaviconChangedEventHandler FaviconChanged;
|
||||
|
@ -35,11 +37,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())
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -11,181 +11,80 @@ using System.Collections.Concurrent;
|
|||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
|
||||
using Syroot.Windows.IO;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
using SafeExamBrowser.Contracts.Browser;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.BrowserSettings;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
/// <remarks>
|
||||
/// See https://cefsharp.github.io/api/73.1.x/html/T_CefSharp_IDownloadHandler.htm.
|
||||
/// </remarks>
|
||||
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 ConcurrentDictionary<int, DownloadFinishedCallback> callbacks;
|
||||
private ILogger logger;
|
||||
|
||||
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
internal event DownloadAbortedEventHandler DownloadAborted;
|
||||
internal event DownloadUpdatedEventHandler DownloadUpdated;
|
||||
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
|
||||
internal DownloadHandler(AppConfig appConfig, ILogger logger, BrowserSettings settings, WindowSettings windowSettings)
|
||||
public DownloadHandler(AppConfig appConfig, BrowserSettings settings, ILogger logger)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.callbacks = new ConcurrentDictionary<int, DownloadFinishedCallback>();
|
||||
this.downloads = new ConcurrentDictionary<int, Guid>();
|
||||
this.logger = logger;
|
||||
this.settings = settings;
|
||||
this.windowSettings = windowSettings;
|
||||
}
|
||||
|
||||
public bool CanDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, string url, string requestMethod)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnBeforeDownload(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback)
|
||||
{
|
||||
var fileExtension = Path.GetExtension(downloadItem.SuggestedFileName);
|
||||
var isConfigurationFile = false;
|
||||
var url = downloadItem.Url;
|
||||
var urlExtension = default(string);
|
||||
var uri = new Uri(downloadItem.Url);
|
||||
var extension = Path.GetExtension(uri.AbsolutePath);
|
||||
var isConfigFile = String.Equals(extension, appConfig.ConfigurationFileExtension, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (downloadItem.Url.StartsWith("data:"))
|
||||
{
|
||||
url = downloadItem.Url.Length <= 100 ? downloadItem.Url : downloadItem.Url.Substring(0, 100) + "...";
|
||||
}
|
||||
logger.Debug($"Handling download request for '{uri}'.");
|
||||
|
||||
if (Uri.TryCreate(downloadItem.Url, UriKind.RelativeOrAbsolute, out var uri))
|
||||
{
|
||||
urlExtension = Path.GetExtension(uri.AbsolutePath);
|
||||
}
|
||||
|
||||
isConfigurationFile |= string.Equals(appConfig.ConfigurationFileExtension, fileExtension, StringComparison.OrdinalIgnoreCase);
|
||||
isConfigurationFile |= string.Equals(appConfig.ConfigurationFileExtension, urlExtension, StringComparison.OrdinalIgnoreCase);
|
||||
isConfigurationFile |= string.Equals(appConfig.ConfigurationFileMimeType, downloadItem.MimeType, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
logger.Debug($"Detected download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")}.");
|
||||
|
||||
if (isConfigurationFile)
|
||||
if (isConfigFile)
|
||||
{
|
||||
Task.Run(() => RequestConfigurationFileDownload(downloadItem, callback));
|
||||
}
|
||||
else if (settings.AllowDownloads)
|
||||
{
|
||||
Task.Run(() => HandleFileDownload(downloadItem, callback));
|
||||
logger.Debug($"Starting download of '{uri}'...");
|
||||
|
||||
using (callback)
|
||||
{
|
||||
callback.Continue(null, true);
|
||||
}
|
||||
}
|
||||
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 for '{uri}', as downloading is not allowed.");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDownloadUpdated(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback)
|
||||
{
|
||||
var hasId = downloads.TryGetValue(downloadItem.Id, out var id);
|
||||
|
||||
if (hasId)
|
||||
{
|
||||
var state = new DownloadItemState(id)
|
||||
{
|
||||
Completion = downloadItem.PercentComplete / 100.0,
|
||||
FullPath = downloadItem.FullPath,
|
||||
IsCancelled = downloadItem.IsCancelled,
|
||||
IsComplete = downloadItem.IsComplete,
|
||||
Url = downloadItem.Url
|
||||
};
|
||||
|
||||
Task.Run(() => DownloadUpdated?.Invoke(state));
|
||||
}
|
||||
|
||||
if (downloadItem.IsComplete || downloadItem.IsCancelled)
|
||||
{
|
||||
logger.Debug($"Download of '{downloadItem.FullPath}' {(downloadItem.IsComplete ? "is complete" : "was cancelled")}.");
|
||||
|
||||
if (callbacks.TryRemove(downloadItem.Id, out var finished) && finished != null)
|
||||
if (callbacks.TryRemove(downloadItem.Id, out DownloadFinishedCallback finished) && finished != null)
|
||||
{
|
||||
Task.Run(() => finished.Invoke(downloadItem.IsComplete, downloadItem.Url, downloadItem.FullPath));
|
||||
Task.Run(() => finished.Invoke(downloadItem.IsComplete, downloadItem.FullPath));
|
||||
}
|
||||
|
||||
if (hasId)
|
||||
{
|
||||
downloads.TryRemove(downloadItem.Id, out _);
|
||||
}
|
||||
logger.Debug($"Download of '{downloadItem.Url}' {(downloadItem.IsComplete ? "is complete" : "was cancelled")}.");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleFileDownload(DownloadItem downloadItem, IBeforeDownloadCallback callback)
|
||||
{
|
||||
var filePath = default(string);
|
||||
var showDialog = settings.AllowCustomDownAndUploadLocation;
|
||||
|
||||
logger.Debug($"Handling download of file '{downloadItem.SuggestedFileName}'.");
|
||||
|
||||
if (!string.IsNullOrEmpty(settings.DownAndUploadDirectory))
|
||||
{
|
||||
filePath = Path.Combine(Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory), downloadItem.SuggestedFileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
filePath = Path.Combine(KnownFolders.Downloads.ExpandedPath, downloadItem.SuggestedFileName);
|
||||
}
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
filePath = AppendIndexSuffixTo(filePath);
|
||||
}
|
||||
|
||||
if (showDialog)
|
||||
{
|
||||
logger.Debug($"Allowing user to select custom download location, with '{filePath}' as suggestion.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Automatically downloading file as '{filePath}'.");
|
||||
}
|
||||
|
||||
downloads[downloadItem.Id] = Guid.NewGuid();
|
||||
|
||||
using (callback)
|
||||
{
|
||||
callback.Continue(filePath, showDialog);
|
||||
}
|
||||
}
|
||||
|
||||
private string AppendIndexSuffixTo(string filePath)
|
||||
{
|
||||
var directory = Path.GetDirectoryName(filePath);
|
||||
var extension = Path.GetExtension(filePath);
|
||||
var name = Path.GetFileNameWithoutExtension(filePath);
|
||||
var path = default(string);
|
||||
|
||||
for (var suffix = 1; suffix < int.MaxValue; suffix++)
|
||||
{
|
||||
path = Path.Combine(directory, $"{name}({suffix}){extension}");
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private void RequestConfigurationFileDownload(DownloadItem downloadItem, IBeforeDownloadCallback callback)
|
||||
{
|
||||
var args = new DownloadEventArgs { Url = downloadItem.Url };
|
||||
var args = new DownloadEventArgs();
|
||||
|
||||
logger.Debug($"Handling download of configuration file '{downloadItem.SuggestedFileName}'.");
|
||||
logger.Debug($"Detected download request for configuration file '{downloadItem.Url}'.");
|
||||
ConfigurationDownloadRequested?.Invoke(downloadItem.SuggestedFileName, args);
|
||||
logger.Debug($"Download of configuration file '{downloadItem.Url}' was {(args.AllowDownload ? "granted" : "denied")}.");
|
||||
|
||||
if (args.AllowDownload)
|
||||
{
|
||||
|
@ -194,17 +93,11 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
callbacks[downloadItem.Id] = args.Callback;
|
||||
}
|
||||
|
||||
logger.Debug($"Starting download of configuration file '{downloadItem.SuggestedFileName}'...");
|
||||
|
||||
using (callback)
|
||||
{
|
||||
callback.Continue(args.DownloadPath, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Download of configuration file '{downloadItem.SuggestedFileName}' was cancelled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -8,68 +8,40 @@
|
|||
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
using SafeExamBrowser.Contracts.UserInterface;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
/// <remarks>
|
||||
/// See https://cefsharp.github.io/api/73.1.x/html/T_CefSharp_IKeyboardHandler.htm.
|
||||
/// </remarks>
|
||||
internal class KeyboardHandler : IKeyboardHandler
|
||||
{
|
||||
internal event ActionRequestedEventHandler FindRequested;
|
||||
internal event ActionRequestedEventHandler HomeNavigationRequested;
|
||||
internal event ActionRequestedEventHandler ReloadRequested;
|
||||
internal event ActionRequestedEventHandler ZoomInRequested;
|
||||
internal event ActionRequestedEventHandler ZoomOutRequested;
|
||||
internal event ActionRequestedEventHandler ZoomResetRequested;
|
||||
internal event ActionRequestedEventHandler FocusAddressBarRequested;
|
||||
internal event TabPressedEventHandler TabPressed;
|
||||
|
||||
private int? currentKeyDown = null;
|
||||
public event ActionRequestedEventHandler ReloadRequested;
|
||||
public event ActionRequestedEventHandler ZoomInRequested;
|
||||
public event ActionRequestedEventHandler ZoomOutRequested;
|
||||
public event ActionRequestedEventHandler ZoomResetRequested;
|
||||
|
||||
public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int keyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
|
||||
{
|
||||
var ctrl = modifiers.HasFlag(CefEventFlags.ControlDown);
|
||||
var shift = modifiers.HasFlag(CefEventFlags.ShiftDown);
|
||||
|
||||
if (type == KeyType.KeyUp)
|
||||
if (type == KeyType.KeyUp && ((keyCode == (int)Keys.Add && ctrl) || (keyCode == (int)Keys.D1 && ctrl && shift)))
|
||||
{
|
||||
if (ctrl && keyCode == (int) Keys.F)
|
||||
{
|
||||
FindRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (keyCode == (int) Keys.Home)
|
||||
{
|
||||
HomeNavigationRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (ctrl && keyCode == (int) Keys.L)
|
||||
{
|
||||
FocusAddressBarRequested?.Invoke();
|
||||
}
|
||||
|
||||
if ((ctrl && keyCode == (int) Keys.Add) || (ctrl && keyCode == (int) Keys.Oemplus) || (ctrl && shift && keyCode == (int) Keys.D1))
|
||||
{
|
||||
ZoomInRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (ctrl && (keyCode == (int) Keys.Subtract || keyCode == (int) Keys.OemMinus))
|
||||
{
|
||||
ZoomOutRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (ctrl && (keyCode == (int) Keys.D0 || keyCode == (int) Keys.NumPad0))
|
||||
{
|
||||
ZoomResetRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (keyCode == (int) Keys.Tab && keyCode == currentKeyDown)
|
||||
{
|
||||
TabPressed?.Invoke(shift);
|
||||
}
|
||||
ZoomInRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (type == KeyType.KeyUp && (keyCode == (int) Keys.Subtract || keyCode == (int) Keys.OemMinus) && ctrl)
|
||||
{
|
||||
ZoomOutRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (type == KeyType.KeyUp && (keyCode == (int) Keys.D0 || keyCode == (int) Keys.NumPad0) && ctrl)
|
||||
{
|
||||
ZoomResetRequested?.Invoke();
|
||||
}
|
||||
|
||||
currentKeyDown = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -82,11 +54,6 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
return true;
|
||||
}
|
||||
|
||||
if (type == KeyType.RawKeyDown || type == KeyType.KeyDown)
|
||||
{
|
||||
currentKeyDown = keyCode;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
44
SafeExamBrowser.Browser/Handlers/LifeSpanHandler.cs
Normal file
44
SafeExamBrowser.Browser/Handlers/LifeSpanHandler.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
/// <remarks>
|
||||
/// See https://cefsharp.github.io/api/73.1.x/html/T_CefSharp_ILifeSpanHandler.htm.
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,80 +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 CefSharp;
|
||||
using SafeExamBrowser.Browser.Content;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class RenderProcessMessageHandler : IRenderProcessMessageHandler
|
||||
{
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly Clipboard clipboard;
|
||||
private readonly ContentLoader contentLoader;
|
||||
private readonly IKeyGenerator keyGenerator;
|
||||
private readonly BrowserSettings settings;
|
||||
private readonly IText text;
|
||||
|
||||
internal RenderProcessMessageHandler(AppConfig appConfig, Clipboard clipboard, IKeyGenerator keyGenerator, BrowserSettings settings, IText text)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.clipboard = clipboard;
|
||||
this.contentLoader = new ContentLoader(text);
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public void OnContextCreated(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
var browserExamKey = keyGenerator.CalculateBrowserExamKeyHash(settings.ConfigurationKey, settings.BrowserExamKeySalt, frame.Url);
|
||||
var configurationKey = keyGenerator.CalculateConfigurationKeyHash(settings.ConfigurationKey, frame.Url);
|
||||
var api = contentLoader.LoadApi(browserExamKey, configurationKey, appConfig.ProgramBuildVersion);
|
||||
var clipboardScript = contentLoader.LoadClipboard();
|
||||
var pageZoomScript = contentLoader.LoadPageZoom();
|
||||
|
||||
frame.ExecuteJavaScriptAsync(api);
|
||||
|
||||
if (!settings.AllowPageZoom)
|
||||
{
|
||||
frame.ExecuteJavaScriptAsync(pageZoomScript);
|
||||
}
|
||||
|
||||
if (!settings.AllowPrint)
|
||||
{
|
||||
frame.ExecuteJavaScriptAsync($"window.print = function() {{ alert('{text.Get(TextKey.Browser_PrintNotAllowed)}') }}");
|
||||
}
|
||||
|
||||
if (settings.UseIsolatedClipboard)
|
||||
{
|
||||
frame.ExecuteJavaScriptAsync(clipboardScript);
|
||||
|
||||
if (clipboard.Content != default)
|
||||
{
|
||||
frame.ExecuteJavaScriptAsync($"SafeExamBrowser.clipboard.update('', '{clipboard.Content}');");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnContextReleased(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnFocusedNodeChanged(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IDomNode node)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnUncaughtException(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, JavascriptException exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -7,214 +7,71 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Specialized;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
using Request = SafeExamBrowser.Browser.Contracts.Filters.Request;
|
||||
using CefSharp.Handler;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class RequestHandler : CefSharp.Handler.RequestHandler
|
||||
/// <remarks>
|
||||
/// See https://cefsharp.github.io/api/71.0.0/html/T_CefSharp_Handler_DefaultRequestHandler.htm.
|
||||
/// </remarks>
|
||||
internal class RequestHandler : DefaultRequestHandler
|
||||
{
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly IRequestFilter filter;
|
||||
private readonly ILogger logger;
|
||||
private readonly ResourceHandler resourceHandler;
|
||||
private readonly WindowSettings windowSettings;
|
||||
private readonly BrowserSettings settings;
|
||||
private AppConfig appConfig;
|
||||
|
||||
private string quitUrlPattern;
|
||||
|
||||
internal event UrlEventHandler QuitUrlVisited;
|
||||
internal event UrlEventHandler RequestBlocked;
|
||||
|
||||
internal RequestHandler(
|
||||
AppConfig appConfig,
|
||||
IRequestFilter filter,
|
||||
ILogger logger,
|
||||
ResourceHandler resourceHandler,
|
||||
BrowserSettings settings,
|
||||
WindowSettings windowSettings)
|
||||
internal RequestHandler(AppConfig appConfig)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.filter = filter;
|
||||
this.logger = logger;
|
||||
this.resourceHandler = resourceHandler;
|
||||
this.settings = settings;
|
||||
this.windowSettings = windowSettings;
|
||||
}
|
||||
|
||||
protected override bool GetAuthCredentials(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
|
||||
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
|
||||
{
|
||||
if (isProxy)
|
||||
{
|
||||
foreach (var proxy in settings.Proxy.Proxies)
|
||||
{
|
||||
if (proxy.RequiresAuthentication && host?.Equals(proxy.Host, StringComparison.OrdinalIgnoreCase) == true && port == proxy.Port)
|
||||
{
|
||||
callback.Continue(proxy.Username, proxy.Password);
|
||||
// TODO: CEF does not yet support intercepting requests from service workers, thus the user agent must be statically set at browser
|
||||
// startup for now. Once CEF has full support of service workers, the static user agent should be removed and the method below
|
||||
// reactivated. See https://bitbucket.org/chromiumembedded/cef/issues/2622 for the current status of development.
|
||||
// AppendCustomUserAgent(request);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (IsMailtoUrl(request.Url))
|
||||
{
|
||||
return CefReturnValue.Cancel;
|
||||
}
|
||||
|
||||
return base.GetAuthCredentials(webBrowser, browser, originUrl, isProxy, host, port, realm, scheme, callback);
|
||||
ReplaceCustomScheme(request);
|
||||
|
||||
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
||||
}
|
||||
|
||||
protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
|
||||
private void AppendCustomUserAgent(IRequest request)
|
||||
{
|
||||
return resourceHandler;
|
||||
var headers = new NameValueCollection(request.Headers);
|
||||
var userAgent = request.Headers["User-Agent"];
|
||||
|
||||
headers["User-Agent"] = $"{userAgent} SEB/{appConfig.ProgramVersion}";
|
||||
request.Headers = headers;
|
||||
}
|
||||
|
||||
protected override bool OnBeforeBrowse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
|
||||
private bool IsMailtoUrl(string url)
|
||||
{
|
||||
if (IsQuitUrl(request))
|
||||
{
|
||||
QuitUrlVisited?.Invoke(request.Url);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Block(request))
|
||||
{
|
||||
if (request.ResourceType == ResourceType.MainFrame)
|
||||
{
|
||||
RequestBlocked?.Invoke(request.Url);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsConfigurationFile(request, out var downloadUrl))
|
||||
{
|
||||
browser.GetHost().StartDownload(downloadUrl);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnBeforeBrowse(webBrowser, browser, frame, request, userGesture, isRedirect);
|
||||
return url.StartsWith(Uri.UriSchemeMailto);
|
||||
}
|
||||
|
||||
protected override bool OnOpenUrlFromTab(IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
|
||||
private void ReplaceCustomScheme(IRequest request)
|
||||
{
|
||||
switch (targetDisposition)
|
||||
if (Uri.IsWellFormedUriString(request.Url, UriKind.RelativeOrAbsolute))
|
||||
{
|
||||
case WindowOpenDisposition.NewBackgroundTab:
|
||||
case WindowOpenDisposition.NewPopup:
|
||||
case WindowOpenDisposition.NewWindow:
|
||||
case WindowOpenDisposition.SaveToDisk:
|
||||
return true;
|
||||
default:
|
||||
return base.OnOpenUrlFromTab(webBrowser, browser, frame, targetUrl, targetDisposition, userGesture);
|
||||
}
|
||||
}
|
||||
var uri = new Uri(request.Url);
|
||||
|
||||
private bool IsConfigurationFile(IRequest request, out string downloadUrl)
|
||||
{
|
||||
var isValidUri = Uri.TryCreate(request.Url, UriKind.RelativeOrAbsolute, out var uri);
|
||||
var hasFileExtension = string.Equals(appConfig.ConfigurationFileExtension, Path.GetExtension(uri.AbsolutePath), StringComparison.OrdinalIgnoreCase);
|
||||
var isDataUri = request.Url.Contains(appConfig.ConfigurationFileMimeType);
|
||||
var isConfigurationFile = isValidUri && (hasFileExtension || isDataUri);
|
||||
|
||||
downloadUrl = request.Url;
|
||||
|
||||
if (isConfigurationFile)
|
||||
{
|
||||
if (isDataUri)
|
||||
if (uri.Scheme == appConfig.SebUriScheme)
|
||||
{
|
||||
if (uri.Scheme == appConfig.SebUriScheme)
|
||||
{
|
||||
downloadUrl = request.Url.Replace($"{appConfig.SebUriScheme}{Uri.SchemeDelimiter}", "data:");
|
||||
}
|
||||
else if (uri.Scheme == appConfig.SebUriSchemeSecure)
|
||||
{
|
||||
downloadUrl = request.Url.Replace($"{appConfig.SebUriSchemeSecure}{Uri.SchemeDelimiter}", "data:");
|
||||
}
|
||||
request.Url = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri.AbsoluteUri;
|
||||
}
|
||||
else
|
||||
else if (uri.Scheme == appConfig.SebUriSchemeSecure)
|
||||
{
|
||||
if (uri.Scheme == appConfig.SebUriScheme)
|
||||
{
|
||||
downloadUrl = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri.AbsoluteUri;
|
||||
}
|
||||
else if (uri.Scheme == appConfig.SebUriSchemeSecure)
|
||||
{
|
||||
downloadUrl = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttps }.Uri.AbsoluteUri;
|
||||
}
|
||||
}
|
||||
|
||||
logger.Debug($"Detected configuration file {(windowSettings.UrlPolicy.CanLog() ? $"'{uri}'" : "")}.");
|
||||
}
|
||||
|
||||
return isConfigurationFile;
|
||||
}
|
||||
|
||||
private bool IsQuitUrl(IRequest request)
|
||||
{
|
||||
var isQuitUrl = false;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(settings.QuitUrl))
|
||||
{
|
||||
if (quitUrlPattern == default)
|
||||
{
|
||||
quitUrlPattern = $"^{Regex.Escape(settings.QuitUrl.TrimEnd('/'))}/?$";
|
||||
}
|
||||
|
||||
isQuitUrl = Regex.IsMatch(request.Url, quitUrlPattern, RegexOptions.IgnoreCase);
|
||||
|
||||
if (isQuitUrl)
|
||||
{
|
||||
logger.Debug($"Detected quit URL{(windowSettings.UrlPolicy.CanLog() ? $"'{request.Url}'" : "")}.");
|
||||
request.Url = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttps }.Uri.AbsoluteUri;
|
||||
}
|
||||
}
|
||||
|
||||
return isQuitUrl;
|
||||
}
|
||||
|
||||
private bool Block(IRequest request)
|
||||
{
|
||||
var block = false;
|
||||
var url = WebUtility.UrlDecode(request.Url);
|
||||
var isValidUrl = Uri.TryCreate(url, UriKind.Absolute, out _);
|
||||
|
||||
if (settings.Filter.ProcessMainRequests && request.ResourceType == ResourceType.MainFrame && isValidUrl)
|
||||
{
|
||||
var result = filter.Process(new Request { Url = url });
|
||||
|
||||
// We apparently can't filter chrome extension requests, as this prevents the rendering of PDFs.
|
||||
if (result == FilterResult.Block && !url.StartsWith("chrome-extension://"))
|
||||
{
|
||||
block = true;
|
||||
logger.Info($"Blocked main request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.Filter.ProcessContentRequests && request.ResourceType != ResourceType.MainFrame && isValidUrl)
|
||||
{
|
||||
var result = filter.Process(new Request { Url = url });
|
||||
|
||||
if (result == FilterResult.Block)
|
||||
{
|
||||
block = true;
|
||||
logger.Info($"Blocked content request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValidUrl)
|
||||
{
|
||||
logger.Warn($"Filter could not process request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType})!");
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,451 +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.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SafeExamBrowser.Browser.Content;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
using Request = SafeExamBrowser.Browser.Contracts.Filters.Request;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class ResourceHandler : CefSharp.Handler.ResourceRequestHandler
|
||||
{
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly ContentLoader contentLoader;
|
||||
private readonly IRequestFilter filter;
|
||||
private readonly IKeyGenerator keyGenerator;
|
||||
private readonly ILogger logger;
|
||||
private readonly SessionMode sessionMode;
|
||||
private readonly BrowserSettings settings;
|
||||
private readonly WindowSettings windowSettings;
|
||||
|
||||
private IResourceHandler contentHandler;
|
||||
private IResourceHandler pageHandler;
|
||||
private string userIdentifier;
|
||||
|
||||
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
|
||||
|
||||
internal ResourceHandler(
|
||||
AppConfig appConfig,
|
||||
IRequestFilter filter,
|
||||
IKeyGenerator keyGenerator,
|
||||
ILogger logger,
|
||||
SessionMode sessionMode,
|
||||
BrowserSettings settings,
|
||||
WindowSettings windowSettings,
|
||||
IText text)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.filter = filter;
|
||||
this.contentLoader = new ContentLoader(text);
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.logger = logger;
|
||||
this.sessionMode = sessionMode;
|
||||
this.settings = settings;
|
||||
this.windowSettings = windowSettings;
|
||||
}
|
||||
|
||||
protected override IResourceHandler GetResourceHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request)
|
||||
{
|
||||
if (Block(request))
|
||||
{
|
||||
return ResourceHandlerFor(request.ResourceType);
|
||||
}
|
||||
|
||||
return base.GetResourceHandler(webBrowser, browser, frame, request);
|
||||
}
|
||||
|
||||
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
|
||||
{
|
||||
if (IsMailtoUrl(request.Url))
|
||||
{
|
||||
return CefReturnValue.Cancel;
|
||||
}
|
||||
|
||||
AppendCustomHeaders(webBrowser, request);
|
||||
ReplaceSebScheme(request);
|
||||
|
||||
return base.OnBeforeResourceLoad(webBrowser, browser, frame, request, callback);
|
||||
}
|
||||
|
||||
protected override bool OnProtocolExecution(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnResourceRedirect(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl)
|
||||
{
|
||||
if (sessionMode == SessionMode.Server)
|
||||
{
|
||||
SearchUserIdentifier(request, response);
|
||||
}
|
||||
|
||||
base.OnResourceRedirect(chromiumWebBrowser, browser, frame, request, response, ref newUrl);
|
||||
}
|
||||
|
||||
protected override bool OnResourceResponse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response)
|
||||
{
|
||||
if (RedirectToDisablePdfReaderToolbar(request, response, out var url))
|
||||
{
|
||||
frame?.LoadUrl(url);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sessionMode == SessionMode.Server)
|
||||
{
|
||||
SearchUserIdentifier(request, response);
|
||||
}
|
||||
|
||||
return base.OnResourceResponse(webBrowser, browser, frame, request, response);
|
||||
}
|
||||
|
||||
private void AppendCustomHeaders(IWebBrowser webBrowser, IRequest request)
|
||||
{
|
||||
Uri.TryCreate(webBrowser.Address, UriKind.Absolute, out var pageUrl);
|
||||
Uri.TryCreate(request.Url, UriKind.Absolute, out var requestUrl);
|
||||
|
||||
if (request.ResourceType == ResourceType.MainFrame || pageUrl?.Host?.Equals(requestUrl?.Host) == true)
|
||||
{
|
||||
var headers = new NameValueCollection(request.Headers);
|
||||
|
||||
if (settings.SendConfigurationKey)
|
||||
{
|
||||
headers["X-SafeExamBrowser-ConfigKeyHash"] = keyGenerator.CalculateConfigurationKeyHash(settings.ConfigurationKey, request.Url);
|
||||
}
|
||||
|
||||
if (settings.SendBrowserExamKey)
|
||||
{
|
||||
headers["X-SafeExamBrowser-RequestHash"] = keyGenerator.CalculateBrowserExamKeyHash(settings.ConfigurationKey, settings.BrowserExamKeySalt, request.Url);
|
||||
}
|
||||
|
||||
request.Headers = headers;
|
||||
}
|
||||
}
|
||||
|
||||
private bool Block(IRequest request)
|
||||
{
|
||||
var block = false;
|
||||
var url = WebUtility.UrlDecode(request.Url);
|
||||
var isValidUri = Uri.TryCreate(url, UriKind.Absolute, out _);
|
||||
|
||||
if (settings.Filter.ProcessContentRequests && isValidUri)
|
||||
{
|
||||
var result = filter.Process(new Request { Url = url });
|
||||
|
||||
if (result == FilterResult.Block)
|
||||
{
|
||||
block = true;
|
||||
logger.Info($"Blocked content request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
|
||||
}
|
||||
}
|
||||
else if (!isValidUri)
|
||||
{
|
||||
logger.Warn($"Filter could not process request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType})!");
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
private bool IsMailtoUrl(string url)
|
||||
{
|
||||
return url.StartsWith(Uri.UriSchemeMailto);
|
||||
}
|
||||
|
||||
private bool RedirectToDisablePdfReaderToolbar(IRequest request, IResponse response, out string url)
|
||||
{
|
||||
const string DISABLE_PDF_READER_TOOLBAR = "#toolbar=0";
|
||||
|
||||
var isPdf = response.Headers["Content-Type"] == MediaTypeNames.Application.Pdf;
|
||||
var isMainFrame = request.ResourceType == ResourceType.MainFrame;
|
||||
var hasFragment = request.Url.Contains(DISABLE_PDF_READER_TOOLBAR);
|
||||
var redirect = settings.AllowPdfReader && !settings.AllowPdfReaderToolbar && isPdf && isMainFrame && !hasFragment;
|
||||
|
||||
url = request.Url + DISABLE_PDF_READER_TOOLBAR;
|
||||
|
||||
if (redirect)
|
||||
{
|
||||
logger.Info($"Redirecting{(windowSettings.UrlPolicy.CanLog() ? $" to '{url}'" : "")} to disable PDF reader toolbar.");
|
||||
}
|
||||
|
||||
return redirect;
|
||||
}
|
||||
|
||||
private void ReplaceSebScheme(IRequest request)
|
||||
{
|
||||
if (Uri.IsWellFormedUriString(request.Url, UriKind.RelativeOrAbsolute))
|
||||
{
|
||||
var uri = new Uri(request.Url);
|
||||
|
||||
if (uri.Scheme == appConfig.SebUriScheme)
|
||||
{
|
||||
request.Url = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri.AbsoluteUri;
|
||||
}
|
||||
else if (uri.Scheme == appConfig.SebUriSchemeSecure)
|
||||
{
|
||||
request.Url = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttps }.Uri.AbsoluteUri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IResourceHandler ResourceHandlerFor(ResourceType resourceType)
|
||||
{
|
||||
if (contentHandler == default(IResourceHandler))
|
||||
{
|
||||
contentHandler = CefSharp.ResourceHandler.FromString(contentLoader.LoadBlockedContent());
|
||||
}
|
||||
|
||||
if (pageHandler == default(IResourceHandler))
|
||||
{
|
||||
pageHandler = CefSharp.ResourceHandler.FromString(contentLoader.LoadBlockedPage());
|
||||
}
|
||||
|
||||
switch (resourceType)
|
||||
{
|
||||
case ResourceType.MainFrame:
|
||||
case ResourceType.SubFrame:
|
||||
return pageHandler;
|
||||
default:
|
||||
return contentHandler;
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchUserIdentifier(IRequest request, IResponse response)
|
||||
{
|
||||
var success = TrySearchGenericUserIdentifier(response);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
success = TrySearchEdxUserIdentifier(response);
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
TrySearchMoodleUserIdentifier(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TrySearchGenericUserIdentifier(IResponse response)
|
||||
{
|
||||
var ids = response.Headers.GetValues("X-LMS-USER-ID");
|
||||
var success = false;
|
||||
|
||||
if (ids != default(string[]))
|
||||
{
|
||||
var userId = ids.FirstOrDefault();
|
||||
|
||||
if (userId != default && userIdentifier != userId)
|
||||
{
|
||||
userIdentifier = userId;
|
||||
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||
logger.Info("Generic LMS user identifier detected.");
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TrySearchEdxUserIdentifier(IResponse response)
|
||||
{
|
||||
var cookies = response.Headers.GetValues("Set-Cookie");
|
||||
var success = false;
|
||||
|
||||
if (cookies != default(string[]))
|
||||
{
|
||||
try
|
||||
{
|
||||
var userInfo = cookies.FirstOrDefault(c => c.Contains("edx-user-info"));
|
||||
|
||||
if (userInfo != default)
|
||||
{
|
||||
var start = userInfo.IndexOf("=") + 1;
|
||||
var end = userInfo.IndexOf("; expires");
|
||||
var cookie = userInfo.Substring(start, end - start);
|
||||
var sanitized = cookie.Replace("\\\"", "\"").Replace("\\054", ",").Trim('"');
|
||||
var json = JsonConvert.DeserializeObject(sanitized) as JObject;
|
||||
var userName = json["username"].Value<string>();
|
||||
|
||||
if (userIdentifier != userName)
|
||||
{
|
||||
userIdentifier = userName;
|
||||
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||
logger.Info("EdX user identifier detected.");
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse edX user identifier!", e);
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TrySearchMoodleUserIdentifier(IRequest request, IResponse response)
|
||||
{
|
||||
var success = TrySearchMoodleUserIdentifierByLocation(response);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
success = TrySearchMoodleUserIdentifierByRequest(MoodleRequestType.Plugin, request, response);
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
success = TrySearchMoodleUserIdentifierByRequest(MoodleRequestType.Theme, request, response);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TrySearchMoodleUserIdentifierByLocation(IResponse response)
|
||||
{
|
||||
var locations = response.Headers.GetValues("Location");
|
||||
|
||||
if (locations != default(string[]))
|
||||
{
|
||||
try
|
||||
{
|
||||
var location = locations.FirstOrDefault(l => l.Contains("/login/index.php?testsession"));
|
||||
|
||||
if (location != default)
|
||||
{
|
||||
var userId = location.Substring(location.IndexOf("=") + 1);
|
||||
|
||||
if (userIdentifier != userId)
|
||||
{
|
||||
userIdentifier = userId;
|
||||
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||
logger.Info("Moodle user identifier detected by location.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to parse Moodle user identifier by location!", e);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TrySearchMoodleUserIdentifierByRequest(MoodleRequestType type, IRequest request, IResponse response)
|
||||
{
|
||||
var cookies = response.Headers.GetValues("Set-Cookie");
|
||||
var success = false;
|
||||
|
||||
if (cookies != default(string[]))
|
||||
{
|
||||
var session = cookies.FirstOrDefault(c => c.Contains("MoodleSession"));
|
||||
|
||||
if (session != default)
|
||||
{
|
||||
var userId = ExecuteMoodleUserIdentifierRequest(request.Url, session, type);
|
||||
|
||||
if (int.TryParse(userId, out var id) && id > 0 && userIdentifier != userId)
|
||||
{
|
||||
userIdentifier = userId;
|
||||
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
|
||||
logger.Info($"Moodle user identifier detected by request ({type}).");
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private string ExecuteMoodleUserIdentifierRequest(string requestUrl, string session, MoodleRequestType type)
|
||||
{
|
||||
var userId = default(string);
|
||||
|
||||
try
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var endpointUrl = default(string);
|
||||
var start = session.IndexOf("=") + 1;
|
||||
var end = session.IndexOf(";");
|
||||
var name = session.Substring(0, start - 1);
|
||||
var value = session.Substring(start, end - start);
|
||||
var uri = new Uri(requestUrl);
|
||||
|
||||
if (type == MoodleRequestType.Plugin)
|
||||
{
|
||||
endpointUrl = $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}/mod/quiz/accessrule/sebserver/classes/external/user.php";
|
||||
}
|
||||
else
|
||||
{
|
||||
endpointUrl = $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}/theme/boost_ethz/sebuser.php";
|
||||
}
|
||||
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, endpointUrl);
|
||||
|
||||
using (var handler = new HttpClientHandler { UseCookies = false })
|
||||
using (var client = new HttpClient(handler))
|
||||
{
|
||||
message.Headers.Add("Cookie", $"{name}={value}");
|
||||
|
||||
var result = await client.SendAsync(message);
|
||||
|
||||
if (result.IsSuccessStatusCode)
|
||||
{
|
||||
userId = await result.Content.ReadAsStringAsync();
|
||||
}
|
||||
else if (result.StatusCode != HttpStatusCode.NotFound)
|
||||
{
|
||||
logger.Error($"Failed to retrieve Moodle user identifier by request ({type})! Response: {(int) result.StatusCode} {result.ReasonPhrase}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to parse Moodle user identifier by request ({type})!", e);
|
||||
}
|
||||
}).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to execute Moodle user identifier request ({type})!", e);
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
|
||||
private enum MoodleRequestType
|
||||
{
|
||||
Plugin,
|
||||
Theme
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue