Compare commits
541 commits
Author | SHA1 | Date | |
---|---|---|---|
|
7029b12d81 | ||
|
9762019499 | ||
|
d5b182ae2f | ||
|
4c0f3cfa6c | ||
|
f096b96741 | ||
|
50ac28f9ea | ||
|
144c3ba752 | ||
|
21353e6d6d | ||
|
26f14f235d | ||
|
1ff7d84375 | ||
|
febfd944e0 | ||
|
a1bfaadcd9 | ||
|
0b1746a82e | ||
|
ede6a926cc | ||
|
d4f5f203db | ||
|
a350949b1b | ||
|
6a77a41564 | ||
|
1f50ab74c9 | ||
|
b48ef21708 | ||
|
f3a9030505 | ||
|
89091acaac | ||
|
04843d3fa8 | ||
|
a3c9271faf | ||
|
68d6d47fe6 | ||
|
b4366adab1 | ||
|
c23b78488c | ||
|
58c8e69716 | ||
|
0fb7f23bcb | ||
|
05f46cd6b4 | ||
|
f2798581a4 | ||
|
471e69d460 | ||
|
62dc690a52 | ||
|
a41b40d428 | ||
|
8cf214b39c | ||
|
04dce13d86 | ||
|
767ac84391 | ||
|
84bbcb82ef | ||
|
b3228aedef | ||
|
3b099688f7 | ||
|
639700abd8 | ||
|
473edc7a2e | ||
|
60ee95a9ee | ||
|
1edde7b6f5 | ||
|
4015e9a574 | ||
|
bbb5ec2571 | ||
|
8b3f9b0838 | ||
|
e4a82e2f63 | ||
|
01db8fd84e | ||
|
a397446252 | ||
|
e8ebd2840e | ||
|
d9662ec31e | ||
|
c2f61ea6ab | ||
|
7801d68b97 | ||
|
ff16743ae7 | ||
|
832eee17d5 | ||
|
2c39668667 | ||
|
577a23b8b4 | ||
|
514414e322 | ||
|
4b222df6c5 | ||
|
acb9b97854 | ||
|
8a47228881 | ||
|
ff33394565 | ||
|
e6e0cca292 | ||
|
3f90d3a58a | ||
|
956771c0e7 | ||
|
9bbcaa2a98 | ||
|
a81837faa0 | ||
|
c46d1a3ade | ||
|
a1d62dd3de | ||
|
9045b852d0 | ||
|
ff5b91c010 | ||
|
787c84cc0e | ||
|
0777644f0e | ||
|
e5c02a1f74 | ||
|
91f2c14a77 | ||
|
4aacf85e9a | ||
|
a021bebde6 | ||
|
f902ee9598 | ||
|
ea9d7e0de7 | ||
|
a213ec0f7d | ||
|
731a748552 | ||
|
cb81906945 | ||
|
456894edb9 | ||
|
acb6b8cf09 | ||
|
6b40b64590 | ||
|
70ba9ad7b6 | ||
|
b62a8bdfe3 | ||
|
654aa14bee | ||
|
1925231a19 | ||
|
55c36f6d7d | ||
|
de5691cb25 | ||
|
93cee788f8 | ||
|
73fefad434 | ||
|
23de2bf8c7 | ||
|
96f67c2085 | ||
|
c9db98159d | ||
|
c52c461dbf | ||
|
cecfe095a7 | ||
|
5f6a57cd24 | ||
|
1e9d37ac13 | ||
|
ef267ef186 | ||
|
ecc8416dfb | ||
|
eae6ab1bdf | ||
|
27155a057d | ||
|
79dedf12b5 | ||
|
8c45af88fb | ||
|
181346b810 | ||
|
04571f51b2 | ||
|
ebca114c2e | ||
|
98fb7a32db | ||
|
1aa32403a7 | ||
|
4e152c26f1 | ||
|
945b9223e7 | ||
|
622df39fca | ||
|
5ef03d4101 | ||
|
bc8235951d | ||
|
ae352a883c | ||
|
7f4aee9058 | ||
|
c535124575 | ||
|
54b4444f8e | ||
|
51c21f8934 | ||
|
f8b354623a | ||
|
5a58a11dd0 | ||
|
e9976ac158 | ||
|
fb70c9c928 | ||
|
09750cefd0 | ||
|
a286b615f4 | ||
|
e563767d6e | ||
|
6f175bf0e7 | ||
|
cb47c985e9 | ||
|
af5e33c2d8 | ||
|
afe8b4bcca | ||
|
c6a8996138 | ||
|
bfd1da3a86 | ||
|
8e17504d35 | ||
|
499629d848 | ||
|
25cb91899c | ||
|
00a562b3c1 | ||
|
a3d0ab433b | ||
|
751bfcb144 | ||
|
8c3d9a31d7 | ||
|
75016158c5 | ||
|
4ac982a3dd | ||
|
ca02b1d674 | ||
|
400b259af7 | ||
|
421b3db53e | ||
|
2ef7c2c5ec | ||
|
f7479cd1a8 | ||
|
c44bac79fd | ||
|
026d1fbfd8 | ||
|
3711555f70 | ||
|
bbfa720b21 | ||
|
722d84978c | ||
|
fa16710bdb | ||
|
d76dbf6b40 | ||
|
b36df9ad5a | ||
|
bd993ecc6b | ||
|
fcebf4b436 | ||
|
c498ef9af1 | ||
|
0769cf6b4b | ||
|
56732537f8 | ||
|
27f2fde904 | ||
|
44432ab023 | ||
|
eff0051469 | ||
|
bd3b348f6a | ||
|
9b0bfa291e | ||
|
5173bf3d6e | ||
|
c1307624d9 | ||
|
210a0419ca | ||
|
f2917f69a6 | ||
|
bc30e56e38 | ||
|
a21c9007ab | ||
|
689e388e23 | ||
|
3b8f552138 | ||
|
e99bdabc51 | ||
|
7fc31f6e90 | ||
|
940baae655 | ||
|
8543c81867 | ||
|
817f598d8a | ||
|
37e3950a6f | ||
|
3dd023b285 | ||
|
543ad7040b | ||
|
5284a52278 | ||
|
204db744aa | ||
|
fd55367a7d | ||
|
11b10e8e45 | ||
|
627c568400 | ||
|
9507888900 | ||
|
82908607e5 | ||
|
23dd94d23c | ||
|
3b8c63ab56 | ||
|
40a7f63524 | ||
|
16fa6a0473 | ||
|
1605f0d00e | ||
|
e3c26335a9 | ||
|
989d414152 | ||
|
e33a12e7ec | ||
|
557e8a6be4 | ||
|
ba128bb6ac | ||
|
e4e0f7c16b | ||
|
efb3c8056a | ||
|
c201389af4 | ||
|
538127661f | ||
|
250ddb5bc9 | ||
|
da609f62c8 | ||
|
bf00b3883b | ||
|
390019048e | ||
|
22ef7ef364 | ||
|
8f667d6efa | ||
|
1fec696909 | ||
|
bd145e14b0 | ||
|
c0f37b309b | ||
|
71b722d215 | ||
|
b5008f9163 | ||
|
eac87bfb87 | ||
|
dab86a31f5 | ||
|
65840f646e | ||
|
4aa856f98a | ||
|
cba73bd727 | ||
|
b2c2508812 | ||
|
e0581620a0 | ||
|
7594a082fa | ||
|
836389942f | ||
|
0cdffd891b | ||
|
669b51d5ff | ||
|
b69280731a | ||
|
cdb08798b8 | ||
|
8c687e69a8 | ||
|
7967087ee6 | ||
|
a5202e3a53 | ||
|
66bd6a2d90 | ||
|
0bb9f42a3a | ||
|
510f127063 | ||
|
296f87727d | ||
|
298e8f34f4 | ||
|
e743d4a564 | ||
|
3ca514e653 | ||
|
094ff4765b | ||
|
5c5a70ad73 | ||
|
1ad20567b8 | ||
|
31f5c75a90 | ||
|
765bfcb516 | ||
|
a0fb74a07e | ||
|
14ef0a2b2a | ||
|
900115d66c | ||
|
20a2c11927 | ||
|
0b9299f88f | ||
|
6c31ce0833 | ||
|
da458bcfb0 | ||
|
ae3755df84 | ||
|
718a4550e9 | ||
|
4d67be099a | ||
|
474d766926 | ||
|
f57771fda9 | ||
|
2aaa74c7b0 | ||
|
baad469be6 | ||
|
e2e5d5ade8 | ||
|
9b8d1fc3b2 | ||
|
f0aecb06d9 | ||
|
3bd786543d | ||
|
f948463685 | ||
|
ef84456311 | ||
|
1c42434b9a | ||
|
04bebdffb2 | ||
|
6912d0f162 | ||
|
7647923d9c | ||
|
89401df6f6 | ||
|
cd053e760e | ||
|
878ac29fe0 | ||
|
bb0f679c6b | ||
|
75907928d7 | ||
|
22e4e3fa7b | ||
|
3a39784af2 | ||
|
5709f6a3dc | ||
|
1ec53d1e1d | ||
|
c13b2d2ac7 | ||
|
72848d09af | ||
|
4bb46c0d7a | ||
|
cb3cee7e4e | ||
|
2d34ed30eb | ||
|
5237bbaa64 | ||
|
194eec131c | ||
|
3f2342a3d3 | ||
|
561e14822d | ||
|
5129c2cb43 | ||
|
29547179c5 | ||
|
255cc216b0 | ||
|
c876e6bbbd | ||
|
20357c8e75 | ||
|
a7bb5f543a | ||
|
6f9420ca8f | ||
|
2875eb4c94 | ||
|
3277892ff2 | ||
|
f5507cc2bc | ||
|
d2d93db9f0 | ||
|
32be808415 | ||
|
9d7b89d36c | ||
|
82e8166fd5 | ||
|
b4e0493b31 | ||
|
1f5efc748d | ||
|
461e0a38b4 | ||
|
4d0f9797c6 | ||
|
20ff39493d | ||
|
d82d62f35f | ||
|
58ec2dde35 | ||
|
a5029dbdd9 | ||
|
b88e26b3ab | ||
|
3e18a6ce41 | ||
|
2fdacfc1b0 | ||
|
bd5f7d4293 | ||
|
4e49821644 | ||
|
a7351aa202 | ||
|
7f53b3ddf4 | ||
|
fd2840a2a0 | ||
|
c2ffb0f15b | ||
|
475c46529f | ||
|
57ab7cabcf | ||
|
cb2859ff60 | ||
|
406e766cf2 | ||
|
a4d29b2301 | ||
|
66445a117f | ||
|
930c07d193 | ||
|
fc1c22d902 | ||
|
dce2477a4b | ||
|
bfe4a32098 | ||
|
bc1583c070 | ||
|
ee852057ce | ||
|
05a4bd126a | ||
|
aa6c765729 | ||
|
043187f4ec | ||
|
dbecaff451 | ||
|
117ced0bf5 | ||
|
91b15eeb98 | ||
|
ef63a67aee | ||
|
ccbeb9d32d | ||
|
1ed747762a | ||
|
9cbb510c48 | ||
|
235735fb3a | ||
|
cc37618da2 | ||
|
997385d95a | ||
|
d0676fa2db | ||
|
a4e56ead3d | ||
|
ffe424725d | ||
|
51c22b840c | ||
|
ccbd478dfc | ||
|
8a19b205de | ||
|
9d5792c7ec | ||
|
a7b6f9cb87 | ||
|
c783578bdf | ||
|
d040615c6e | ||
|
a4d1904b81 | ||
|
05430f6926 | ||
|
6205bef251 | ||
|
3dda11956e | ||
|
291c107fdd | ||
|
172845b3c0 | ||
|
ab73695d12 | ||
|
d82b416667 | ||
|
fcbc641127 | ||
|
a155c56198 | ||
|
ab95995bf0 | ||
|
01af8beedb | ||
|
eb0dbe0ab4 | ||
|
71423803e5 | ||
|
5241def188 | ||
|
83c387cffd | ||
|
fa3763a32a | ||
|
1c3e4b450c | ||
|
0d8d05166f | ||
|
c5e51ae2c1 | ||
|
caf373bcc3 | ||
|
a0e0c3e579 | ||
|
e8df5ad6a9 | ||
|
4444187cdd | ||
|
3a5000a130 | ||
|
43f468dc68 | ||
|
87728f8301 | ||
|
ab44eb6422 | ||
|
a34bc6b9e9 | ||
|
5d5e15e522 | ||
|
0abbef749e | ||
|
aa88feb918 | ||
|
eb5f0e980d | ||
|
4897d0ab6f | ||
|
e522a68ead | ||
|
9b092f98de | ||
|
29a7681492 | ||
|
804889301b | ||
|
8180aaf962 | ||
|
c478b3b4a2 | ||
|
34f25d51e8 | ||
|
529c6bd7ac | ||
|
db50ba22b8 | ||
|
4e7c597458 | ||
|
05db975785 | ||
|
0da587e521 | ||
|
deccb3340c | ||
|
01187071c2 | ||
|
9da7c16718 | ||
|
f11d4cb093 | ||
|
55db0f8bf9 | ||
|
b5ec48498f | ||
|
8a3039ec16 | ||
|
bda36647f3 | ||
|
b5b5ad4feb | ||
|
09141d44cc | ||
|
d6c4c69745 | ||
|
e14aa4b96c | ||
|
ada64afe40 | ||
|
ef72e89508 | ||
|
cd74aea6c4 | ||
|
f5e1cf869d | ||
|
98e37bec59 | ||
|
69328af6f9 | ||
|
5b90491f61 | ||
|
d1ff36f493 | ||
|
c92f318a08 | ||
|
40d2709371 | ||
|
6b1aa69715 | ||
|
d2cc87f12d | ||
|
27d423489c | ||
|
f2a1181049 | ||
|
6d5ed6d1e5 | ||
|
66e7c50a8e | ||
|
c6853f9239 | ||
|
0e13f4f3f1 | ||
|
aab9e424fc | ||
|
53f9e3618e | ||
|
4195ee8309 | ||
|
235992471c | ||
|
8b4d081c0f | ||
|
a0a577991f | ||
|
35726c99a7 | ||
|
645ae7d307 | ||
|
66c28bd826 | ||
|
da2c709360 | ||
|
8fb5519f4d | ||
|
88faf4cafd | ||
|
a791d49c1b | ||
|
f77181ab25 | ||
|
c91292493b | ||
|
bf851f2535 | ||
|
316c384428 | ||
|
0a2b1670d4 | ||
|
b67716b724 | ||
|
a87e0c88a7 | ||
|
90d7712ae2 | ||
|
35b1a07c19 | ||
|
213f45ad3a | ||
|
ee2133c0c2 | ||
|
d141b747eb | ||
|
f0b71ec10c | ||
|
2775abe172 | ||
|
d732e3ee4f | ||
|
bb06217187 | ||
|
cc8d8dddfc | ||
|
7b822b0278 | ||
|
dbeb4ba8ea | ||
|
adb09b8a4a | ||
|
da39fb1f59 | ||
|
7ad1d6ae5d | ||
|
cad8f21ff3 | ||
|
5e03997049 | ||
|
9252121b57 | ||
|
8d7bde5d6c | ||
|
d943af8828 | ||
|
ce2f01775e | ||
|
c028d8d5d7 | ||
|
c3fd9660c4 | ||
|
4ef5a3ace6 | ||
|
e954ab09ef | ||
|
8ba5c80d86 | ||
|
d804d50ac3 | ||
|
f4f4f10343 | ||
|
ceb01441f5 | ||
|
025febc124 | ||
|
d3bfe958aa | ||
|
b456f0821f | ||
|
fd2eab589c | ||
|
ce625e4074 | ||
|
f2b66900c9 | ||
|
f8ffcd173a | ||
|
590e152ea2 | ||
|
2364b57f40 | ||
|
7ad97b954c | ||
|
8d6dc3b51b | ||
|
22fa0e55a0 | ||
|
fbc9bead2d | ||
|
21dfed3727 | ||
|
b1436ea884 | ||
|
0f72ac7bc4 | ||
|
0720698874 | ||
|
c4a358e70c | ||
|
a33c7c0ff9 | ||
|
a9af0f41bc | ||
|
20d25b50cd | ||
|
65dd736822 | ||
|
e5b94c5cda | ||
|
417a16ea49 | ||
|
8187799929 | ||
|
5d05acb6d7 | ||
|
68f4349a4d | ||
|
2d1d3d9dde | ||
|
ee3ebfd3a9 | ||
|
222ce21070 | ||
|
62b72c85e1 | ||
|
42c614cf24 | ||
|
4286291877 | ||
|
0e120998b8 | ||
|
8d5560d3c4 | ||
|
55603f3221 | ||
|
d309de2050 | ||
|
d4f9a9cc17 | ||
|
28ee48f433 | ||
|
8cea72d18b | ||
|
ce67d4a475 | ||
|
31a16caa87 | ||
|
a3a5d42f98 | ||
|
4d4e2bf5f1 | ||
|
48f9344164 | ||
|
f92c717e32 | ||
|
52217fa477 | ||
|
e72456b79e | ||
|
985f0a81f1 | ||
|
9f9f7c847d | ||
|
bcb3dd602a | ||
|
345c5b7b14 | ||
|
d3b5df6180 | ||
|
46fa25718b | ||
|
584951752a | ||
|
b3e5863538 | ||
|
d113d59223 | ||
|
b27bf24eea | ||
|
f4a00beebb | ||
|
d48c333b6e | ||
|
c5c832d6c3 | ||
|
7ba6d9204c | ||
|
fce97d9fca | ||
|
a19617c53b | ||
|
138e77f9c1 | ||
|
30c9c244f0 |
1160 changed files with 36339 additions and 8804 deletions
123
.editorconfig
123
.editorconfig
|
@ -5,10 +5,131 @@ 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
|
||||
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
|
||||
|
|
8
.github/ISSUE_TEMPLATE/bug-report.md
vendored
8
.github/ISSUE_TEMPLATE/bug-report.md
vendored
|
@ -1,15 +1,15 @@
|
|||
---
|
||||
name: Bug Report
|
||||
about: Create a report to help us improve Safe Exam Browser
|
||||
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
|
||||
> [!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.
|
||||
|
|
2
.github/ISSUE_TEMPLATE/feature-request.md
vendored
2
.github/ISSUE_TEMPLATE/feature-request.md
vendored
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea for Safe Exam Browser
|
||||
about: Suggest an idea or new feature for Safe Exam Browser.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: dbuechel
|
||||
|
|
54
.github/workflows/codeql.yml
vendored
Normal file
54
.github/workflows/codeql.yml
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
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
Normal file
25
.github/workflows/issues.yml
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
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 }}
|
Binary file not shown.
30
README.md
30
README.md
|
@ -2,25 +2,25 @@
|
|||
|
||||
Refactored version of Safe Exam Browser for Windows with Chromium as integrated browser engine.
|
||||
|
||||
### Requirements
|
||||
## Requirements
|
||||
|
||||
SEB 3.x requires the prerequisites listed below in order to work correctly. These are automatically installed with the setup bundle and need only be manually installed when using the MSI packages.
|
||||
|
||||
* .NET Framework 4.7.2 Runtime: https://dotnet.microsoft.com/download/dotnet-framework/net472
|
||||
* .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
|
||||
|
||||
### Project Status
|
||||
## Project Status
|
||||
|
||||
**_DISCLAIMER_**\
|
||||
**The builds linked below are for testing purposes only.** They may be unstable and should thus _never_ be used in a production environment! Always use the latest, official release version of SEB.
|
||||
> [!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 |
|
||||
| --------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| Release Build | ![Release Build Status](https://sebdev-let.ethz.ch/api/projects/status/kq78qrjtnpk82ti0?svg=true) | https://sebdev-let.ethz.ch/project/appveyor/seb-win-refactoring |
|
||||
| Test Build | ![Test Build Status](https://ci.appveyor.com/api/projects/status/a56akt9r174570m7?svg=true) | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
|
||||
| Test Run | ![AppVeyor Tests](https://img.shields.io/appveyor/tests/dbuechel/seb-win-refactoring?logo=appveyor&logoColor=%23ccc) | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
|
||||
| Code Coverage | ![Code Coverage](https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring/branch/master/graph/badge.svg) | https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring |
|
||||
| Issue Status | ![GitHub Issues](https://img.shields.io/github/issues/safeexambrowser/seb-win-refactoring?logo=github) | https://github.com/SafeExamBrowser/seb-win-refactoring/issues |
|
||||
| Downloads | ![GitHub All Releases](https://img.shields.io/github/downloads/safeexambrowser/seb-win-refactoring/total?logo=github) | https://github.com/SafeExamBrowser/seb-win-refactoring/releases |
|
||||
| Development | ![GitHub Last Commit](https://img.shields.io/github/last-commit/safeexambrowser/seb-win-refactoring?logo=github) | n/a |
|
||||
| Repository Size | ![GitHub Repo Size](https://img.shields.io/github/repo-size/safeexambrowser/seb-win-refactoring?logo=github) | n/a |
|
||||
| Aspect | Status | Details |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| Development Build | ![Development Build Status](https://sebdev.ethz.ch/api/projects/status/kq78qrjtnpk82ti0?svg=true) | https://sebdev.ethz.ch/project/appveyor/seb-win-refactoring |
|
||||
| Test Build | ![Test Build Status](https://ci.appveyor.com/api/projects/status/a56akt9r174570m7?svg=true) | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
|
||||
| Test Run | ![AppVeyor Tests](https://img.shields.io/appveyor/tests/dbuechel/seb-win-refactoring?logo=appveyor&logoColor=%23ccc) | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
|
||||
| Code Coverage | ![Code Coverage](https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring/branch/master/graph/badge.svg) | https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring |
|
||||
| Issue Status | ![GitHub Issues](https://img.shields.io/github/issues/safeexambrowser/seb-win-refactoring?logo=github) | https://github.com/SafeExamBrowser/seb-win-refactoring/issues |
|
||||
| Downloads | ![GitHub All Releases](https://img.shields.io/github/downloads/safeexambrowser/seb-win-refactoring/total?logo=github) | https://github.com/SafeExamBrowser/seb-win-refactoring/releases |
|
||||
| Development | ![GitHub Last Commit](https://img.shields.io/github/last-commit/safeexambrowser/seb-win-refactoring?logo=github) | n/a |
|
||||
| Repository Size | ![GitHub Repo Size](https://img.shields.io/github/repo-size/safeexambrowser/seb-win-refactoring?logo=github) | n/a |
|
||||
|
|
35
SECURITY.md
Normal file
35
SECURITY.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# 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,12 +1,12 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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.Resources.Icons;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
|
||||
namespace SafeExamBrowser.Applications.Contracts.Events
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -9,14 +9,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
|
||||
namespace SafeExamBrowser.Applications.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls the lifetime and functionality of an application.
|
||||
/// </summary>
|
||||
public interface IApplication
|
||||
public interface IApplication<out TWindow> where TWindow : IApplicationWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether the application should be automatically started.
|
||||
|
@ -51,7 +51,7 @@ namespace SafeExamBrowser.Applications.Contracts
|
|||
/// <summary>
|
||||
/// Returns all windows of the application.
|
||||
/// </summary>
|
||||
IEnumerable<IApplicationWindow> GetWindows();
|
||||
IEnumerable<TWindow> GetWindows();
|
||||
|
||||
/// <summary>
|
||||
/// Performs any initialization work, if necessary.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -18,6 +18,6 @@ namespace SafeExamBrowser.Applications.Contracts
|
|||
/// <summary>
|
||||
/// Attempts to create an application according to the given settings.
|
||||
/// </summary>
|
||||
FactoryResult TryCreate(WhitelistApplication settings, out IApplication application);
|
||||
FactoryResult TryCreate(WhitelistApplication settings, out IApplication<IApplicationWindow> application);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -8,12 +8,12 @@
|
|||
|
||||
using System;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
|
||||
namespace SafeExamBrowser.Applications.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a window of an <see cref="IApplication"/>.
|
||||
/// Defines a window of an <see cref="IApplication{TWindow}"/>.
|
||||
/// </summary>
|
||||
public interface IApplicationWindow
|
||||
{
|
||||
|
|
|
@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyDescription("Safe Exam Browser")]
|
||||
[assembly: AssemblyCompany("ETH Zürich")]
|
||||
[assembly: AssemblyProduct("SafeExamBrowser.Applications.Contracts")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2020 ETH Zürich, Educational Development and Technology (LET)")]
|
||||
[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
|
||||
|
|
|
@ -9,9 +9,10 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SafeExamBrowser.Applications.Contracts</RootNamespace>
|
||||
<AssemblyName>SafeExamBrowser.Applications.Contracts</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
@ -61,14 +62,13 @@
|
|||
<Compile Include="IApplication.cs" />
|
||||
<Compile Include="IApplicationFactory.cs" />
|
||||
<Compile Include="IApplicationWindow.cs" />
|
||||
<Compile Include="Resources\Icons\BitmapIconResource.cs" />
|
||||
<Compile Include="Resources\Icons\EmbeddedIconResource.cs" />
|
||||
<Compile Include="Resources\Icons\IconResource.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Resources\Icons\NativeIconResource.cs" />
|
||||
<Compile Include="Resources\Icons\XamlIconResource.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Core.Contracts\SafeExamBrowser.Core.Contracts.csproj">
|
||||
<Project>{fe0e1224-b447-4b14-81e7-ed7d84822aa0}</Project>
|
||||
<Name>SafeExamBrowser.Core.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
|
||||
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
|
||||
<Name>SafeExamBrowser.Settings</Name>
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
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")]
|
|
@ -0,0 +1,199 @@
|
|||
<?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>
|
35
SafeExamBrowser.Applications.UnitTests/app.config
Normal file
35
SafeExamBrowser.Applications.UnitTests/app.config
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?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>
|
23
SafeExamBrowser.Applications.UnitTests/packages.config
Normal file
23
SafeExamBrowser.Applications.UnitTests/packages.config
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?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,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -9,39 +9,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.Win32;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||
using SafeExamBrowser.Settings.Applications;
|
||||
using SafeExamBrowser.SystemComponents.Contracts.Registry;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Applications
|
||||
{
|
||||
public class ApplicationFactory : IApplicationFactory
|
||||
{
|
||||
private IApplicationMonitor applicationMonitor;
|
||||
private IModuleLogger logger;
|
||||
private INativeMethods nativeMethods;
|
||||
private IProcessFactory processFactory;
|
||||
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)
|
||||
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 application)
|
||||
public FactoryResult TryCreate(WhitelistApplication settings, out IApplication<IApplicationWindow> application)
|
||||
{
|
||||
var name = $"'{settings.DisplayName}' ({ settings.ExecutableName})";
|
||||
var name = $"'{settings.DisplayName}' ({settings.ExecutableName})";
|
||||
|
||||
application = default(IApplication);
|
||||
application = default;
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -69,10 +72,12 @@ namespace SafeExamBrowser.Applications
|
|||
return FactoryResult.Error;
|
||||
}
|
||||
|
||||
private IApplication BuildApplication(string executablePath, WhitelistApplication settings)
|
||||
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);
|
||||
var application = new ExternalApplication(applicationMonitor, executablePath, applicationLogger, nativeMethods, processFactory, settings, ONE_SECOND);
|
||||
|
||||
return application;
|
||||
}
|
||||
|
@ -82,14 +87,14 @@ namespace SafeExamBrowser.Applications
|
|||
var paths = new List<string[]>();
|
||||
var registryPath = QueryPathFromRegistry(settings);
|
||||
|
||||
mainExecutable = default(string);
|
||||
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(string))
|
||||
if (settings.ExecutablePath != default)
|
||||
{
|
||||
paths.Add(new[] { settings.ExecutablePath, settings.ExecutableName });
|
||||
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), settings.ExecutablePath, settings.ExecutableName });
|
||||
|
@ -98,11 +103,11 @@ namespace SafeExamBrowser.Applications
|
|||
paths.Add(new[] { Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), settings.ExecutablePath, settings.ExecutableName });
|
||||
}
|
||||
|
||||
if (registryPath != default(string))
|
||||
if (registryPath != default)
|
||||
{
|
||||
paths.Add(new[] { registryPath, settings.ExecutableName });
|
||||
|
||||
if (settings.ExecutablePath != default(string))
|
||||
if (settings.ExecutablePath != default)
|
||||
{
|
||||
paths.Add(new[] { registryPath, settings.ExecutablePath, settings.ExecutableName });
|
||||
}
|
||||
|
@ -131,22 +136,12 @@ namespace SafeExamBrowser.Applications
|
|||
|
||||
private string QueryPathFromRegistry(WhitelistApplication settings)
|
||||
{
|
||||
try
|
||||
if (registry.TryRead($@"{RegistryValue.MachineHive.AppPaths_Key}\{settings.ExecutableName}", "Path", out var value))
|
||||
{
|
||||
using (var key = Registry.LocalMachine.OpenSubKey($@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{settings.ExecutableName}"))
|
||||
{
|
||||
if (key != null)
|
||||
{
|
||||
return key.GetValue("Path") as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to query path in registry for '{settings.ExecutableName}'!", e);
|
||||
return value as string;
|
||||
}
|
||||
|
||||
return default(string);
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -11,7 +11,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||
using SafeExamBrowser.Settings.Applications;
|
||||
|
@ -19,17 +19,18 @@ using SafeExamBrowser.WindowsApi.Contracts;
|
|||
|
||||
namespace SafeExamBrowser.Applications
|
||||
{
|
||||
internal class ExternalApplication : IApplication
|
||||
internal class ExternalApplication : IApplication<IApplicationWindow>
|
||||
{
|
||||
private readonly object @lock = new object();
|
||||
|
||||
private IApplicationMonitor applicationMonitor;
|
||||
private string executablePath;
|
||||
private IModuleLogger logger;
|
||||
private INativeMethods nativeMethods;
|
||||
private IList<ExternalApplicationInstance> instances;
|
||||
private IProcessFactory processFactory;
|
||||
private WhitelistApplication settings;
|
||||
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; }
|
||||
|
@ -45,7 +46,8 @@ namespace SafeExamBrowser.Applications
|
|||
IModuleLogger logger,
|
||||
INativeMethods nativeMethods,
|
||||
IProcessFactory processFactory,
|
||||
WhitelistApplication settings)
|
||||
WhitelistApplication settings,
|
||||
int windowMonitoringInterval_ms)
|
||||
{
|
||||
this.applicationMonitor = applicationMonitor;
|
||||
this.executablePath = executablePath;
|
||||
|
@ -54,6 +56,7 @@ namespace SafeExamBrowser.Applications
|
|||
this.instances = new List<ExternalApplicationInstance>();
|
||||
this.processFactory = processFactory;
|
||||
this.settings = settings;
|
||||
this.windowMonitoringInterval = windowMonitoringInterval_ms;
|
||||
}
|
||||
|
||||
public IEnumerable<IApplicationWindow> GetWindows()
|
||||
|
@ -89,18 +92,6 @@ namespace SafeExamBrowser.Applications
|
|||
}
|
||||
}
|
||||
|
||||
private string[] BuildArguments()
|
||||
{
|
||||
var arguments = new List<string>();
|
||||
|
||||
foreach (var argument in settings.Arguments)
|
||||
{
|
||||
arguments.Add(Environment.ExpandEnvironmentVariables(argument));
|
||||
}
|
||||
|
||||
return arguments.ToArray();
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
applicationMonitor.InstanceStarted -= ApplicationMonitor_InstanceStarted;
|
||||
|
@ -153,12 +144,24 @@ namespace SafeExamBrowser.Applications
|
|||
WindowsChanged?.Invoke();
|
||||
}
|
||||
|
||||
private string[] BuildArguments()
|
||||
{
|
||||
var arguments = new List<string>();
|
||||
|
||||
foreach (var argument in settings.Arguments)
|
||||
{
|
||||
arguments.Add(Environment.ExpandEnvironmentVariables(argument));
|
||||
}
|
||||
|
||||
return arguments.ToArray();
|
||||
}
|
||||
|
||||
private void InitializeInstance(IProcess process)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
var instanceLogger = logger.CloneFor($"{Name} ({process.Id})");
|
||||
var instance = new ExternalApplicationInstance(Icon, instanceLogger, nativeMethods, process);
|
||||
var instance = new ExternalApplicationInstance(Icon, instanceLogger, nativeMethods, process, windowMonitoringInterval);
|
||||
|
||||
instance.Terminated += Instance_Terminated;
|
||||
instance.WindowsChanged += () => WindowsChanged?.Invoke();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -12,8 +12,8 @@ using System.Linq;
|
|||
using System.Timers;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.Applications.Events;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
|
@ -23,24 +23,32 @@ namespace SafeExamBrowser.Applications
|
|||
{
|
||||
private readonly object @lock = new object();
|
||||
|
||||
private IconResource icon;
|
||||
private ILogger logger;
|
||||
private INativeMethods nativeMethods;
|
||||
private IProcess process;
|
||||
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;
|
||||
private IList<ExternalApplicationWindow> windows;
|
||||
|
||||
internal int Id { get; private set; }
|
||||
|
||||
internal event InstanceTerminatedEventHandler Terminated;
|
||||
internal event WindowsChangedEventHandler WindowsChanged;
|
||||
|
||||
internal ExternalApplicationInstance(IconResource icon, ILogger logger, INativeMethods nativeMethods, IProcess process)
|
||||
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>();
|
||||
}
|
||||
|
||||
|
@ -145,19 +153,20 @@ namespace SafeExamBrowser.Applications
|
|||
|
||||
private void InitializeEvents()
|
||||
{
|
||||
const int ONE_SECOND = 1000;
|
||||
|
||||
process.Terminated += Process_Terminated;
|
||||
|
||||
timer = new Timer(ONE_SECOND);
|
||||
timer = new Timer(windowMonitoringInterval);
|
||||
timer.Elapsed += Timer_Elapsed;
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
private void FinalizeEvents()
|
||||
{
|
||||
timer.Elapsed -= Timer_Elapsed;
|
||||
timer.Stop();
|
||||
if (timer != default)
|
||||
{
|
||||
timer.Elapsed -= Timer_Elapsed;
|
||||
timer.Stop();
|
||||
}
|
||||
|
||||
process.Terminated -= Process_Terminated;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -9,14 +9,14 @@
|
|||
using System;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Applications
|
||||
{
|
||||
internal class ExternalApplicationWindow : IApplicationWindow
|
||||
{
|
||||
private INativeMethods nativeMethods;
|
||||
private readonly INativeMethods nativeMethods;
|
||||
|
||||
public IntPtr Handle { get; }
|
||||
public IconResource Icon { get; private set; }
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
|
@ -8,12 +9,13 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyDescription("Safe Exam Browser")]
|
||||
[assembly: AssemblyCompany("ETH Zürich")]
|
||||
[assembly: AssemblyProduct("SafeExamBrowser.Applications")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2020 ETH Zürich, Educational Development and Technology (LET)")]
|
||||
[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")]
|
||||
|
|
|
@ -9,9 +9,10 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SafeExamBrowser.Applications</RootNamespace>
|
||||
<AssemblyName>SafeExamBrowser.Applications</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
@ -66,6 +67,10 @@
|
|||
<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>
|
||||
|
@ -78,6 +83,10 @@
|
|||
<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>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.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);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.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,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -9,7 +9,7 @@
|
|||
namespace SafeExamBrowser.Browser.Contracts.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Event handler used to indicate that the browser has detected a session identifier of a LMS.
|
||||
/// Event handler used to indicate that the browser has detected a user identifier of an LMS.
|
||||
/// </summary>
|
||||
public delegate void SessionIdentifierDetectedEventHandler(string identifier);
|
||||
public delegate void UserIdentifierDetectedEventHandler(string identifier);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -14,7 +14,7 @@ namespace SafeExamBrowser.Browser.Contracts
|
|||
/// <summary>
|
||||
/// Controls the lifetime and functionality of the browser application.
|
||||
/// </summary>
|
||||
public interface IBrowserApplication : IApplication
|
||||
public interface IBrowserApplication : IApplication<IBrowserWindow>
|
||||
{
|
||||
/// <summary>
|
||||
/// Event fired when the browser application detects a download request for an application configuration file.
|
||||
|
@ -22,13 +22,24 @@ namespace SafeExamBrowser.Browser.Contracts
|
|||
event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the browser application detects a session identifier of an LMS.
|
||||
/// Event fired when the user tries to focus the taskbar.
|
||||
/// </summary>
|
||||
event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
28
SafeExamBrowser.Browser.Contracts/IBrowserWindow.cs
Normal file
28
SafeExamBrowser.Browser.Contracts/IBrowserWindow.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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; }
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyDescription("Safe Exam Browser")]
|
||||
[assembly: AssemblyCompany("ETH Zürich")]
|
||||
[assembly: AssemblyProduct("SafeExamBrowser.Browser.Contracts")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2020 ETH Zürich, Educational Development and Technology (LET)")]
|
||||
[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
|
||||
|
|
|
@ -9,9 +9,10 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SafeExamBrowser.Browser.Contracts</RootNamespace>
|
||||
<AssemblyName>SafeExamBrowser.Browser.Contracts</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
@ -57,13 +58,16 @@
|
|||
<Compile Include="Events\DownloadEventArgs.cs" />
|
||||
<Compile Include="Events\DownloadFinishedCallback.cs" />
|
||||
<Compile Include="Events\DownloadRequestedEventHandler.cs" />
|
||||
<Compile Include="Events\SessionIdentifierDetectedEventHandler.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>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -31,7 +31,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
[TestMethod]
|
||||
public void MustCorrectlyCancelDialog()
|
||||
{
|
||||
RequestDialog(default(CefFileDialogMode), false);
|
||||
RequestDialog(default, false);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -71,7 +71,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
var threadId = default(int);
|
||||
|
||||
callback.Setup(c => c.Cancel()).Callback(() => sync.Set());
|
||||
callback.Setup(c => c.Continue(It.IsAny<int>(), It.IsAny<List<string>>())).Callback(() => sync.Set());
|
||||
callback.Setup(c => c.Continue(It.IsAny<List<string>>())).Callback(() => sync.Set());
|
||||
sut.DialogRequested += (a) =>
|
||||
{
|
||||
args = a;
|
||||
|
@ -80,18 +80,18 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
};
|
||||
|
||||
var status = sut.OnFileDialog(default(IWebBrowser), default(IBrowser), mode, default(CefFileDialogFlags), title, initialPath, default(List<string>), default(int), callback.Object);
|
||||
var status = sut.OnFileDialog(default, default, mode, title, initialPath, default, callback.Object);
|
||||
|
||||
sync.WaitOne();
|
||||
|
||||
if (confirm)
|
||||
{
|
||||
callback.Verify(c => c.Continue(It.IsAny<int>(), It.IsAny<List<string>>()), Times.Once);
|
||||
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<int>(), It.IsAny<List<string>>()), Times.Never);
|
||||
callback.Verify(c => c.Continue(It.IsAny<List<string>>()), Times.Never);
|
||||
callback.Verify(c => c.Cancel(), Times.Once);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -7,8 +7,6 @@
|
|||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using CefSharp;
|
||||
using CefSharp.Structs;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using SafeExamBrowser.Browser.Handlers;
|
||||
|
||||
|
@ -30,9 +28,10 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
{
|
||||
var text = default(string);
|
||||
|
||||
Assert.IsFalse(sut.OnAutoResize(default(IWebBrowser), default(IBrowser), default(Size)));
|
||||
Assert.IsFalse(sut.OnConsoleMessage(default(IWebBrowser), default(ConsoleMessageEventArgs)));
|
||||
Assert.IsFalse(sut.OnTooltipChanged(default(IWebBrowser), ref text));
|
||||
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]
|
||||
|
@ -47,12 +46,12 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
called = true;
|
||||
url = u;
|
||||
};
|
||||
sut.OnFaviconUrlChange(default(IWebBrowser), default(IBrowser), new List<string>());
|
||||
sut.OnFaviconUrlChange(default, default, new List<string>());
|
||||
|
||||
Assert.AreEqual(default(string), url);
|
||||
Assert.AreEqual(default, url);
|
||||
Assert.IsFalse(called);
|
||||
|
||||
sut.OnFaviconUrlChange(default(IWebBrowser), default(IBrowser), new List<string> { newUrl });
|
||||
sut.OnFaviconUrlChange(default, default, new List<string> { newUrl });
|
||||
|
||||
Assert.AreEqual(newUrl, url);
|
||||
Assert.IsTrue(called);
|
||||
|
@ -65,7 +64,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
var actual = default(double);
|
||||
|
||||
sut.ProgressChanged += (p) => actual = p;
|
||||
sut.OnLoadingProgressChange(default(IWebBrowser), default(IBrowser), expected);
|
||||
sut.OnLoadingProgressChange(default, default, expected);
|
||||
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -25,14 +25,52 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustDetectReload()
|
||||
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);
|
||||
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);
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Browser.Handlers;
|
||||
|
||||
namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
||||
{
|
||||
[TestClass]
|
||||
public class LifeSpanHandlerTests
|
||||
{
|
||||
private LifeSpanHandler sut;
|
||||
|
||||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
sut = new LifeSpanHandler();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustUseDefaultBehavior()
|
||||
{
|
||||
Assert.IsFalse(sut.DoClose(default(IWebBrowser), default(IBrowser)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustHandlePopup()
|
||||
{
|
||||
var args = default(PopupRequestedEventArgs);
|
||||
var jsAccess = false;
|
||||
var url = "https://www.host.org/some-url";
|
||||
|
||||
sut.PopupRequested += (a) => args = a;
|
||||
|
||||
var result = sut.OnBeforePopup(default(IWebBrowser), default(IBrowser), default(IFrame), url, default(string), default(WindowOpenDisposition), default(bool), default(IPopupFeatures), default(IWindowInfo), default(IBrowserSettings), ref jsAccess, out var newBrowser);
|
||||
|
||||
Assert.IsTrue(result);
|
||||
Assert.AreEqual(default(IWebBrowser), newBrowser);
|
||||
Assert.AreEqual(url, args.Url);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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;
|
||||
|
@ -28,6 +30,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
{
|
||||
private AppConfig appConfig;
|
||||
private Mock<IRequestFilter> filter;
|
||||
private Mock<IKeyGenerator> keyGenerator;
|
||||
private Mock<ILogger> logger;
|
||||
private BrowserSettings settings;
|
||||
private WindowSettings windowSettings;
|
||||
|
@ -40,13 +43,23 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
{
|
||||
appConfig = new AppConfig();
|
||||
filter = new Mock<IRequestFilter>();
|
||||
keyGenerator = new Mock<IKeyGenerator>();
|
||||
logger = new Mock<ILogger>();
|
||||
settings = new BrowserSettings();
|
||||
windowSettings = new WindowSettings();
|
||||
text = new Mock<IText>();
|
||||
resourceHandler = new ResourceHandler(appConfig, filter.Object, logger.Object, settings, windowSettings, text.Object);
|
||||
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, 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]
|
||||
|
@ -56,6 +69,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
var quitUrl = "http://www.byebye.com";
|
||||
var request = new Mock<IRequest>();
|
||||
|
||||
appConfig.ConfigurationFileMimeType = "application/seb";
|
||||
request.SetupGet(r => r.Url).Returns(quitUrl);
|
||||
settings.QuitUrl = quitUrl;
|
||||
sut.QuitUrlVisited += (url) => eventFired = true;
|
||||
|
@ -107,8 +121,9 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
{
|
||||
var eventFired = false;
|
||||
var request = new Mock<IRequest>();
|
||||
var url = "www.test.org";
|
||||
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);
|
||||
|
@ -140,8 +155,9 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
{
|
||||
var eventFired = false;
|
||||
var request = new Mock<IRequest>();
|
||||
var url = "www.test.org";
|
||||
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);
|
||||
|
@ -168,6 +184,75 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
Assert.IsFalse(eventFired);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustInitiateDataUriConfigurationFileDownload()
|
||||
{
|
||||
var browser = new Mock<IBrowser>();
|
||||
var host = new Mock<IBrowserHost>();
|
||||
var request = new Mock<IRequest>();
|
||||
|
||||
appConfig.ConfigurationFileExtension = ".xyz";
|
||||
appConfig.ConfigurationFileMimeType = "application/seb";
|
||||
appConfig.SebUriScheme = "abc";
|
||||
appConfig.SebUriSchemeSecure = "abcd";
|
||||
browser.Setup(b => b.GetHost()).Returns(host.Object);
|
||||
request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriSchemeSecure}://{appConfig.ConfigurationFileMimeType};base64,H4sIAAAAAAAAE41WbXPaRhD...");
|
||||
|
||||
var handled = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), browser.Object, Mock.Of<IFrame>(), request.Object, false, false);
|
||||
|
||||
host.Verify(h => h.StartDownload(It.Is<string>(u => u == $"data:{appConfig.ConfigurationFileMimeType};base64,H4sIAAAAAAAAE41WbXPaRhD...")));
|
||||
Assert.IsTrue(handled);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustInitiateHttpConfigurationFileDownload()
|
||||
{
|
||||
var browser = new Mock<IBrowser>();
|
||||
var host = new Mock<IBrowserHost>();
|
||||
var request = new Mock<IRequest>();
|
||||
|
||||
appConfig.ConfigurationFileExtension = ".xyz";
|
||||
appConfig.ConfigurationFileMimeType = "application/seb";
|
||||
appConfig.SebUriScheme = "abc";
|
||||
appConfig.SebUriSchemeSecure = "abcd";
|
||||
browser.Setup(b => b.GetHost()).Returns(host.Object);
|
||||
request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriScheme}://host.com/path/file{appConfig.ConfigurationFileExtension}");
|
||||
|
||||
var handled = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), browser.Object, Mock.Of<IFrame>(), request.Object, false, false);
|
||||
|
||||
host.Verify(h => h.StartDownload(It.Is<string>(u => u == $"{Uri.UriSchemeHttp}://host.com/path/file{appConfig.ConfigurationFileExtension}")));
|
||||
Assert.IsTrue(handled);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustInitiateHttpsConfigurationFileDownload()
|
||||
{
|
||||
var browser = new Mock<IBrowser>();
|
||||
var host = new Mock<IBrowserHost>();
|
||||
var request = new Mock<IRequest>();
|
||||
|
||||
appConfig.ConfigurationFileExtension = ".xyz";
|
||||
appConfig.ConfigurationFileMimeType = "application/seb";
|
||||
appConfig.SebUriScheme = "abc";
|
||||
appConfig.SebUriSchemeSecure = "abcd";
|
||||
browser.Setup(b => b.GetHost()).Returns(host.Object);
|
||||
request.SetupGet(r => r.Url).Returns($"{appConfig.SebUriSchemeSecure}://host.com/path/file{appConfig.ConfigurationFileExtension}");
|
||||
|
||||
var handled = sut.OnBeforeBrowse(Mock.Of<IWebBrowser>(), browser.Object, Mock.Of<IFrame>(), request.Object, false, false);
|
||||
|
||||
host.Verify(h => h.StartDownload(It.Is<string>(u => u == $"{Uri.UriSchemeHttps}://host.com/path/file{appConfig.ConfigurationFileExtension}")));
|
||||
Assert.IsTrue(handled);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustReturnResourceHandler()
|
||||
{
|
||||
var disableDefaultHandling = default(bool);
|
||||
var handler = sut.GetResourceRequestHandler(default, default, default, default, default, default, default, ref disableDefaultHandling);
|
||||
|
||||
Assert.AreSame(resourceHandler, handler);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustUseProxyCredentials()
|
||||
{
|
||||
|
@ -178,7 +263,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
settings.Proxy.Proxies.Add(proxy1);
|
||||
settings.Proxy.Proxies.Add(proxy2);
|
||||
|
||||
var result = sut.GetAuthCredentials(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), default(string), true, "WWW.tEst.Com", 10, default(string), default(string), callback.Object);
|
||||
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);
|
||||
|
@ -192,7 +277,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
{
|
||||
var callback = new Mock<IAuthCallback>();
|
||||
|
||||
sut.GetAuthCredentials(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), default(string), false, default(string), default(int), default(string), default(string), callback.Object);
|
||||
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);
|
||||
|
@ -200,14 +285,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
|
||||
private class TestableRequestHandler : RequestHandler
|
||||
{
|
||||
internal TestableRequestHandler(
|
||||
AppConfig appConfig,
|
||||
IRequestFilter filter,
|
||||
ILogger logger,
|
||||
ResourceHandler resourceHandler,
|
||||
BrowserSettings settings,
|
||||
WindowSettings windowSettings,
|
||||
IText text) : base(appConfig, filter, logger, resourceHandler, settings, windowSettings, text)
|
||||
internal TestableRequestHandler(AppConfig appConfig, IRequestFilter filter, ILogger logger, ResourceHandler resourceHandler, BrowserSettings settings, WindowSettings windowSettings) : base(appConfig, filter, logger, resourceHandler, settings, windowSettings)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -216,10 +294,20 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
return base.GetAuthCredentials(webBrowser, browser, originUrl, isProxy, host, port, realm, scheme, callback);
|
||||
}
|
||||
|
||||
public new IResourceRequestHandler GetResourceRequestHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
|
||||
{
|
||||
return base.GetResourceRequestHandler(webBrowser, browser, frame, request, isNavigation, isDownload, requestInitiator, ref disableDefaultHandling);
|
||||
}
|
||||
|
||||
public new bool OnBeforeBrowse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
|
||||
{
|
||||
return base.OnBeforeBrowse(webBrowser, browser, frame, request, userGesture, isRedirect);
|
||||
}
|
||||
|
||||
public new bool OnOpenUrlFromTab(IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
|
||||
{
|
||||
return base.OnOpenUrlFromTab(webBrowser, browser, frame, targetUrl, targetDisposition, userGesture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -9,13 +9,16 @@
|
|||
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;
|
||||
|
@ -29,6 +32,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
{
|
||||
private AppConfig appConfig;
|
||||
private Mock<IRequestFilter> filter;
|
||||
private Mock<IKeyGenerator> keyGenerator;
|
||||
private Mock<ILogger> logger;
|
||||
private BrowserSettings settings;
|
||||
private WindowSettings windowSettings;
|
||||
|
@ -40,12 +44,13 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
{
|
||||
appConfig = new AppConfig();
|
||||
filter = new Mock<IRequestFilter>();
|
||||
keyGenerator = new Mock<IKeyGenerator>();
|
||||
logger = new Mock<ILogger>();
|
||||
settings = new BrowserSettings();
|
||||
windowSettings = new WindowSettings();
|
||||
text = new Mock<IText>();
|
||||
|
||||
sut = new TestableResourceHandler(appConfig, filter.Object, logger.Object, settings, windowSettings, text.Object);
|
||||
sut = new TestableResourceHandler(appConfig, filter.Object, keyGenerator.Object, logger.Object, SessionMode.Server, settings, windowSettings, text.Object);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -56,11 +61,13 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
var request = new Mock<IRequest>();
|
||||
|
||||
browser.SetupGet(b => b.Address).Returns("http://www.host.org");
|
||||
keyGenerator.Setup(g => g.CalculateBrowserExamKeyHash(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<string>())).Returns(new Random().Next().ToString());
|
||||
keyGenerator.Setup(g => g.CalculateConfigurationKeyHash(It.IsAny<string>(), It.IsAny<string>())).Returns(new Random().Next().ToString());
|
||||
request.SetupGet(r => r.Headers).Returns(new NameValueCollection());
|
||||
request.SetupGet(r => r.Url).Returns("http://www.host.org");
|
||||
request.SetupSet(r => r.Headers = It.IsAny<NameValueCollection>()).Callback<NameValueCollection>((h) => headers = h);
|
||||
settings.SendConfigurationKey = true;
|
||||
settings.SendExamKey = true;
|
||||
settings.SendBrowserExamKey = true;
|
||||
|
||||
var result = sut.OnBeforeResourceLoad(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
|
||||
|
||||
|
@ -73,18 +80,46 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustNotAppendCustomHeadersForCrossDomain()
|
||||
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.SendExamKey = 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>());
|
||||
|
||||
|
@ -113,7 +148,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
public void MustFilterContentRequests()
|
||||
{
|
||||
var request = new Mock<IRequest>();
|
||||
var url = "www.test.org";
|
||||
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);
|
||||
|
@ -128,10 +163,16 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
Assert.IsNotNull(resourceHandler);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustLetOperatingSystemHandleUnknownProtocols()
|
||||
{
|
||||
Assert.IsTrue(sut.OnProtocolExecution(default, default, default, default));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustRedirectToDisablePdfToolbar()
|
||||
{
|
||||
var browser = new Mock<IWebBrowser>();
|
||||
var frame = new Mock<IFrame>();
|
||||
var headers = new NameValueCollection { { "Content-Type", MediaTypeNames.Application.Pdf } };
|
||||
var request = new Mock<IRequest>();
|
||||
var response = new Mock<IResponse>();
|
||||
|
@ -143,17 +184,17 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
settings.AllowPdfReader = true;
|
||||
settings.AllowPdfReaderToolbar = false;
|
||||
|
||||
var result = sut.OnResourceResponse(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
|
||||
var result = sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), frame.Object, request.Object, response.Object);
|
||||
|
||||
browser.Verify(b => b.Load(It.Is<string>(s => s.Equals($"{url}#toolbar=0"))), Times.Once);
|
||||
frame.Verify(b => b.LoadUrl(It.Is<string>(s => s.Equals($"{url}#toolbar=0"))), Times.Once);
|
||||
Assert.IsTrue(result);
|
||||
|
||||
browser.Reset();
|
||||
frame.Reset();
|
||||
request.SetupGet(r => r.Url).Returns($"{url}#toolbar=0");
|
||||
|
||||
result = sut.OnResourceResponse(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
|
||||
result = sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), frame.Object, request.Object, response.Object);
|
||||
|
||||
browser.Verify(b => b.Load(It.IsAny<string>()), Times.Never);
|
||||
frame.Verify(b => b.LoadUrl(It.IsAny<string>()), Times.Never);
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
|
@ -183,15 +224,113 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
Assert.AreEqual("https://www.host.org/", url);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustSearchGenericLmsSessionIdentifier()
|
||||
{
|
||||
var @event = new AutoResetEvent(false);
|
||||
var headers = new NameValueCollection();
|
||||
var newUrl = default(string);
|
||||
var request = new Mock<IRequest>();
|
||||
var response = new Mock<IResponse>();
|
||||
var userId = default(string);
|
||||
|
||||
headers.Add("X-LMS-USER-ID", "some-session-id-123");
|
||||
request.SetupGet(r => r.Url).Returns("https://www.somelms.org");
|
||||
response.SetupGet(r => r.Headers).Returns(headers);
|
||||
sut.UserIdentifierDetected += (id) =>
|
||||
{
|
||||
userId = id;
|
||||
@event.Set();
|
||||
};
|
||||
|
||||
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
|
||||
@event.WaitOne();
|
||||
Assert.AreEqual("some-session-id-123", userId);
|
||||
|
||||
headers.Clear();
|
||||
headers.Add("X-LMS-USER-ID", "other-session-id-123");
|
||||
userId = default;
|
||||
|
||||
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
|
||||
@event.WaitOne();
|
||||
Assert.AreEqual("other-session-id-123", userId);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustSearchEdxSessionIdentifier()
|
||||
{
|
||||
var @event = new AutoResetEvent(false);
|
||||
var headers = new NameValueCollection();
|
||||
var newUrl = default(string);
|
||||
var request = new Mock<IRequest>();
|
||||
var response = new Mock<IResponse>();
|
||||
var userId = default(string);
|
||||
|
||||
headers.Add("Set-Cookie", "edx-user-info=\"{\\\"username\\\": \\\"edx-123\\\"}\"; expires");
|
||||
request.SetupGet(r => r.Url).Returns("https://www.somelms.org");
|
||||
response.SetupGet(r => r.Headers).Returns(headers);
|
||||
sut.UserIdentifierDetected += (id) =>
|
||||
{
|
||||
userId = id;
|
||||
@event.Set();
|
||||
};
|
||||
|
||||
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
|
||||
@event.WaitOne();
|
||||
Assert.AreEqual("edx-123", userId);
|
||||
|
||||
headers.Clear();
|
||||
headers.Add("Set-Cookie", "edx-user-info=\"{\\\"username\\\": \\\"edx-345\\\"}\"; expires");
|
||||
userId = default;
|
||||
|
||||
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
|
||||
@event.WaitOne();
|
||||
Assert.AreEqual("edx-345", userId);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustSearchMoodleSessionIdentifier()
|
||||
{
|
||||
var @event = new AutoResetEvent(false);
|
||||
var headers = new NameValueCollection();
|
||||
var newUrl = default(string);
|
||||
var request = new Mock<IRequest>();
|
||||
var response = new Mock<IResponse>();
|
||||
var userId = default(string);
|
||||
|
||||
headers.Add("Location", "https://www.some-moodle-instance.org/moodle/login/index.php?testsession=123");
|
||||
request.SetupGet(r => r.Url).Returns("https://www.some-moodle-instance.org");
|
||||
response.SetupGet(r => r.Headers).Returns(headers);
|
||||
sut.UserIdentifierDetected += (id) =>
|
||||
{
|
||||
userId = id;
|
||||
@event.Set();
|
||||
};
|
||||
|
||||
sut.OnResourceRedirect(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), Mock.Of<IRequest>(), response.Object, ref newUrl);
|
||||
@event.WaitOne();
|
||||
Assert.AreEqual("123", userId);
|
||||
|
||||
headers.Clear();
|
||||
headers.Add("Location", "https://www.some-moodle-instance.org/moodle/login/index.php?testsession=456");
|
||||
userId = default;
|
||||
|
||||
sut.OnResourceResponse(Mock.Of<IWebBrowser>(), Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, response.Object);
|
||||
@event.WaitOne();
|
||||
Assert.AreEqual("456", userId);
|
||||
}
|
||||
|
||||
private class TestableResourceHandler : ResourceHandler
|
||||
{
|
||||
internal TestableResourceHandler(
|
||||
AppConfig appConfig,
|
||||
IRequestFilter filter,
|
||||
IKeyGenerator keyGenerator,
|
||||
ILogger logger,
|
||||
SessionMode sessionMode,
|
||||
BrowserSettings settings,
|
||||
WindowSettings windowSettings,
|
||||
IText text) : base(appConfig, filter, logger, settings, windowSettings, text)
|
||||
IText text) : base(appConfig, filter, keyGenerator, logger, sessionMode, settings, windowSettings, text)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -205,6 +344,16 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
|
|||
return base.OnBeforeResourceLoad(webBrowser, browser, frame, request, callback);
|
||||
}
|
||||
|
||||
public new bool OnProtocolExecution(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request)
|
||||
{
|
||||
return base.OnProtocolExecution(webBrowser, browser, frame, request);
|
||||
}
|
||||
|
||||
public new void OnResourceRedirect(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl)
|
||||
{
|
||||
base.OnResourceRedirect(webBrowser, browser, frame, request, response, ref newUrl);
|
||||
}
|
||||
|
||||
public new bool OnResourceResponse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response)
|
||||
{
|
||||
return base.OnResourceResponse(webBrowser, browser, frame, request, response);
|
||||
|
|
|
@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
|
|||
[assembly: AssemblyDescription("Safe Exam Browser")]
|
||||
[assembly: AssemblyCompany("ETH Zürich")]
|
||||
[assembly: AssemblyProduct("SafeExamBrowser.Browser.UnitTests")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2020 ETH Zürich, Educational Development and Technology (LET)")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
|
||||
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.props" Condition="Exists('..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.props')" />
|
||||
<Import Project="..\packages\cef.redist.x86.86.0.24\build\cef.redist.x86.props" Condition="Exists('..\packages\cef.redist.x86.86.0.24\build\cef.redist.x86.props')" />
|
||||
<Import Project="..\packages\cef.redist.x64.86.0.24\build\cef.redist.x64.props" Condition="Exists('..\packages\cef.redist.x64.86.0.24\build\cef.redist.x64.props')" />
|
||||
<Import Project="..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.props')" />
|
||||
<Import Project="..\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>
|
||||
|
@ -13,7 +15,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SafeExamBrowser.Browser.UnitTests</RootNamespace>
|
||||
<AssemblyName>SafeExamBrowser.Browser.UnitTests</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<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>
|
||||
|
@ -23,6 +25,7 @@
|
|||
<TestProjectType>UnitTest</TestProjectType>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
|
@ -61,28 +64,88 @@
|
|||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Castle.Core.4.4.1\lib\net45\Castle.Core.dll</HintPath>
|
||||
<Reference 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.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
|
||||
<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.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
|
||||
<HintPath>..\packages\MSTest.TestFramework.3.2.2\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Moq, Version=4.15.0.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Moq.4.15.2\lib\net45\Moq.dll</HintPath>
|
||||
<Reference 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.Runtime.CompilerServices.Unsafe, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
<Reference 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" />
|
||||
|
@ -95,7 +158,6 @@
|
|||
<Compile Include="Handlers\DisplayHandlerTests.cs" />
|
||||
<Compile Include="Handlers\DownloadHandlerTests.cs" />
|
||||
<Compile Include="Handlers\KeyboardHandlerTests.cs" />
|
||||
<Compile Include="Handlers\LifeSpanHandlerTests.cs" />
|
||||
<Compile Include="Handlers\RequestHandlerTests.cs" />
|
||||
<Compile Include="Handlers\ResourceHandlerTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
@ -142,13 +204,17 @@
|
|||
<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\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.props'))" />
|
||||
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\cef.redist.x64.86.0.24\build\cef.redist.x64.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\cef.redist.x64.86.0.24\build\cef.redist.x64.props'))" />
|
||||
<Error Condition="!Exists('..\packages\cef.redist.x86.86.0.24\build\cef.redist.x86.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\cef.redist.x86.86.0.24\build\cef.redist.x86.props'))" />
|
||||
<Error Condition="!Exists('..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.props'))" />
|
||||
<Error Condition="!Exists('..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.targets'))" />
|
||||
<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\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.2.1.2\build\net45\MSTest.TestAdapter.targets')" />
|
||||
<Import Project="..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.targets" Condition="Exists('..\packages\CefSharp.Common.86.0.241\build\CefSharp.Common.targets')" />
|
||||
<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>
|
|
@ -8,12 +8,44 @@
|
|||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
|
||||
<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>
|
||||
</configuration>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" /></startup></configuration>
|
||||
|
|
|
@ -1,12 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Castle.Core" version="4.4.1" targetFramework="net472" />
|
||||
<package id="cef.redist.x64" version="86.0.24" targetFramework="net472" />
|
||||
<package id="cef.redist.x86" version="86.0.24" targetFramework="net472" />
|
||||
<package id="CefSharp.Common" version="86.0.241" targetFramework="net472" />
|
||||
<package id="Moq" version="4.15.2" targetFramework="net472" />
|
||||
<package id="MSTest.TestAdapter" version="2.1.2" targetFramework="net472" />
|
||||
<package id="MSTest.TestFramework" version="2.1.2" targetFramework="net472" />
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="5.0.0" targetFramework="net472" />
|
||||
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
|
||||
<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,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -8,21 +8,22 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.Browser.Contracts;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Browser.Proxy;
|
||||
using SafeExamBrowser.Settings.Logging;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
|
@ -35,18 +36,21 @@ namespace SafeExamBrowser.Browser
|
|||
{
|
||||
public class BrowserApplication : IBrowserApplication
|
||||
{
|
||||
private int instanceIdCounter = default(int);
|
||||
private int windowIdCounter = default;
|
||||
|
||||
private AppConfig appConfig;
|
||||
private List<BrowserApplicationInstance> instances;
|
||||
private IFileSystemDialog fileSystemDialog;
|
||||
private IHashAlgorithm hashAlgorithm;
|
||||
private INativeMethods nativeMethods;
|
||||
private IMessageBox messageBox;
|
||||
private IModuleLogger logger;
|
||||
private BrowserSettings settings;
|
||||
private IText text;
|
||||
private IUserInterfaceFactory uiFactory;
|
||||
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; }
|
||||
|
@ -55,8 +59,9 @@ namespace SafeExamBrowser.Browser
|
|||
public string Tooltip { get; private set; }
|
||||
|
||||
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
public event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
|
||||
public event LoseFocusRequestedEventHandler LoseFocusRequested;
|
||||
public event TerminationRequestedEventHandler TerminationRequested;
|
||||
public event UserIdentifierDetectedEventHandler UserIdentifierDetected;
|
||||
public event WindowsChangedEventHandler WindowsChanged;
|
||||
|
||||
public BrowserApplication(
|
||||
|
@ -64,27 +69,40 @@ namespace SafeExamBrowser.Browser
|
|||
BrowserSettings settings,
|
||||
IFileSystemDialog fileSystemDialog,
|
||||
IHashAlgorithm hashAlgorithm,
|
||||
INativeMethods nativeMethods,
|
||||
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.nativeMethods = nativeMethods;
|
||||
this.instances = new List<BrowserApplicationInstance>();
|
||||
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 IEnumerable<IApplicationWindow> GetWindows()
|
||||
public void Focus(bool forward)
|
||||
{
|
||||
return new List<IApplicationWindow>(instances);
|
||||
windows.ForEach(window =>
|
||||
{
|
||||
window.Focus(forward);
|
||||
});
|
||||
}
|
||||
|
||||
public IEnumerable<IBrowserWindow> GetWindows()
|
||||
{
|
||||
return new List<IBrowserWindow>(windows);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
|
@ -98,11 +116,18 @@ namespace SafeExamBrowser.Browser
|
|||
|
||||
if (success)
|
||||
{
|
||||
InitializeIntegrityKeys();
|
||||
|
||||
if (settings.DeleteCookiesOnStartup)
|
||||
{
|
||||
DeleteCookies();
|
||||
}
|
||||
|
||||
if (settings.UseTemporaryDownAndUploadDirectory)
|
||||
{
|
||||
CreateTemporaryDownAndUploadDirectory();
|
||||
}
|
||||
|
||||
logger.Info("Initialized browser.");
|
||||
}
|
||||
else
|
||||
|
@ -113,18 +138,24 @@ namespace SafeExamBrowser.Browser
|
|||
|
||||
public void Start()
|
||||
{
|
||||
CreateNewInstance();
|
||||
CreateNewWindow();
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
logger.Info("Initiating termination...");
|
||||
AwaitReady();
|
||||
|
||||
foreach (var instance in instances)
|
||||
foreach (var window in windows)
|
||||
{
|
||||
instance.Terminated -= Instance_Terminated;
|
||||
instance.Terminate();
|
||||
logger.Info($"Terminated browser instance {instance.Id}.");
|
||||
window.Closed -= Window_Closed;
|
||||
window.Close();
|
||||
logger.Info($"Closed browser window #{window.Id}.");
|
||||
}
|
||||
|
||||
if (settings.UseTemporaryDownAndUploadDirectory)
|
||||
{
|
||||
DeleteTemporaryDownAndUploadDirectory();
|
||||
}
|
||||
|
||||
if (settings.DeleteCookiesOnShutdown)
|
||||
|
@ -145,28 +176,88 @@ namespace SafeExamBrowser.Browser
|
|||
}
|
||||
}
|
||||
|
||||
private void CreateNewInstance(string url = null)
|
||||
private void AwaitReady()
|
||||
{
|
||||
var id = ++instanceIdCounter;
|
||||
var isMainInstance = instances.Count == 0;
|
||||
var instanceLogger = logger.CloneFor($"Browser Instance #{id}");
|
||||
var startUrl = url ?? GenerateStartUrl();
|
||||
var instance = new BrowserApplicationInstance(appConfig, settings, id, isMainInstance, fileSystemDialog, hashAlgorithm, messageBox, instanceLogger, text, uiFactory, startUrl);
|
||||
// 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).
|
||||
|
||||
instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args);
|
||||
instance.PopupRequested += Instance_PopupRequested;
|
||||
instance.ResetRequested += Instance_ResetRequested;
|
||||
instance.SessionIdentifierDetected += (i) => SessionIdentifierDetected?.Invoke(i);
|
||||
instance.Terminated += Instance_Terminated;
|
||||
instance.TerminationRequested += () => TerminationRequested?.Invoke();
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
|
||||
instance.Initialize();
|
||||
instances.Add(instance);
|
||||
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);
|
||||
|
||||
logger.Info($"Created browser instance {instance.Id}.");
|
||||
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
|
||||
|
@ -240,6 +331,7 @@ namespace SafeExamBrowser.Browser
|
|||
var error = logger.LogLevel == LogLevel.Error;
|
||||
var cefSettings = new CefSettings();
|
||||
|
||||
cefSettings.AcceptLanguageList = CultureInfo.CurrentUICulture.Name;
|
||||
cefSettings.CachePath = appConfig.BrowserCachePath;
|
||||
cefSettings.CefCommandLineArgs.Add("touch-events", "enabled");
|
||||
cefSettings.LogFile = appConfig.BrowserLogFilePath;
|
||||
|
@ -247,6 +339,11 @@ namespace SafeExamBrowser.Browser
|
|||
cefSettings.PersistSessionCookies = !settings.DeleteCookiesOnStartup || !settings.DeleteCookiesOnShutdown;
|
||||
cefSettings.UserAgent = InitializeUserAgent();
|
||||
|
||||
if (!settings.AllowPageZoom)
|
||||
{
|
||||
cefSettings.CefCommandLineArgs.Add("disable-pinch");
|
||||
}
|
||||
|
||||
if (!settings.AllowPdfReader)
|
||||
{
|
||||
cefSettings.CefCommandLineArgs.Add("disable-pdf-extension");
|
||||
|
@ -257,8 +354,13 @@ namespace SafeExamBrowser.Browser
|
|||
cefSettings.CefCommandLineArgs.Add("disable-spell-checking");
|
||||
}
|
||||
|
||||
cefSettings.CefCommandLineArgs.Add("enable-media-stream");
|
||||
cefSettings.CefCommandLineArgs.Add("enable-usermedia-screen-capturing");
|
||||
cefSettings.CefCommandLineArgs.Add("use-fake-ui-for-media-stream");
|
||||
|
||||
InitializeProxySettings(cefSettings);
|
||||
|
||||
logger.Debug($"Accept Language: {cefSettings.AcceptLanguageList}");
|
||||
logger.Debug($"Cache Path: {cefSettings.CachePath}");
|
||||
logger.Debug($"Engine Version: Chromium {Cef.ChromiumVersion}, CEF {Cef.CefVersion}, CefSharp {Cef.CefSharpVersion}");
|
||||
logger.Debug($"Log File: {cefSettings.LogFile}");
|
||||
|
@ -269,6 +371,22 @@ namespace SafeExamBrowser.Browser
|
|||
return cefSettings;
|
||||
}
|
||||
|
||||
private void InitializeIntegrityKeys()
|
||||
{
|
||||
logger.Debug($"Browser Exam Key (BEK) transmission is {(settings.SendBrowserExamKey ? "enabled" : "disabled")}.");
|
||||
logger.Debug($"Configuration Key (CK) transmission is {(settings.SendConfigurationKey ? "enabled" : "disabled")}.");
|
||||
|
||||
if (settings.CustomBrowserExamKey != default)
|
||||
{
|
||||
keyGenerator.UseCustomBrowserExamKey(settings.CustomBrowserExamKey);
|
||||
logger.Debug($"The browser application will be using a custom browser exam key.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"The browser application will be using the default browser exam key.");
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeProxySettings(CefSettings cefSettings)
|
||||
{
|
||||
if (settings.Proxy.Policy == ProxyPolicy.Custom)
|
||||
|
@ -342,24 +460,32 @@ namespace SafeExamBrowser.Browser
|
|||
throw new NotImplementedException($"Mapping for proxy protocol '{protocol}' is not yet implemented!");
|
||||
}
|
||||
|
||||
private void Instance_PopupRequested(PopupRequestedEventArgs args)
|
||||
private void Window_Closed(int id)
|
||||
{
|
||||
logger.Info($"Received request to create new instance{(settings.AdditionalWindow.UrlPolicy.CanLog() ? $" for '{args.Url}'" : "")}...");
|
||||
CreateNewInstance(args.Url);
|
||||
windows.Remove(windows.First(i => i.Id == id));
|
||||
WindowsChanged?.Invoke();
|
||||
logger.Info($"Window #{id} has been closed.");
|
||||
}
|
||||
|
||||
private void Instance_ResetRequested()
|
||||
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 instance in instances)
|
||||
foreach (var window in windows)
|
||||
{
|
||||
instance.Terminated -= Instance_Terminated;
|
||||
instance.Terminate();
|
||||
logger.Info($"Terminated browser instance {instance.Id}.");
|
||||
window.Closed -= Window_Closed;
|
||||
window.Close();
|
||||
logger.Info($"Closed browser window #{window.Id}.");
|
||||
}
|
||||
|
||||
instances.Clear();
|
||||
windows.Clear();
|
||||
WindowsChanged?.Invoke();
|
||||
|
||||
if (settings.DeleteCookiesOnStartup && settings.DeleteCookiesOnShutdown)
|
||||
|
@ -368,14 +494,8 @@ namespace SafeExamBrowser.Browser
|
|||
}
|
||||
|
||||
nativeMethods.EmptyClipboard();
|
||||
CreateNewInstance();
|
||||
CreateNewWindow();
|
||||
logger.Info("Successfully reset browser.");
|
||||
}
|
||||
|
||||
private void Instance_Terminated(int id)
|
||||
{
|
||||
instances.Remove(instances.First(i => i.Id == id));
|
||||
WindowsChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,141 +1,195 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using CefSharp.WinForms;
|
||||
using SafeExamBrowser.Browser.Wrapper;
|
||||
using SafeExamBrowser.Browser.Wrapper.Events;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Data;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Browser.Events;
|
||||
|
||||
namespace SafeExamBrowser.Browser
|
||||
{
|
||||
internal class BrowserControl : ChromiumWebBrowser, IBrowserControl
|
||||
internal class BrowserControl : IBrowserControl
|
||||
{
|
||||
private IContextMenuHandler contextMenuHandler;
|
||||
private IDialogHandler dialogHandler;
|
||||
private IDisplayHandler displayHandler;
|
||||
private IDownloadHandler downloadHandler;
|
||||
private IKeyboardHandler keyboardHandler;
|
||||
private ILifeSpanHandler lifeSpanHandler;
|
||||
private IRequestHandler requestHandler;
|
||||
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 AddressChangedEventHandler addressChanged;
|
||||
private LoadFailedEventHandler loadFailed;
|
||||
private LoadingStateChangedEventHandler loadingStateChanged;
|
||||
private TitleChangedEventHandler titleChanged;
|
||||
public string Address => control.Address;
|
||||
public bool CanNavigateBackwards => control.IsBrowserInitialized && control.BrowserCore.CanGoBack;
|
||||
public bool CanNavigateForwards => control.IsBrowserInitialized && control.BrowserCore.CanGoForward;
|
||||
public object EmbeddableControl => control;
|
||||
|
||||
public bool CanNavigateBackwards => GetBrowser().CanGoBack;
|
||||
public bool CanNavigateForwards => GetBrowser().CanGoForward;
|
||||
|
||||
event AddressChangedEventHandler IBrowserControl.AddressChanged
|
||||
{
|
||||
add { addressChanged += value; }
|
||||
remove { addressChanged -= value; }
|
||||
}
|
||||
|
||||
event LoadFailedEventHandler IBrowserControl.LoadFailed
|
||||
{
|
||||
add { loadFailed += value; }
|
||||
remove { loadFailed -= value; }
|
||||
}
|
||||
|
||||
event LoadingStateChangedEventHandler IBrowserControl.LoadingStateChanged
|
||||
{
|
||||
add { loadingStateChanged += value; }
|
||||
remove { loadingStateChanged -= value; }
|
||||
}
|
||||
|
||||
event TitleChangedEventHandler IBrowserControl.TitleChanged
|
||||
{
|
||||
add { titleChanged += value; }
|
||||
remove { titleChanged -= value; }
|
||||
}
|
||||
public event AddressChangedEventHandler AddressChanged;
|
||||
public event LoadFailedEventHandler LoadFailed;
|
||||
public event LoadingStateChangedEventHandler LoadingStateChanged;
|
||||
public event TitleChangedEventHandler TitleChanged;
|
||||
|
||||
public BrowserControl(
|
||||
IContextMenuHandler contextMenuHandler,
|
||||
Clipboard clipboard,
|
||||
ICefSharpControl control,
|
||||
IDialogHandler dialogHandler,
|
||||
IDisplayHandler displayHandler,
|
||||
IDownloadHandler downloadHandler,
|
||||
IKeyboardHandler keyboardHandler,
|
||||
ILifeSpanHandler lifeSpanHandler,
|
||||
IRequestHandler requestHandler,
|
||||
string url) : base(url)
|
||||
ILogger logger,
|
||||
IRenderProcessMessageHandler renderProcessMessageHandler,
|
||||
IRequestHandler requestHandler)
|
||||
{
|
||||
this.contextMenuHandler = contextMenuHandler;
|
||||
this.control = control;
|
||||
this.clipboard = clipboard;
|
||||
this.dialogHandler = dialogHandler;
|
||||
this.displayHandler = displayHandler;
|
||||
this.downloadHandler = downloadHandler;
|
||||
this.keyboardHandler = keyboardHandler;
|
||||
this.lifeSpanHandler = lifeSpanHandler;
|
||||
this.logger = logger;
|
||||
this.renderProcessMessageHandler = renderProcessMessageHandler;
|
||||
this.requestHandler = requestHandler;
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
if (!IsDisposed)
|
||||
if (!control.IsDisposed)
|
||||
{
|
||||
Dispose(true);
|
||||
control.Dispose(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
public void ExecuteJavaScript(string code, Action<JavaScriptResult> callback = default)
|
||||
{
|
||||
AddressChanged += (o, args) => addressChanged?.Invoke(args.Address);
|
||||
LoadError += BrowserControl_LoadError;
|
||||
LoadingStateChanged += (o, args) => loadingStateChanged?.Invoke(args.IsLoading);
|
||||
TitleChanged += (o, args) => titleChanged?.Invoke(args.Title);
|
||||
|
||||
DialogHandler = dialogHandler;
|
||||
DisplayHandler = displayHandler;
|
||||
DownloadHandler = downloadHandler;
|
||||
KeyboardHandler = keyboardHandler;
|
||||
LifeSpanHandler = lifeSpanHandler;
|
||||
MenuHandler = contextMenuHandler;
|
||||
RequestHandler = requestHandler;
|
||||
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)
|
||||
{
|
||||
this.Find(0, term, forward, caseSensitive, !isInitial);
|
||||
control.Find(term, forward, caseSensitive, !isInitial);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
clipboard.Changed += Clipboard_Changed;
|
||||
|
||||
control.AddressChanged += (o, e) => AddressChanged?.Invoke(e.Address);
|
||||
control.AuthCredentialsRequired += (w, b, o, i, h, p, r, s, c, a) => a.Value = requestHandler.GetAuthCredentials(w, b, o, i, h, p, r, s, c);
|
||||
control.BeforeBrowse += (w, b, f, r, u, i, a) => a.Value = requestHandler.OnBeforeBrowse(w, b, f, r, u, i);
|
||||
control.BeforeDownload += (w, b, d, c) => downloadHandler.OnBeforeDownload(w, b, d, c);
|
||||
control.CanDownload += (w, b, u, r, a) => a.Value = downloadHandler.CanDownload(w, b, u, r);
|
||||
control.ContextCreated += (w, b, f) => renderProcessMessageHandler.OnContextCreated(w, b, f);
|
||||
control.ContextReleased += (w, b, f) => renderProcessMessageHandler.OnContextReleased(w, b, f);
|
||||
control.DownloadUpdated += (w, b, d, c) => downloadHandler.OnDownloadUpdated(w, b, d, c);
|
||||
control.FaviconUrlChanged += (w, b, u) => displayHandler.OnFaviconUrlChange(w, b, u);
|
||||
control.FileDialogRequested += (w, b, m, t, d, f, c) => dialogHandler.OnFileDialog(w, b, m, t, d, f, c);
|
||||
control.FocusedNodeChanged += (w, b, f, n) => renderProcessMessageHandler.OnFocusedNodeChanged(w, b, f, n);
|
||||
control.IsBrowserInitializedChanged += Control_IsBrowserInitializedChanged;
|
||||
control.KeyEvent += (w, b, t, k, n, m, s) => keyboardHandler.OnKeyEvent(w, b, t, k, n, m, s);
|
||||
control.LoadError += (o, e) => LoadFailed?.Invoke((int) e.ErrorCode, e.ErrorText, e.Frame.IsMain, e.FailedUrl);
|
||||
control.LoadingProgressChanged += (w, b, p) => displayHandler.OnLoadingProgressChange(w, b, p);
|
||||
control.LoadingStateChanged += (o, e) => LoadingStateChanged?.Invoke(e.IsLoading);
|
||||
control.OpenUrlFromTab += (w, b, f, u, t, g, a) => a.Value = requestHandler.OnOpenUrlFromTab(w, b, f, u, t, g);
|
||||
control.PreKeyEvent += (IWebBrowser w, IBrowser b, KeyType t, int k, int n, CefEventFlags m, bool i, ref bool s, GenericEventArgs a) => a.Value = keyboardHandler.OnPreKeyEvent(w, b, t, k, n, m, i, ref s);
|
||||
control.ResourceRequestHandlerRequired += (IWebBrowser w, IBrowser b, IFrame f, IRequest r, bool n, bool d, string i, ref bool h, ResourceRequestEventArgs a) => a.Handler = requestHandler.GetResourceRequestHandler(w, b, f, r, n, d, i, ref h);
|
||||
control.TitleChanged += (o, e) => TitleChanged?.Invoke(e.Title);
|
||||
control.UncaughtExceptionEvent += (w, b, f, e) => renderProcessMessageHandler.OnUncaughtException(w, b, f, e);
|
||||
|
||||
if (control is IWebBrowser webBrowser)
|
||||
{
|
||||
webBrowser.JavascriptMessageReceived += WebBrowser_JavascriptMessageReceived;
|
||||
}
|
||||
}
|
||||
|
||||
public void NavigateBackwards()
|
||||
{
|
||||
GetBrowser().GoBack();
|
||||
control.BrowserCore.GoBack();
|
||||
}
|
||||
|
||||
public void NavigateForwards()
|
||||
{
|
||||
GetBrowser().GoForward();
|
||||
control.BrowserCore.GoForward();
|
||||
}
|
||||
|
||||
public void NavigateTo(string address)
|
||||
{
|
||||
Load(address);
|
||||
control.Load(address);
|
||||
}
|
||||
|
||||
public void ShowDeveloperConsole()
|
||||
{
|
||||
GetBrowser().ShowDevTools();
|
||||
control.BrowserCore.ShowDevTools();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
GetBrowser().Reload();
|
||||
control.BrowserCore.Reload();
|
||||
}
|
||||
|
||||
public void Zoom(double level)
|
||||
{
|
||||
GetBrowser().SetZoomLevel(level);
|
||||
control.BrowserCore.SetZoomLevel(level);
|
||||
}
|
||||
|
||||
private void BrowserControl_LoadError(object sender, LoadErrorEventArgs e)
|
||||
private void Clipboard_Changed(long id)
|
||||
{
|
||||
loadFailed?.Invoke((int) e.ErrorCode, e.ErrorText, e.FailedUrl);
|
||||
ExecuteJavaScript($"SafeExamBrowser.clipboard.update({id}, '{clipboard.Content}');");
|
||||
}
|
||||
|
||||
private void Control_IsBrowserInitializedChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (control.IsBrowserInitialized)
|
||||
{
|
||||
control.BrowserCore.GetHost().SetFocus(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void WebBrowser_JavascriptMessageReceived(object sender, JavascriptMessageReceivedEventArgs e)
|
||||
{
|
||||
clipboard.Process(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
|
||||
namespace SafeExamBrowser.Browser
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -7,21 +7,25 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using CefSharp.WinForms.Handler;
|
||||
using CefSharp.WinForms.Host;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Applications.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Browser.Filters;
|
||||
using SafeExamBrowser.Browser.Handlers;
|
||||
using SafeExamBrowser.Browser.Wrapper;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Core.Contracts.Resources.Icons;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
|
@ -31,77 +35,94 @@ 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 BrowserApplicationInstance : IApplicationWindow
|
||||
internal class BrowserWindow : Contracts.IBrowserWindow
|
||||
{
|
||||
private const string CLEAR_FIND_TERM = "thisisahacktoclearthesearchresultsasitappearsthatthereisnosuchfunctionalityincef";
|
||||
private const double ZOOM_FACTOR = 0.2;
|
||||
|
||||
private AppConfig appConfig;
|
||||
private IBrowserControl control;
|
||||
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 HttpClient httpClient;
|
||||
private bool isMainInstance;
|
||||
private IFileSystemDialog fileSystemDialog;
|
||||
private IHashAlgorithm hashAlgorithm;
|
||||
private IMessageBox messageBox;
|
||||
private IModuleLogger logger;
|
||||
private BrowserSettings settings;
|
||||
private string startUrl;
|
||||
private IText text;
|
||||
private IUserInterfaceFactory uiFactory;
|
||||
private double zoomLevel;
|
||||
|
||||
private WindowSettings WindowSettings
|
||||
{
|
||||
get { return isMainInstance ? settings.MainWindow : settings.AdditionalWindow; }
|
||||
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 SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
|
||||
internal event InstanceTerminatedEventHandler Terminated;
|
||||
internal event TerminationRequestedEventHandler TerminationRequested;
|
||||
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
|
||||
|
||||
public event IconChangedEventHandler IconChanged;
|
||||
public event TitleChangedEventHandler TitleChanged;
|
||||
|
||||
public BrowserApplicationInstance(
|
||||
public BrowserWindow(
|
||||
AppConfig appConfig,
|
||||
BrowserSettings settings,
|
||||
int id,
|
||||
bool isMainInstance,
|
||||
Clipboard clipboard,
|
||||
IFileSystemDialog fileSystemDialog,
|
||||
IHashAlgorithm hashAlgorithm,
|
||||
IMessageBox messageBox,
|
||||
int id,
|
||||
bool isMainWindow,
|
||||
IKeyGenerator keyGenerator,
|
||||
IModuleLogger logger,
|
||||
IMessageBox messageBox,
|
||||
SessionMode sessionMode,
|
||||
BrowserSettings settings,
|
||||
string startUrl,
|
||||
IText text,
|
||||
IUserInterfaceFactory uiFactory,
|
||||
string startUrl)
|
||||
IUserInterfaceFactory uiFactory)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.Id = id;
|
||||
this.httpClient = new HttpClient();
|
||||
this.isMainInstance = isMainInstance;
|
||||
this.clipboard = clipboard;
|
||||
this.fileSystemDialog = fileSystemDialog;
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.messageBox = messageBox;
|
||||
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;
|
||||
this.startUrl = startUrl;
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
|
@ -109,70 +130,116 @@ namespace SafeExamBrowser.Browser
|
|||
window.BringToForeground();
|
||||
}
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
InitializeControl();
|
||||
InitializeWindow();
|
||||
}
|
||||
|
||||
internal void Terminate()
|
||||
internal void Close()
|
||||
{
|
||||
window.Close();
|
||||
control.Destroy();
|
||||
Control.Destroy();
|
||||
}
|
||||
|
||||
private void InitializeControl()
|
||||
internal void Focus(bool forward)
|
||||
{
|
||||
var contextMenuHandler = new ContextMenuHandler();
|
||||
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 lifeSpanHandler = new LifeSpanHandler();
|
||||
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, logger, settings, WindowSettings, text);
|
||||
var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, resourceHandler, settings, WindowSettings, text);
|
||||
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;
|
||||
lifeSpanHandler.PopupRequested += LifeSpanHandler_PopupRequested;
|
||||
resourceHandler.SessionIdentifierDetected += (id) => SessionIdentifierDetected?.Invoke(id);
|
||||
requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited;
|
||||
requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
|
||||
resourceHandler.UserIdentifierDetected += (id) => UserIdentifierDetected?.Invoke(id);
|
||||
|
||||
InitializeRequestFilter(requestFilter);
|
||||
|
||||
control = new BrowserControl(
|
||||
contextMenuHandler,
|
||||
dialogHandler,
|
||||
displayHandler,
|
||||
downloadHandler,
|
||||
keyboardHandler,
|
||||
lifeSpanHandler,
|
||||
requestHandler,
|
||||
startUrl);
|
||||
control.AddressChanged += Control_AddressChanged;
|
||||
control.LoadFailed += Control_LoadFailed;
|
||||
control.LoadingStateChanged += Control_LoadingStateChanged;
|
||||
control.TitleChanged += Control_TitleChanged;
|
||||
Control = 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();
|
||||
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)
|
||||
|
@ -201,31 +268,11 @@ namespace SafeExamBrowser.Browser
|
|||
}
|
||||
}
|
||||
|
||||
private void InitializeWindow()
|
||||
{
|
||||
window = uiFactory.CreateBrowserWindow(control, settings, isMainInstance);
|
||||
window.Closing += Window_Closing;
|
||||
window.AddressChanged += Window_AddressChanged;
|
||||
window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
|
||||
window.DeveloperConsoleRequested += Window_DeveloperConsoleRequested;
|
||||
window.FindRequested += Window_FindRequested;
|
||||
window.ForwardNavigationRequested += Window_ForwardNavigationRequested;
|
||||
window.HomeNavigationRequested += HomeNavigationRequested;
|
||||
window.ReloadRequested += ReloadRequested;
|
||||
window.ZoomInRequested += ZoomInRequested;
|
||||
window.ZoomOutRequested += ZoomOutRequested;
|
||||
window.ZoomResetRequested += ZoomResetRequested;
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
window.Show();
|
||||
|
||||
Handle = window.Handle;
|
||||
|
||||
logger.Debug("Initialized browser window.");
|
||||
}
|
||||
|
||||
private void Control_AddressChanged(string address)
|
||||
{
|
||||
logger.Debug($"Navigated{(WindowSettings.UrlPolicy.CanLog() ? $" to '{address}'" : "")}.");
|
||||
logger.Info($"Navigated{(WindowSettings.UrlPolicy.CanLog() ? $" to '{address}'" : "")}.");
|
||||
|
||||
Url = address;
|
||||
window.UpdateAddress(address);
|
||||
|
||||
if (WindowSettings.UrlPolicy == UrlPolicy.Always || WindowSettings.UrlPolicy == UrlPolicy.BeforeTitle)
|
||||
|
@ -234,37 +281,51 @@ namespace SafeExamBrowser.Browser
|
|||
window.UpdateTitle(address);
|
||||
TitleChanged?.Invoke(address);
|
||||
}
|
||||
|
||||
AutoFind();
|
||||
}
|
||||
|
||||
private void Control_LoadFailed(int errorCode, string errorText, string url)
|
||||
private void Control_LoadFailed(int errorCode, string errorText, bool isMainRequest, string url)
|
||||
{
|
||||
if (errorCode == (int) CefErrorCode.None)
|
||||
switch (errorCode)
|
||||
{
|
||||
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was successful.");
|
||||
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;
|
||||
}
|
||||
else if (errorCode == (int) CefErrorCode.Aborted)
|
||||
{
|
||||
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} was aborted.");
|
||||
}
|
||||
else if (errorCode == (int) CefErrorCode.UnknownUrlScheme)
|
||||
{
|
||||
logger.Info($"Request{(WindowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} contains unknown URL scheme and will be handled by the OS.");
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
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 : "") + $" {errorText} ({errorCode})";
|
||||
var message = text.Get(TextKey.Browser_LoadErrorMessage).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "") + $" {requestInfo}";
|
||||
|
||||
logger.Warn($"Request{(WindowSettings.UrlPolicy.CanLogError() ? $" for '{url}'" : "")} failed: {errorText} ({errorCode}).");
|
||||
|
||||
Task.Run(() => messageBox.Show(message, title, icon: MessageBoxIcon.Error, parent: window)).ContinueWith(_ => control.NavigateBackwards());
|
||||
Task.Run(() => messageBox.Show(message, title, icon: MessageBoxIcon.Error, parent: window)).ContinueWith(_ => Control.NavigateBackwards());
|
||||
}
|
||||
}
|
||||
|
||||
private void Control_LoadingStateChanged(bool isLoading)
|
||||
{
|
||||
window.CanNavigateBackwards = WindowSettings.AllowBackwardNavigation && control.CanNavigateBackwards;
|
||||
window.CanNavigateForwards = WindowSettings.AllowForwardNavigation && control.CanNavigateForwards;
|
||||
window.CanNavigateBackwards = WindowSettings.AllowBackwardNavigation && Control.CanNavigateBackwards;
|
||||
window.CanNavigateForwards = WindowSettings.AllowForwardNavigation && Control.CanNavigateForwards;
|
||||
window.UpdateLoadingState(isLoading);
|
||||
}
|
||||
|
||||
|
@ -289,9 +350,13 @@ namespace SafeExamBrowser.Browser
|
|||
{
|
||||
initialPath = args.InitialPath;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(settings.DownAndUploadDirectory))
|
||||
{
|
||||
initialPath = KnownFolders.Downloads.ExpandedPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
initialPath = string.IsNullOrEmpty(settings.DownAndUploadDirectory) ? KnownFolders.Downloads.ExpandedPath : Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory);
|
||||
initialPath = Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory);
|
||||
}
|
||||
|
||||
if (isAllowed)
|
||||
|
@ -302,7 +367,8 @@ namespace SafeExamBrowser.Browser
|
|||
initialPath,
|
||||
title: args.Title,
|
||||
parent: window,
|
||||
restrictNavigation: !settings.AllowCustomDownAndUploadLocation);
|
||||
restrictNavigation: !settings.AllowCustomDownAndUploadLocation,
|
||||
showElementPath: settings.ShowFileSystemElementPath);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
|
@ -318,6 +384,7 @@ namespace SafeExamBrowser.Browser
|
|||
else
|
||||
{
|
||||
logger.Info($"Blocked file system dialog to {args.Operation}->{args.Element}, as {(isDownload ? "downloading" : "uploading")} is not allowed.");
|
||||
ShowDownUploadNotAllowedMessage(isDownload);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -367,6 +434,11 @@ namespace SafeExamBrowser.Browser
|
|||
}
|
||||
}
|
||||
|
||||
private void DownloadHandler_DownloadAborted()
|
||||
{
|
||||
ShowDownUploadNotAllowedMessage();
|
||||
}
|
||||
|
||||
private void DownloadHandler_DownloadUpdated(DownloadItemState state)
|
||||
{
|
||||
window.UpdateDownloadState(state);
|
||||
|
@ -374,7 +446,7 @@ namespace SafeExamBrowser.Browser
|
|||
|
||||
private void HomeNavigationRequested()
|
||||
{
|
||||
if (isMainInstance && (settings.UseStartUrlAsHomeUrl || !string.IsNullOrWhiteSpace(settings.HomeUrl)))
|
||||
if (IsMainWindow && (settings.UseStartUrlAsHomeUrl || !string.IsNullOrWhiteSpace(settings.HomeUrl)))
|
||||
{
|
||||
var navigate = false;
|
||||
var url = settings.UseStartUrlAsHomeUrl ? settings.StartUrl : settings.HomeUrl;
|
||||
|
@ -411,7 +483,7 @@ namespace SafeExamBrowser.Browser
|
|||
|
||||
if (navigate)
|
||||
{
|
||||
control.NavigateTo(url);
|
||||
Control.NavigateTo(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -424,32 +496,91 @@ namespace SafeExamBrowser.Browser
|
|||
}
|
||||
}
|
||||
|
||||
private void LifeSpanHandler_PopupRequested(PopupRequestedEventArgs args)
|
||||
private void KeyboardHandler_FocusAddressBarRequested()
|
||||
{
|
||||
var validCurrentUri = Uri.TryCreate(control.Address, UriKind.Absolute, out var currentUri);
|
||||
var validNewUri = Uri.TryCreate(args.Url, UriKind.Absolute, out var newUri);
|
||||
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 '{args.Url}'" : "")}...");
|
||||
PopupRequested?.Invoke(args);
|
||||
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() ? $" '{args.Url}'" : "")} directly...");
|
||||
control.NavigateTo(args.Url);
|
||||
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 '{args.Url}'" : "")} as it targets a different host.");
|
||||
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 '{args.Url}'" : "")}.");
|
||||
logger.Info($"Blocked request to open new window{(WindowSettings.UrlPolicy.CanLog() ? $" for '{targetUrl}'" : "")}.");
|
||||
break;
|
||||
}
|
||||
|
||||
return creation;
|
||||
}
|
||||
|
||||
private void RequestHandler_QuitUrlVisited(string url)
|
||||
|
@ -496,7 +627,7 @@ namespace SafeExamBrowser.Browser
|
|||
var message = text.Get(TextKey.MessageBox_BrowserNavigationBlocked).Replace("%%URL%%", WindowSettings.UrlPolicy.CanLogError() ? url : "");
|
||||
var title = text.Get(TextKey.MessageBox_BrowserNavigationBlockedTitle);
|
||||
|
||||
control.TitleChanged -= Control_TitleChanged;
|
||||
Control.TitleChanged -= Control_TitleChanged;
|
||||
|
||||
if (url.Equals(startUrl, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
@ -505,7 +636,7 @@ namespace SafeExamBrowser.Browser
|
|||
}
|
||||
|
||||
messageBox.Show(message, title, parent: window);
|
||||
control.TitleChanged += Control_TitleChanged;
|
||||
Control.TitleChanged += Control_TitleChanged;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -518,7 +649,7 @@ namespace SafeExamBrowser.Browser
|
|||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
logger.Debug("The user confirmed reloading the current page...");
|
||||
control.Reload();
|
||||
Control.Reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -528,7 +659,7 @@ namespace SafeExamBrowser.Browser
|
|||
else if (WindowSettings.AllowReloading)
|
||||
{
|
||||
logger.Debug("Reloading current page...");
|
||||
control.Reload();
|
||||
Control.Reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -536,6 +667,14 @@ namespace SafeExamBrowser.Browser
|
|||
}
|
||||
}
|
||||
|
||||
private void ShowDownUploadNotAllowedMessage(bool isDownload = true)
|
||||
{
|
||||
var message = isDownload ? TextKey.MessageBox_DownloadNotAllowed : TextKey.MessageBox_UploadNotAllowed;
|
||||
var title = isDownload ? TextKey.MessageBox_DownloadNotAllowedTitle : TextKey.MessageBox_UploadNotAllowedTitle;
|
||||
|
||||
messageBox.Show(message, title, icon: MessageBoxIcon.Warning, parent: window);
|
||||
}
|
||||
|
||||
private void Window_AddressChanged(string address)
|
||||
{
|
||||
var isValid = Uri.TryCreate(address, UriKind.Absolute, out _) || Uri.TryCreate($"https://{address}", UriKind.Absolute, out _);
|
||||
|
@ -543,7 +682,7 @@ namespace SafeExamBrowser.Browser
|
|||
if (isValid)
|
||||
{
|
||||
logger.Debug($"The user requested to navigate to '{address}', the URI is valid.");
|
||||
control.NavigateTo(address);
|
||||
Control.NavigateTo(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -555,34 +694,49 @@ namespace SafeExamBrowser.Browser
|
|||
private void Window_BackwardNavigationRequested()
|
||||
{
|
||||
logger.Debug("Navigating backwards...");
|
||||
control.NavigateBackwards();
|
||||
Control.NavigateBackwards();
|
||||
}
|
||||
|
||||
private void Window_Closing()
|
||||
{
|
||||
logger.Info($"Instance has terminated.");
|
||||
control.Destroy();
|
||||
Terminated?.Invoke(Id);
|
||||
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();
|
||||
Control.ShowDeveloperConsole();
|
||||
}
|
||||
|
||||
private void Window_FindRequested(string term, bool isInitial, bool caseSensitive, bool forward = true)
|
||||
{
|
||||
if (settings.AllowFind)
|
||||
{
|
||||
control.Find(term, isInitial, caseSensitive, forward);
|
||||
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();
|
||||
Control.NavigateForwards();
|
||||
}
|
||||
|
||||
private void Window_LoseFocusRequested(bool forward)
|
||||
{
|
||||
LoseFocusRequested?.Invoke(forward);
|
||||
}
|
||||
|
||||
private void ZoomInRequested()
|
||||
|
@ -590,7 +744,7 @@ namespace SafeExamBrowser.Browser
|
|||
if (settings.AllowPageZoom && CalculateZoomPercentage() < 300)
|
||||
{
|
||||
zoomLevel += ZOOM_FACTOR;
|
||||
control.Zoom(zoomLevel);
|
||||
Control.Zoom(zoomLevel);
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
logger.Debug($"Increased page zoom to {CalculateZoomPercentage()}%.");
|
||||
}
|
||||
|
@ -601,7 +755,7 @@ namespace SafeExamBrowser.Browser
|
|||
if (settings.AllowPageZoom && CalculateZoomPercentage() > 25)
|
||||
{
|
||||
zoomLevel -= ZOOM_FACTOR;
|
||||
control.Zoom(zoomLevel);
|
||||
Control.Zoom(zoomLevel);
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
logger.Debug($"Decreased page zoom to {CalculateZoomPercentage()}%.");
|
||||
}
|
||||
|
@ -612,12 +766,20 @@ namespace SafeExamBrowser.Browser
|
|||
if (settings.AllowPageZoom)
|
||||
{
|
||||
zoomLevel = 0;
|
||||
control.Zoom(0);
|
||||
Control.Zoom(0);
|
||||
window.UpdateZoomLevel(CalculateZoomPercentage());
|
||||
logger.Debug($"Reset page zoom to {CalculateZoomPercentage()}%.");
|
||||
}
|
||||
}
|
||||
|
||||
private void AutoFind()
|
||||
{
|
||||
if (settings.AllowFind && !string.IsNullOrEmpty(findParameters.term) && !CLEAR_FIND_TERM.Equals(findParameters.term, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Control.Find(findParameters.term, findParameters.isInitial, findParameters.caseSensitive, findParameters.forward);
|
||||
}
|
||||
}
|
||||
|
||||
private double CalculateZoomPercentage()
|
||||
{
|
||||
return (zoomLevel * 25.0) + 100.0;
|
72
SafeExamBrowser.Browser/Clipboard.cs
Normal file
72
SafeExamBrowser.Browser/Clipboard.cs
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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; }
|
||||
}
|
||||
}
|
||||
}
|
16
SafeExamBrowser.Browser/Content/Api.js
Normal file
16
SafeExamBrowser.Browser/Content/Api.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
SafeExamBrowser = {
|
||||
version: 'SEB_Windows_%%_VERSION_%%',
|
||||
security: {
|
||||
browserExamKey: '%%_BEK_%%',
|
||||
configKey: '%%_CK_%%',
|
||||
updateKeys: (callback) => callback()
|
||||
}
|
||||
}
|
195
SafeExamBrowser.Browser/Content/Clipboard.js
Normal file
195
SafeExamBrowser.Browser/Content/Clipboard.js
Normal file
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Original code taken and slightly adapted from https://github.com/eqsoft/seb2/blob/master/browser/app/modules/SebBrowser.jsm#L1215.
|
||||
*/
|
||||
|
||||
SafeExamBrowser.clipboard = {
|
||||
id: Math.round((Date.now() + Math.random()) * 1000),
|
||||
ranges: [],
|
||||
text: "",
|
||||
|
||||
clear: function () {
|
||||
this.ranges = [];
|
||||
this.text = "";
|
||||
},
|
||||
|
||||
getContentEncoded: function () {
|
||||
var bytes = new TextEncoder().encode(this.text);
|
||||
var base64 = btoa(String.fromCodePoint(...bytes));
|
||||
|
||||
return base64;
|
||||
},
|
||||
|
||||
update: function (id, base64) {
|
||||
if (this.id != id) {
|
||||
var bytes = Uint8Array.from(atob(base64), (m) => m.codePointAt(0));
|
||||
var content = new TextDecoder().decode(bytes);
|
||||
|
||||
this.ranges = [];
|
||||
this.text = content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copySelectedData(e) {
|
||||
if (e.target.contentEditable && e.target.setRangeText) {
|
||||
SafeExamBrowser.clipboard.text = e.target.value.substring(e.target.selectionStart, e.target.selectionEnd);
|
||||
SafeExamBrowser.clipboard.ranges = [];
|
||||
} else {
|
||||
var selection = e.target.ownerDocument.defaultView.getSelection();
|
||||
var text = "";
|
||||
|
||||
for (var i = 0; i < selection.rangeCount; i++) {
|
||||
SafeExamBrowser.clipboard.ranges[i] = selection.getRangeAt(i).cloneContents();
|
||||
text += SafeExamBrowser.clipboard.ranges[i].textContent;
|
||||
}
|
||||
|
||||
SafeExamBrowser.clipboard.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
function cutSelectedData(e) {
|
||||
if (e.target.contentEditable && e.target.setRangeText) {
|
||||
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
|
||||
} else {
|
||||
var designMode = e.target.ownerDocument.designMode;
|
||||
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
|
||||
var selection = e.target.ownerDocument.defaultView.getSelection();
|
||||
|
||||
for (var i = 0; i < selection.rangeCount; i++) {
|
||||
var range = selection.getRangeAt(i);
|
||||
|
||||
if (designMode === 'on') {
|
||||
range.deleteContents();
|
||||
} else {
|
||||
if (contentEditables.length) {
|
||||
contentEditables.forEach(node => {
|
||||
if (node.contains(range.commonAncestorContainer)) {
|
||||
range.deleteContents();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pasteSelectedData(e) {
|
||||
if (e.target.contentEditable && e.target.setRangeText) {
|
||||
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
|
||||
e.target.setRangeText(SafeExamBrowser.clipboard.text, e.target.selectionStart, e.target.selectionStart + SafeExamBrowser.clipboard.text.length, 'end');
|
||||
} else {
|
||||
var w = e.target.ownerDocument.defaultView;
|
||||
var designMode = e.target.ownerDocument.designMode;
|
||||
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
|
||||
var selection = w.getSelection();
|
||||
|
||||
for (var i = 0; i < selection.rangeCount; i++) {
|
||||
var r = selection.getRangeAt(i);
|
||||
|
||||
if (designMode === 'on') {
|
||||
r.deleteContents();
|
||||
} else {
|
||||
if (contentEditables.length) {
|
||||
contentEditables.forEach(node => {
|
||||
if (node.contains(r.commonAncestorContainer)) {
|
||||
r.deleteContents();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (designMode === 'on') {
|
||||
var range = w.getSelection().getRangeAt(0);
|
||||
|
||||
if (SafeExamBrowser.clipboard.ranges.length > 0) {
|
||||
SafeExamBrowser.clipboard.ranges.map(r => {
|
||||
range = w.getSelection().getRangeAt(0);
|
||||
range.collapse();
|
||||
const newNode = r.cloneNode(true);
|
||||
range.insertNode(newNode);
|
||||
range.collapse();
|
||||
});
|
||||
} else {
|
||||
range.collapse();
|
||||
range.insertNode(w.document.createTextNode(SafeExamBrowser.clipboard.text));
|
||||
range.collapse();
|
||||
}
|
||||
} else {
|
||||
if (contentEditables.length) {
|
||||
contentEditables.forEach(node => {
|
||||
var range = w.getSelection().getRangeAt(0);
|
||||
|
||||
if (node.contains(range.commonAncestorContainer)) {
|
||||
if (SafeExamBrowser.clipboard.ranges.length > 0) {
|
||||
SafeExamBrowser.clipboard.ranges.map(r => {
|
||||
range = w.getSelection().getRangeAt(0);
|
||||
range.collapse();
|
||||
const newNode = r.cloneNode(true);
|
||||
range.insertNode(newNode);
|
||||
range.collapse();
|
||||
});
|
||||
} else {
|
||||
range = w.getSelection().getRangeAt(0);
|
||||
range.collapse();
|
||||
range.insertNode(w.document.createTextNode(SafeExamBrowser.clipboard.text));
|
||||
range.collapse();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onCopy(e) {
|
||||
SafeExamBrowser.clipboard.clear();
|
||||
|
||||
try {
|
||||
copySelectedData(e);
|
||||
|
||||
CefSharp.PostMessage({ Type: "Clipboard", Id: SafeExamBrowser.clipboard.id, Content: SafeExamBrowser.clipboard.getContentEncoded() });
|
||||
} finally {
|
||||
e.preventDefault();
|
||||
e.returnValue = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function onCut(e) {
|
||||
SafeExamBrowser.clipboard.clear();
|
||||
|
||||
try {
|
||||
copySelectedData(e);
|
||||
cutSelectedData(e);
|
||||
|
||||
CefSharp.PostMessage({ Type: "Clipboard", Id: SafeExamBrowser.clipboard.id, Content: SafeExamBrowser.clipboard.getContentEncoded() });
|
||||
} finally {
|
||||
e.preventDefault();
|
||||
e.returnValue = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function onPaste(e) {
|
||||
try {
|
||||
pasteSelectedData(e);
|
||||
} finally {
|
||||
e.preventDefault();
|
||||
e.returnValue = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
window.document.addEventListener("copy", onCopy, true);
|
||||
window.document.addEventListener("cut", onCut, true);
|
||||
window.document.addEventListener("paste", onPaste, true);
|
119
SafeExamBrowser.Browser/Content/ContentLoader.cs
Normal file
119
SafeExamBrowser.Browser/Content/ContentLoader.cs
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Content
|
||||
{
|
||||
internal class ContentLoader
|
||||
{
|
||||
private readonly IText text;
|
||||
|
||||
private string api;
|
||||
private string clipboard;
|
||||
private string pageZoom;
|
||||
|
||||
internal ContentLoader(IText text)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
internal string LoadApi(string browserExamKey, string configurationKey, string version)
|
||||
{
|
||||
if (api == default)
|
||||
{
|
||||
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
|
||||
var path = $"{typeof(ContentLoader).Namespace}.Api.js";
|
||||
|
||||
using (var stream = assembly.GetManifestResourceStream(path))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
api = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
var js = api;
|
||||
|
||||
js = js.Replace("%%_BEK_%%", browserExamKey);
|
||||
js = js.Replace("%%_CK_%%", configurationKey);
|
||||
js = js.Replace("%%_VERSION_%%", version);
|
||||
|
||||
return js;
|
||||
}
|
||||
|
||||
internal string LoadBlockedContent()
|
||||
{
|
||||
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
|
||||
var path = $"{typeof(ContentLoader).Namespace}.BlockedContent.html";
|
||||
|
||||
using (var stream = assembly.GetManifestResourceStream(path))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
var html = reader.ReadToEnd();
|
||||
|
||||
html = html.Replace("%%MESSAGE%%", text.Get(TextKey.Browser_BlockedContentMessage));
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
internal string LoadBlockedPage()
|
||||
{
|
||||
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
|
||||
var path = $"{typeof(ContentLoader).Namespace}.BlockedPage.html";
|
||||
|
||||
using (var stream = assembly.GetManifestResourceStream(path))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
var html = reader.ReadToEnd();
|
||||
|
||||
html = html.Replace("%%BACK_BUTTON%%", text.Get(TextKey.Browser_BlockedPageButton));
|
||||
html = html.Replace("%%MESSAGE%%", text.Get(TextKey.Browser_BlockedPageMessage));
|
||||
html = html.Replace("%%TITLE%%", text.Get(TextKey.Browser_BlockedPageTitle));
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
internal string LoadClipboard()
|
||||
{
|
||||
if (clipboard == default)
|
||||
{
|
||||
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
|
||||
var path = $"{typeof(ContentLoader).Namespace}.Clipboard.js";
|
||||
|
||||
using (var stream = assembly.GetManifestResourceStream(path))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
clipboard = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
return clipboard;
|
||||
}
|
||||
|
||||
internal string LoadPageZoom()
|
||||
{
|
||||
if (pageZoom == default)
|
||||
{
|
||||
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
|
||||
var path = $"{typeof(ContentLoader).Namespace}.PageZoom.js";
|
||||
|
||||
using (var stream = assembly.GetManifestResourceStream(path))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
pageZoom = reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
return pageZoom;
|
||||
}
|
||||
}
|
||||
}
|
16
SafeExamBrowser.Browser/Content/PageZoom.js
Normal file
16
SafeExamBrowser.Browser/Content/PageZoom.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
function disableMouseWheelZoom(e) {
|
||||
if (e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('wheel', disableMouseWheelZoom, { passive: false });
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -8,5 +8,5 @@
|
|||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void InstanceTerminatedEventHandler(int id);
|
||||
internal delegate void ClipboardChangedEventHandler(long id);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void DownloadAbortedEventHandler();
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -10,6 +10,6 @@ namespace SafeExamBrowser.Browser.Events
|
|||
{
|
||||
internal class PopupRequestedEventArgs
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public BrowserWindow Window { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
12
SafeExamBrowser.Browser/Events/WindowClosedEventHandler.cs
Normal file
12
SafeExamBrowser.Browser/Events/WindowClosedEventHandler.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Browser.Events
|
||||
{
|
||||
internal delegate void WindowClosedEventHandler(int id);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -10,7 +10,7 @@ using System.Collections.Generic;
|
|||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
|
||||
using SafeExamBrowser.Browser.Wrapper;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
|
@ -18,13 +18,13 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
{
|
||||
internal event DialogRequestedEventHandler DialogRequested;
|
||||
|
||||
public bool OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, CefFileDialogFlags flags, string title, string defaultFilePath, List<string> acceptFilters, int selectedAcceptFilter, IFileDialogCallback callback)
|
||||
public bool OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, IFileDialogCallback callback)
|
||||
{
|
||||
var args = new DialogRequestedEventArgs
|
||||
{
|
||||
Element = ToElement(mode),
|
||||
Element = mode.ToElement(),
|
||||
InitialPath = defaultFilePath,
|
||||
Operation = ToOperation(mode),
|
||||
Operation = mode.ToOperation(),
|
||||
Title = title
|
||||
};
|
||||
|
||||
|
@ -36,7 +36,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
{
|
||||
if (args.Success)
|
||||
{
|
||||
callback.Continue(selectedAcceptFilter, new List<string> { args.FullPath });
|
||||
callback.Continue(new List<string> { args.FullPath });
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -47,27 +47,5 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
private FileSystemElement ToElement(CefFileDialogMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case CefFileDialogMode.OpenFolder:
|
||||
return FileSystemElement.Folder;
|
||||
default:
|
||||
return FileSystemElement.File;
|
||||
}
|
||||
}
|
||||
|
||||
private FileSystemOperation ToOperation(CefFileDialogMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case CefFileDialogMode.Save:
|
||||
return FileSystemOperation.Save;
|
||||
default:
|
||||
return FileSystemOperation.Open;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CefSharp;
|
||||
using CefSharp.Enums;
|
||||
using CefSharp.Structs;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
|
||||
|
@ -33,6 +35,11 @@ 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) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -24,14 +24,15 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
{
|
||||
internal class DownloadHandler : IDownloadHandler
|
||||
{
|
||||
private AppConfig appConfig;
|
||||
private BrowserSettings settings;
|
||||
private WindowSettings windowSettings;
|
||||
private ConcurrentDictionary<int, DownloadFinishedCallback> callbacks;
|
||||
private ConcurrentDictionary<int, Guid> downloads;
|
||||
private ILogger logger;
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly ConcurrentDictionary<int, DownloadFinishedCallback> callbacks;
|
||||
private readonly ConcurrentDictionary<int, Guid> downloads;
|
||||
private readonly ILogger logger;
|
||||
private readonly BrowserSettings settings;
|
||||
private readonly WindowSettings windowSettings;
|
||||
|
||||
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
internal event DownloadAbortedEventHandler DownloadAborted;
|
||||
internal event DownloadUpdatedEventHandler DownloadUpdated;
|
||||
|
||||
internal DownloadHandler(AppConfig appConfig, ILogger logger, BrowserSettings settings, WindowSettings windowSettings)
|
||||
|
@ -44,18 +45,33 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
this.windowSettings = windowSettings;
|
||||
}
|
||||
|
||||
public bool CanDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, string url, string requestMethod)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnBeforeDownload(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback)
|
||||
{
|
||||
var uri = new Uri(downloadItem.Url);
|
||||
var uriExtension = Path.GetExtension(uri.AbsolutePath);
|
||||
var fileExtension = Path.GetExtension(downloadItem.SuggestedFileName);
|
||||
var isConfigurationFile = false;
|
||||
var url = downloadItem.Url;
|
||||
var urlExtension = default(string);
|
||||
|
||||
if (downloadItem.Url.StartsWith("data:"))
|
||||
{
|
||||
url = downloadItem.Url.Length <= 100 ? downloadItem.Url : downloadItem.Url.Substring(0, 100) + "...";
|
||||
}
|
||||
|
||||
if (Uri.TryCreate(downloadItem.Url, UriKind.RelativeOrAbsolute, out var uri))
|
||||
{
|
||||
urlExtension = Path.GetExtension(uri.AbsolutePath);
|
||||
}
|
||||
|
||||
isConfigurationFile |= string.Equals(appConfig.ConfigurationFileExtension, fileExtension, StringComparison.OrdinalIgnoreCase);
|
||||
isConfigurationFile |= string.Equals(appConfig.ConfigurationFileExtension, uriExtension, 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 '{uri}'" : "")}.");
|
||||
logger.Debug($"Detected download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")}.");
|
||||
|
||||
if (isConfigurationFile)
|
||||
{
|
||||
|
@ -67,7 +83,8 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"Aborted download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{uri}'" : "")}, as downloading is not allowed.");
|
||||
logger.Info($"Aborted download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")}, as downloading is not allowed.");
|
||||
Task.Run(() => DownloadAborted?.Invoke());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +102,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
IsComplete = downloadItem.IsComplete,
|
||||
Url = downloadItem.Url
|
||||
};
|
||||
|
||||
|
||||
Task.Run(() => DownloadUpdated?.Invoke(state));
|
||||
}
|
||||
|
||||
|
@ -93,7 +110,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
{
|
||||
logger.Debug($"Download of '{downloadItem.FullPath}' {(downloadItem.IsComplete ? "is complete" : "was cancelled")}.");
|
||||
|
||||
if (callbacks.TryRemove(downloadItem.Id, out DownloadFinishedCallback finished) && finished != null)
|
||||
if (callbacks.TryRemove(downloadItem.Id, out var finished) && finished != null)
|
||||
{
|
||||
Task.Run(() => finished.Invoke(downloadItem.IsComplete, downloadItem.Url, downloadItem.FullPath));
|
||||
}
|
||||
|
@ -121,6 +138,11 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
filePath = Path.Combine(KnownFolders.Downloads.ExpandedPath, downloadItem.SuggestedFileName);
|
||||
}
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
filePath = AppendIndexSuffixTo(filePath);
|
||||
}
|
||||
|
||||
if (showDialog)
|
||||
{
|
||||
logger.Debug($"Allowing user to select custom download location, with '{filePath}' as suggestion.");
|
||||
|
@ -138,6 +160,26 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
}
|
||||
}
|
||||
|
||||
private string AppendIndexSuffixTo(string filePath)
|
||||
{
|
||||
var directory = Path.GetDirectoryName(filePath);
|
||||
var extension = Path.GetExtension(filePath);
|
||||
var name = Path.GetFileNameWithoutExtension(filePath);
|
||||
var path = default(string);
|
||||
|
||||
for (var suffix = 1; suffix < int.MaxValue; suffix++)
|
||||
{
|
||||
path = Path.Combine(directory, $"{name}({suffix}){extension}");
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private void RequestConfigurationFileDownload(DownloadItem downloadItem, IBeforeDownloadCallback callback)
|
||||
{
|
||||
var args = new DownloadEventArgs { Url = downloadItem.Url };
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -8,6 +8,7 @@
|
|||
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
|
@ -20,6 +21,10 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
internal event ActionRequestedEventHandler ZoomInRequested;
|
||||
internal event ActionRequestedEventHandler ZoomOutRequested;
|
||||
internal event ActionRequestedEventHandler ZoomResetRequested;
|
||||
internal event ActionRequestedEventHandler FocusAddressBarRequested;
|
||||
internal event TabPressedEventHandler TabPressed;
|
||||
|
||||
private int? currentKeyDown = null;
|
||||
|
||||
public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int keyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
|
||||
{
|
||||
|
@ -38,7 +43,12 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
HomeNavigationRequested?.Invoke();
|
||||
}
|
||||
|
||||
if ((ctrl && keyCode == (int) Keys.Add) || (ctrl && shift && keyCode == (int) Keys.D1))
|
||||
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();
|
||||
}
|
||||
|
@ -52,8 +62,14 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
{
|
||||
ZoomResetRequested?.Invoke();
|
||||
}
|
||||
|
||||
if (keyCode == (int) Keys.Tab && keyCode == currentKeyDown)
|
||||
{
|
||||
TabPressed?.Invoke(shift);
|
||||
}
|
||||
}
|
||||
|
||||
currentKeyDown = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -66,6 +82,11 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
return true;
|
||||
}
|
||||
|
||||
if (type == KeyType.RawKeyDown || type == KeyType.KeyDown)
|
||||
{
|
||||
currentKeyDown = keyCode;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class LifeSpanHandler : ILifeSpanHandler
|
||||
{
|
||||
public event PopupRequestedEventHandler PopupRequested;
|
||||
|
||||
public bool DoClose(IWebBrowser chromiumWebBrowser, IBrowser browser)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnAfterCreated(IWebBrowser chromiumWebBrowser, IBrowser browser)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnBeforeClose(IWebBrowser chromiumWebBrowser, IBrowser browser)
|
||||
{
|
||||
}
|
||||
|
||||
public bool OnBeforePopup(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser)
|
||||
{
|
||||
var args = new PopupRequestedEventArgs { Url = targetUrl };
|
||||
|
||||
newBrowser = default(IWebBrowser);
|
||||
PopupRequested?.Invoke(args);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Content;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
|
||||
|
||||
namespace SafeExamBrowser.Browser.Handlers
|
||||
{
|
||||
internal class RenderProcessMessageHandler : IRenderProcessMessageHandler
|
||||
{
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly Clipboard clipboard;
|
||||
private readonly ContentLoader contentLoader;
|
||||
private readonly IKeyGenerator keyGenerator;
|
||||
private readonly BrowserSettings settings;
|
||||
private readonly IText text;
|
||||
|
||||
internal RenderProcessMessageHandler(AppConfig appConfig, Clipboard clipboard, IKeyGenerator keyGenerator, BrowserSettings settings, IText text)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.clipboard = clipboard;
|
||||
this.contentLoader = new ContentLoader(text);
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.settings = settings;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public void OnContextCreated(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
var browserExamKey = keyGenerator.CalculateBrowserExamKeyHash(settings.ConfigurationKey, settings.BrowserExamKeySalt, frame.Url);
|
||||
var configurationKey = keyGenerator.CalculateConfigurationKeyHash(settings.ConfigurationKey, frame.Url);
|
||||
var api = contentLoader.LoadApi(browserExamKey, configurationKey, appConfig.ProgramBuildVersion);
|
||||
var clipboardScript = contentLoader.LoadClipboard();
|
||||
var pageZoomScript = contentLoader.LoadPageZoom();
|
||||
|
||||
frame.ExecuteJavaScriptAsync(api);
|
||||
|
||||
if (!settings.AllowPageZoom)
|
||||
{
|
||||
frame.ExecuteJavaScriptAsync(pageZoomScript);
|
||||
}
|
||||
|
||||
if (!settings.AllowPrint)
|
||||
{
|
||||
frame.ExecuteJavaScriptAsync($"window.print = function() {{ alert('{text.Get(TextKey.Browser_PrintNotAllowed)}') }}");
|
||||
}
|
||||
|
||||
if (settings.UseIsolatedClipboard)
|
||||
{
|
||||
frame.ExecuteJavaScriptAsync(clipboardScript);
|
||||
|
||||
if (clipboard.Content != default)
|
||||
{
|
||||
frame.ExecuteJavaScriptAsync($"SafeExamBrowser.clipboard.update('', '{clipboard.Content}');");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnContextReleased(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnFocusedNodeChanged(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IDomNode node)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnUncaughtException(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, JavascriptException exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -7,12 +7,13 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using CefSharp;
|
||||
using SafeExamBrowser.Browser.Contracts.Filters;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.Settings.Browser.Filter;
|
||||
|
@ -23,12 +24,14 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
{
|
||||
internal class RequestHandler : CefSharp.Handler.RequestHandler
|
||||
{
|
||||
private IRequestFilter filter;
|
||||
private ILogger logger;
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly IRequestFilter filter;
|
||||
private readonly ILogger logger;
|
||||
private readonly ResourceHandler resourceHandler;
|
||||
private readonly WindowSettings windowSettings;
|
||||
private readonly BrowserSettings settings;
|
||||
|
||||
private string quitUrlPattern;
|
||||
private ResourceHandler resourceHandler;
|
||||
private WindowSettings windowSettings;
|
||||
private BrowserSettings settings;
|
||||
|
||||
internal event UrlEventHandler QuitUrlVisited;
|
||||
internal event UrlEventHandler RequestBlocked;
|
||||
|
@ -39,9 +42,9 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
ILogger logger,
|
||||
ResourceHandler resourceHandler,
|
||||
BrowserSettings settings,
|
||||
WindowSettings windowSettings,
|
||||
IText text)
|
||||
WindowSettings windowSettings)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.filter = filter;
|
||||
this.logger = logger;
|
||||
this.resourceHandler = resourceHandler;
|
||||
|
@ -91,18 +94,79 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
return true;
|
||||
}
|
||||
|
||||
if (IsConfigurationFile(request, out var downloadUrl))
|
||||
{
|
||||
browser.GetHost().StartDownload(downloadUrl);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnBeforeBrowse(webBrowser, browser, frame, request, userGesture, isRedirect);
|
||||
}
|
||||
|
||||
protected override bool OnOpenUrlFromTab(IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
|
||||
{
|
||||
switch (targetDisposition)
|
||||
{
|
||||
case WindowOpenDisposition.NewBackgroundTab:
|
||||
case WindowOpenDisposition.NewPopup:
|
||||
case WindowOpenDisposition.NewWindow:
|
||||
case WindowOpenDisposition.SaveToDisk:
|
||||
return true;
|
||||
default:
|
||||
return base.OnOpenUrlFromTab(webBrowser, browser, frame, targetUrl, targetDisposition, userGesture);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsConfigurationFile(IRequest request, out string downloadUrl)
|
||||
{
|
||||
var isValidUri = Uri.TryCreate(request.Url, UriKind.RelativeOrAbsolute, out var uri);
|
||||
var hasFileExtension = string.Equals(appConfig.ConfigurationFileExtension, Path.GetExtension(uri.AbsolutePath), StringComparison.OrdinalIgnoreCase);
|
||||
var isDataUri = request.Url.Contains(appConfig.ConfigurationFileMimeType);
|
||||
var isConfigurationFile = isValidUri && (hasFileExtension || isDataUri);
|
||||
|
||||
downloadUrl = request.Url;
|
||||
|
||||
if (isConfigurationFile)
|
||||
{
|
||||
if (isDataUri)
|
||||
{
|
||||
if (uri.Scheme == appConfig.SebUriScheme)
|
||||
{
|
||||
downloadUrl = request.Url.Replace($"{appConfig.SebUriScheme}{Uri.SchemeDelimiter}", "data:");
|
||||
}
|
||||
else if (uri.Scheme == appConfig.SebUriSchemeSecure)
|
||||
{
|
||||
downloadUrl = request.Url.Replace($"{appConfig.SebUriSchemeSecure}{Uri.SchemeDelimiter}", "data:");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (uri.Scheme == appConfig.SebUriScheme)
|
||||
{
|
||||
downloadUrl = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri.AbsoluteUri;
|
||||
}
|
||||
else if (uri.Scheme == appConfig.SebUriSchemeSecure)
|
||||
{
|
||||
downloadUrl = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttps }.Uri.AbsoluteUri;
|
||||
}
|
||||
}
|
||||
|
||||
logger.Debug($"Detected configuration file {(windowSettings.UrlPolicy.CanLog() ? $"'{uri}'" : "")}.");
|
||||
}
|
||||
|
||||
return isConfigurationFile;
|
||||
}
|
||||
|
||||
private bool IsQuitUrl(IRequest request)
|
||||
{
|
||||
var isQuitUrl = false;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(settings.QuitUrl))
|
||||
{
|
||||
if (quitUrlPattern == default(string))
|
||||
if (quitUrlPattern == default)
|
||||
{
|
||||
quitUrlPattern = Regex.Escape(settings.QuitUrl.TrimEnd('/')) + @"\/?";
|
||||
quitUrlPattern = $"^{Regex.Escape(settings.QuitUrl.TrimEnd('/'))}/?$";
|
||||
}
|
||||
|
||||
isQuitUrl = Regex.IsMatch(request.Url, quitUrlPattern, RegexOptions.IgnoreCase);
|
||||
|
@ -119,29 +183,37 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
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)
|
||||
if (settings.Filter.ProcessMainRequests && request.ResourceType == ResourceType.MainFrame && isValidUrl)
|
||||
{
|
||||
var result = filter.Process(new Request { Url = request.Url });
|
||||
var result = filter.Process(new Request { Url = url });
|
||||
|
||||
if (result == FilterResult.Block)
|
||||
// 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 '{request.Url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
|
||||
logger.Info($"Blocked main request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.Filter.ProcessContentRequests && request.ResourceType != ResourceType.MainFrame)
|
||||
if (settings.Filter.ProcessContentRequests && request.ResourceType != ResourceType.MainFrame && isValidUrl)
|
||||
{
|
||||
var result = filter.Process(new Request { Url = request.Url });
|
||||
var result = filter.Process(new Request { Url = url });
|
||||
|
||||
if (result == FilterResult.Block)
|
||||
{
|
||||
block = true;
|
||||
logger.Info($"Blocked content request{(windowSettings.UrlPolicy.CanLog() ? $" for '{request.Url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
|
||||
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,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
* 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
|
||||
|
@ -9,19 +9,21 @@
|
|||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
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.Browser.Pages;
|
||||
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;
|
||||
|
@ -31,36 +33,39 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
{
|
||||
internal class ResourceHandler : CefSharp.Handler.ResourceRequestHandler
|
||||
{
|
||||
private SHA256Managed algorithm;
|
||||
private AppConfig appConfig;
|
||||
private string browserExamKey;
|
||||
private IResourceHandler contentHandler;
|
||||
private IRequestFilter filter;
|
||||
private HtmlLoader htmlLoader;
|
||||
private ILogger logger;
|
||||
private IResourceHandler pageHandler;
|
||||
private BrowserSettings settings;
|
||||
private WindowSettings windowSettings;
|
||||
private IText text;
|
||||
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;
|
||||
|
||||
internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
|
||||
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.algorithm = new SHA256Managed();
|
||||
this.filter = filter;
|
||||
this.htmlLoader = new HtmlLoader(text);
|
||||
this.contentLoader = new ContentLoader(text);
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.logger = logger;
|
||||
this.sessionMode = sessionMode;
|
||||
this.settings = settings;
|
||||
this.windowSettings = windowSettings;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
protected override IResourceHandler GetResourceHandler(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request)
|
||||
|
@ -93,21 +98,27 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
|
||||
protected override void OnResourceRedirect(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl)
|
||||
{
|
||||
SearchSessionIdentifiers(response);
|
||||
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 (RedirectToDisablePdfToolbar(request, response, out var url))
|
||||
if (RedirectToDisablePdfReaderToolbar(request, response, out var url))
|
||||
{
|
||||
webBrowser.Load(url);
|
||||
frame?.LoadUrl(url);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SearchSessionIdentifiers(response);
|
||||
if (sessionMode == SessionMode.Server)
|
||||
{
|
||||
SearchUserIdentifier(request, response);
|
||||
}
|
||||
|
||||
return base.OnResourceResponse(webBrowser, browser, frame, request, response);
|
||||
}
|
||||
|
@ -117,25 +128,18 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
Uri.TryCreate(webBrowser.Address, UriKind.Absolute, out var pageUrl);
|
||||
Uri.TryCreate(request.Url, UriKind.Absolute, out var requestUrl);
|
||||
|
||||
if (pageUrl?.Host?.Equals(requestUrl?.Host) == true)
|
||||
if (request.ResourceType == ResourceType.MainFrame || pageUrl?.Host?.Equals(requestUrl?.Host) == true)
|
||||
{
|
||||
var headers = new NameValueCollection(request.Headers);
|
||||
var urlWithoutFragment = request.Url.Split('#')[0];
|
||||
|
||||
if (settings.SendConfigurationKey)
|
||||
{
|
||||
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + settings.ConfigurationKey));
|
||||
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
|
||||
|
||||
headers["X-SafeExamBrowser-ConfigKeyHash"] = key;
|
||||
headers["X-SafeExamBrowser-ConfigKeyHash"] = keyGenerator.CalculateConfigurationKeyHash(settings.ConfigurationKey, request.Url);
|
||||
}
|
||||
|
||||
if (settings.SendExamKey)
|
||||
if (settings.SendBrowserExamKey)
|
||||
{
|
||||
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + (browserExamKey ?? ComputeBrowserExamKey())));
|
||||
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
|
||||
|
||||
headers["X-SafeExamBrowser-RequestHash"] = key;
|
||||
headers["X-SafeExamBrowser-RequestHash"] = keyGenerator.CalculateBrowserExamKeyHash(settings.ConfigurationKey, settings.BrowserExamKeySalt, request.Url);
|
||||
}
|
||||
|
||||
request.Headers = headers;
|
||||
|
@ -145,56 +149,42 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
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)
|
||||
if (settings.Filter.ProcessContentRequests && isValidUri)
|
||||
{
|
||||
var result = filter.Process(new Request { Url = request.Url });
|
||||
var result = filter.Process(new Request { Url = url });
|
||||
|
||||
if (result == FilterResult.Block)
|
||||
{
|
||||
block = true;
|
||||
logger.Info($"Blocked content request{(windowSettings.UrlPolicy.CanLog() ? $" for '{request.Url}'" : "")} ({request.ResourceType}, {request.TransitionType}).");
|
||||
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 string ComputeBrowserExamKey()
|
||||
{
|
||||
var salt = settings.ExamKeySalt;
|
||||
|
||||
if (salt == default(byte[]))
|
||||
{
|
||||
salt = new byte[0];
|
||||
logger.Warn("The current configuration does not contain a salt value for the browser exam key!");
|
||||
}
|
||||
|
||||
using (var algorithm = new HMACSHA256(salt))
|
||||
{
|
||||
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(appConfig.CodeSignatureHash + appConfig.ProgramBuildVersion + settings.ConfigurationKey));
|
||||
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
|
||||
|
||||
browserExamKey = key;
|
||||
|
||||
return browserExamKey;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsMailtoUrl(string url)
|
||||
{
|
||||
return url.StartsWith(Uri.UriSchemeMailto);
|
||||
}
|
||||
|
||||
private bool RedirectToDisablePdfToolbar(IRequest request, IResponse response, out string url)
|
||||
private bool RedirectToDisablePdfReaderToolbar(IRequest request, IResponse response, out string url)
|
||||
{
|
||||
const string DISABLE_PDF_TOOLBAR = "#toolbar=0";
|
||||
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_TOOLBAR);
|
||||
var hasFragment = request.Url.Contains(DISABLE_PDF_READER_TOOLBAR);
|
||||
var redirect = settings.AllowPdfReader && !settings.AllowPdfReaderToolbar && isPdf && isMainFrame && !hasFragment;
|
||||
|
||||
url = request.Url + DISABLE_PDF_TOOLBAR;
|
||||
url = request.Url + DISABLE_PDF_READER_TOOLBAR;
|
||||
|
||||
if (redirect)
|
||||
{
|
||||
|
@ -225,12 +215,12 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
{
|
||||
if (contentHandler == default(IResourceHandler))
|
||||
{
|
||||
contentHandler = CefSharp.ResourceHandler.FromString(htmlLoader.LoadBlockedContent());
|
||||
contentHandler = CefSharp.ResourceHandler.FromString(contentLoader.LoadBlockedContent());
|
||||
}
|
||||
|
||||
if (pageHandler == default(IResourceHandler))
|
||||
{
|
||||
pageHandler = CefSharp.ResourceHandler.FromString(htmlLoader.LoadBlockedPage());
|
||||
pageHandler = CefSharp.ResourceHandler.FromString(contentLoader.LoadBlockedPage());
|
||||
}
|
||||
|
||||
switch (resourceType)
|
||||
|
@ -243,15 +233,46 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
}
|
||||
}
|
||||
|
||||
private void SearchSessionIdentifiers(IResponse response)
|
||||
private void SearchUserIdentifier(IRequest request, IResponse response)
|
||||
{
|
||||
SearchEdxIdentifier(response);
|
||||
SearchMoodleIdentifier(response);
|
||||
var success = TrySearchGenericUserIdentifier(response);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
success = TrySearchEdxUserIdentifier(response);
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
TrySearchMoodleUserIdentifier(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchEdxIdentifier(IResponse 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[]))
|
||||
{
|
||||
|
@ -259,7 +280,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
{
|
||||
var userInfo = cookies.FirstOrDefault(c => c.Contains("edx-user-info"));
|
||||
|
||||
if (userInfo != default(string))
|
||||
if (userInfo != default)
|
||||
{
|
||||
var start = userInfo.IndexOf("=") + 1;
|
||||
var end = userInfo.IndexOf("; expires");
|
||||
|
@ -268,17 +289,42 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
var json = JsonConvert.DeserializeObject(sanitized) as JObject;
|
||||
var userName = json["username"].Value<string>();
|
||||
|
||||
Task.Run(() => SessionIdentifierDetected?.Invoke(userName));
|
||||
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 session identifier!", e);
|
||||
logger.Error("Failed to parse edX user identifier!", e);
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private void SearchMoodleIdentifier(IResponse response)
|
||||
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");
|
||||
|
||||
|
@ -288,18 +334,118 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
{
|
||||
var location = locations.FirstOrDefault(l => l.Contains("/login/index.php?testsession"));
|
||||
|
||||
if (location != default(string))
|
||||
if (location != default)
|
||||
{
|
||||
var userId = location.Substring(location.IndexOf("=") + 1);
|
||||
|
||||
Task.Run(() => SessionIdentifierDetected?.Invoke(userId));
|
||||
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 session identifier!", 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…
Reference in a new issue