summaryrefslogtreecommitdiffstats
path: root/dom/security
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/security
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/security')
-rw-r--r--dom/security/ContentVerifier.cpp230
-rw-r--r--dom/security/ContentVerifier.h64
-rw-r--r--dom/security/SRICheck.cpp543
-rw-r--r--dom/security/SRICheck.h119
-rw-r--r--dom/security/SRILogHelper.h28
-rw-r--r--dom/security/SRIMetadata.cpp169
-rw-r--r--dom/security/SRIMetadata.h83
-rw-r--r--dom/security/moz.build48
-rw-r--r--dom/security/nsCSPContext.cpp1567
-rw-r--r--dom/security/nsCSPContext.h164
-rw-r--r--dom/security/nsCSPParser.cpp1358
-rw-r--r--dom/security/nsCSPParser.h262
-rw-r--r--dom/security/nsCSPService.cpp336
-rw-r--r--dom/security/nsCSPService.h38
-rw-r--r--dom/security/nsCSPUtils.cpp1616
-rw-r--r--dom/security/nsCSPUtils.h642
-rw-r--r--dom/security/nsContentSecurityManager.cpp695
-rw-r--r--dom/security/nsContentSecurityManager.h42
-rw-r--r--dom/security/nsMixedContentBlocker.cpp1191
-rw-r--r--dom/security/nsMixedContentBlocker.h105
-rw-r--r--dom/security/test/contentverifier/browser.ini19
-rw-r--r--dom/security/test/contentverifier/browser_verify_content_about_newtab.js20
-rw-r--r--dom/security/test/contentverifier/browser_verify_content_about_newtab2.js19
-rw-r--r--dom/security/test/contentverifier/file_about_newtab.html11
-rw-r--r--dom/security/test/contentverifier/file_about_newtab_bad.html11
-rw-r--r--dom/security/test/contentverifier/file_about_newtab_bad_csp.html14
-rw-r--r--dom/security/test/contentverifier/file_about_newtab_bad_csp_signature1
-rw-r--r--dom/security/test/contentverifier/file_about_newtab_bad_signature1
-rw-r--r--dom/security/test/contentverifier/file_about_newtab_broken_signature1
-rw-r--r--dom/security/test/contentverifier/file_about_newtab_good_signature1
-rw-r--r--dom/security/test/contentverifier/file_about_newtab_sri.html36
-rw-r--r--dom/security/test/contentverifier/file_about_newtab_sri_signature1
-rw-r--r--dom/security/test/contentverifier/file_contentserver.sjs261
-rw-r--r--dom/security/test/contentverifier/goodChain.pem51
-rw-r--r--dom/security/test/contentverifier/head.js210
-rw-r--r--dom/security/test/contentverifier/script.js1
-rw-r--r--dom/security/test/contentverifier/signature.derbin0 -> 103 bytes
-rw-r--r--dom/security/test/contentverifier/sk.pem9
-rw-r--r--dom/security/test/contentverifier/style.css3
-rw-r--r--dom/security/test/cors/file_CrossSiteXHR_cache_server.sjs49
-rw-r--r--dom/security/test/cors/file_CrossSiteXHR_inner.html121
-rw-r--r--dom/security/test/cors/file_CrossSiteXHR_inner.jarbin0 -> 1105 bytes
-rw-r--r--dom/security/test/cors/file_CrossSiteXHR_inner_data.sjs103
-rw-r--r--dom/security/test/cors/file_CrossSiteXHR_server.sjs179
-rw-r--r--dom/security/test/cors/mochitest.ini11
-rw-r--r--dom/security/test/cors/test_CrossSiteXHR.html1461
-rw-r--r--dom/security/test/cors/test_CrossSiteXHR_cache.html587
-rw-r--r--dom/security/test/cors/test_CrossSiteXHR_origin.html174
-rw-r--r--dom/security/test/csp/browser.ini13
-rw-r--r--dom/security/test/csp/browser_manifest-src-override-default-src.js108
-rw-r--r--dom/security/test/csp/browser_test_web_manifest.js224
-rw-r--r--dom/security/test/csp/browser_test_web_manifest_mixed_content.js53
-rw-r--r--dom/security/test/csp/file_CSP.css20
-rw-r--r--dom/security/test/csp/file_CSP.sjs26
-rw-r--r--dom/security/test/csp/file_allow_https_schemes.html14
-rw-r--r--dom/security/test/csp/file_base_uri_server.sjs61
-rw-r--r--dom/security/test/csp/file_blob_data_schemes.html49
-rw-r--r--dom/security/test/csp/file_block_all_mcb.sjs76
-rw-r--r--dom/security/test/csp/file_block_all_mixed_content_frame_navigation1.html19
-rw-r--r--dom/security/test/csp/file_block_all_mixed_content_frame_navigation2.html15
-rw-r--r--dom/security/test/csp/file_bug1229639.html7
-rw-r--r--dom/security/test/csp/file_bug1229639.html^headers^1
-rw-r--r--dom/security/test/csp/file_bug1312272.html13
-rw-r--r--dom/security/test/csp/file_bug1312272.html^headers^1
-rw-r--r--dom/security/test/csp/file_bug1312272.js8
-rw-r--r--dom/security/test/csp/file_bug663567.xsl27
-rw-r--r--dom/security/test/csp/file_bug663567_allows.xml28
-rw-r--r--dom/security/test/csp/file_bug663567_allows.xml^headers^1
-rw-r--r--dom/security/test/csp/file_bug663567_blocks.xml28
-rw-r--r--dom/security/test/csp/file_bug663567_blocks.xml^headers^1
-rw-r--r--dom/security/test/csp/file_bug802872.html12
-rw-r--r--dom/security/test/csp/file_bug802872.html^headers^1
-rw-r--r--dom/security/test/csp/file_bug802872.js43
-rw-r--r--dom/security/test/csp/file_bug802872.sjs7
-rw-r--r--dom/security/test/csp/file_bug836922_npolicies.html12
-rw-r--r--dom/security/test/csp/file_bug836922_npolicies.html^headers^2
-rw-r--r--dom/security/test/csp/file_bug836922_npolicies_ro_violation.sjs53
-rw-r--r--dom/security/test/csp/file_bug836922_npolicies_violation.sjs59
-rw-r--r--dom/security/test/csp/file_bug885433_allows.html38
-rw-r--r--dom/security/test/csp/file_bug885433_allows.html^headers^1
-rw-r--r--dom/security/test/csp/file_bug885433_blocks.html37
-rw-r--r--dom/security/test/csp/file_bug885433_blocks.html^headers^1
-rw-r--r--dom/security/test/csp/file_bug886164.html15
-rw-r--r--dom/security/test/csp/file_bug886164.html^headers^1
-rw-r--r--dom/security/test/csp/file_bug886164_2.html14
-rw-r--r--dom/security/test/csp/file_bug886164_2.html^headers^1
-rw-r--r--dom/security/test/csp/file_bug886164_3.html12
-rw-r--r--dom/security/test/csp/file_bug886164_3.html^headers^1
-rw-r--r--dom/security/test/csp/file_bug886164_4.html12
-rw-r--r--dom/security/test/csp/file_bug886164_4.html^headers^1
-rw-r--r--dom/security/test/csp/file_bug886164_5.html26
-rw-r--r--dom/security/test/csp/file_bug886164_5.html^headers^1
-rw-r--r--dom/security/test/csp/file_bug886164_6.html35
-rw-r--r--dom/security/test/csp/file_bug886164_6.html^headers^1
-rw-r--r--dom/security/test/csp/file_bug888172.html28
-rw-r--r--dom/security/test/csp/file_bug888172.sjs43
-rw-r--r--dom/security/test/csp/file_bug909029_none.html20
-rw-r--r--dom/security/test/csp/file_bug909029_none.html^headers^1
-rw-r--r--dom/security/test/csp/file_bug909029_star.html19
-rw-r--r--dom/security/test/csp/file_bug909029_star.html^headers^1
-rw-r--r--dom/security/test/csp/file_bug910139.sjs52
-rw-r--r--dom/security/test/csp/file_bug910139.xml28
-rw-r--r--dom/security/test/csp/file_bug910139.xsl27
-rw-r--r--dom/security/test/csp/file_bug941404.html27
-rw-r--r--dom/security/test/csp/file_bug941404_xhr.html5
-rw-r--r--dom/security/test/csp/file_bug941404_xhr.html^headers^1
-rw-r--r--dom/security/test/csp/file_child-src_iframe.html61
-rw-r--r--dom/security/test/csp/file_child-src_inner_frame.html21
-rw-r--r--dom/security/test/csp/file_child-src_service_worker.html30
-rw-r--r--dom/security/test/csp/file_child-src_service_worker.js3
-rw-r--r--dom/security/test/csp/file_child-src_shared_worker-redirect.html47
-rw-r--r--dom/security/test/csp/file_child-src_shared_worker.html34
-rw-r--r--dom/security/test/csp/file_child-src_shared_worker.js8
-rw-r--r--dom/security/test/csp/file_child-src_shared_worker_data.html37
-rw-r--r--dom/security/test/csp/file_child-src_worker-redirect.html50
-rw-r--r--dom/security/test/csp/file_child-src_worker.html32
-rw-r--r--dom/security/test/csp/file_child-src_worker.js4
-rw-r--r--dom/security/test/csp/file_child-src_worker_data.html33
-rw-r--r--dom/security/test/csp/file_child_worker.js39
-rw-r--r--dom/security/test/csp/file_child_worker.js^headers^1
-rw-r--r--dom/security/test/csp/file_connect-src-fetch.html16
-rw-r--r--dom/security/test/csp/file_connect-src.html21
-rw-r--r--dom/security/test/csp/file_data-uri_blocked.html15
-rw-r--r--dom/security/test/csp/file_data-uri_blocked.html^headers^1
-rw-r--r--dom/security/test/csp/file_doccomment_meta.html28
-rw-r--r--dom/security/test/csp/file_docwrite_meta.css3
-rw-r--r--dom/security/test/csp/file_docwrite_meta.html26
-rw-r--r--dom/security/test/csp/file_docwrite_meta.js3
-rw-r--r--dom/security/test/csp/file_dual_header_testserver.sjs46
-rw-r--r--dom/security/test/csp/file_evalscript_main.html12
-rw-r--r--dom/security/test/csp/file_evalscript_main.html^headers^2
-rw-r--r--dom/security/test/csp/file_evalscript_main.js154
-rw-r--r--dom/security/test/csp/file_evalscript_main_allowed.html12
-rw-r--r--dom/security/test/csp/file_evalscript_main_allowed.html^headers^2
-rw-r--r--dom/security/test/csp/file_evalscript_main_allowed.js121
-rw-r--r--dom/security/test/csp/file_fontloader.sjs58
-rw-r--r--dom/security/test/csp/file_fontloader.woffbin0 -> 11140 bytes
-rw-r--r--dom/security/test/csp/file_form-action.html15
-rw-r--r--dom/security/test/csp/file_form_action_server.sjs33
-rw-r--r--dom/security/test/csp/file_frameancestors.sjs54
-rw-r--r--dom/security/test/csp/file_frameancestors_main.html44
-rw-r--r--dom/security/test/csp/file_frameancestors_main.js65
-rw-r--r--dom/security/test/csp/file_hash_source.html65
-rw-r--r--dom/security/test/csp/file_hash_source.html^headers^2
-rw-r--r--dom/security/test/csp/file_iframe_sandbox_document_write.html21
-rw-r--r--dom/security/test/csp/file_iframe_sandbox_srcdoc.html11
-rw-r--r--dom/security/test/csp/file_iframe_sandbox_srcdoc.html^headers^1
-rw-r--r--dom/security/test/csp/file_iframe_srcdoc.sjs79
-rw-r--r--dom/security/test/csp/file_ignore_unsafe_inline.html26
-rw-r--r--dom/security/test/csp/file_ignore_unsafe_inline_multiple_policies_server.sjs56
-rw-r--r--dom/security/test/csp/file_inlinescript.html15
-rw-r--r--dom/security/test/csp/file_inlinestyle_main.html79
-rw-r--r--dom/security/test/csp/file_inlinestyle_main.html^headers^2
-rw-r--r--dom/security/test/csp/file_inlinestyle_main_allowed.html84
-rw-r--r--dom/security/test/csp/file_inlinestyle_main_allowed.html^headers^2
-rw-r--r--dom/security/test/csp/file_invalid_source_expression.html11
-rw-r--r--dom/security/test/csp/file_leading_wildcard.html11
-rw-r--r--dom/security/test/csp/file_main.html55
-rw-r--r--dom/security/test/csp/file_main.html^headers^1
-rw-r--r--dom/security/test/csp/file_main.js51
-rw-r--r--dom/security/test/csp/file_main_worker.js48
-rw-r--r--dom/security/test/csp/file_main_worker.js^headers^1
-rw-r--r--dom/security/test/csp/file_meta_element.html25
-rw-r--r--dom/security/test/csp/file_meta_header_dual.sjs98
-rw-r--r--dom/security/test/csp/file_meta_whitespace_skipping.html31
-rw-r--r--dom/security/test/csp/file_multi_policy_injection_bypass.html15
-rw-r--r--dom/security/test/csp/file_multi_policy_injection_bypass.html^headers^1
-rw-r--r--dom/security/test/csp/file_multi_policy_injection_bypass_2.html15
-rw-r--r--dom/security/test/csp/file_multi_policy_injection_bypass_2.html^headers^1
-rw-r--r--dom/security/test/csp/file_multipart_testserver.sjs50
-rw-r--r--dom/security/test/csp/file_nonce_source.html73
-rw-r--r--dom/security/test/csp/file_nonce_source.html^headers^2
-rw-r--r--dom/security/test/csp/file_null_baseuri.html21
-rw-r--r--dom/security/test/csp/file_path_matching.html10
-rw-r--r--dom/security/test/csp/file_path_matching.js1
-rw-r--r--dom/security/test/csp/file_path_matching_incl_query.html10
-rw-r--r--dom/security/test/csp/file_path_matching_redirect.html10
-rw-r--r--dom/security/test/csp/file_path_matching_redirect_server.sjs13
-rw-r--r--dom/security/test/csp/file_ping.html19
-rw-r--r--dom/security/test/csp/file_policyuri_regression_from_multipolicy.html9
-rw-r--r--dom/security/test/csp/file_policyuri_regression_from_multipolicy.html^headers^1
-rw-r--r--dom/security/test/csp/file_policyuri_regression_from_multipolicy_policy1
-rw-r--r--dom/security/test/csp/file_redirect_content.sjs38
-rw-r--r--dom/security/test/csp/file_redirect_report.sjs17
-rw-r--r--dom/security/test/csp/file_redirect_worker.sjs34
-rw-r--r--dom/security/test/csp/file_redirects_main.html37
-rw-r--r--dom/security/test/csp/file_redirects_page.sjs103
-rw-r--r--dom/security/test/csp/file_redirects_resource.sjs149
-rw-r--r--dom/security/test/csp/file_referrerdirective.html55
-rw-r--r--dom/security/test/csp/file_report.html13
-rw-r--r--dom/security/test/csp/file_report_chromescript.js54
-rw-r--r--dom/security/test/csp/file_report_for_import.css1
-rw-r--r--dom/security/test/csp/file_report_for_import.html10
-rw-r--r--dom/security/test/csp/file_report_for_import_server.sjs49
-rw-r--r--dom/security/test/csp/file_report_uri_missing_in_report_only_header.html0
-rw-r--r--dom/security/test/csp/file_report_uri_missing_in_report_only_header.html^headers^1
-rw-r--r--dom/security/test/csp/file_require_sri_meta.js1
-rw-r--r--dom/security/test/csp/file_require_sri_meta.sjs54
-rw-r--r--dom/security/test/csp/file_sandbox_1.html16
-rw-r--r--dom/security/test/csp/file_sandbox_10.html12
-rw-r--r--dom/security/test/csp/file_sandbox_11.html25
-rw-r--r--dom/security/test/csp/file_sandbox_12.html40
-rw-r--r--dom/security/test/csp/file_sandbox_13.html25
-rw-r--r--dom/security/test/csp/file_sandbox_2.html16
-rw-r--r--dom/security/test/csp/file_sandbox_3.html13
-rw-r--r--dom/security/test/csp/file_sandbox_4.html13
-rw-r--r--dom/security/test/csp/file_sandbox_5.html26
-rw-r--r--dom/security/test/csp/file_sandbox_6.html35
-rw-r--r--dom/security/test/csp/file_sandbox_7.html15
-rw-r--r--dom/security/test/csp/file_sandbox_8.html15
-rw-r--r--dom/security/test/csp/file_sandbox_9.html12
-rw-r--r--dom/security/test/csp/file_sandbox_allow_scripts.html12
-rw-r--r--dom/security/test/csp/file_sandbox_allow_scripts.html^headers^1
-rw-r--r--dom/security/test/csp/file_sandbox_fail.js4
-rw-r--r--dom/security/test/csp/file_sandbox_pass.js4
-rw-r--r--dom/security/test/csp/file_scheme_relative_sources.js1
-rw-r--r--dom/security/test/csp/file_scheme_relative_sources.sjs42
-rw-r--r--dom/security/test/csp/file_self_none_as_hostname_confusion.html11
-rw-r--r--dom/security/test/csp/file_self_none_as_hostname_confusion.html^headers^1
-rw-r--r--dom/security/test/csp/file_sendbeacon.html21
-rw-r--r--dom/security/test/csp/file_service_worker.html19
-rw-r--r--dom/security/test/csp/file_service_worker.js1
-rw-r--r--dom/security/test/csp/file_shouldprocess.html25
-rw-r--r--dom/security/test/csp/file_strict_dynamic.js1
-rw-r--r--dom/security/test/csp/file_strict_dynamic_default_src.html14
-rw-r--r--dom/security/test/csp/file_strict_dynamic_default_src.js1
-rw-r--r--dom/security/test/csp/file_strict_dynamic_js_url.html15
-rw-r--r--dom/security/test/csp/file_strict_dynamic_non_parser_inserted.html17
-rw-r--r--dom/security/test/csp/file_strict_dynamic_non_parser_inserted_inline.html16
-rw-r--r--dom/security/test/csp/file_strict_dynamic_parser_inserted_doc_write.html15
-rw-r--r--dom/security/test/csp/file_strict_dynamic_parser_inserted_doc_write_correct_nonce.html15
-rw-r--r--dom/security/test/csp/file_strict_dynamic_script_events.html14
-rw-r--r--dom/security/test/csp/file_strict_dynamic_script_events_xbl.html14
-rw-r--r--dom/security/test/csp/file_strict_dynamic_script_extern.html10
-rw-r--r--dom/security/test/csp/file_strict_dynamic_script_inline.html14
-rw-r--r--dom/security/test/csp/file_strict_dynamic_unsafe_eval.html14
-rw-r--r--dom/security/test/csp/file_subframe_run_js_if_allowed.html13
-rw-r--r--dom/security/test/csp/file_subframe_run_js_if_allowed.html^headers^1
-rw-r--r--dom/security/test/csp/file_testserver.sjs57
-rw-r--r--dom/security/test/csp/file_upgrade_insecure.html78
-rw-r--r--dom/security/test/csp/file_upgrade_insecure_cors.html49
-rw-r--r--dom/security/test/csp/file_upgrade_insecure_cors_server.sjs62
-rw-r--r--dom/security/test/csp/file_upgrade_insecure_docwrite_iframe.sjs54
-rw-r--r--dom/security/test/csp/file_upgrade_insecure_meta.html79
-rw-r--r--dom/security/test/csp/file_upgrade_insecure_referrer.sjs55
-rw-r--r--dom/security/test/csp/file_upgrade_insecure_referrer_server.sjs56
-rw-r--r--dom/security/test/csp/file_upgrade_insecure_reporting.html23
-rw-r--r--dom/security/test/csp/file_upgrade_insecure_reporting_server.sjs80
-rw-r--r--dom/security/test/csp/file_upgrade_insecure_server.sjs102
-rw-r--r--dom/security/test/csp/file_upgrade_insecure_wsh.py7
-rw-r--r--dom/security/test/csp/file_web_manifest.html6
-rw-r--r--dom/security/test/csp/file_web_manifest.json1
-rw-r--r--dom/security/test/csp/file_web_manifest.json^headers^1
-rw-r--r--dom/security/test/csp/file_web_manifest_https.html4
-rw-r--r--dom/security/test/csp/file_web_manifest_https.json1
-rw-r--r--dom/security/test/csp/file_web_manifest_mixed_content.html9
-rw-r--r--dom/security/test/csp/file_web_manifest_remote.html8
-rw-r--r--dom/security/test/csp/mochitest.ini300
-rw-r--r--dom/security/test/csp/referrerdirective.sjs36
-rw-r--r--dom/security/test/csp/test_301_redirect.html74
-rw-r--r--dom/security/test/csp/test_302_redirect.html74
-rw-r--r--dom/security/test/csp/test_303_redirect.html74
-rw-r--r--dom/security/test/csp/test_307_redirect.html75
-rw-r--r--dom/security/test/csp/test_CSP.html147
-rw-r--r--dom/security/test/csp/test_allow_https_schemes.html76
-rw-r--r--dom/security/test/csp/test_base-uri.html124
-rw-r--r--dom/security/test/csp/test_blob_data_schemes.html89
-rw-r--r--dom/security/test/csp/test_block_all_mixed_content.html99
-rw-r--r--dom/security/test/csp/test_block_all_mixed_content_frame_navigation.html46
-rw-r--r--dom/security/test/csp/test_blocked_uri_in_reports.html79
-rw-r--r--dom/security/test/csp/test_bug1229639.html51
-rw-r--r--dom/security/test/csp/test_bug1242019.html51
-rw-r--r--dom/security/test/csp/test_bug1312272.html32
-rw-r--r--dom/security/test/csp/test_bug663567.html76
-rw-r--r--dom/security/test/csp/test_bug802872.html53
-rw-r--r--dom/security/test/csp/test_bug836922_npolicies.html240
-rw-r--r--dom/security/test/csp/test_bug885433.html61
-rw-r--r--dom/security/test/csp/test_bug886164.html172
-rw-r--r--dom/security/test/csp/test_bug888172.html73
-rw-r--r--dom/security/test/csp/test_bug909029.html129
-rw-r--r--dom/security/test/csp/test_bug910139.html66
-rw-r--r--dom/security/test/csp/test_bug941404.html107
-rw-r--r--dom/security/test/csp/test_child-src_iframe.html114
-rw-r--r--dom/security/test/csp/test_child-src_worker-redirect.html125
-rw-r--r--dom/security/test/csp/test_child-src_worker.html148
-rw-r--r--dom/security/test/csp/test_child-src_worker_data.html126
-rw-r--r--dom/security/test/csp/test_connect-src.html129
-rw-r--r--dom/security/test/csp/test_docwrite_meta.html86
-rw-r--r--dom/security/test/csp/test_dual_header.html66
-rw-r--r--dom/security/test/csp/test_evalscript.html59
-rw-r--r--dom/security/test/csp/test_fontloader.html98
-rw-r--r--dom/security/test/csp/test_form-action.html105
-rw-r--r--dom/security/test/csp/test_form_action_blocks_url.html76
-rw-r--r--dom/security/test/csp/test_frameancestors.html157
-rw-r--r--dom/security/test/csp/test_hash_source.html135
-rw-r--r--dom/security/test/csp/test_iframe_sandbox.html239
-rw-r--r--dom/security/test/csp/test_iframe_sandbox_srcdoc.html62
-rw-r--r--dom/security/test/csp/test_iframe_sandbox_top_1.html80
-rw-r--r--dom/security/test/csp/test_iframe_sandbox_top_1.html^headers^1
-rw-r--r--dom/security/test/csp/test_iframe_srcdoc.html140
-rw-r--r--dom/security/test/csp/test_ignore_unsafe_inline.html122
-rw-r--r--dom/security/test/csp/test_inlinescript.html123
-rw-r--r--dom/security/test/csp/test_inlinestyle.html107
-rw-r--r--dom/security/test/csp/test_invalid_source_expression.html57
-rw-r--r--dom/security/test/csp/test_leading_wildcard.html101
-rw-r--r--dom/security/test/csp/test_meta_element.html90
-rw-r--r--dom/security/test/csp/test_meta_header_dual.html137
-rw-r--r--dom/security/test/csp/test_meta_whitespace_skipping.html81
-rw-r--r--dom/security/test/csp/test_multi_policy_injection_bypass.html119
-rw-r--r--dom/security/test/csp/test_multipartchannel.html34
-rw-r--r--dom/security/test/csp/test_nonce_source.html122
-rw-r--r--dom/security/test/csp/test_null_baseuri.html67
-rw-r--r--dom/security/test/csp/test_path_matching.html115
-rw-r--r--dom/security/test/csp/test_path_matching_redirect.html89
-rw-r--r--dom/security/test/csp/test_ping.html103
-rw-r--r--dom/security/test/csp/test_policyuri_regression_from_multipolicy.html27
-rw-r--r--dom/security/test/csp/test_redirects.html137
-rw-r--r--dom/security/test/csp/test_referrerdirective.html145
-rw-r--r--dom/security/test/csp/test_report.html107
-rw-r--r--dom/security/test/csp/test_report_for_import.html112
-rw-r--r--dom/security/test/csp/test_report_uri_missing_in_report_only_header.html47
-rw-r--r--dom/security/test/csp/test_require_sri_meta.html77
-rw-r--r--dom/security/test/csp/test_sandbox.html249
-rw-r--r--dom/security/test/csp/test_sandbox_allow_scripts.html31
-rw-r--r--dom/security/test/csp/test_scheme_relative_sources.html91
-rw-r--r--dom/security/test/csp/test_self_none_as_hostname_confusion.html55
-rw-r--r--dom/security/test/csp/test_sendbeacon.html34
-rw-r--r--dom/security/test/csp/test_service_worker.html61
-rw-r--r--dom/security/test/csp/test_shouldprocess.html98
-rw-r--r--dom/security/test/csp/test_strict_dynamic.html134
-rw-r--r--dom/security/test/csp/test_strict_dynamic_default_src.html136
-rw-r--r--dom/security/test/csp/test_strict_dynamic_parser_inserted.html95
-rw-r--r--dom/security/test/csp/test_subframe_run_js_if_allowed.html33
-rw-r--r--dom/security/test/csp/test_upgrade_insecure.html181
-rw-r--r--dom/security/test/csp/test_upgrade_insecure_cors.html86
-rw-r--r--dom/security/test/csp/test_upgrade_insecure_docwrite_iframe.html54
-rw-r--r--dom/security/test/csp/test_upgrade_insecure_referrer.html85
-rw-r--r--dom/security/test/csp/test_upgrade_insecure_reporting.html69
-rw-r--r--dom/security/test/general/bug1277803.html11
-rw-r--r--dom/security/test/general/chrome.ini7
-rw-r--r--dom/security/test/general/favicon_bug1277803.icobin0 -> 1406 bytes
-rw-r--r--dom/security/test/general/file_block_script_wrong_mime_server.sjs34
-rw-r--r--dom/security/test/general/file_contentpolicytype_targeted_link_iframe.sjs46
-rw-r--r--dom/security/test/general/file_nosniff_testserver.sjs60
-rw-r--r--dom/security/test/general/mochitest.ini9
-rw-r--r--dom/security/test/general/test_block_script_wrong_mime.html100
-rw-r--r--dom/security/test/general/test_bug1277803.xul99
-rw-r--r--dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html92
-rw-r--r--dom/security/test/general/test_nosniff.html118
-rw-r--r--dom/security/test/gtest/TestCSPParser.cpp1132
-rw-r--r--dom/security/test/gtest/moz.build11
-rw-r--r--dom/security/test/hsts/browser.ini19
-rw-r--r--dom/security/test/hsts/browser_hsts-priming_allow_active.js24
-rw-r--r--dom/security/test/hsts/browser_hsts-priming_allow_display.js24
-rw-r--r--dom/security/test/hsts/browser_hsts-priming_block_active.js24
-rw-r--r--dom/security/test/hsts/browser_hsts-priming_block_active_css.js24
-rw-r--r--dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js24
-rw-r--r--dom/security/test/hsts/browser_hsts-priming_block_display.js24
-rw-r--r--dom/security/test/hsts/browser_hsts-priming_cache-timeout.js36
-rw-r--r--dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js24
-rw-r--r--dom/security/test/hsts/browser_hsts-priming_no-duplicates.js30
-rw-r--r--dom/security/test/hsts/file_1x1.pngbin0 -> 17811 bytes
-rw-r--r--dom/security/test/hsts/file_priming-top.html84
-rw-r--r--dom/security/test/hsts/file_priming.js4
-rw-r--r--dom/security/test/hsts/file_stylesheet.css0
-rw-r--r--dom/security/test/hsts/file_testserver.sjs66
-rw-r--r--dom/security/test/hsts/head.js308
-rw-r--r--dom/security/test/mixedcontentblocker/file_bug803225_test_mailto.html13
-rw-r--r--dom/security/test/mixedcontentblocker/file_frameNavigation.html74
-rw-r--r--dom/security/test/mixedcontentblocker/file_frameNavigation_blankTarget.html32
-rw-r--r--dom/security/test/mixedcontentblocker/file_frameNavigation_grandchild.html57
-rw-r--r--dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html72
-rw-r--r--dom/security/test/mixedcontentblocker/file_frameNavigation_secure.html73
-rw-r--r--dom/security/test/mixedcontentblocker/file_frameNavigation_secure_grandchild.html58
-rw-r--r--dom/security/test/mixedcontentblocker/file_main.html261
-rw-r--r--dom/security/test/mixedcontentblocker/file_main_bug803225.html182
-rw-r--r--dom/security/test/mixedcontentblocker/file_main_bug803225_websocket_wsh.py7
-rw-r--r--dom/security/test/mixedcontentblocker/file_server.sjs45
-rw-r--r--dom/security/test/mixedcontentblocker/mochitest.ini23
-rw-r--r--dom/security/test/mixedcontentblocker/test_bug803225.html152
-rw-r--r--dom/security/test/mixedcontentblocker/test_frameNavigation.html127
-rw-r--r--dom/security/test/mixedcontentblocker/test_main.html187
-rw-r--r--dom/security/test/moz.build31
-rw-r--r--dom/security/test/sri/file_bug_1271796.css2
-rw-r--r--dom/security/test/sri/iframe_csp_directive_style_imports.html6
-rw-r--r--dom/security/test/sri/iframe_csp_directive_style_imports.html^headers^1
-rw-r--r--dom/security/test/sri/iframe_require-sri-for_main.html47
-rw-r--r--dom/security/test/sri/iframe_require-sri-for_main.html^headers^1
-rw-r--r--dom/security/test/sri/iframe_require-sri-for_no_csp.html5
-rw-r--r--dom/security/test/sri/iframe_script_crossdomain.html135
-rw-r--r--dom/security/test/sri/iframe_script_sameorigin.html249
-rw-r--r--dom/security/test/sri/iframe_sri_disabled.html74
-rw-r--r--dom/security/test/sri/iframe_style_crossdomain.html117
-rw-r--r--dom/security/test/sri/iframe_style_sameorigin.html164
-rw-r--r--dom/security/test/sri/mochitest.ini57
-rw-r--r--dom/security/test/sri/rsf_csp_worker.js9
-rw-r--r--dom/security/test/sri/rsf_csp_worker.js^headers^1
-rw-r--r--dom/security/test/sri/rsf_imported.js1
-rw-r--r--dom/security/test/sri/rsf_spawn_CSPd_worker.js3
-rw-r--r--dom/security/test/sri/rsf_worker.js2
-rw-r--r--dom/security/test/sri/script.js1
-rw-r--r--dom/security/test/sri/script.js^headers^1
-rw-r--r--dom/security/test/sri/script_301.js1
-rw-r--r--dom/security/test/sri/script_301.js^headers^2
-rw-r--r--dom/security/test/sri/script_302.js1
-rw-r--r--dom/security/test/sri/script_302.js^headers^2
-rw-r--r--dom/security/test/sri/script_401.js1
-rw-r--r--dom/security/test/sri/script_401.js^headers^2
-rw-r--r--dom/security/test/sri/script_crossdomain1.js4
-rw-r--r--dom/security/test/sri/script_crossdomain1.js^headers^1
-rw-r--r--dom/security/test/sri/script_crossdomain2.js5
-rw-r--r--dom/security/test/sri/script_crossdomain3.js1
-rw-r--r--dom/security/test/sri/script_crossdomain3.js^headers^1
-rw-r--r--dom/security/test/sri/script_crossdomain4.js1
-rw-r--r--dom/security/test/sri/script_crossdomain4.js^headers^1
-rw-r--r--dom/security/test/sri/script_crossdomain5.js1
-rw-r--r--dom/security/test/sri/script_crossdomain5.js^headers^1
-rw-r--r--dom/security/test/sri/style1.css3
-rw-r--r--dom/security/test/sri/style1.css^headers^1
-rw-r--r--dom/security/test/sri/style2.css1
-rw-r--r--dom/security/test/sri/style3.css3
-rw-r--r--dom/security/test/sri/style4.css4
-rw-r--r--dom/security/test/sri/style4.css^headers^1
-rw-r--r--dom/security/test/sri/style5.css4
-rw-r--r--dom/security/test/sri/style6.css4
-rw-r--r--dom/security/test/sri/style6.css^headers^1
-rw-r--r--dom/security/test/sri/style_301.css3
-rw-r--r--dom/security/test/sri/style_301.css^headers^2
-rw-r--r--dom/security/test/sri/style_imported.css6
-rw-r--r--dom/security/test/sri/style_importing.css4
-rw-r--r--dom/security/test/sri/test_bug_1271796.html30
-rw-r--r--dom/security/test/sri/test_csp_directive_style_imports.html42
-rw-r--r--dom/security/test/sri/test_require-sri-for_csp_directive.html76
-rw-r--r--dom/security/test/sri/test_require-sri-for_csp_directive_disabled.html46
-rw-r--r--dom/security/test/sri/test_script_crossdomain.html18
-rw-r--r--dom/security/test/sri/test_script_sameorigin.html18
-rw-r--r--dom/security/test/sri/test_sri_disabled.html18
-rw-r--r--dom/security/test/sri/test_style_crossdomain.html18
-rw-r--r--dom/security/test/sri/test_style_sameorigin.html18
-rw-r--r--dom/security/test/unit/test_csp_reports.js231
-rw-r--r--dom/security/test/unit/test_csp_upgrade_insecure_request_header.js107
-rw-r--r--dom/security/test/unit/test_isOriginPotentiallyTrustworthy.js47
-rw-r--r--dom/security/test/unit/xpcshell.ini7
443 files changed, 31574 insertions, 0 deletions
diff --git a/dom/security/ContentVerifier.cpp b/dom/security/ContentVerifier.cpp
new file mode 100644
index 000000000..2d3fadea8
--- /dev/null
+++ b/dom/security/ContentVerifier.cpp
@@ -0,0 +1,230 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form 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/. */
+
+#include "ContentVerifier.h"
+
+#include "mozilla/fallible.h"
+#include "mozilla/Logging.h"
+#include "MainThreadUtils.h"
+#include "nsIInputStream.h"
+#include "nsIRequest.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStringStream.h"
+
+using namespace mozilla;
+
+static LazyLogModule gContentVerifierPRLog("ContentVerifier");
+#define CSV_LOG(args) MOZ_LOG(gContentVerifierPRLog, LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS(ContentVerifier,
+ nsIContentSignatureReceiverCallback,
+ nsIStreamListener);
+
+nsresult
+ContentVerifier::Init(const nsACString& aContentSignatureHeader,
+ nsIRequest* aRequest, nsISupports* aContext)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (aContentSignatureHeader.IsEmpty()) {
+ CSV_LOG(("Content-Signature header must not be empty!\n"));
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ // initialise the content signature "service"
+ nsresult rv;
+ mVerifier =
+ do_CreateInstance("@mozilla.org/security/contentsignatureverifier;1", &rv);
+ if (NS_FAILED(rv) || !mVerifier) {
+ return NS_ERROR_INVALID_SIGNATURE;
+ }
+
+ // Keep references to the request and context. We need them in FinishSignature
+ // and the ContextCreated callback.
+ mContentRequest = aRequest;
+ mContentContext = aContext;
+
+ rv = mVerifier->CreateContextWithoutCertChain(
+ this, aContentSignatureHeader,
+ NS_LITERAL_CSTRING("remotenewtab.content-signature.mozilla.org"));
+ if (NS_FAILED(rv)){
+ mVerifier = nullptr;
+ }
+ return rv;
+}
+
+/**
+ * Implement nsIStreamListener
+ * We buffer the entire content here and kick off verification
+ */
+nsresult
+AppendNextSegment(nsIInputStream* aInputStream, void* aClosure,
+ const char* aRawSegment, uint32_t aToOffset, uint32_t aCount,
+ uint32_t* outWrittenCount)
+{
+ FallibleTArray<nsCString>* decodedData =
+ static_cast<FallibleTArray<nsCString>*>(aClosure);
+ nsDependentCSubstring segment(aRawSegment, aCount);
+ if (!decodedData->AppendElement(segment, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ *outWrittenCount = aCount;
+ return NS_OK;
+}
+
+void
+ContentVerifier::FinishSignature()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIStreamListener> nextListener;
+ nextListener.swap(mNextListener);
+
+ // Verify the content:
+ // If this fails, we return an invalid signature error to load a fallback page.
+ // If everthing is good, we return a new stream to the next listener and kick
+ // that one off.
+ bool verified = false;
+ nsresult rv = NS_OK;
+
+ // If the content signature check fails, stop the load
+ // and return a signature error. NSS resources are freed by the
+ // ContentSignatureVerifier on destruction.
+ if (NS_FAILED(mVerifier->End(&verified)) || !verified) {
+ CSV_LOG(("failed to verify content\n"));
+ (void)nextListener->OnStopRequest(mContentRequest, mContentContext,
+ NS_ERROR_INVALID_SIGNATURE);
+ return;
+ }
+ CSV_LOG(("Successfully verified content signature.\n"));
+
+ // We emptied the input stream so we have to create a new one from mContent
+ // to hand it to the consuming listener.
+ uint64_t offset = 0;
+ for (uint32_t i = 0; i < mContent.Length(); ++i) {
+ nsCOMPtr<nsIInputStream> oInStr;
+ rv = NS_NewCStringInputStream(getter_AddRefs(oInStr), mContent[i]);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ // let the next listener know that there is data in oInStr
+ rv = nextListener->OnDataAvailable(mContentRequest, mContentContext, oInStr,
+ offset, mContent[i].Length());
+ offset += mContent[i].Length();
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ }
+
+ // propagate OnStopRequest and return
+ nextListener->OnStopRequest(mContentRequest, mContentContext, rv);
+}
+
+NS_IMETHODIMP
+ContentVerifier::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
+{
+ MOZ_CRASH("This OnStartRequest should've never been called!");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentVerifier::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
+ nsresult aStatus)
+{
+ // If we don't have a next listener, we handed off this request already.
+ // Return, there's nothing to do here.
+ if (!mNextListener) {
+ return NS_OK;
+ }
+
+ if (NS_FAILED(aStatus)) {
+ CSV_LOG(("Stream failed\n"));
+ nsCOMPtr<nsIStreamListener> nextListener;
+ nextListener.swap(mNextListener);
+ return nextListener->OnStopRequest(aRequest, aContext, aStatus);
+ }
+
+ mContentRead = true;
+
+ // If the ContentSignatureVerifier is initialised, finish the verification.
+ if (mContextCreated) {
+ FinishSignature();
+ return aStatus;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentVerifier::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
+ nsIInputStream* aInputStream, uint64_t aOffset,
+ uint32_t aCount)
+{
+ // buffer the entire stream
+ uint32_t read;
+ nsresult rv = aInputStream->ReadSegments(AppendNextSegment, &mContent, aCount,
+ &read);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Update the signature verifier if the context has been created.
+ if (mContextCreated) {
+ return mVerifier->Update(mContent.LastElement());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentVerifier::ContextCreated(bool successful)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!successful) {
+ // If we don't have a next listener, the request has been handed off already.
+ if (!mNextListener) {
+ return NS_OK;
+ }
+ // Get local reference to mNextListener and null it to ensure that we don't
+ // call it twice.
+ nsCOMPtr<nsIStreamListener> nextListener;
+ nextListener.swap(mNextListener);
+
+ // Make sure that OnStartRequest was called and we have a request.
+ MOZ_ASSERT(mContentRequest);
+
+ // In this case something went wrong with the cert. Let's stop this load.
+ CSV_LOG(("failed to get a valid cert chain\n"));
+ if (mContentRequest && nextListener) {
+ mContentRequest->Cancel(NS_ERROR_INVALID_SIGNATURE);
+ nsresult rv = nextListener->OnStopRequest(mContentRequest, mContentContext,
+ NS_ERROR_INVALID_SIGNATURE);
+ mContentRequest = nullptr;
+ mContentContext = nullptr;
+ return rv;
+ }
+
+ // We should never get here!
+ MOZ_ASSERT_UNREACHABLE(
+ "ContentVerifier was used without getting OnStartRequest!");
+ return NS_OK;
+ }
+
+ // In this case the content verifier is initialised and we have to feed it
+ // the buffered content.
+ mContextCreated = true;
+ for (size_t i = 0; i < mContent.Length(); ++i) {
+ if (NS_FAILED(mVerifier->Update(mContent[i]))) {
+ // Bail out if this fails. We can't return an error here, but if this
+ // failed, NS_ERROR_INVALID_SIGNATURE is returned in FinishSignature.
+ break;
+ }
+ }
+
+ // We read all content, let's verify the signature.
+ if (mContentRead) {
+ FinishSignature();
+ }
+
+ return NS_OK;
+}
diff --git a/dom/security/ContentVerifier.h b/dom/security/ContentVerifier.h
new file mode 100644
index 000000000..e0c940197
--- /dev/null
+++ b/dom/security/ContentVerifier.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form 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/. */
+
+#ifndef mozilla_dom_ContentVerifier_h
+#define mozilla_dom_ContentVerifier_h
+
+#include "nsCOMPtr.h"
+#include "nsIContentSignatureVerifier.h"
+#include "nsIObserver.h"
+#include "nsIStreamListener.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+/**
+ * Mediator intercepting OnStartRequest in nsHttpChannel, blocks until all
+ * data is read from the input stream, verifies the content signature and
+ * releases the request to the next listener if the verification is successful.
+ * If the verification fails or anything else goes wrong, a
+ * NS_ERROR_INVALID_SIGNATURE is thrown.
+ */
+class ContentVerifier : public nsIStreamListener
+ , public nsIContentSignatureReceiverCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSICONTENTSIGNATURERECEIVERCALLBACK
+
+ explicit ContentVerifier(nsIStreamListener* aMediatedListener,
+ nsISupports* aMediatedContext)
+ : mNextListener(aMediatedListener)
+ , mContextCreated(false)
+ , mContentRead(false) {}
+
+ nsresult Init(const nsACString& aContentSignatureHeader, nsIRequest* aRequest,
+ nsISupports* aContext);
+
+protected:
+ virtual ~ContentVerifier() {}
+
+private:
+ void FinishSignature();
+
+ // buffered content to verify
+ FallibleTArray<nsCString> mContent;
+ // content and next listener for nsIStreamListener
+ nsCOMPtr<nsIStreamListener> mNextListener;
+ // the verifier
+ nsCOMPtr<nsIContentSignatureVerifier> mVerifier;
+ // holding a pointer to the content request and context to resume/cancel it
+ nsCOMPtr<nsIRequest> mContentRequest;
+ nsCOMPtr<nsISupports> mContentContext;
+ // Semaphors to indicate that the verifying context was created, the entire
+ // content was read resp. The context gets created by ContentSignatureVerifier
+ // and mContextCreated is set in the ContextCreated callback. The content is
+ // read, i.e. mContentRead is set, when the content OnStopRequest is called.
+ bool mContextCreated;
+ bool mContentRead;
+};
+
+#endif /* mozilla_dom_ContentVerifier_h */
diff --git a/dom/security/SRICheck.cpp b/dom/security/SRICheck.cpp
new file mode 100644
index 000000000..534909f81
--- /dev/null
+++ b/dom/security/SRICheck.cpp
@@ -0,0 +1,543 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#include "SRICheck.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/LoadTainting.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/SRILogHelper.h"
+#include "mozilla/dom/SRIMetadata.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsIProtocolHandler.h"
+#include "nsIScriptError.h"
+#include "nsIIncrementalStreamLoader.h"
+#include "nsIUnicharStreamLoader.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsWhitespaceTokenizer.h"
+
+#define SRIVERBOSE(args) \
+ MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Verbose, args)
+#define SRILOG(args) \
+ MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, args)
+#define SRIERROR(args) \
+ MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Error, args)
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * Returns whether or not the sub-resource about to be loaded is eligible
+ * for integrity checks. If it's not, the checks will be skipped and the
+ * sub-resource will be loaded.
+ */
+static nsresult
+IsEligible(nsIChannel* aChannel, mozilla::LoadTainting aTainting,
+ const nsACString& aSourceFileURI,
+ nsIConsoleReportCollector* aReporter)
+{
+ NS_ENSURE_ARG_POINTER(aReporter);
+
+ if (!aChannel) {
+ SRILOG(("SRICheck::IsEligible, null channel"));
+ return NS_ERROR_SRI_NOT_ELIGIBLE;
+ }
+
+ // Was the sub-resource loaded via CORS?
+ if (aTainting == LoadTainting::CORS) {
+ SRILOG(("SRICheck::IsEligible, CORS mode"));
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> finalURI;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> originalURI;
+ rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString requestSpec;
+ rv = originalURI->GetSpec(requestSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
+ SRILOG(("SRICheck::IsEligible, requestURI=%s; finalURI=%s",
+ requestSpec.get(),
+ finalURI ? finalURI->GetSpecOrDefault().get() : ""));
+ }
+
+ // Is the sub-resource same-origin?
+ if (aTainting == LoadTainting::Basic) {
+ SRILOG(("SRICheck::IsEligible, same-origin"));
+ return NS_OK;
+ }
+ SRILOG(("SRICheck::IsEligible, NOT same origin"));
+
+ NS_ConvertUTF8toUTF16 requestSpecUTF16(requestSpec);
+ nsTArray<nsString> params;
+ params.AppendElement(requestSpecUTF16);
+ aReporter->AddConsoleReport(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("Sub-resource Integrity"),
+ nsContentUtils::eSECURITY_PROPERTIES,
+ aSourceFileURI, 0, 0,
+ NS_LITERAL_CSTRING("IneligibleResource"),
+ const_cast<const nsTArray<nsString>&>(params));
+ return NS_ERROR_SRI_NOT_ELIGIBLE;
+}
+
+/* static */ nsresult
+SRICheck::IntegrityMetadata(const nsAString& aMetadataList,
+ const nsACString& aSourceFileURI,
+ nsIConsoleReportCollector* aReporter,
+ SRIMetadata* outMetadata)
+{
+ NS_ENSURE_ARG_POINTER(outMetadata);
+ NS_ENSURE_ARG_POINTER(aReporter);
+ MOZ_ASSERT(outMetadata->IsEmpty()); // caller must pass empty metadata
+
+ if (!Preferences::GetBool("security.sri.enable", false)) {
+ SRILOG(("SRICheck::IntegrityMetadata, sri is disabled (pref)"));
+ return NS_ERROR_SRI_DISABLED;
+ }
+
+ // put a reasonable bound on the length of the metadata
+ NS_ConvertUTF16toUTF8 metadataList(aMetadataList);
+ if (metadataList.Length() > SRICheck::MAX_METADATA_LENGTH) {
+ metadataList.Truncate(SRICheck::MAX_METADATA_LENGTH);
+ }
+ MOZ_ASSERT(metadataList.Length() <= aMetadataList.Length());
+
+ // the integrity attribute is a list of whitespace-separated hashes
+ // and options so we need to look at them one by one and pick the
+ // strongest (valid) one
+ nsCWhitespaceTokenizer tokenizer(metadataList);
+ nsAutoCString token;
+ for (uint32_t i=0; tokenizer.hasMoreTokens() &&
+ i < SRICheck::MAX_METADATA_TOKENS; ++i) {
+ token = tokenizer.nextToken();
+
+ SRIMetadata metadata(token);
+ if (metadata.IsMalformed()) {
+ NS_ConvertUTF8toUTF16 tokenUTF16(token);
+ nsTArray<nsString> params;
+ params.AppendElement(tokenUTF16);
+ aReporter->AddConsoleReport(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Sub-resource Integrity"),
+ nsContentUtils::eSECURITY_PROPERTIES,
+ aSourceFileURI, 0, 0,
+ NS_LITERAL_CSTRING("MalformedIntegrityHash"),
+ const_cast<const nsTArray<nsString>&>(params));
+ } else if (!metadata.IsAlgorithmSupported()) {
+ nsAutoCString alg;
+ metadata.GetAlgorithm(&alg);
+ NS_ConvertUTF8toUTF16 algUTF16(alg);
+ nsTArray<nsString> params;
+ params.AppendElement(algUTF16);
+ aReporter->AddConsoleReport(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Sub-resource Integrity"),
+ nsContentUtils::eSECURITY_PROPERTIES,
+ aSourceFileURI, 0, 0,
+ NS_LITERAL_CSTRING("UnsupportedHashAlg"),
+ const_cast<const nsTArray<nsString>&>(params));
+ }
+
+ nsAutoCString alg1, alg2;
+ if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
+ outMetadata->GetAlgorithm(&alg1);
+ metadata.GetAlgorithm(&alg2);
+ }
+ if (*outMetadata == metadata) {
+ SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is the same as '%s'",
+ alg1.get(), alg2.get()));
+ *outMetadata += metadata; // add new hash to strongest metadata
+ } else if (*outMetadata < metadata) {
+ SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is weaker than '%s'",
+ alg1.get(), alg2.get()));
+ *outMetadata = metadata; // replace strongest metadata with current
+ }
+ }
+
+ outMetadata->mIntegrityString = aMetadataList;
+
+ if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
+ if (outMetadata->IsValid()) {
+ nsAutoCString alg;
+ outMetadata->GetAlgorithm(&alg);
+ SRILOG(("SRICheck::IntegrityMetadata, using a '%s' hash", alg.get()));
+ } else if (outMetadata->IsEmpty()) {
+ SRILOG(("SRICheck::IntegrityMetadata, no metadata"));
+ } else {
+ SRILOG(("SRICheck::IntegrityMetadata, no valid metadata found"));
+ }
+ }
+ return NS_OK;
+}
+
+/* static */ nsresult
+SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
+ nsIUnicharStreamLoader* aLoader,
+ const nsAString& aString,
+ const nsACString& aSourceFileURI,
+ nsIConsoleReportCollector* aReporter)
+{
+ NS_ENSURE_ARG_POINTER(aLoader);
+ NS_ENSURE_ARG_POINTER(aReporter);
+
+ nsCOMPtr<nsIChannel> channel;
+ aLoader->GetChannel(getter_AddRefs(channel));
+
+ if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
+ nsAutoCString requestURL;
+ nsCOMPtr<nsIURI> originalURI;
+ if (channel &&
+ NS_SUCCEEDED(channel->GetOriginalURI(getter_AddRefs(originalURI))) &&
+ originalURI) {
+ originalURI->GetAsciiSpec(requestURL);
+ }
+ SRILOG(("SRICheck::VerifyIntegrity (unichar stream)"));
+ }
+
+ SRICheckDataVerifier verifier(aMetadata, aSourceFileURI, aReporter);
+ nsresult rv;
+ nsDependentCString rawBuffer;
+ rv = aLoader->GetRawBuffer(rawBuffer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = verifier.Update(rawBuffer.Length(), (const uint8_t*)rawBuffer.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return verifier.Verify(aMetadata, channel, aSourceFileURI, aReporter);
+}
+
+//////////////////////////////////////////////////////////////
+//
+//////////////////////////////////////////////////////////////
+SRICheckDataVerifier::SRICheckDataVerifier(const SRIMetadata& aMetadata,
+ const nsACString& aSourceFileURI,
+ nsIConsoleReportCollector* aReporter)
+ : mCryptoHash(nullptr),
+ mBytesHashed(0),
+ mInvalidMetadata(false),
+ mComplete(false)
+{
+ MOZ_ASSERT(!aMetadata.IsEmpty()); // should be checked by caller
+
+ // IntegrityMetadata() checks this and returns "no metadata" if
+ // it's disabled so we should never make it this far
+ MOZ_ASSERT(Preferences::GetBool("security.sri.enable", false));
+ MOZ_ASSERT(aReporter);
+
+ if (!aMetadata.IsValid()) {
+ nsTArray<nsString> params;
+ aReporter->AddConsoleReport(nsIScriptError::warningFlag,
+ NS_LITERAL_CSTRING("Sub-resource Integrity"),
+ nsContentUtils::eSECURITY_PROPERTIES,
+ aSourceFileURI, 0, 0,
+ NS_LITERAL_CSTRING("NoValidMetadata"),
+ const_cast<const nsTArray<nsString>&>(params));
+ mInvalidMetadata = true;
+ return; // ignore invalid metadata for forward-compatibility
+ }
+
+ aMetadata.GetHashType(&mHashType, &mHashLength);
+}
+
+nsresult
+SRICheckDataVerifier::EnsureCryptoHash()
+{
+ MOZ_ASSERT(!mInvalidMetadata);
+
+ if (mCryptoHash) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsICryptoHash> cryptoHash =
+ do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = cryptoHash->Init(mHashType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCryptoHash = cryptoHash;
+ return NS_OK;
+}
+
+nsresult
+SRICheckDataVerifier::Update(uint32_t aStringLen, const uint8_t* aString)
+{
+ NS_ENSURE_ARG_POINTER(aString);
+ if (mInvalidMetadata) {
+ return NS_OK; // ignoring any data updates, see mInvalidMetadata usage
+ }
+
+ nsresult rv;
+ rv = EnsureCryptoHash();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mBytesHashed += aStringLen;
+
+ return mCryptoHash->Update(aString, aStringLen);
+}
+
+nsresult
+SRICheckDataVerifier::Finish()
+{
+ if (mInvalidMetadata || mComplete) {
+ return NS_OK; // already finished or invalid metadata
+ }
+
+ nsresult rv;
+ rv = EnsureCryptoHash(); // we need computed hash even for 0-length data
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mCryptoHash->Finish(false, mComputedHash);
+ mCryptoHash = nullptr;
+ mComplete = true;
+ return rv;
+}
+
+nsresult
+SRICheckDataVerifier::VerifyHash(const SRIMetadata& aMetadata,
+ uint32_t aHashIndex,
+ const nsACString& aSourceFileURI,
+ nsIConsoleReportCollector* aReporter)
+{
+ NS_ENSURE_ARG_POINTER(aReporter);
+
+ nsAutoCString base64Hash;
+ aMetadata.GetHash(aHashIndex, &base64Hash);
+ SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u]=%s", aHashIndex, base64Hash.get()));
+
+ nsAutoCString binaryHash;
+ if (NS_WARN_IF(NS_FAILED(Base64Decode(base64Hash, binaryHash)))) {
+ nsTArray<nsString> params;
+ aReporter->AddConsoleReport(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("Sub-resource Integrity"),
+ nsContentUtils::eSECURITY_PROPERTIES,
+ aSourceFileURI, 0, 0,
+ NS_LITERAL_CSTRING("InvalidIntegrityBase64"),
+ const_cast<const nsTArray<nsString>&>(params));
+ return NS_ERROR_SRI_CORRUPT;
+ }
+
+ uint32_t hashLength;
+ int8_t hashType;
+ aMetadata.GetHashType(&hashType, &hashLength);
+ if (binaryHash.Length() != hashLength) {
+ nsTArray<nsString> params;
+ aReporter->AddConsoleReport(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("Sub-resource Integrity"),
+ nsContentUtils::eSECURITY_PROPERTIES,
+ aSourceFileURI, 0, 0,
+ NS_LITERAL_CSTRING("InvalidIntegrityLength"),
+ const_cast<const nsTArray<nsString>&>(params));
+ return NS_ERROR_SRI_CORRUPT;
+ }
+
+ if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
+ nsAutoCString encodedHash;
+ nsresult rv = Base64Encode(mComputedHash, encodedHash);
+ if (NS_SUCCEEDED(rv)) {
+ SRILOG(("SRICheckDataVerifier::VerifyHash, mComputedHash=%s",
+ encodedHash.get()));
+ }
+ }
+
+ if (!binaryHash.Equals(mComputedHash)) {
+ SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u] did not match", aHashIndex));
+ return NS_ERROR_SRI_CORRUPT;
+ }
+
+ SRILOG(("SRICheckDataVerifier::VerifyHash, hash[%u] verified successfully", aHashIndex));
+ return NS_OK;
+}
+
+nsresult
+SRICheckDataVerifier::Verify(const SRIMetadata& aMetadata,
+ nsIChannel* aChannel,
+ const nsACString& aSourceFileURI,
+ nsIConsoleReportCollector* aReporter)
+{
+ NS_ENSURE_ARG_POINTER(aReporter);
+
+ if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
+ nsAutoCString requestURL;
+ nsCOMPtr<nsIRequest> request;
+ request = do_QueryInterface(aChannel);
+ request->GetName(requestURL);
+ SRILOG(("SRICheckDataVerifier::Verify, url=%s (length=%lu)",
+ requestURL.get(), mBytesHashed));
+ }
+
+ nsresult rv = Finish();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE);
+ LoadTainting tainting = loadInfo->GetTainting();
+
+ if (NS_FAILED(IsEligible(aChannel, tainting, aSourceFileURI, aReporter))) {
+ return NS_ERROR_SRI_NOT_ELIGIBLE;
+ }
+
+ if (mInvalidMetadata) {
+ return NS_OK; // ignore invalid metadata for forward-compatibility
+ }
+
+ for (uint32_t i = 0; i < aMetadata.HashCount(); i++) {
+ if (NS_SUCCEEDED(VerifyHash(aMetadata, i, aSourceFileURI, aReporter))) {
+ return NS_OK; // stop at the first valid hash
+ }
+ }
+
+ nsAutoCString alg;
+ aMetadata.GetAlgorithm(&alg);
+ NS_ConvertUTF8toUTF16 algUTF16(alg);
+ nsTArray<nsString> params;
+ params.AppendElement(algUTF16);
+ aReporter->AddConsoleReport(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("Sub-resource Integrity"),
+ nsContentUtils::eSECURITY_PROPERTIES,
+ aSourceFileURI, 0, 0,
+ NS_LITERAL_CSTRING("IntegrityMismatch"),
+ const_cast<const nsTArray<nsString>&>(params));
+ return NS_ERROR_SRI_CORRUPT;
+}
+
+uint32_t
+SRICheckDataVerifier::DataSummaryLength()
+{
+ MOZ_ASSERT(!mInvalidMetadata);
+ return sizeof(mHashType) + sizeof(mHashLength) + mHashLength;
+}
+
+uint32_t
+SRICheckDataVerifier::EmptyDataSummaryLength()
+{
+ return sizeof(int8_t) + sizeof(uint32_t);
+}
+
+nsresult
+SRICheckDataVerifier::DataSummaryLength(uint32_t aDataLen, const uint8_t* aData, uint32_t* length)
+{
+ *length = 0;
+ NS_ENSURE_ARG_POINTER(aData);
+
+ // we expect to always encode an SRI, even if it is empty or incomplete
+ if (aDataLen < EmptyDataSummaryLength()) {
+ SRILOG(("SRICheckDataVerifier::DataSummaryLength, encoded length[%u] is too small", aDataLen));
+ return NS_ERROR_SRI_IMPORT;
+ }
+
+ // decode the content of the buffer
+ size_t offset = sizeof(mHashType);
+ size_t len = *reinterpret_cast<const decltype(mHashLength)*>(&aData[offset]);
+ offset += sizeof(mHashLength);
+
+ SRIVERBOSE(("SRICheckDataVerifier::DataSummaryLength, header {%x, %x, %x, %x, %x, ...}",
+ aData[0], aData[1], aData[2], aData[3], aData[4]));
+
+ if (offset + len > aDataLen) {
+ SRILOG(("SRICheckDataVerifier::DataSummaryLength, encoded length[%u] overflow the buffer size", aDataLen));
+ SRIVERBOSE(("SRICheckDataVerifier::DataSummaryLength, offset[%u], len[%u]",
+ uint32_t(offset), uint32_t(len)));
+ return NS_ERROR_SRI_IMPORT;
+ }
+ *length = uint32_t(offset + len);
+ return NS_OK;
+}
+
+nsresult
+SRICheckDataVerifier::ImportDataSummary(uint32_t aDataLen, const uint8_t* aData)
+{
+ MOZ_ASSERT(!mInvalidMetadata); // mHashType and mHashLength should be valid
+ MOZ_ASSERT(!mCryptoHash); // EnsureCryptoHash should not have been called
+ NS_ENSURE_ARG_POINTER(aData);
+ if (mInvalidMetadata) {
+ return NS_OK; // ignoring any data updates, see mInvalidMetadata usage
+ }
+
+ // we expect to always encode an SRI, even if it is empty or incomplete
+ if (aDataLen < DataSummaryLength()) {
+ SRILOG(("SRICheckDataVerifier::ImportDataSummary, encoded length[%u] is too small", aDataLen));
+ return NS_ERROR_SRI_IMPORT;
+ }
+
+ SRIVERBOSE(("SRICheckDataVerifier::ImportDataSummary, header {%x, %x, %x, %x, %x, ...}",
+ aData[0], aData[1], aData[2], aData[3], aData[4]));
+
+ // decode the content of the buffer
+ size_t offset = 0;
+ if (*reinterpret_cast<const decltype(mHashType)*>(&aData[offset]) != mHashType) {
+ SRILOG(("SRICheckDataVerifier::ImportDataSummary, hash type[%d] does not match[%d]",
+ *reinterpret_cast<const decltype(mHashType)*>(&aData[offset]),
+ mHashType));
+ return NS_ERROR_SRI_UNEXPECTED_HASH_TYPE;
+ }
+ offset += sizeof(mHashType);
+
+ if (*reinterpret_cast<const decltype(mHashLength)*>(&aData[offset]) != mHashLength) {
+ SRILOG(("SRICheckDataVerifier::ImportDataSummary, hash length[%d] does not match[%d]",
+ *reinterpret_cast<const decltype(mHashLength)*>(&aData[offset]),
+ mHashLength));
+ return NS_ERROR_SRI_UNEXPECTED_HASH_TYPE;
+ }
+ offset += sizeof(mHashLength);
+
+ // copy the hash to mComputedHash, as-if we had finished streaming the bytes
+ mComputedHash.Assign(reinterpret_cast<const char*>(&aData[offset]), mHashLength);
+ mCryptoHash = nullptr;
+ mComplete = true;
+ return NS_OK;
+}
+
+nsresult
+SRICheckDataVerifier::ExportDataSummary(uint32_t aDataLen, uint8_t* aData)
+{
+ MOZ_ASSERT(!mInvalidMetadata); // mHashType and mHashLength should be valid
+ MOZ_ASSERT(mComplete); // finished streaming
+ NS_ENSURE_ARG_POINTER(aData);
+ NS_ENSURE_TRUE(aDataLen >= DataSummaryLength(), NS_ERROR_INVALID_ARG);
+
+ // serialize the hash in the buffer
+ size_t offset = 0;
+ *reinterpret_cast<decltype(mHashType)*>(&aData[offset]) = mHashType;
+ offset += sizeof(mHashType);
+ *reinterpret_cast<decltype(mHashLength)*>(&aData[offset]) = mHashLength;
+ offset += sizeof(mHashLength);
+
+ SRIVERBOSE(("SRICheckDataVerifier::ExportDataSummary, header {%x, %x, %x, %x, %x, ...}",
+ aData[0], aData[1], aData[2], aData[3], aData[4]));
+
+ // copy the hash to mComputedHash, as-if we had finished streaming the bytes
+ nsCharTraits<char>::copy(reinterpret_cast<char*>(&aData[offset]),
+ mComputedHash.get(), mHashLength);
+ return NS_OK;
+}
+
+nsresult
+SRICheckDataVerifier::ExportEmptyDataSummary(uint32_t aDataLen, uint8_t* aData)
+{
+ NS_ENSURE_ARG_POINTER(aData);
+ NS_ENSURE_TRUE(aDataLen >= EmptyDataSummaryLength(), NS_ERROR_INVALID_ARG);
+
+ // serialize an unknown hash in the buffer, to be able to skip it later
+ size_t offset = 0;
+ *reinterpret_cast<decltype(mHashType)*>(&aData[offset]) = 0;
+ offset += sizeof(mHashType);
+ *reinterpret_cast<decltype(mHashLength)*>(&aData[offset]) = 0;
+ offset += sizeof(mHashLength);
+
+ SRIVERBOSE(("SRICheckDataVerifier::ExportEmptyDataSummary, header {%x, %x, %x, %x, %x, ...}",
+ aData[0], aData[1], aData[2], aData[3], aData[4]));
+
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/security/SRICheck.h b/dom/security/SRICheck.h
new file mode 100644
index 000000000..82929fe36
--- /dev/null
+++ b/dom/security/SRICheck.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#ifndef mozilla_dom_SRICheck_h
+#define mozilla_dom_SRICheck_h
+
+#include "nsCOMPtr.h"
+#include "nsICryptoHash.h"
+
+class nsIChannel;
+class nsIUnicharStreamLoader;
+class nsIConsoleReportCollector;
+
+namespace mozilla {
+namespace dom {
+
+class SRIMetadata;
+
+class SRICheck final
+{
+public:
+ static const uint32_t MAX_METADATA_LENGTH = 24*1024;
+ static const uint32_t MAX_METADATA_TOKENS = 512;
+
+ /**
+ * Parse the multiple hashes specified in the integrity attribute and
+ * return the strongest supported hash.
+ */
+ static nsresult IntegrityMetadata(const nsAString& aMetadataList,
+ const nsACString& aSourceFileURI,
+ nsIConsoleReportCollector* aReporter,
+ SRIMetadata* outMetadata);
+
+ /**
+ * Process the integrity attribute of the element. A result of false
+ * must prevent the resource from loading.
+ */
+ static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
+ nsIUnicharStreamLoader* aLoader,
+ const nsAString& aString,
+ const nsACString& aSourceFileURI,
+ nsIConsoleReportCollector* aReporter);
+};
+
+// The SRICheckDataVerifier can be used in 2 different mode:
+//
+// 1. The streaming mode involves reading bytes from an input, and to use
+// the |Update| function to stream new bytes, and to use the |Verify|
+// function to check the hash of the content with the hash provided by
+// the metadata.
+//
+// Optionally, one can serialize the verified hash with |ExportDataSummary|,
+// in a buffer in order to rely on the second mode the next time.
+//
+// 2. The pre-computed mode, involves reading a hash with |ImportDataSummary|,
+// which got exported by the SRICheckDataVerifier and potentially cached, and
+// then use the |Verify| function to check against the hash provided by the
+// metadata.
+class SRICheckDataVerifier final
+{
+ public:
+ SRICheckDataVerifier(const SRIMetadata& aMetadata,
+ const nsACString& aSourceFileURI,
+ nsIConsoleReportCollector* aReporter);
+
+ // Append the following bytes to the content used to compute the hash. Once
+ // all bytes are streamed, use the Verify function to check the integrity.
+ nsresult Update(uint32_t aStringLen, const uint8_t* aString);
+
+ // Verify that the computed hash corresponds to the metadata.
+ nsresult Verify(const SRIMetadata& aMetadata, nsIChannel* aChannel,
+ const nsACString& aSourceFileURI,
+ nsIConsoleReportCollector* aReporter);
+
+ bool IsComplete() const {
+ return mComplete;
+ }
+
+ // Report the length of the computed hash and its type, such that we can
+ // reserve the space for encoding it in a vector.
+ uint32_t DataSummaryLength();
+ static uint32_t EmptyDataSummaryLength();
+
+ // Write the computed hash and its type in a pre-allocated buffer.
+ nsresult ExportDataSummary(uint32_t aDataLen, uint8_t* aData);
+ static nsresult ExportEmptyDataSummary(uint32_t aDataLen, uint8_t* aData);
+
+ // Report the length of the computed hash and its type, such that we can
+ // skip these data while reading a buffer.
+ static nsresult DataSummaryLength(uint32_t aDataLen, const uint8_t* aData, uint32_t* length);
+
+ // Extract the computed hash and its type, such that we can |Verify| if it
+ // matches the metadata. The buffer should be at least the same size or
+ // larger than the value returned by |DataSummaryLength|.
+ nsresult ImportDataSummary(uint32_t aDataLen, const uint8_t* aData);
+
+ private:
+ nsCOMPtr<nsICryptoHash> mCryptoHash;
+ nsAutoCString mComputedHash;
+ size_t mBytesHashed;
+ uint32_t mHashLength;
+ int8_t mHashType;
+ bool mInvalidMetadata;
+ bool mComplete;
+
+ nsresult EnsureCryptoHash();
+ nsresult Finish();
+ nsresult VerifyHash(const SRIMetadata& aMetadata, uint32_t aHashIndex,
+ const nsACString& aSourceFileURI,
+ nsIConsoleReportCollector* aReporter);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_SRICheck_h
diff --git a/dom/security/SRILogHelper.h b/dom/security/SRILogHelper.h
new file mode 100644
index 000000000..90638136d
--- /dev/null
+++ b/dom/security/SRILogHelper.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#ifndef mozilla_dom_SRILogHelper_h
+#define mozilla_dom_SRILogHelper_h
+
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+namespace dom {
+
+class SRILogHelper final
+{
+public:
+ static LogModule* GetSriLog()
+ {
+ static LazyLogModule gSriPRLog("SRI");
+ return gSriPRLog;
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_SRILogHelper_h
diff --git a/dom/security/SRIMetadata.cpp b/dom/security/SRIMetadata.cpp
new file mode 100644
index 000000000..801ff0477
--- /dev/null
+++ b/dom/security/SRIMetadata.cpp
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#include "SRIMetadata.h"
+
+#include "hasht.h"
+#include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/Logging.h"
+#include "nsICryptoHash.h"
+
+static mozilla::LogModule*
+GetSriMetadataLog()
+{
+ static mozilla::LazyLogModule gSriMetadataPRLog("SRIMetadata");
+ return gSriMetadataPRLog;
+}
+
+#define SRIMETADATALOG(args) MOZ_LOG(GetSriMetadataLog(), mozilla::LogLevel::Debug, args)
+#define SRIMETADATAERROR(args) MOZ_LOG(GetSriMetadataLog(), mozilla::LogLevel::Error, args)
+
+namespace mozilla {
+namespace dom {
+
+SRIMetadata::SRIMetadata(const nsACString& aToken)
+ : mAlgorithmType(SRIMetadata::UNKNOWN_ALGORITHM), mEmpty(false)
+{
+ MOZ_ASSERT(!aToken.IsEmpty()); // callers should check this first
+
+ SRIMETADATALOG(("SRIMetadata::SRIMetadata, aToken='%s'",
+ PromiseFlatCString(aToken).get()));
+
+ int32_t hyphen = aToken.FindChar('-');
+ if (hyphen == -1) {
+ SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (no hyphen)"));
+ return; // invalid metadata
+ }
+
+ // split the token into its components
+ mAlgorithm = Substring(aToken, 0, hyphen);
+ uint32_t hashStart = hyphen + 1;
+ if (hashStart >= aToken.Length()) {
+ SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (missing digest)"));
+ return; // invalid metadata
+ }
+ int32_t question = aToken.FindChar('?');
+ if (question == -1) {
+ mHashes.AppendElement(Substring(aToken, hashStart,
+ aToken.Length() - hashStart));
+ } else {
+ MOZ_ASSERT(question > 0);
+ if (static_cast<uint32_t>(question) <= hashStart) {
+ SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (options w/o digest)"));
+ return; // invalid metadata
+ }
+ mHashes.AppendElement(Substring(aToken, hashStart,
+ question - hashStart));
+ }
+
+ if (mAlgorithm.EqualsLiteral("sha256")) {
+ mAlgorithmType = nsICryptoHash::SHA256;
+ } else if (mAlgorithm.EqualsLiteral("sha384")) {
+ mAlgorithmType = nsICryptoHash::SHA384;
+ } else if (mAlgorithm.EqualsLiteral("sha512")) {
+ mAlgorithmType = nsICryptoHash::SHA512;
+ }
+
+ SRIMETADATALOG(("SRIMetadata::SRIMetadata, hash='%s'; alg='%s'",
+ mHashes[0].get(), mAlgorithm.get()));
+}
+
+bool
+SRIMetadata::operator<(const SRIMetadata& aOther) const
+{
+ static_assert(nsICryptoHash::SHA256 < nsICryptoHash::SHA384,
+ "We rely on the order indicating relative alg strength");
+ static_assert(nsICryptoHash::SHA384 < nsICryptoHash::SHA512,
+ "We rely on the order indicating relative alg strength");
+ MOZ_ASSERT(mAlgorithmType == SRIMetadata::UNKNOWN_ALGORITHM ||
+ mAlgorithmType == nsICryptoHash::SHA256 ||
+ mAlgorithmType == nsICryptoHash::SHA384 ||
+ mAlgorithmType == nsICryptoHash::SHA512);
+ MOZ_ASSERT(aOther.mAlgorithmType == SRIMetadata::UNKNOWN_ALGORITHM ||
+ aOther.mAlgorithmType == nsICryptoHash::SHA256 ||
+ aOther.mAlgorithmType == nsICryptoHash::SHA384 ||
+ aOther.mAlgorithmType == nsICryptoHash::SHA512);
+
+ if (mEmpty) {
+ SRIMETADATALOG(("SRIMetadata::operator<, first metadata is empty"));
+ return true; // anything beats the empty metadata (incl. invalid ones)
+ }
+
+ SRIMETADATALOG(("SRIMetadata::operator<, alg1='%d'; alg2='%d'",
+ mAlgorithmType, aOther.mAlgorithmType));
+ return (mAlgorithmType < aOther.mAlgorithmType);
+}
+
+bool
+SRIMetadata::operator>(const SRIMetadata& aOther) const
+{
+ MOZ_ASSERT(false);
+ return false;
+}
+
+SRIMetadata&
+SRIMetadata::operator+=(const SRIMetadata& aOther)
+{
+ MOZ_ASSERT(!aOther.IsEmpty() && !IsEmpty());
+ MOZ_ASSERT(aOther.IsValid() && IsValid());
+ MOZ_ASSERT(mAlgorithmType == aOther.mAlgorithmType);
+
+ // We only pull in the first element of the other metadata
+ MOZ_ASSERT(aOther.mHashes.Length() == 1);
+ if (mHashes.Length() < SRIMetadata::MAX_ALTERNATE_HASHES) {
+ SRIMETADATALOG(("SRIMetadata::operator+=, appending another '%s' hash (new length=%d)",
+ mAlgorithm.get(), mHashes.Length()));
+ mHashes.AppendElement(aOther.mHashes[0]);
+ }
+
+ MOZ_ASSERT(mHashes.Length() > 1);
+ MOZ_ASSERT(mHashes.Length() <= SRIMetadata::MAX_ALTERNATE_HASHES);
+ return *this;
+}
+
+bool
+SRIMetadata::operator==(const SRIMetadata& aOther) const
+{
+ if (IsEmpty() || !IsValid()) {
+ return false;
+ }
+ return mAlgorithmType == aOther.mAlgorithmType;
+}
+
+void
+SRIMetadata::GetHash(uint32_t aIndex, nsCString* outHash) const
+{
+ MOZ_ASSERT(aIndex < SRIMetadata::MAX_ALTERNATE_HASHES);
+ if (NS_WARN_IF(aIndex >= mHashes.Length())) {
+ *outHash = nullptr;
+ return;
+ }
+ *outHash = mHashes[aIndex];
+}
+
+void
+SRIMetadata::GetHashType(int8_t* outType, uint32_t* outLength) const
+{
+ // these constants are defined in security/nss/lib/util/hasht.h and
+ // netwerk/base/public/nsICryptoHash.idl
+ switch (mAlgorithmType) {
+ case nsICryptoHash::SHA256:
+ *outLength = SHA256_LENGTH;
+ break;
+ case nsICryptoHash::SHA384:
+ *outLength = SHA384_LENGTH;
+ break;
+ case nsICryptoHash::SHA512:
+ *outLength = SHA512_LENGTH;
+ break;
+ default:
+ *outLength = 0;
+ }
+ *outType = mAlgorithmType;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/security/SRIMetadata.h b/dom/security/SRIMetadata.h
new file mode 100644
index 000000000..4b6bdb47a
--- /dev/null
+++ b/dom/security/SRIMetadata.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#ifndef mozilla_dom_SRIMetadata_h
+#define mozilla_dom_SRIMetadata_h
+
+#include "nsTArray.h"
+#include "nsString.h"
+#include "SRICheck.h"
+
+namespace mozilla {
+namespace dom {
+
+class SRIMetadata final
+{
+ friend class SRICheck;
+
+public:
+ static const uint32_t MAX_ALTERNATE_HASHES = 256;
+ static const int8_t UNKNOWN_ALGORITHM = -1;
+
+ /**
+ * Create an empty metadata object.
+ */
+ SRIMetadata() : mAlgorithmType(UNKNOWN_ALGORITHM), mEmpty(true) {}
+
+ /**
+ * Split a string token into the components of an SRI metadata
+ * attribute.
+ */
+ explicit SRIMetadata(const nsACString& aToken);
+
+ /**
+ * Returns true when this object's hash algorithm is weaker than the
+ * other object's hash algorithm.
+ */
+ bool operator<(const SRIMetadata& aOther) const;
+
+ /**
+ * Not implemented. Should not be used.
+ */
+ bool operator>(const SRIMetadata& aOther) const;
+
+ /**
+ * Add another metadata's hash to this one.
+ */
+ SRIMetadata& operator+=(const SRIMetadata& aOther);
+
+ /**
+ * Returns true when the two metadata use the same hash algorithm.
+ */
+ bool operator==(const SRIMetadata& aOther) const;
+
+ bool IsEmpty() const { return mEmpty; }
+ bool IsMalformed() const { return mHashes.IsEmpty() || mAlgorithm.IsEmpty(); }
+ bool IsAlgorithmSupported() const { return mAlgorithmType != UNKNOWN_ALGORITHM; }
+ bool IsValid() const { return !IsMalformed() && IsAlgorithmSupported(); }
+
+ uint32_t HashCount() const { return mHashes.Length(); }
+ void GetHash(uint32_t aIndex, nsCString* outHash) const;
+ void GetAlgorithm(nsCString* outAlg) const { *outAlg = mAlgorithm; }
+ void GetHashType(int8_t* outType, uint32_t* outLength) const;
+
+ const nsString& GetIntegrityString() const
+ {
+ return mIntegrityString;
+ }
+
+private:
+ nsTArray<nsCString> mHashes;
+ nsString mIntegrityString;
+ nsCString mAlgorithm;
+ int8_t mAlgorithmType;
+ bool mEmpty;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_SRIMetadata_h
diff --git a/dom/security/moz.build b/dom/security/moz.build
new file mode 100644
index 000000000..00f7376a8
--- /dev/null
+++ b/dom/security/moz.build
@@ -0,0 +1,48 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form 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/.
+
+TEST_DIRS += ['test']
+
+EXPORTS.mozilla.dom += [
+ 'ContentVerifier.h',
+ 'nsContentSecurityManager.h',
+ 'nsCSPContext.h',
+ 'nsCSPService.h',
+ 'nsCSPUtils.h',
+ 'nsMixedContentBlocker.h',
+ 'SRICheck.h',
+ 'SRILogHelper.h',
+ 'SRIMetadata.h',
+]
+
+EXPORTS += [
+ 'nsContentSecurityManager.h',
+ 'nsMixedContentBlocker.h',
+]
+
+UNIFIED_SOURCES += [
+ 'ContentVerifier.cpp',
+ 'nsContentSecurityManager.cpp',
+ 'nsCSPContext.cpp',
+ 'nsCSPParser.cpp',
+ 'nsCSPService.cpp',
+ 'nsCSPUtils.cpp',
+ 'nsMixedContentBlocker.cpp',
+ 'SRICheck.cpp',
+ 'SRIMetadata.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '/caps',
+ '/netwerk/base',
+]
+
+if CONFIG['GNU_CC']:
+ CFLAGS += ['-Wformat-security']
+ CXXFLAGS += ['-Wformat-security']
diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp
new file mode 100644
index 000000000..815c7734d
--- /dev/null
+++ b/dom/security/nsCSPContext.cpp
@@ -0,0 +1,1567 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#include "nsCOMPtr.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsCSPContext.h"
+#include "nsCSPParser.h"
+#include "nsCSPService.h"
+#include "nsError.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIDOMNode.h"
+#include "nsIHttpChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIStringStream.h"
+#include "nsIUploadChannel.h"
+#include "nsIScriptError.h"
+#include "nsIWebNavigation.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsIContentPolicy.h"
+#include "nsSupportsPrimitives.h"
+#include "nsThreadUtils.h"
+#include "nsString.h"
+#include "nsScriptSecurityManager.h"
+#include "nsStringStream.h"
+#include "mozilla/Logging.h"
+#include "mozilla/dom/CSPReportBinding.h"
+#include "mozilla/dom/CSPDictionariesBinding.h"
+#include "mozilla/net/ReferrerPolicy.h"
+#include "nsINetworkInterceptController.h"
+#include "nsSandboxFlags.h"
+#include "nsIScriptElement.h"
+
+using namespace mozilla;
+
+static LogModule*
+GetCspContextLog()
+{
+ static LazyLogModule gCspContextPRLog("CSPContext");
+ return gCspContextPRLog;
+}
+
+#define CSPCONTEXTLOG(args) MOZ_LOG(GetCspContextLog(), mozilla::LogLevel::Debug, args)
+#define CSPCONTEXTLOGENABLED() MOZ_LOG_TEST(GetCspContextLog(), mozilla::LogLevel::Debug)
+
+static const uint32_t CSP_CACHE_URI_CUTOFF_SIZE = 512;
+
+/**
+ * Creates a key for use in the ShouldLoad cache.
+ * Looks like: <uri>!<nsIContentPolicy::LOAD_TYPE>
+ */
+nsresult
+CreateCacheKey_Internal(nsIURI* aContentLocation,
+ nsContentPolicyType aContentType,
+ nsACString& outCacheKey)
+{
+ if (!aContentLocation) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isDataScheme = false;
+ nsresult rv = aContentLocation->SchemeIs("data", &isDataScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outCacheKey.Truncate();
+ if (aContentType != nsIContentPolicy::TYPE_SCRIPT && isDataScheme) {
+ // For non-script data: URI, use ("data:", aContentType) as the cache key.
+ outCacheKey.Append(NS_LITERAL_CSTRING("data:"));
+ outCacheKey.AppendInt(aContentType);
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ rv = aContentLocation->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't cache for a URI longer than the cutoff size.
+ if (spec.Length() <= CSP_CACHE_URI_CUTOFF_SIZE) {
+ outCacheKey.Append(spec);
+ outCacheKey.Append(NS_LITERAL_CSTRING("!"));
+ outCacheKey.AppendInt(aContentType);
+ }
+
+ return NS_OK;
+}
+
+/* ===== nsIContentSecurityPolicy impl ====== */
+
+NS_IMETHODIMP
+nsCSPContext::ShouldLoad(nsContentPolicyType aContentType,
+ nsIURI* aContentLocation,
+ nsIURI* aRequestOrigin,
+ nsISupports* aRequestContext,
+ const nsACString& aMimeTypeGuess,
+ nsISupports* aExtra,
+ int16_t* outDecision)
+{
+ if (CSPCONTEXTLOGENABLED()) {
+ CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, aContentLocation: %s",
+ aContentLocation->GetSpecOrDefault().get()));
+ CSPCONTEXTLOG((">>>> aContentType: %d", aContentType));
+ }
+
+ bool isPreload = nsContentUtils::IsPreloadType(aContentType);
+
+ // Since we know whether we are dealing with a preload, we have to convert
+ // the internal policytype ot the external policy type before moving on.
+ // We still need to know if this is a worker so child-src can handle that
+ // case correctly.
+ aContentType = nsContentUtils::InternalContentPolicyTypeToExternalOrWorker(aContentType);
+
+ nsresult rv = NS_OK;
+
+ // This ShouldLoad function is called from nsCSPService::ShouldLoad,
+ // which already checked a number of things, including:
+ // * aContentLocation is not null; we can consume this without further checks
+ // * scheme is not a whitelisted scheme (about: chrome:, etc).
+ // * CSP is enabled
+ // * Content Type is not whitelisted (CSP Reports, TYPE_DOCUMENT, etc).
+ // * Fast Path for Apps
+
+ nsAutoCString cacheKey;
+ rv = CreateCacheKey_Internal(aContentLocation, aContentType, cacheKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isCached = mShouldLoadCache.Get(cacheKey, outDecision);
+ if (isCached && cacheKey.Length() > 0) {
+ // this is cached, use the cached value.
+ return NS_OK;
+ }
+
+ // Default decision, CSP can revise it if there's a policy to enforce
+ *outDecision = nsIContentPolicy::ACCEPT;
+
+ // If the content type doesn't map to a CSP directive, there's nothing for
+ // CSP to do.
+ CSPDirective dir = CSP_ContentTypeToDirective(aContentType);
+ if (dir == nsIContentSecurityPolicy::NO_DIRECTIVE) {
+ return NS_OK;
+ }
+
+ nsAutoString nonce;
+ bool parserCreated = false;
+ if (!isPreload) {
+ nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(aRequestContext);
+ if (htmlElement) {
+ rv = htmlElement->GetAttribute(NS_LITERAL_STRING("nonce"), nonce);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIScriptElement> script = do_QueryInterface(aRequestContext);
+ if (script && script->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER) {
+ parserCreated = true;
+ }
+ }
+
+ // aExtra is only non-null if the channel got redirected.
+ bool wasRedirected = (aExtra != nullptr);
+ nsCOMPtr<nsIURI> originalURI = do_QueryInterface(aExtra);
+
+ bool permitted = permitsInternal(dir,
+ aContentLocation,
+ originalURI,
+ nonce,
+ wasRedirected,
+ isPreload,
+ false, // allow fallback to default-src
+ true, // send violation reports
+ true, // send blocked URI in violation reports
+ parserCreated);
+
+ *outDecision = permitted ? nsIContentPolicy::ACCEPT
+ : nsIContentPolicy::REJECT_SERVER;
+
+ // Done looping, cache any relevant result
+ if (cacheKey.Length() > 0 && !isPreload) {
+ mShouldLoadCache.Put(cacheKey, *outDecision);
+ }
+
+ if (CSPCONTEXTLOGENABLED()) {
+ CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, decision: %s, "
+ "aContentLocation: %s",
+ *outDecision > 0 ? "load" : "deny",
+ aContentLocation->GetSpecOrDefault().get()));
+ }
+ return NS_OK;
+}
+
+bool
+nsCSPContext::permitsInternal(CSPDirective aDir,
+ nsIURI* aContentLocation,
+ nsIURI* aOriginalURI,
+ const nsAString& aNonce,
+ bool aWasRedirected,
+ bool aIsPreload,
+ bool aSpecific,
+ bool aSendViolationReports,
+ bool aSendContentLocationInViolationReports,
+ bool aParserCreated)
+{
+ bool permits = true;
+
+ nsAutoString violatedDirective;
+ for (uint32_t p = 0; p < mPolicies.Length(); p++) {
+
+ // According to the W3C CSP spec, frame-ancestors checks are ignored for
+ // report-only policies (when "monitoring").
+ if (aDir == nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE &&
+ mPolicies[p]->getReportOnlyFlag()) {
+ continue;
+ }
+
+ if (!mPolicies[p]->permits(aDir,
+ aContentLocation,
+ aNonce,
+ aWasRedirected,
+ aSpecific,
+ aParserCreated,
+ violatedDirective)) {
+ // If the policy is violated and not report-only, reject the load and
+ // report to the console
+ if (!mPolicies[p]->getReportOnlyFlag()) {
+ CSPCONTEXTLOG(("nsCSPContext::permitsInternal, false"));
+ permits = false;
+ }
+
+ // Do not send a report or notify observers if this is a preload - the
+ // decision may be wrong due to the inability to get the nonce, and will
+ // incorrectly fail the unit tests.
+ if (!aIsPreload && aSendViolationReports) {
+ this->AsyncReportViolation((aSendContentLocationInViolationReports ?
+ aContentLocation : nullptr),
+ aOriginalURI, /* in case of redirect originalURI is not null */
+ violatedDirective,
+ p, /* policy index */
+ EmptyString(), /* no observer subject */
+ EmptyString(), /* no source file */
+ EmptyString(), /* no script sample */
+ 0); /* no line number */
+ }
+ }
+ }
+
+ return permits;
+}
+
+
+
+/* ===== nsISupports implementation ========== */
+
+NS_IMPL_CLASSINFO(nsCSPContext,
+ nullptr,
+ nsIClassInfo::MAIN_THREAD_ONLY,
+ NS_CSPCONTEXT_CID)
+
+NS_IMPL_ISUPPORTS_CI(nsCSPContext,
+ nsIContentSecurityPolicy,
+ nsISerializable)
+
+nsCSPContext::nsCSPContext()
+ : mInnerWindowID(0)
+ , mLoadingContext(nullptr)
+ , mLoadingPrincipal(nullptr)
+ , mQueueUpMessages(true)
+{
+ CSPCONTEXTLOG(("nsCSPContext::nsCSPContext"));
+}
+
+nsCSPContext::~nsCSPContext()
+{
+ CSPCONTEXTLOG(("nsCSPContext::~nsCSPContext"));
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ delete mPolicies[i];
+ }
+ mShouldLoadCache.Clear();
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetPolicyString(uint32_t aIndex, nsAString& outStr)
+{
+ if (aIndex >= mPolicies.Length()) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ mPolicies[aIndex]->toString(outStr);
+ return NS_OK;
+}
+
+const nsCSPPolicy*
+nsCSPContext::GetPolicy(uint32_t aIndex)
+{
+ if (aIndex >= mPolicies.Length()) {
+ return nullptr;
+ }
+ return mPolicies[aIndex];
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetPolicyCount(uint32_t *outPolicyCount)
+{
+ *outPolicyCount = mPolicies.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetUpgradeInsecureRequests(bool *outUpgradeRequest)
+{
+ *outUpgradeRequest = false;
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ if (mPolicies[i]->hasDirective(nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) {
+ *outUpgradeRequest = true;
+ return NS_OK;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetBlockAllMixedContent(bool *outBlockAllMixedContent)
+{
+ *outBlockAllMixedContent = false;
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ if (!mPolicies[i]->getReportOnlyFlag() &&
+ mPolicies[i]->hasDirective(nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT)) {
+ *outBlockAllMixedContent = true;
+ return NS_OK;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetReferrerPolicy(uint32_t* outPolicy, bool* outIsSet)
+{
+ *outIsSet = false;
+ *outPolicy = mozilla::net::RP_Default;
+ nsAutoString refpol;
+ mozilla::net::ReferrerPolicy previousPolicy = mozilla::net::RP_Default;
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ mPolicies[i]->getReferrerPolicy(refpol);
+ // only set the referrer policy if not delievered through a CSPRO and
+ // note that and an empty string in refpol means it wasn't set
+ // (that's the default in nsCSPPolicy).
+ if (!mPolicies[i]->getReportOnlyFlag() && !refpol.IsEmpty()) {
+ // Referrer Directive in CSP is no more used and going to be replaced by
+ // Referrer-Policy HTTP header. But we still keep using referrer directive,
+ // and would remove it later.
+ // Referrer Directive specs is not fully compliant with new referrer policy
+ // specs. What we are using here:
+ // - If the value of the referrer directive is invalid, the user agent
+ // should set the referrer policy to no-referrer.
+ // - If there are two policies that specify a referrer policy, then they
+ // must agree or the employed policy is no-referrer.
+ if (!mozilla::net::IsValidReferrerPolicy(refpol)) {
+ *outPolicy = mozilla::net::RP_No_Referrer;
+ *outIsSet = true;
+ return NS_OK;
+ }
+
+ uint32_t currentPolicy = mozilla::net::ReferrerPolicyFromString(refpol);
+ if (*outIsSet && previousPolicy != currentPolicy) {
+ *outPolicy = mozilla::net::RP_No_Referrer;
+ return NS_OK;
+ }
+
+ *outPolicy = currentPolicy;
+ *outIsSet = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::AppendPolicy(const nsAString& aPolicyString,
+ bool aReportOnly,
+ bool aDeliveredViaMetaTag)
+{
+ CSPCONTEXTLOG(("nsCSPContext::AppendPolicy: %s",
+ NS_ConvertUTF16toUTF8(aPolicyString).get()));
+
+ // Use the mSelfURI from setRequestContext, see bug 991474
+ NS_ASSERTION(mSelfURI, "mSelfURI required for AppendPolicy, but not set");
+ nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy(aPolicyString, mSelfURI,
+ aReportOnly, this,
+ aDeliveredViaMetaTag);
+ if (policy) {
+ mPolicies.AppendElement(policy);
+ // reset cache since effective policy changes
+ mShouldLoadCache.Clear();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetAllowsEval(bool* outShouldReportViolation,
+ bool* outAllowsEval)
+{
+ *outShouldReportViolation = false;
+ *outAllowsEval = true;
+
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ if (!mPolicies[i]->allows(nsIContentPolicy::TYPE_SCRIPT,
+ CSP_UNSAFE_EVAL,
+ EmptyString(),
+ false)) {
+ // policy is violated: must report the violation and allow the inline
+ // script if the policy is report-only.
+ *outShouldReportViolation = true;
+ if (!mPolicies[i]->getReportOnlyFlag()) {
+ *outAllowsEval = false;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+// Helper function to report inline violations
+void
+nsCSPContext::reportInlineViolation(nsContentPolicyType aContentType,
+ const nsAString& aNonce,
+ const nsAString& aContent,
+ const nsAString& aViolatedDirective,
+ uint32_t aViolatedPolicyIndex, // TODO, use report only flag for that
+ uint32_t aLineNumber)
+{
+ nsString observerSubject;
+ // if the nonce is non empty, then we report the nonce error, otherwise
+ // let's report the hash error; no need to report the unsafe-inline error
+ // anymore.
+ if (!aNonce.IsEmpty()) {
+ observerSubject = (aContentType == nsIContentPolicy::TYPE_SCRIPT)
+ ? NS_LITERAL_STRING(SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC)
+ : NS_LITERAL_STRING(STYLE_NONCE_VIOLATION_OBSERVER_TOPIC);
+ }
+ else {
+ observerSubject = (aContentType == nsIContentPolicy::TYPE_SCRIPT)
+ ? NS_LITERAL_STRING(SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC)
+ : NS_LITERAL_STRING(STYLE_HASH_VIOLATION_OBSERVER_TOPIC);
+ }
+
+ nsCOMPtr<nsISupportsCString> selfICString(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+ if (selfICString) {
+ selfICString->SetData(nsDependentCString("self"));
+ }
+ nsCOMPtr<nsISupports> selfISupports(do_QueryInterface(selfICString));
+
+ // use selfURI as the sourceFile
+ nsAutoCString sourceFile;
+ if (mSelfURI) {
+ mSelfURI->GetSpec(sourceFile);
+ }
+
+ nsAutoString codeSample(aContent);
+ // cap the length of the script sample at 40 chars
+ if (codeSample.Length() > 40) {
+ codeSample.Truncate(40);
+ codeSample.AppendLiteral("...");
+ }
+ AsyncReportViolation(selfISupports, // aBlockedContentSource
+ mSelfURI, // aOriginalURI
+ aViolatedDirective, // aViolatedDirective
+ aViolatedPolicyIndex, // aViolatedPolicyIndex
+ observerSubject, // aObserverSubject
+ NS_ConvertUTF8toUTF16(sourceFile), // aSourceFile
+ codeSample, // aScriptSample
+ aLineNumber); // aLineNum
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType,
+ const nsAString& aNonce,
+ bool aParserCreated,
+ const nsAString& aContent,
+ uint32_t aLineNumber,
+ bool* outAllowsInline)
+{
+ *outAllowsInline = true;
+
+ MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
+ "We should only see external content policy types here.");
+
+ if (aContentType != nsIContentPolicy::TYPE_SCRIPT &&
+ aContentType != nsIContentPolicy::TYPE_STYLESHEET) {
+ MOZ_ASSERT(false, "can only allow inline for script or style");
+ return NS_OK;
+ }
+
+ // always iterate all policies, otherwise we might not send out all reports
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ bool allowed =
+ mPolicies[i]->allows(aContentType, CSP_UNSAFE_INLINE, EmptyString(), aParserCreated) ||
+ mPolicies[i]->allows(aContentType, CSP_NONCE, aNonce, aParserCreated) ||
+ mPolicies[i]->allows(aContentType, CSP_HASH, aContent, aParserCreated);
+
+ if (!allowed) {
+ // policy is violoated: deny the load unless policy is report only and
+ // report the violation.
+ if (!mPolicies[i]->getReportOnlyFlag()) {
+ *outAllowsInline = false;
+ }
+ nsAutoString violatedDirective;
+ mPolicies[i]->getDirectiveStringForContentType(aContentType, violatedDirective);
+ reportInlineViolation(aContentType,
+ aNonce,
+ aContent,
+ violatedDirective,
+ i,
+ aLineNumber);
+ }
+ }
+ return NS_OK;
+}
+
+
+/**
+ * Reduces some code repetition for the various logging situations in
+ * LogViolationDetails.
+ *
+ * Call-sites for the eval/inline checks recieve two return values: allows
+ * and violates. Based on those, they must choose whether to call
+ * LogViolationDetails or not. Policies that are report-only allow the
+ * loads/compilations but violations should still be reported. Not all
+ * policies in this nsIContentSecurityPolicy instance will be violated,
+ * which is why we must check allows() again here.
+ *
+ * Note: This macro uses some parameters from its caller's context:
+ * p, mPolicies, this, aSourceFile, aScriptSample, aLineNum, selfISupports
+ *
+ * @param violationType: the VIOLATION_TYPE_* constant (partial symbol)
+ * such as INLINE_SCRIPT
+ * @param contentPolicyType: a constant from nsIContentPolicy such as TYPE_STYLESHEET
+ * @param nonceOrHash: for NONCE and HASH violations, it's the nonce or content
+ * string. For other violations, it is an empty string.
+ * @param keyword: the keyword corresponding to violation (UNSAFE_INLINE for most)
+ * @param observerTopic: the observer topic string to send with the CSP
+ * observer notifications.
+ *
+ * Please note that inline violations for scripts are reported within
+ * GetAllowsInline() and do not call this macro, hence we can pass 'false'
+ * as the argument _aParserCreated_ to allows().
+ */
+#define CASE_CHECK_AND_REPORT(violationType, contentPolicyType, nonceOrHash, \
+ keyword, observerTopic) \
+ case nsIContentSecurityPolicy::VIOLATION_TYPE_ ## violationType : \
+ PR_BEGIN_MACRO \
+ if (!mPolicies[p]->allows(nsIContentPolicy::TYPE_ ## contentPolicyType, \
+ keyword, nonceOrHash, false)) \
+ { \
+ nsAutoString violatedDirective; \
+ mPolicies[p]->getDirectiveStringForContentType( \
+ nsIContentPolicy::TYPE_ ## contentPolicyType, \
+ violatedDirective); \
+ this->AsyncReportViolation(selfISupports, nullptr, violatedDirective, p, \
+ NS_LITERAL_STRING(observerTopic), \
+ aSourceFile, aScriptSample, aLineNum); \
+ } \
+ PR_END_MACRO; \
+ break
+
+/**
+ * For each policy, log any violation on the Error Console and send a report
+ * if a report-uri is present in the policy
+ *
+ * @param aViolationType
+ * one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
+ * @param aSourceFile
+ * name of the source file containing the violation (if available)
+ * @param aContentSample
+ * sample of the violating content (to aid debugging)
+ * @param aLineNum
+ * source line number of the violation (if available)
+ * @param aNonce
+ * (optional) If this is a nonce violation, include the nonce so we can
+ * recheck to determine which policies were violated and send the
+ * appropriate reports.
+ * @param aContent
+ * (optional) If this is a hash violation, include contents of the inline
+ * resource in the question so we can recheck the hash in order to
+ * determine which policies were violated and send the appropriate
+ * reports.
+ */
+NS_IMETHODIMP
+nsCSPContext::LogViolationDetails(uint16_t aViolationType,
+ const nsAString& aSourceFile,
+ const nsAString& aScriptSample,
+ int32_t aLineNum,
+ const nsAString& aNonce,
+ const nsAString& aContent)
+{
+ for (uint32_t p = 0; p < mPolicies.Length(); p++) {
+ NS_ASSERTION(mPolicies[p], "null pointer in nsTArray<nsCSPPolicy>");
+
+ nsCOMPtr<nsISupportsCString> selfICString(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+ if (selfICString) {
+ selfICString->SetData(nsDependentCString("self"));
+ }
+ nsCOMPtr<nsISupports> selfISupports(do_QueryInterface(selfICString));
+
+ switch (aViolationType) {
+ CASE_CHECK_AND_REPORT(EVAL, SCRIPT, NS_LITERAL_STRING(""),
+ CSP_UNSAFE_EVAL, EVAL_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(INLINE_STYLE, STYLESHEET, NS_LITERAL_STRING(""),
+ CSP_UNSAFE_INLINE, INLINE_STYLE_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(INLINE_SCRIPT, SCRIPT, NS_LITERAL_STRING(""),
+ CSP_UNSAFE_INLINE, INLINE_SCRIPT_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(NONCE_SCRIPT, SCRIPT, aNonce,
+ CSP_UNSAFE_INLINE, SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(NONCE_STYLE, STYLESHEET, aNonce,
+ CSP_UNSAFE_INLINE, STYLE_NONCE_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(HASH_SCRIPT, SCRIPT, aContent,
+ CSP_UNSAFE_INLINE, SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(HASH_STYLE, STYLESHEET, aContent,
+ CSP_UNSAFE_INLINE, STYLE_HASH_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(REQUIRE_SRI_FOR_STYLE, STYLESHEET, NS_LITERAL_STRING(""),
+ CSP_REQUIRE_SRI_FOR, REQUIRE_SRI_STYLE_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(REQUIRE_SRI_FOR_SCRIPT, SCRIPT, NS_LITERAL_STRING(""),
+ CSP_REQUIRE_SRI_FOR, REQUIRE_SRI_SCRIPT_VIOLATION_OBSERVER_TOPIC);
+
+
+ default:
+ NS_ASSERTION(false, "LogViolationDetails with invalid type");
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+#undef CASE_CHECK_AND_REPORT
+
+NS_IMETHODIMP
+nsCSPContext::SetRequestContext(nsIDOMDocument* aDOMDocument,
+ nsIPrincipal* aPrincipal)
+{
+ NS_PRECONDITION(aDOMDocument || aPrincipal,
+ "Can't set context without doc or principal");
+ NS_ENSURE_ARG(aDOMDocument || aPrincipal);
+
+ if (aDOMDocument) {
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDOMDocument);
+ mLoadingContext = do_GetWeakReference(doc);
+ mSelfURI = doc->GetDocumentURI();
+ mLoadingPrincipal = doc->NodePrincipal();
+ doc->GetReferrer(mReferrer);
+ mInnerWindowID = doc->InnerWindowID();
+ // the innerWindowID is not available for CSPs delivered through the
+ // header at the time setReqeustContext is called - let's queue up
+ // console messages until it becomes available, see flushConsoleMessages
+ mQueueUpMessages = !mInnerWindowID;
+ mCallingChannelLoadGroup = doc->GetDocumentLoadGroup();
+
+ // set the flag on the document for CSP telemetry
+ doc->SetHasCSP(true);
+ }
+ else {
+ CSPCONTEXTLOG(("No Document in SetRequestContext; can not query loadgroup; sending reports may fail."));
+ mLoadingPrincipal = aPrincipal;
+ mLoadingPrincipal->GetURI(getter_AddRefs(mSelfURI));
+ // if no document is available, then it also does not make sense to queue console messages
+ // sending messages to the browser conolse instead of the web console in that case.
+ mQueueUpMessages = false;
+ }
+
+ NS_ASSERTION(mSelfURI, "mSelfURI not available, can not translate 'self' into actual URI");
+ return NS_OK;
+}
+
+struct ConsoleMsgQueueElem {
+ nsXPIDLString mMsg;
+ nsString mSourceName;
+ nsString mSourceLine;
+ uint32_t mLineNumber;
+ uint32_t mColumnNumber;
+ uint32_t mSeverityFlag;
+};
+
+void
+nsCSPContext::flushConsoleMessages()
+{
+ // should flush messages even if doc is not available
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
+ if (doc) {
+ mInnerWindowID = doc->InnerWindowID();
+ }
+ mQueueUpMessages = false;
+
+ for (uint32_t i = 0; i < mConsoleMsgQueue.Length(); i++) {
+ ConsoleMsgQueueElem &elem = mConsoleMsgQueue[i];
+ CSP_LogMessage(elem.mMsg, elem.mSourceName, elem.mSourceLine,
+ elem.mLineNumber, elem.mColumnNumber,
+ elem.mSeverityFlag, "CSP", mInnerWindowID);
+ }
+ mConsoleMsgQueue.Clear();
+}
+
+void
+nsCSPContext::logToConsole(const char16_t* aName,
+ const char16_t** aParams,
+ uint32_t aParamsLength,
+ const nsAString& aSourceName,
+ const nsAString& aSourceLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aSeverityFlag)
+{
+ // let's check if we have to queue up console messages
+ if (mQueueUpMessages) {
+ nsXPIDLString msg;
+ CSP_GetLocalizedStr(aName, aParams, aParamsLength, getter_Copies(msg));
+ ConsoleMsgQueueElem &elem = *mConsoleMsgQueue.AppendElement();
+ elem.mMsg = msg;
+ elem.mSourceName = PromiseFlatString(aSourceName);
+ elem.mSourceLine = PromiseFlatString(aSourceLine);
+ elem.mLineNumber = aLineNumber;
+ elem.mColumnNumber = aColumnNumber;
+ elem.mSeverityFlag = aSeverityFlag;
+ return;
+ }
+ CSP_LogLocalizedStr(aName, aParams, aParamsLength, aSourceName,
+ aSourceLine, aLineNumber, aColumnNumber,
+ aSeverityFlag, "CSP", mInnerWindowID);
+}
+
+/**
+ * Strip URI for reporting according to:
+ * http://www.w3.org/TR/CSP/#violation-reports
+ *
+ * @param aURI
+ * The uri to be stripped for reporting
+ * @param aSelfURI
+ * The uri of the protected resource
+ * which is needed to enforce the SOP.
+ * @return ASCII serialization of the uri to be reported.
+ */
+void
+StripURIForReporting(nsIURI* aURI,
+ nsIURI* aSelfURI,
+ nsACString& outStrippedURI)
+{
+ // 1) If the origin of uri is a globally unique identifier (for example,
+ // aURI has a scheme of data, blob, or filesystem), then return the
+ // ASCII serialization of uri’s scheme.
+ bool isHttpOrFtp =
+ (NS_SUCCEEDED(aURI->SchemeIs("http", &isHttpOrFtp)) && isHttpOrFtp) ||
+ (NS_SUCCEEDED(aURI->SchemeIs("https", &isHttpOrFtp)) && isHttpOrFtp) ||
+ (NS_SUCCEEDED(aURI->SchemeIs("ftp", &isHttpOrFtp)) && isHttpOrFtp);
+
+ if (!isHttpOrFtp) {
+ // not strictly spec compliant, but what we really care about is
+ // http/https and also ftp. If it's not http/https or ftp, then treat aURI
+ // as if it's a globally unique identifier and just return the scheme.
+ aURI->GetScheme(outStrippedURI);
+ return;
+ }
+
+ // 2) If the origin of uri is not the same as the origin of the protected
+ // resource, then return the ASCII serialization of uri’s origin.
+ if (!NS_SecurityCompareURIs(aSelfURI, aURI, false)) {
+ // cross origin redirects also fall into this category, see:
+ // http://www.w3.org/TR/CSP/#violation-reports
+ aURI->GetPrePath(outStrippedURI);
+ return;
+ }
+
+ // 3) Return uri, with any fragment component removed.
+ aURI->GetSpecIgnoringRef(outStrippedURI);
+}
+
+/**
+ * Sends CSP violation reports to all sources listed under report-uri.
+ *
+ * @param aBlockedContentSource
+ * Either a CSP Source (like 'self', as string) or nsIURI: the source
+ * of the violation.
+ * @param aOriginalUri
+ * The original URI if the blocked content is a redirect, else null
+ * @param aViolatedDirective
+ * the directive that was violated (string).
+ * @param aSourceFile
+ * name of the file containing the inline script violation
+ * @param aScriptSample
+ * a sample of the violating inline script
+ * @param aLineNum
+ * source line number of the violation (if available)
+ */
+nsresult
+nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
+ nsIURI* aOriginalURI,
+ nsAString& aViolatedDirective,
+ uint32_t aViolatedPolicyIndex,
+ nsAString& aSourceFile,
+ nsAString& aScriptSample,
+ uint32_t aLineNum)
+{
+ NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
+
+#ifdef MOZ_B2G
+ // load group information (on process-split necko implementations like b2g).
+ // (fix this in bug 1011086)
+ if (!mCallingChannelLoadGroup) {
+ NS_WARNING("Load group required but not present for report sending; cannot send CSP violation reports");
+ return NS_ERROR_FAILURE;
+ }
+#endif
+
+ dom::CSPReport report;
+ nsresult rv;
+
+ // blocked-uri
+ if (aBlockedContentSource) {
+ nsAutoCString reportBlockedURI;
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(aBlockedContentSource);
+ // could be a string or URI
+ if (uri) {
+ StripURIForReporting(uri, mSelfURI, reportBlockedURI);
+ } else {
+ nsCOMPtr<nsISupportsCString> cstr = do_QueryInterface(aBlockedContentSource);
+ if (cstr) {
+ cstr->GetData(reportBlockedURI);
+ }
+ }
+ if (reportBlockedURI.IsEmpty()) {
+ // this can happen for frame-ancestors violation where the violating
+ // ancestor is cross-origin.
+ NS_WARNING("No blocked URI (null aBlockedContentSource) for CSP violation report.");
+ }
+ report.mCsp_report.mBlocked_uri = NS_ConvertUTF8toUTF16(reportBlockedURI);
+ }
+
+ // document-uri
+ nsAutoCString reportDocumentURI;
+ StripURIForReporting(mSelfURI, mSelfURI, reportDocumentURI);
+ report.mCsp_report.mDocument_uri = NS_ConvertUTF8toUTF16(reportDocumentURI);
+
+ // original-policy
+ nsAutoString originalPolicy;
+ rv = this->GetPolicyString(aViolatedPolicyIndex, originalPolicy);
+ NS_ENSURE_SUCCESS(rv, rv);
+ report.mCsp_report.mOriginal_policy = originalPolicy;
+
+ // referrer
+ if (!mReferrer.IsEmpty()) {
+ report.mCsp_report.mReferrer = mReferrer;
+ }
+
+ // violated-directive
+ report.mCsp_report.mViolated_directive = aViolatedDirective;
+
+ // source-file
+ if (!aSourceFile.IsEmpty()) {
+ // if aSourceFile is a URI, we have to make sure to strip fragments
+ nsCOMPtr<nsIURI> sourceURI;
+ NS_NewURI(getter_AddRefs(sourceURI), aSourceFile);
+ if (sourceURI) {
+ nsAutoCString spec;
+ sourceURI->GetSpecIgnoringRef(spec);
+ aSourceFile = NS_ConvertUTF8toUTF16(spec);
+ }
+ report.mCsp_report.mSource_file.Construct();
+ report.mCsp_report.mSource_file.Value() = aSourceFile;
+ }
+
+ // script-sample
+ if (!aScriptSample.IsEmpty()) {
+ report.mCsp_report.mScript_sample.Construct();
+ report.mCsp_report.mScript_sample.Value() = aScriptSample;
+ }
+
+ // line-number
+ if (aLineNum != 0) {
+ report.mCsp_report.mLine_number.Construct();
+ report.mCsp_report.mLine_number.Value() = aLineNum;
+ }
+
+ nsString csp_report;
+ if (!report.ToJSON(csp_report)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // ---------- Assembled, now send it to all the report URIs ----------- //
+
+ nsTArray<nsString> reportURIs;
+ mPolicies[aViolatedPolicyIndex]->getReportURIs(reportURIs);
+
+
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
+ nsCOMPtr<nsIURI> reportURI;
+ nsCOMPtr<nsIChannel> reportChannel;
+
+ for (uint32_t r = 0; r < reportURIs.Length(); r++) {
+ nsAutoCString reportURICstring = NS_ConvertUTF16toUTF8(reportURIs[r]);
+ // try to create a new uri from every report-uri string
+ rv = NS_NewURI(getter_AddRefs(reportURI), reportURIs[r]);
+ if (NS_FAILED(rv)) {
+ const char16_t* params[] = { reportURIs[r].get() };
+ CSPCONTEXTLOG(("Could not create nsIURI for report URI %s",
+ reportURICstring.get()));
+ logToConsole(u"triedToSendReport", params, ArrayLength(params),
+ aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
+ continue; // don't return yet, there may be more URIs
+ }
+
+ // try to create a new channel for every report-uri
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI;
+ if (doc) {
+ rv = NS_NewChannel(getter_AddRefs(reportChannel),
+ reportURI,
+ doc,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_CSP_REPORT,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ loadFlags);
+ }
+ else {
+ rv = NS_NewChannel(getter_AddRefs(reportChannel),
+ reportURI,
+ mLoadingPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_CSP_REPORT,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ loadFlags);
+ }
+
+ if (NS_FAILED(rv)) {
+ CSPCONTEXTLOG(("Could not create new channel for report URI %s",
+ reportURICstring.get()));
+ continue; // don't return yet, there may be more URIs
+ }
+
+ // log a warning to console if scheme is not http or https
+ bool isHttpScheme =
+ (NS_SUCCEEDED(reportURI->SchemeIs("http", &isHttpScheme)) && isHttpScheme) ||
+ (NS_SUCCEEDED(reportURI->SchemeIs("https", &isHttpScheme)) && isHttpScheme);
+
+ if (!isHttpScheme) {
+ const char16_t* params[] = { reportURIs[r].get() };
+ logToConsole(u"reportURInotHttpsOrHttp2", params, ArrayLength(params),
+ aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
+ continue;
+ }
+
+ // make sure this is an anonymous request (no cookies) so in case the
+ // policy URI is injected, it can't be abused for CSRF.
+ nsLoadFlags flags;
+ rv = reportChannel->GetLoadFlags(&flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ flags |= nsIRequest::LOAD_ANONYMOUS;
+ rv = reportChannel->SetLoadFlags(flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we need to set an nsIChannelEventSink on the channel object
+ // so we can tell it to not follow redirects when posting the reports
+ RefPtr<CSPReportRedirectSink> reportSink = new CSPReportRedirectSink();
+ if (doc && doc->GetDocShell()) {
+ nsCOMPtr<nsINetworkInterceptController> interceptController =
+ do_QueryInterface(doc->GetDocShell());
+ reportSink->SetInterceptController(interceptController);
+ }
+ reportChannel->SetNotificationCallbacks(reportSink);
+
+ // apply the loadgroup from the channel taken by setRequestContext. If
+ // there's no loadgroup, AsyncOpen will fail on process-split necko (since
+ // the channel cannot query the iTabChild).
+ rv = reportChannel->SetLoadGroup(mCallingChannelLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // wire in the string input stream to send the report
+ nsCOMPtr<nsIStringInputStream> sis(do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID));
+ NS_ASSERTION(sis, "nsIStringInputStream is needed but not available to send CSP violation reports");
+ nsAutoCString utf8CSPReport = NS_ConvertUTF16toUTF8(csp_report);
+ rv = sis->SetData(utf8CSPReport.get(), utf8CSPReport.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(reportChannel));
+ if (!uploadChannel) {
+ // It's possible the URI provided can't be uploaded to, in which case
+ // we skip this one. We'll already have warned about a non-HTTP URI earlier.
+ continue;
+ }
+
+ rv = uploadChannel->SetUploadStream(sis, NS_LITERAL_CSTRING("application/csp-report"), -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if this is an HTTP channel, set the request method to post
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(reportChannel));
+ if (httpChannel) {
+ httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
+ }
+
+ RefPtr<CSPViolationReportListener> listener = new CSPViolationReportListener();
+ rv = reportChannel->AsyncOpen2(listener);
+
+ // AsyncOpen should not fail, but could if there's no load group (like if
+ // SetRequestContext is not given a channel). This should fail quietly and
+ // not return an error since it's really ok if reports don't go out, but
+ // it's good to log the error locally.
+
+ if (NS_FAILED(rv)) {
+ const char16_t* params[] = { reportURIs[r].get() };
+ CSPCONTEXTLOG(("AsyncOpen failed for report URI %s", params[0]));
+ logToConsole(u"triedToSendReport", params, ArrayLength(params),
+ aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
+ } else {
+ CSPCONTEXTLOG(("Sent violation report to URI %s", reportURICstring.get()));
+ }
+ }
+ return NS_OK;
+}
+
+/**
+ * Dispatched from the main thread to send reports for one CSP violation.
+ */
+class CSPReportSenderRunnable final : public Runnable
+{
+ public:
+ CSPReportSenderRunnable(nsISupports* aBlockedContentSource,
+ nsIURI* aOriginalURI,
+ uint32_t aViolatedPolicyIndex,
+ bool aReportOnlyFlag,
+ const nsAString& aViolatedDirective,
+ const nsAString& aObserverSubject,
+ const nsAString& aSourceFile,
+ const nsAString& aScriptSample,
+ uint32_t aLineNum,
+ nsCSPContext* aCSPContext)
+ : mBlockedContentSource(aBlockedContentSource)
+ , mOriginalURI(aOriginalURI)
+ , mViolatedPolicyIndex(aViolatedPolicyIndex)
+ , mReportOnlyFlag(aReportOnlyFlag)
+ , mViolatedDirective(aViolatedDirective)
+ , mSourceFile(aSourceFile)
+ , mScriptSample(aScriptSample)
+ , mLineNum(aLineNum)
+ , mCSPContext(aCSPContext)
+ {
+ NS_ASSERTION(!aViolatedDirective.IsEmpty(), "Can not send reports without a violated directive");
+ // the observer subject is an nsISupports: either an nsISupportsCString
+ // from the arg passed in directly, or if that's empty, it's the blocked
+ // source.
+ if (aObserverSubject.IsEmpty()) {
+ mObserverSubject = aBlockedContentSource;
+ } else {
+ nsCOMPtr<nsISupportsCString> supportscstr =
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
+ NS_ASSERTION(supportscstr, "Couldn't allocate nsISupportsCString");
+ supportscstr->SetData(NS_ConvertUTF16toUTF8(aObserverSubject));
+ mObserverSubject = do_QueryInterface(supportscstr);
+ }
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // 1) notify observers
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ NS_ASSERTION(observerService, "needs observer service");
+ nsresult rv = observerService->NotifyObservers(mObserverSubject,
+ CSP_VIOLATION_TOPIC,
+ mViolatedDirective.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 2) send reports for the policy that was violated
+ mCSPContext->SendReports(mBlockedContentSource, mOriginalURI,
+ mViolatedDirective, mViolatedPolicyIndex,
+ mSourceFile, mScriptSample, mLineNum);
+
+ // 3) log to console (one per policy violation)
+ // mBlockedContentSource could be a URI or a string.
+ nsCOMPtr<nsIURI> blockedURI = do_QueryInterface(mBlockedContentSource);
+ // if mBlockedContentSource is not a URI, it could be a string
+ nsCOMPtr<nsISupportsCString> blockedString = do_QueryInterface(mBlockedContentSource);
+
+ nsCString blockedDataStr;
+
+ if (blockedURI) {
+ blockedURI->GetSpec(blockedDataStr);
+ bool isData = false;
+ rv = blockedURI->SchemeIs("data", &isData);
+ if (NS_SUCCEEDED(rv) && isData) {
+ blockedDataStr.Truncate(40);
+ blockedDataStr.AppendASCII("...");
+ }
+ } else if (blockedString) {
+ blockedString->GetData(blockedDataStr);
+ }
+
+ if (blockedDataStr.Length() > 0) {
+ nsString blockedDataChar16 = NS_ConvertUTF8toUTF16(blockedDataStr);
+ const char16_t* params[] = { mViolatedDirective.get(),
+ blockedDataChar16.get() };
+ mCSPContext->logToConsole(mReportOnlyFlag ? u"CSPROViolationWithURI" :
+ u"CSPViolationWithURI",
+ params, ArrayLength(params), mSourceFile, mScriptSample,
+ mLineNum, 0, nsIScriptError::errorFlag);
+ }
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsISupports> mBlockedContentSource;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ uint32_t mViolatedPolicyIndex;
+ bool mReportOnlyFlag;
+ nsString mViolatedDirective;
+ nsCOMPtr<nsISupports> mObserverSubject;
+ nsString mSourceFile;
+ nsString mScriptSample;
+ uint32_t mLineNum;
+ RefPtr<nsCSPContext> mCSPContext;
+};
+
+/**
+ * Asynchronously notifies any nsIObservers listening to the CSP violation
+ * topic that a violation occurred. Also triggers report sending and console
+ * logging. All asynchronous on the main thread.
+ *
+ * @param aBlockedContentSource
+ * Either a CSP Source (like 'self', as string) or nsIURI: the source
+ * of the violation.
+ * @param aOriginalUri
+ * The original URI if the blocked content is a redirect, else null
+ * @param aViolatedDirective
+ * the directive that was violated (string).
+ * @param aViolatedPolicyIndex
+ * the index of the policy that was violated (so we know where to send
+ * the reports).
+ * @param aObserverSubject
+ * optional, subject sent to the nsIObservers listening to the CSP
+ * violation topic.
+ * @param aSourceFile
+ * name of the file containing the inline script violation
+ * @param aScriptSample
+ * a sample of the violating inline script
+ * @param aLineNum
+ * source line number of the violation (if available)
+ */
+nsresult
+nsCSPContext::AsyncReportViolation(nsISupports* aBlockedContentSource,
+ nsIURI* aOriginalURI,
+ const nsAString& aViolatedDirective,
+ uint32_t aViolatedPolicyIndex,
+ const nsAString& aObserverSubject,
+ const nsAString& aSourceFile,
+ const nsAString& aScriptSample,
+ uint32_t aLineNum)
+{
+ NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
+
+ NS_DispatchToMainThread(new CSPReportSenderRunnable(aBlockedContentSource,
+ aOriginalURI,
+ aViolatedPolicyIndex,
+ mPolicies[aViolatedPolicyIndex]->getReportOnlyFlag(),
+ aViolatedDirective,
+ aObserverSubject,
+ aSourceFile,
+ aScriptSample,
+ aLineNum,
+ this));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::RequireSRIForType(nsContentPolicyType aContentType, bool* outRequiresSRIForType)
+{
+ *outRequiresSRIForType = false;
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ if (mPolicies[i]->hasDirective(REQUIRE_SRI_FOR)) {
+ if (mPolicies[i]->requireSRIForType(aContentType)) {
+ *outRequiresSRIForType = true;
+ return NS_OK;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+/**
+ * Based on the given docshell, determines if this CSP context allows the
+ * ancestry.
+ *
+ * In order to determine the URI of the parent document (one causing the load
+ * of this protected document), this function obtains the docShellTreeItem,
+ * then walks up the hierarchy until it finds a privileged (chrome) tree item.
+ * Getting the a tree item's URI looks like this in pseudocode:
+ *
+ * nsIDocShellTreeItem->GetDocument()->GetDocumentURI();
+ *
+ * aDocShell is the docShell for the protected document.
+ */
+NS_IMETHODIMP
+nsCSPContext::PermitsAncestry(nsIDocShell* aDocShell, bool* outPermitsAncestry)
+{
+ nsresult rv;
+
+ // Can't check ancestry without a docShell.
+ if (aDocShell == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *outPermitsAncestry = true;
+
+ // extract the ancestry as an array
+ nsCOMArray<nsIURI> ancestorsArray;
+
+ nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(aDocShell));
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(do_GetInterface(ir));
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ nsCOMPtr<nsIURI> currentURI;
+ nsCOMPtr<nsIURI> uriClone;
+
+ // iterate through each docShell parent item
+ while (NS_SUCCEEDED(treeItem->GetParent(getter_AddRefs(parentTreeItem))) &&
+ parentTreeItem != nullptr) {
+
+ nsIDocument* doc = parentTreeItem->GetDocument();
+ NS_ASSERTION(doc, "Could not get nsIDocument from nsIDocShellTreeItem in nsCSPContext::PermitsAncestry");
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ currentURI = doc->GetDocumentURI();
+
+ if (currentURI) {
+ // stop when reaching chrome
+ bool isChrome = false;
+ rv = currentURI->SchemeIs("chrome", &isChrome);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isChrome) { break; }
+
+ // delete the userpass from the URI.
+ rv = currentURI->CloneIgnoringRef(getter_AddRefs(uriClone));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We don't care if this succeeds, just want to delete a userpass if
+ // there was one.
+ uriClone->SetUserPass(EmptyCString());
+
+ if (CSPCONTEXTLOGENABLED()) {
+ CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, found ancestor: %s",
+ uriClone->GetSpecOrDefault().get()));
+ }
+ ancestorsArray.AppendElement(uriClone);
+ }
+
+ // next ancestor
+ treeItem = parentTreeItem;
+ }
+
+ nsAutoString violatedDirective;
+
+ // Now that we've got the ancestry chain in ancestorsArray, time to check
+ // them against any CSP.
+ // NOTE: the ancestors are not allowed to be sent cross origin; this is a
+ // restriction not placed on subresource loads.
+
+ for (uint32_t a = 0; a < ancestorsArray.Length(); a++) {
+ if (CSPCONTEXTLOGENABLED()) {
+ CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, checking ancestor: %s",
+ ancestorsArray[a]->GetSpecOrDefault().get()));
+ }
+ // omit the ancestor URI in violation reports if cross-origin as per spec
+ // (it is a violation of the same-origin policy).
+ bool okToSendAncestor = NS_SecurityCompareURIs(ancestorsArray[a], mSelfURI, true);
+
+
+ bool permits = permitsInternal(nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE,
+ ancestorsArray[a],
+ nullptr, // no redirect here.
+ EmptyString(), // no nonce
+ false, // no redirect here.
+ false, // not a preload.
+ true, // specific, do not use default-src
+ true, // send violation reports
+ okToSendAncestor,
+ false); // not parser created
+ if (!permits) {
+ *outPermitsAncestry = false;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::Permits(nsIURI* aURI,
+ CSPDirective aDir,
+ bool aSpecific,
+ bool* outPermits)
+{
+ // Can't perform check without aURI
+ if (aURI == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *outPermits = permitsInternal(aDir,
+ aURI,
+ nullptr, // no original (pre-redirect) URI
+ EmptyString(), // no nonce
+ false, // not redirected.
+ false, // not a preload.
+ aSpecific,
+ true, // send violation reports
+ true, // send blocked URI in violation reports
+ false); // not parser created
+
+ if (CSPCONTEXTLOGENABLED()) {
+ CSPCONTEXTLOG(("nsCSPContext::Permits, aUri: %s, aDir: %d, isAllowed: %s",
+ aURI->GetSpecOrDefault().get(), aDir,
+ *outPermits ? "allow" : "deny"));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::ToJSON(nsAString& outCSPinJSON)
+{
+ outCSPinJSON.Truncate();
+ dom::CSPPolicies jsonPolicies;
+ jsonPolicies.mCsp_policies.Construct();
+
+ for (uint32_t p = 0; p < mPolicies.Length(); p++) {
+ dom::CSP jsonCSP;
+ mPolicies[p]->toDomCSPStruct(jsonCSP);
+ jsonPolicies.mCsp_policies.Value().AppendElement(jsonCSP, fallible);
+ }
+
+ // convert the gathered information to JSON
+ if (!jsonPolicies.ToJSON(outCSPinJSON)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetCSPSandboxFlags(uint32_t* aOutSandboxFlags)
+{
+ if (!aOutSandboxFlags) {
+ return NS_ERROR_FAILURE;
+ }
+ *aOutSandboxFlags = SANDBOXED_NONE;
+
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ uint32_t flags = mPolicies[i]->getSandboxFlags();
+
+ // current policy doesn't have sandbox flag, check next policy
+ if (!flags) {
+ continue;
+ }
+
+ // current policy has sandbox flags, if the policy is in enforcement-mode
+ // (i.e. not report-only) set these flags and check for policies with more
+ // restrictions
+ if (!mPolicies[i]->getReportOnlyFlag()) {
+ *aOutSandboxFlags |= flags;
+ } else {
+ // sandbox directive is ignored in report-only mode, warn about it and
+ // continue the loop checking for an enforcement policy.
+ nsAutoString policy;
+ mPolicies[i]->toString(policy);
+
+ CSPCONTEXTLOG(("nsCSPContext::GetCSPSandboxFlags, report only policy, ignoring sandbox in: %s",
+ policy.get()));
+
+ const char16_t* params[] = { policy.get() };
+ logToConsole(u"ignoringReportOnlyDirective", params, ArrayLength(params),
+ EmptyString(), EmptyString(), 0, 0, nsIScriptError::warningFlag);
+ }
+ }
+
+ return NS_OK;
+}
+
+/* ========== CSPViolationReportListener implementation ========== */
+
+NS_IMPL_ISUPPORTS(CSPViolationReportListener, nsIStreamListener, nsIRequestObserver, nsISupports);
+
+CSPViolationReportListener::CSPViolationReportListener()
+{
+}
+
+CSPViolationReportListener::~CSPViolationReportListener()
+{
+}
+
+nsresult
+AppendSegmentToString(nsIInputStream* aInputStream,
+ void* aClosure,
+ const char* aRawSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* outWrittenCount)
+{
+ nsCString* decodedData = static_cast<nsCString*>(aClosure);
+ decodedData->Append(aRawSegment, aCount);
+ *outWrittenCount = aCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CSPViolationReportListener::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ uint32_t read;
+ nsCString decodedData;
+ return aInputStream->ReadSegments(AppendSegmentToString,
+ &decodedData,
+ aCount,
+ &read);
+}
+
+NS_IMETHODIMP
+CSPViolationReportListener::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CSPViolationReportListener::OnStartRequest(nsIRequest* aRequest,
+ nsISupports* aContext)
+{
+ return NS_OK;
+}
+
+/* ========== CSPReportRedirectSink implementation ========== */
+
+NS_IMPL_ISUPPORTS(CSPReportRedirectSink, nsIChannelEventSink, nsIInterfaceRequestor);
+
+CSPReportRedirectSink::CSPReportRedirectSink()
+{
+}
+
+CSPReportRedirectSink::~CSPReportRedirectSink()
+{
+}
+
+NS_IMETHODIMP
+CSPReportRedirectSink::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aRedirFlags,
+ nsIAsyncVerifyRedirectCallback* aCallback)
+{
+ // cancel the old channel so XHR failure callback happens
+ nsresult rv = aOldChannel->Cancel(NS_ERROR_ABORT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // notify an observer that we have blocked the report POST due to a redirect,
+ // used in testing, do this async since we're in an async call now to begin with
+ nsCOMPtr<nsIURI> uri;
+ rv = aOldChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ NS_ASSERTION(observerService, "Observer service required to log CSP violations");
+ observerService->NotifyObservers(uri,
+ CSP_VIOLATION_TOPIC,
+ u"denied redirect while sending violation report");
+
+ return NS_BINDING_REDIRECTED;
+}
+
+NS_IMETHODIMP
+CSPReportRedirectSink::GetInterface(const nsIID& aIID, void** aResult)
+{
+ if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) &&
+ mInterceptController) {
+ nsCOMPtr<nsINetworkInterceptController> copy(mInterceptController);
+ *aResult = copy.forget().take();
+
+ return NS_OK;
+ }
+
+ return QueryInterface(aIID, aResult);
+}
+
+void
+CSPReportRedirectSink::SetInterceptController(nsINetworkInterceptController* aInterceptController)
+{
+ mInterceptController = aInterceptController;
+}
+
+/* ===== nsISerializable implementation ====== */
+
+NS_IMETHODIMP
+nsCSPContext::Read(nsIObjectInputStream* aStream)
+{
+ nsresult rv;
+ nsCOMPtr<nsISupports> supports;
+
+ rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSelfURI = do_QueryInterface(supports);
+ NS_ASSERTION(mSelfURI, "need a self URI to de-serialize");
+
+ uint32_t numPolicies;
+ rv = aStream->Read32(&numPolicies);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString policyString;
+
+ while (numPolicies > 0) {
+ numPolicies--;
+
+ rv = aStream->ReadString(policyString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool reportOnly = false;
+ rv = aStream->ReadBoolean(&reportOnly);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // @param deliveredViaMetaTag:
+ // when parsing the CSP policy string initially we already remove directives
+ // that should not be processed when delivered via the meta tag. Such directives
+ // will not be present at this point anymore.
+ nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy(policyString,
+ mSelfURI,
+ reportOnly,
+ this,
+ false);
+ if (policy) {
+ mPolicies.AppendElement(policy);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::Write(nsIObjectOutputStream* aStream)
+{
+ nsresult rv = NS_WriteOptionalCompoundObject(aStream,
+ mSelfURI,
+ NS_GET_IID(nsIURI),
+ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Serialize all the policies.
+ aStream->Write32(mPolicies.Length());
+
+ nsAutoString polStr;
+ for (uint32_t p = 0; p < mPolicies.Length(); p++) {
+ polStr.Truncate();
+ mPolicies[p]->toString(polStr);
+ aStream->WriteWStringZ(polStr.get());
+ aStream->WriteBoolean(mPolicies[p]->getReportOnlyFlag());
+ }
+ return NS_OK;
+}
diff --git a/dom/security/nsCSPContext.h b/dom/security/nsCSPContext.h
new file mode 100644
index 000000000..729f440ce
--- /dev/null
+++ b/dom/security/nsCSPContext.h
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#ifndef nsCSPContext_h___
+#define nsCSPContext_h___
+
+#include "mozilla/dom/nsCSPUtils.h"
+#include "nsDataHashtable.h"
+#include "nsIChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIClassInfo.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsISerializable.h"
+#include "nsIStreamListener.h"
+#include "nsWeakReference.h"
+#include "nsXPCOM.h"
+
+#define NS_CSPCONTEXT_CONTRACTID "@mozilla.org/cspcontext;1"
+ // 09d9ed1a-e5d4-4004-bfe0-27ceb923d9ac
+#define NS_CSPCONTEXT_CID \
+{ 0x09d9ed1a, 0xe5d4, 0x4004, \
+ { 0xbf, 0xe0, 0x27, 0xce, 0xb9, 0x23, 0xd9, 0xac } }
+
+class nsINetworkInterceptController;
+struct ConsoleMsgQueueElem;
+
+class nsCSPContext : public nsIContentSecurityPolicy
+{
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTSECURITYPOLICY
+ NS_DECL_NSISERIALIZABLE
+
+ protected:
+ virtual ~nsCSPContext();
+
+ public:
+ nsCSPContext();
+
+ /**
+ * SetRequestContext() needs to be called before the innerWindowID
+ * is initialized on the document. Use this function to call back to
+ * flush queued up console messages and initalize the innerWindowID.
+ */
+ void flushConsoleMessages();
+
+ void logToConsole(const char16_t* aName,
+ const char16_t** aParams,
+ uint32_t aParamsLength,
+ const nsAString& aSourceName,
+ const nsAString& aSourceLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aSeverityFlag);
+
+ nsresult SendReports(nsISupports* aBlockedContentSource,
+ nsIURI* aOriginalURI,
+ nsAString& aViolatedDirective,
+ uint32_t aViolatedPolicyIndex,
+ nsAString& aSourceFile,
+ nsAString& aScriptSample,
+ uint32_t aLineNum);
+
+ nsresult AsyncReportViolation(nsISupports* aBlockedContentSource,
+ nsIURI* aOriginalURI,
+ const nsAString& aViolatedDirective,
+ uint32_t aViolatedPolicyIndex,
+ const nsAString& aObserverSubject,
+ const nsAString& aSourceFile,
+ const nsAString& aScriptSample,
+ uint32_t aLineNum);
+
+ // Hands off! Don't call this method unless you know what you
+ // are doing. It's only supposed to be called from within
+ // the principal destructor to avoid a tangling pointer.
+ void clearLoadingPrincipal() {
+ mLoadingPrincipal = nullptr;
+ }
+
+ nsWeakPtr GetLoadingContext(){
+ return mLoadingContext;
+ }
+
+ private:
+ bool permitsInternal(CSPDirective aDir,
+ nsIURI* aContentLocation,
+ nsIURI* aOriginalURI,
+ const nsAString& aNonce,
+ bool aWasRedirected,
+ bool aIsPreload,
+ bool aSpecific,
+ bool aSendViolationReports,
+ bool aSendContentLocationInViolationReports,
+ bool aParserCreated);
+
+ // helper to report inline script/style violations
+ void reportInlineViolation(nsContentPolicyType aContentType,
+ const nsAString& aNonce,
+ const nsAString& aContent,
+ const nsAString& aViolatedDirective,
+ uint32_t aViolatedPolicyIndex,
+ uint32_t aLineNumber);
+
+ nsString mReferrer;
+ uint64_t mInnerWindowID; // used for web console logging
+ nsTArray<nsCSPPolicy*> mPolicies;
+ nsCOMPtr<nsIURI> mSelfURI;
+ nsDataHashtable<nsCStringHashKey, int16_t> mShouldLoadCache;
+ nsCOMPtr<nsILoadGroup> mCallingChannelLoadGroup;
+ nsWeakPtr mLoadingContext;
+ // The CSP hangs off the principal, so let's store a raw pointer of the principal
+ // to avoid memory leaks. Within the destructor of the principal we explicitly
+ // set mLoadingPrincipal to null.
+ nsIPrincipal* mLoadingPrincipal;
+
+ // helper members used to queue up web console messages till
+ // the windowID becomes available. see flushConsoleMessages()
+ nsTArray<ConsoleMsgQueueElem> mConsoleMsgQueue;
+ bool mQueueUpMessages;
+};
+
+// Class that listens to violation report transmission and logs errors.
+class CSPViolationReportListener : public nsIStreamListener
+{
+ public:
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_ISUPPORTS
+
+ public:
+ CSPViolationReportListener();
+
+ protected:
+ virtual ~CSPViolationReportListener();
+};
+
+// The POST of the violation report (if it happens) should not follow
+// redirects, per the spec. hence, we implement an nsIChannelEventSink
+// with an object so we can tell XHR to abort if a redirect happens.
+class CSPReportRedirectSink final : public nsIChannelEventSink,
+ public nsIInterfaceRequestor
+{
+ public:
+ NS_DECL_NSICHANNELEVENTSINK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_ISUPPORTS
+
+ public:
+ CSPReportRedirectSink();
+
+ void SetInterceptController(nsINetworkInterceptController* aInterceptController);
+
+ protected:
+ virtual ~CSPReportRedirectSink();
+
+ private:
+ nsCOMPtr<nsINetworkInterceptController> mInterceptController;
+};
+
+#endif /* nsCSPContext_h___ */
diff --git a/dom/security/nsCSPParser.cpp b/dom/security/nsCSPParser.cpp
new file mode 100644
index 000000000..a662c9cd1
--- /dev/null
+++ b/dom/security/nsCSPParser.cpp
@@ -0,0 +1,1358 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsCSPParser.h"
+#include "nsCSPUtils.h"
+#include "nsIConsoleService.h"
+#include "nsIContentPolicy.h"
+#include "nsIScriptError.h"
+#include "nsIStringBundle.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/net/ReferrerPolicy.h"
+
+using namespace mozilla;
+
+static LogModule*
+GetCspParserLog()
+{
+ static LazyLogModule gCspParserPRLog("CSPParser");
+ return gCspParserPRLog;
+}
+
+#define CSPPARSERLOG(args) MOZ_LOG(GetCspParserLog(), mozilla::LogLevel::Debug, args)
+#define CSPPARSERLOGENABLED() MOZ_LOG_TEST(GetCspParserLog(), mozilla::LogLevel::Debug)
+
+static const char16_t COLON = ':';
+static const char16_t SEMICOLON = ';';
+static const char16_t SLASH = '/';
+static const char16_t PLUS = '+';
+static const char16_t DASH = '-';
+static const char16_t DOT = '.';
+static const char16_t UNDERLINE = '_';
+static const char16_t TILDE = '~';
+static const char16_t WILDCARD = '*';
+static const char16_t SINGLEQUOTE = '\'';
+static const char16_t OPEN_CURL = '{';
+static const char16_t CLOSE_CURL = '}';
+static const char16_t NUMBER_SIGN = '#';
+static const char16_t QUESTIONMARK = '?';
+static const char16_t PERCENT_SIGN = '%';
+static const char16_t EXCLAMATION = '!';
+static const char16_t DOLLAR = '$';
+static const char16_t AMPERSAND = '&';
+static const char16_t OPENBRACE = '(';
+static const char16_t CLOSINGBRACE = ')';
+static const char16_t EQUALS = '=';
+static const char16_t ATSYMBOL = '@';
+
+static const uint32_t kSubHostPathCharacterCutoff = 512;
+
+static const char *const kHashSourceValidFns [] = { "sha256", "sha384", "sha512" };
+static const uint32_t kHashSourceValidFnsLen = 3;
+
+static const char* const kStyle = "style";
+static const char* const kScript = "script";
+
+/* ===== nsCSPTokenizer ==================== */
+
+nsCSPTokenizer::nsCSPTokenizer(const char16_t* aStart,
+ const char16_t* aEnd)
+ : mCurChar(aStart)
+ , mEndChar(aEnd)
+{
+ CSPPARSERLOG(("nsCSPTokenizer::nsCSPTokenizer"));
+}
+
+nsCSPTokenizer::~nsCSPTokenizer()
+{
+ CSPPARSERLOG(("nsCSPTokenizer::~nsCSPTokenizer"));
+}
+
+void
+nsCSPTokenizer::generateNextToken()
+{
+ skipWhiteSpaceAndSemicolon();
+ while (!atEnd() &&
+ !nsContentUtils::IsHTMLWhitespace(*mCurChar) &&
+ *mCurChar != SEMICOLON) {
+ mCurToken.Append(*mCurChar++);
+ }
+ CSPPARSERLOG(("nsCSPTokenizer::generateNextToken: %s", NS_ConvertUTF16toUTF8(mCurToken).get()));
+}
+
+void
+nsCSPTokenizer::generateTokens(cspTokens& outTokens)
+{
+ CSPPARSERLOG(("nsCSPTokenizer::generateTokens"));
+
+ // dirAndSrcs holds one set of [ name, src, src, src, ... ]
+ nsTArray <nsString> dirAndSrcs;
+
+ while (!atEnd()) {
+ generateNextToken();
+ dirAndSrcs.AppendElement(mCurToken);
+ skipWhiteSpace();
+ if (atEnd() || accept(SEMICOLON)) {
+ outTokens.AppendElement(dirAndSrcs);
+ dirAndSrcs.Clear();
+ }
+ }
+}
+
+void
+nsCSPTokenizer::tokenizeCSPPolicy(const nsAString &aPolicyString,
+ cspTokens& outTokens)
+{
+ CSPPARSERLOG(("nsCSPTokenizer::tokenizeCSPPolicy"));
+
+ nsCSPTokenizer tokenizer(aPolicyString.BeginReading(),
+ aPolicyString.EndReading());
+
+ tokenizer.generateTokens(outTokens);
+}
+
+/* ===== nsCSPParser ==================== */
+bool nsCSPParser::sCSPExperimentalEnabled = false;
+bool nsCSPParser::sStrictDynamicEnabled = false;
+
+nsCSPParser::nsCSPParser(cspTokens& aTokens,
+ nsIURI* aSelfURI,
+ nsCSPContext* aCSPContext,
+ bool aDeliveredViaMetaTag)
+ : mCurChar(nullptr)
+ , mEndChar(nullptr)
+ , mHasHashOrNonce(false)
+ , mStrictDynamic(false)
+ , mUnsafeInlineKeywordSrc(nullptr)
+ , mChildSrc(nullptr)
+ , mFrameSrc(nullptr)
+ , mTokens(aTokens)
+ , mSelfURI(aSelfURI)
+ , mPolicy(nullptr)
+ , mCSPContext(aCSPContext)
+ , mDeliveredViaMetaTag(aDeliveredViaMetaTag)
+{
+ static bool initialized = false;
+ if (!initialized) {
+ initialized = true;
+ Preferences::AddBoolVarCache(&sCSPExperimentalEnabled, "security.csp.experimentalEnabled");
+ Preferences::AddBoolVarCache(&sStrictDynamicEnabled, "security.csp.enableStrictDynamic");
+ }
+ CSPPARSERLOG(("nsCSPParser::nsCSPParser"));
+}
+
+nsCSPParser::~nsCSPParser()
+{
+ CSPPARSERLOG(("nsCSPParser::~nsCSPParser"));
+}
+
+static bool
+isCharacterToken(char16_t aSymbol)
+{
+ return (aSymbol >= 'a' && aSymbol <= 'z') ||
+ (aSymbol >= 'A' && aSymbol <= 'Z');
+}
+
+static bool
+isNumberToken(char16_t aSymbol)
+{
+ return (aSymbol >= '0' && aSymbol <= '9');
+}
+
+static bool
+isValidHexDig(char16_t aHexDig)
+{
+ return (isNumberToken(aHexDig) ||
+ (aHexDig >= 'A' && aHexDig <= 'F') ||
+ (aHexDig >= 'a' && aHexDig <= 'f'));
+}
+
+void
+nsCSPParser::resetCurChar(const nsAString& aToken)
+{
+ mCurChar = aToken.BeginReading();
+ mEndChar = aToken.EndReading();
+ resetCurValue();
+}
+
+// The path is terminated by the first question mark ("?") or
+// number sign ("#") character, or by the end of the URI.
+// http://tools.ietf.org/html/rfc3986#section-3.3
+bool
+nsCSPParser::atEndOfPath()
+{
+ return (atEnd() || peek(QUESTIONMARK) || peek(NUMBER_SIGN));
+}
+
+// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+bool
+nsCSPParser::atValidUnreservedChar()
+{
+ return (peek(isCharacterToken) || peek(isNumberToken) ||
+ peek(DASH) || peek(DOT) ||
+ peek(UNDERLINE) || peek(TILDE));
+}
+
+// sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+// / "*" / "+" / "," / ";" / "="
+// Please note that even though ',' and ';' appear to be
+// valid sub-delims according to the RFC production of paths,
+// both can not appear here by itself, they would need to be
+// pct-encoded in order to be part of the path.
+bool
+nsCSPParser::atValidSubDelimChar()
+{
+ return (peek(EXCLAMATION) || peek(DOLLAR) || peek(AMPERSAND) ||
+ peek(SINGLEQUOTE) || peek(OPENBRACE) || peek(CLOSINGBRACE) ||
+ peek(WILDCARD) || peek(PLUS) || peek(EQUALS));
+}
+
+// pct-encoded = "%" HEXDIG HEXDIG
+bool
+nsCSPParser::atValidPctEncodedChar()
+{
+ const char16_t* pctCurChar = mCurChar;
+
+ if ((pctCurChar + 2) >= mEndChar) {
+ // string too short, can't be a valid pct-encoded char.
+ return false;
+ }
+
+ // Any valid pct-encoding must follow the following format:
+ // "% HEXDIG HEXDIG"
+ if (PERCENT_SIGN != *pctCurChar ||
+ !isValidHexDig(*(pctCurChar+1)) ||
+ !isValidHexDig(*(pctCurChar+2))) {
+ return false;
+ }
+ return true;
+}
+
+// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
+// http://tools.ietf.org/html/rfc3986#section-3.3
+bool
+nsCSPParser::atValidPathChar()
+{
+ return (atValidUnreservedChar() ||
+ atValidSubDelimChar() ||
+ atValidPctEncodedChar() ||
+ peek(COLON) || peek(ATSYMBOL));
+}
+
+void
+nsCSPParser::logWarningErrorToConsole(uint32_t aSeverityFlag,
+ const char* aProperty,
+ const char16_t* aParams[],
+ uint32_t aParamsLength)
+{
+ CSPPARSERLOG(("nsCSPParser::logWarningErrorToConsole: %s", aProperty));
+ // send console messages off to the context and let the context
+ // deal with it (potentially messages need to be queued up)
+ mCSPContext->logToConsole(NS_ConvertUTF8toUTF16(aProperty).get(),
+ aParams,
+ aParamsLength,
+ EmptyString(), // aSourceName
+ EmptyString(), // aSourceLine
+ 0, // aLineNumber
+ 0, // aColumnNumber
+ aSeverityFlag); // aFlags
+}
+
+bool
+nsCSPParser::hostChar()
+{
+ if (atEnd()) {
+ return false;
+ }
+ return accept(isCharacterToken) ||
+ accept(isNumberToken) ||
+ accept(DASH);
+}
+
+// (ALPHA / DIGIT / "+" / "-" / "." )
+bool
+nsCSPParser::schemeChar()
+{
+ if (atEnd()) {
+ return false;
+ }
+ return accept(isCharacterToken) ||
+ accept(isNumberToken) ||
+ accept(PLUS) ||
+ accept(DASH) ||
+ accept(DOT);
+}
+
+// port = ":" ( 1*DIGIT / "*" )
+bool
+nsCSPParser::port()
+{
+ CSPPARSERLOG(("nsCSPParser::port, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ // Consume the COLON we just peeked at in houstSource
+ accept(COLON);
+
+ // Resetting current value since we start to parse a port now.
+ // e.g; "http://www.example.com:8888" then we have already parsed
+ // everything up to (including) ":";
+ resetCurValue();
+
+ // Port might be "*"
+ if (accept(WILDCARD)) {
+ return true;
+ }
+
+ // Port must start with a number
+ if (!accept(isNumberToken)) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "couldntParsePort",
+ params, ArrayLength(params));
+ return false;
+ }
+ // Consume more numbers and set parsed port to the nsCSPHost
+ while (accept(isNumberToken)) { /* consume */ }
+ return true;
+}
+
+bool
+nsCSPParser::subPath(nsCSPHostSrc* aCspHost)
+{
+ CSPPARSERLOG(("nsCSPParser::subPath, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ // Emergency exit to avoid endless loops in case a path in a CSP policy
+ // is longer than 512 characters, or also to avoid endless loops
+ // in case we are parsing unrecognized characters in the following loop.
+ uint32_t charCounter = 0;
+ nsString pctDecodedSubPath;
+
+ while (!atEndOfPath()) {
+ if (peek(SLASH)) {
+ // before appendig any additional portion of a subpath we have to pct-decode
+ // that portion of the subpath. atValidPathChar() already verified a correct
+ // pct-encoding, now we can safely decode and append the decoded-sub path.
+ CSP_PercentDecodeStr(mCurValue, pctDecodedSubPath);
+ aCspHost->appendPath(pctDecodedSubPath);
+ // Resetting current value since we are appending parts of the path
+ // to aCspHost, e.g; "http://www.example.com/path1/path2" then the
+ // first part is "/path1", second part "/path2"
+ resetCurValue();
+ }
+ else if (!atValidPathChar()) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag,
+ "couldntParseInvalidSource",
+ params, ArrayLength(params));
+ return false;
+ }
+ // potentially we have encountred a valid pct-encoded character in atValidPathChar();
+ // if so, we have to account for "% HEXDIG HEXDIG" and advance the pointer past
+ // the pct-encoded char.
+ if (peek(PERCENT_SIGN)) {
+ advance();
+ advance();
+ }
+ advance();
+ if (++charCounter > kSubHostPathCharacterCutoff) {
+ return false;
+ }
+ }
+ // before appendig any additional portion of a subpath we have to pct-decode
+ // that portion of the subpath. atValidPathChar() already verified a correct
+ // pct-encoding, now we can safely decode and append the decoded-sub path.
+ CSP_PercentDecodeStr(mCurValue, pctDecodedSubPath);
+ aCspHost->appendPath(pctDecodedSubPath);
+ resetCurValue();
+ return true;
+}
+
+bool
+nsCSPParser::path(nsCSPHostSrc* aCspHost)
+{
+ CSPPARSERLOG(("nsCSPParser::path, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ // Resetting current value and forgetting everything we have parsed so far
+ // e.g. parsing "http://www.example.com/path1/path2", then
+ // "http://www.example.com" has already been parsed so far
+ // forget about it.
+ resetCurValue();
+
+ if (!accept(SLASH)) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "couldntParseInvalidSource",
+ params, ArrayLength(params));
+ return false;
+ }
+ if (atEndOfPath()) {
+ // one slash right after host [port] is also considered a path, e.g.
+ // www.example.com/ should result in www.example.com/
+ // please note that we do not have to perform any pct-decoding here
+ // because we are just appending a '/' and not any actual chars.
+ aCspHost->appendPath(NS_LITERAL_STRING("/"));
+ return true;
+ }
+ // path can begin with "/" but not "//"
+ // see http://tools.ietf.org/html/rfc3986#section-3.3
+ if (peek(SLASH)) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "couldntParseInvalidSource",
+ params, ArrayLength(params));
+ return false;
+ }
+ return subPath(aCspHost);
+}
+
+bool
+nsCSPParser::subHost()
+{
+ CSPPARSERLOG(("nsCSPParser::subHost, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ // Emergency exit to avoid endless loops in case a host in a CSP policy
+ // is longer than 512 characters, or also to avoid endless loops
+ // in case we are parsing unrecognized characters in the following loop.
+ uint32_t charCounter = 0;
+
+ while (!atEndOfPath() && !peek(COLON) && !peek(SLASH)) {
+ ++charCounter;
+ while (hostChar()) {
+ /* consume */
+ ++charCounter;
+ }
+ if (accept(DOT) && !hostChar()) {
+ return false;
+ }
+ if (charCounter > kSubHostPathCharacterCutoff) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// host = "*" / [ "*." ] 1*host-char *( "." 1*host-char )
+nsCSPHostSrc*
+nsCSPParser::host()
+{
+ CSPPARSERLOG(("nsCSPParser::host, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ // Check if the token starts with "*"; please remember that we handle
+ // a single "*" as host in sourceExpression, but we still have to handle
+ // the case where a scheme was defined, e.g., as:
+ // "https://*", "*.example.com", "*:*", etc.
+ if (accept(WILDCARD)) {
+ // Might solely be the wildcard
+ if (atEnd() || peek(COLON)) {
+ return new nsCSPHostSrc(mCurValue);
+ }
+ // If the token is not only the "*", a "." must follow right after
+ if (!accept(DOT)) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "couldntParseInvalidHost",
+ params, ArrayLength(params));
+ return nullptr;
+ }
+ }
+
+ // Expecting at least one host-char
+ if (!hostChar()) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "couldntParseInvalidHost",
+ params, ArrayLength(params));
+ return nullptr;
+ }
+
+ // There might be several sub hosts defined.
+ if (!subHost()) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "couldntParseInvalidHost",
+ params, ArrayLength(params));
+ return nullptr;
+ }
+
+ // HostName might match a keyword, log to the console.
+ if (CSP_IsQuotelessKeyword(mCurValue)) {
+ nsString keyword = mCurValue;
+ ToLowerCase(keyword);
+ const char16_t* params[] = { mCurToken.get(), keyword.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "hostNameMightBeKeyword",
+ params, ArrayLength(params));
+ }
+
+ // Create a new nsCSPHostSrc with the parsed host.
+ return new nsCSPHostSrc(mCurValue);
+}
+
+// apps use special hosts; "app://{app-host-is-uid}""
+nsCSPHostSrc*
+nsCSPParser::appHost()
+{
+ CSPPARSERLOG(("nsCSPParser::appHost, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ while (hostChar()) { /* consume */ }
+
+ // appHosts have to end with "}", otherwise we have to report an error
+ if (!accept(CLOSE_CURL)) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "couldntParseInvalidSource",
+ params, ArrayLength(params));
+ return nullptr;
+ }
+ return new nsCSPHostSrc(mCurValue);
+}
+
+// keyword-source = "'self'" / "'unsafe-inline'" / "'unsafe-eval'"
+nsCSPBaseSrc*
+nsCSPParser::keywordSource()
+{
+ CSPPARSERLOG(("nsCSPParser::keywordSource, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ // Special case handling for 'self' which is not stored internally as a keyword,
+ // but rather creates a nsCSPHostSrc using the selfURI
+ if (CSP_IsKeyword(mCurToken, CSP_SELF)) {
+ return CSP_CreateHostSrcFromURI(mSelfURI);
+ }
+
+ if (CSP_IsKeyword(mCurToken, CSP_STRICT_DYNAMIC)) {
+ // make sure strict dynamic is enabled
+ if (!sStrictDynamicEnabled) {
+ return nullptr;
+ }
+ if (!CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE)) {
+ // Todo: Enforce 'strict-dynamic' within default-src; see Bug 1313937
+ const char16_t* params[] = { u"strict-dynamic" };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringStrictDynamic",
+ params, ArrayLength(params));
+ return nullptr;
+ }
+ mStrictDynamic = true;
+ return new nsCSPKeywordSrc(CSP_KeywordToEnum(mCurToken));
+ }
+
+ if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_INLINE)) {
+ nsWeakPtr ctx = mCSPContext->GetLoadingContext();
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(ctx);
+ if (doc) {
+ doc->SetHasUnsafeInlineCSP(true);
+ }
+ // make sure script-src only contains 'unsafe-inline' once;
+ // ignore duplicates and log warning
+ if (mUnsafeInlineKeywordSrc) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringDuplicateSrc",
+ params, ArrayLength(params));
+ return nullptr;
+ }
+ // cache if we encounter 'unsafe-inline' so we can invalidate (ignore) it in
+ // case that script-src directive also contains hash- or nonce-.
+ mUnsafeInlineKeywordSrc = new nsCSPKeywordSrc(CSP_KeywordToEnum(mCurToken));
+ return mUnsafeInlineKeywordSrc;
+ }
+
+ if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_EVAL)) {
+ nsWeakPtr ctx = mCSPContext->GetLoadingContext();
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(ctx);
+ if (doc) {
+ doc->SetHasUnsafeEvalCSP(true);
+ }
+ return new nsCSPKeywordSrc(CSP_KeywordToEnum(mCurToken));
+ }
+ return nullptr;
+}
+
+// host-source = [ scheme "://" ] host [ port ] [ path ]
+nsCSPHostSrc*
+nsCSPParser::hostSource()
+{
+ CSPPARSERLOG(("nsCSPParser::hostSource, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ // Special case handling for app specific hosts
+ if (accept(OPEN_CURL)) {
+ // If appHost() returns null, the error was handled in appHost().
+ // appHosts can not have a port, or path, we can return.
+ return appHost();
+ }
+
+ nsCSPHostSrc* cspHost = host();
+ if (!cspHost) {
+ // Error was reported in host()
+ return nullptr;
+ }
+
+ // Calling port() to see if there is a port to parse, if an error
+ // occurs, port() reports the error, if port() returns true;
+ // we have a valid port, so we add it to cspHost.
+ if (peek(COLON)) {
+ if (!port()) {
+ delete cspHost;
+ return nullptr;
+ }
+ cspHost->setPort(mCurValue);
+ }
+
+ if (atEndOfPath()) {
+ return cspHost;
+ }
+
+ // Calling path() to see if there is a path to parse, if an error
+ // occurs, path() reports the error; handing cspHost as an argument
+ // which simplifies parsing of several paths.
+ if (!path(cspHost)) {
+ // If the host [port] is followed by a path, it has to be a valid path,
+ // otherwise we pass the nullptr, indicating an error, up the callstack.
+ // see also http://www.w3.org/TR/CSP11/#source-list
+ delete cspHost;
+ return nullptr;
+ }
+ return cspHost;
+}
+
+// scheme-source = scheme ":"
+nsCSPSchemeSrc*
+nsCSPParser::schemeSource()
+{
+ CSPPARSERLOG(("nsCSPParser::schemeSource, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ if (!accept(isCharacterToken)) {
+ return nullptr;
+ }
+ while (schemeChar()) { /* consume */ }
+ nsString scheme = mCurValue;
+
+ // If the potential scheme is not followed by ":" - it's not a scheme
+ if (!accept(COLON)) {
+ return nullptr;
+ }
+
+ // If the chraracter following the ":" is a number or the "*"
+ // then we are not parsing a scheme; but rather a host;
+ if (peek(isNumberToken) || peek(WILDCARD)) {
+ return nullptr;
+ }
+
+ return new nsCSPSchemeSrc(scheme);
+}
+
+// nonce-source = "'nonce-" nonce-value "'"
+nsCSPNonceSrc*
+nsCSPParser::nonceSource()
+{
+ CSPPARSERLOG(("nsCSPParser::nonceSource, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ // Check if mCurToken begins with "'nonce-" and ends with "'"
+ if (!StringBeginsWith(mCurToken, NS_ConvertUTF8toUTF16(CSP_EnumToKeyword(CSP_NONCE)),
+ nsASCIICaseInsensitiveStringComparator()) ||
+ mCurToken.Last() != SINGLEQUOTE) {
+ return nullptr;
+ }
+
+ // Trim surrounding single quotes
+ const nsAString& expr = Substring(mCurToken, 1, mCurToken.Length() - 2);
+
+ int32_t dashIndex = expr.FindChar(DASH);
+ if (dashIndex < 0) {
+ return nullptr;
+ }
+ // cache if encountering hash or nonce to invalidate unsafe-inline
+ mHasHashOrNonce = true;
+ return new nsCSPNonceSrc(Substring(expr,
+ dashIndex + 1,
+ expr.Length() - dashIndex + 1));
+}
+
+// hash-source = "'" hash-algo "-" base64-value "'"
+nsCSPHashSrc*
+nsCSPParser::hashSource()
+{
+ CSPPARSERLOG(("nsCSPParser::hashSource, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ // Check if mCurToken starts and ends with "'"
+ if (mCurToken.First() != SINGLEQUOTE ||
+ mCurToken.Last() != SINGLEQUOTE) {
+ return nullptr;
+ }
+
+ // Trim surrounding single quotes
+ const nsAString& expr = Substring(mCurToken, 1, mCurToken.Length() - 2);
+
+ int32_t dashIndex = expr.FindChar(DASH);
+ if (dashIndex < 0) {
+ return nullptr;
+ }
+
+ nsAutoString algo(Substring(expr, 0, dashIndex));
+ nsAutoString hash(Substring(expr, dashIndex + 1, expr.Length() - dashIndex + 1));
+
+ for (uint32_t i = 0; i < kHashSourceValidFnsLen; i++) {
+ if (algo.LowerCaseEqualsASCII(kHashSourceValidFns[i])) {
+ // cache if encountering hash or nonce to invalidate unsafe-inline
+ mHasHashOrNonce = true;
+ return new nsCSPHashSrc(algo, hash);
+ }
+ }
+ return nullptr;
+}
+
+// source-expression = scheme-source / host-source / keyword-source
+// / nonce-source / hash-source
+nsCSPBaseSrc*
+nsCSPParser::sourceExpression()
+{
+ CSPPARSERLOG(("nsCSPParser::sourceExpression, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ // Check if it is a keyword
+ if (nsCSPBaseSrc *cspKeyword = keywordSource()) {
+ return cspKeyword;
+ }
+
+ // Check if it is a nonce-source
+ if (nsCSPNonceSrc* cspNonce = nonceSource()) {
+ return cspNonce;
+ }
+
+ // Check if it is a hash-source
+ if (nsCSPHashSrc* cspHash = hashSource()) {
+ return cspHash;
+ }
+
+ // We handle a single "*" as host here, to avoid any confusion when applying the default scheme.
+ // However, we still would need to apply the default scheme in case we would parse "*:80".
+ if (mCurToken.EqualsASCII("*")) {
+ return new nsCSPHostSrc(NS_LITERAL_STRING("*"));
+ }
+
+ // Calling resetCurChar allows us to use mCurChar and mEndChar
+ // to parse mCurToken; e.g. mCurToken = "http://www.example.com", then
+ // mCurChar = 'h'
+ // mEndChar = points just after the last 'm'
+ // mCurValue = ""
+ resetCurChar(mCurToken);
+
+ // Check if mCurToken starts with a scheme
+ nsAutoString parsedScheme;
+ if (nsCSPSchemeSrc* cspScheme = schemeSource()) {
+ // mCurToken might only enforce a specific scheme
+ if (atEnd()) {
+ return cspScheme;
+ }
+ // If something follows the scheme, we do not create
+ // a nsCSPSchemeSrc, but rather a nsCSPHostSrc, which
+ // needs to know the scheme to enforce; remember the
+ // scheme and delete cspScheme;
+ cspScheme->toString(parsedScheme);
+ parsedScheme.Trim(":", false, true);
+ delete cspScheme;
+
+ // If mCurToken provides not only a scheme, but also a host, we have to check
+ // if two slashes follow the scheme.
+ if (!accept(SLASH) || !accept(SLASH)) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "failedToParseUnrecognizedSource",
+ params, ArrayLength(params));
+ return nullptr;
+ }
+ }
+
+ // Calling resetCurValue allows us to keep pointers for mCurChar and mEndChar
+ // alive, but resets mCurValue; e.g. mCurToken = "http://www.example.com", then
+ // mCurChar = 'w'
+ // mEndChar = 'm'
+ // mCurValue = ""
+ resetCurValue();
+
+ // If mCurToken does not provide a scheme (scheme-less source), we apply the scheme
+ // from selfURI
+ if (parsedScheme.IsEmpty()) {
+ // Resetting internal helpers, because we might already have parsed some of the host
+ // when trying to parse a scheme.
+ resetCurChar(mCurToken);
+ nsAutoCString selfScheme;
+ mSelfURI->GetScheme(selfScheme);
+ parsedScheme.AssignASCII(selfScheme.get());
+ }
+
+ // At this point we are expecting a host to be parsed.
+ // Trying to create a new nsCSPHost.
+ if (nsCSPHostSrc *cspHost = hostSource()) {
+ // Do not forget to set the parsed scheme.
+ cspHost->setScheme(parsedScheme);
+ return cspHost;
+ }
+ // Error was reported in hostSource()
+ return nullptr;
+}
+
+// source-list = *WSP [ source-expression *( 1*WSP source-expression ) *WSP ]
+// / *WSP "'none'" *WSP
+void
+nsCSPParser::sourceList(nsTArray<nsCSPBaseSrc*>& outSrcs)
+{
+ bool isNone = false;
+
+ // remember, srcs start at index 1
+ for (uint32_t i = 1; i < mCurDir.Length(); i++) {
+ // mCurToken is only set here and remains the current token
+ // to be processed, which avoid passing arguments between functions.
+ mCurToken = mCurDir[i];
+ resetCurValue();
+
+ CSPPARSERLOG(("nsCSPParser::sourceList, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ // Special case handling for none:
+ // Ignore 'none' if any other src is available.
+ // (See http://www.w3.org/TR/CSP11/#parsing)
+ if (CSP_IsKeyword(mCurToken, CSP_NONE)) {
+ isNone = true;
+ continue;
+ }
+ // Must be a regular source expression
+ nsCSPBaseSrc* src = sourceExpression();
+ if (src) {
+ outSrcs.AppendElement(src);
+ }
+ }
+
+ // Check if the directive contains a 'none'
+ if (isNone) {
+ // If the directive contains no other srcs, then we set the 'none'
+ if (outSrcs.Length() == 0) {
+ nsCSPKeywordSrc *keyword = new nsCSPKeywordSrc(CSP_NONE);
+ outSrcs.AppendElement(keyword);
+ }
+ // Otherwise, we ignore 'none' and report a warning
+ else {
+ NS_ConvertUTF8toUTF16 unicodeNone(CSP_EnumToKeyword(CSP_NONE));
+ const char16_t* params[] = { unicodeNone.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringUnknownOption",
+ params, ArrayLength(params));
+ }
+ }
+}
+
+void
+nsCSPParser::referrerDirectiveValue(nsCSPDirective* aDir)
+{
+ // directive-value = "none" / "none-when-downgrade" / "origin" / "origin-when-cross-origin" / "unsafe-url"
+ // directive name is token 0, we need to examine the remaining tokens (and
+ // there should only be one token in the value).
+ CSPPARSERLOG(("nsCSPParser::referrerDirectiveValue"));
+
+ if (mCurDir.Length() != 2) {
+ CSPPARSERLOG(("Incorrect number of tokens in referrer directive, got %d expected 1",
+ mCurDir.Length() - 1));
+ delete aDir;
+ return;
+ }
+
+ if (!mozilla::net::IsValidReferrerPolicy(mCurDir[1])) {
+ CSPPARSERLOG(("invalid value for referrer directive: %s",
+ NS_ConvertUTF16toUTF8(mCurDir[1]).get()));
+ delete aDir;
+ return;
+ }
+
+ //referrer-directive deprecation warning
+ const char16_t* params[] = { mCurDir[1].get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "deprecatedReferrerDirective",
+ params, ArrayLength(params));
+
+ // the referrer policy is valid, so go ahead and use it.
+ mPolicy->setReferrerPolicy(&mCurDir[1]);
+ mPolicy->addDirective(aDir);
+}
+
+void
+nsCSPParser::requireSRIForDirectiveValue(nsRequireSRIForDirective* aDir)
+{
+ CSPPARSERLOG(("nsCSPParser::requireSRIForDirectiveValue"));
+
+ // directive-value = "style" / "script"
+ // directive name is token 0, we need to examine the remaining tokens
+ for (uint32_t i = 1; i < mCurDir.Length(); i++) {
+ // mCurToken is only set here and remains the current token
+ // to be processed, which avoid passing arguments between functions.
+ mCurToken = mCurDir[i];
+ resetCurValue();
+ CSPPARSERLOG(("nsCSPParser:::directive (require-sri-for directive), "
+ "mCurToken: %s (valid), mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+ // add contentPolicyTypes to the CSP's required-SRI list for this token
+ if (mCurToken.LowerCaseEqualsASCII(kScript)) {
+ aDir->addType(nsIContentPolicy::TYPE_SCRIPT);
+ }
+ else if (mCurToken.LowerCaseEqualsASCII(kStyle)) {
+ aDir->addType(nsIContentPolicy::TYPE_STYLESHEET);
+ } else {
+ const char16_t* invalidTokenName[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "failedToParseUnrecognizedSource",
+ invalidTokenName, ArrayLength(invalidTokenName));
+ CSPPARSERLOG(("nsCSPParser:::directive (require-sri-for directive), "
+ "mCurToken: %s (invalid), mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+ }
+ }
+
+ if (!(aDir->hasType(nsIContentPolicy::TYPE_STYLESHEET)) &&
+ !(aDir->hasType(nsIContentPolicy::TYPE_SCRIPT))) {
+ const char16_t* directiveName[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringDirectiveWithNoValues",
+ directiveName, ArrayLength(directiveName));
+ delete aDir;
+ return;
+ }
+
+ mPolicy->addDirective(aDir);
+}
+
+void
+nsCSPParser::reportURIList(nsCSPDirective* aDir)
+{
+ CSPPARSERLOG(("nsCSPParser::reportURIList"));
+
+ nsTArray<nsCSPBaseSrc*> srcs;
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv;
+
+ // remember, srcs start at index 1
+ for (uint32_t i = 1; i < mCurDir.Length(); i++) {
+ mCurToken = mCurDir[i];
+
+ CSPPARSERLOG(("nsCSPParser::reportURIList, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ rv = NS_NewURI(getter_AddRefs(uri), mCurToken, "", mSelfURI);
+
+ // If creating the URI casued an error, skip this URI
+ if (NS_FAILED(rv)) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "couldNotParseReportURI",
+ params, ArrayLength(params));
+ continue;
+ }
+
+ // Create new nsCSPReportURI and append to the list.
+ nsCSPReportURI* reportURI = new nsCSPReportURI(uri);
+ srcs.AppendElement(reportURI);
+ }
+
+ if (srcs.Length() == 0) {
+ const char16_t* directiveName[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringDirectiveWithNoValues",
+ directiveName, ArrayLength(directiveName));
+ delete aDir;
+ return;
+ }
+
+ aDir->addSrcs(srcs);
+ mPolicy->addDirective(aDir);
+}
+
+/* Helper function for parsing sandbox flags. This function solely concatenates
+ * all the source list tokens (the sandbox flags) so the attribute parser
+ * (nsContentUtils::ParseSandboxAttributeToFlags) can parse them.
+ */
+void
+nsCSPParser::sandboxFlagList(nsCSPDirective* aDir)
+{
+ CSPPARSERLOG(("nsCSPParser::sandboxFlagList"));
+
+ nsAutoString flags;
+
+ // remember, srcs start at index 1
+ for (uint32_t i = 1; i < mCurDir.Length(); i++) {
+ mCurToken = mCurDir[i];
+
+ CSPPARSERLOG(("nsCSPParser::sandboxFlagList, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ if (!nsContentUtils::IsValidSandboxFlag(mCurToken)) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag,
+ "couldntParseInvalidSandboxFlag",
+ params, ArrayLength(params));
+ continue;
+ }
+
+ flags.Append(mCurToken);
+ if (i != mCurDir.Length() - 1) {
+ flags.AppendASCII(" ");
+ }
+ }
+
+ // Please note that the sandbox directive can exist
+ // by itself (not containing any flags).
+ nsTArray<nsCSPBaseSrc*> srcs;
+ srcs.AppendElement(new nsCSPSandboxFlags(flags));
+ aDir->addSrcs(srcs);
+ mPolicy->addDirective(aDir);
+}
+
+// directive-value = *( WSP / <VCHAR except ";" and ","> )
+void
+nsCSPParser::directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs)
+{
+ CSPPARSERLOG(("nsCSPParser::directiveValue"));
+
+ // Just forward to sourceList
+ sourceList(outSrcs);
+}
+
+// directive-name = 1*( ALPHA / DIGIT / "-" )
+nsCSPDirective*
+nsCSPParser::directiveName()
+{
+ CSPPARSERLOG(("nsCSPParser::directiveName, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ // Check if it is a valid directive
+ if (!CSP_IsValidDirective(mCurToken) ||
+ (!sCSPExperimentalEnabled &&
+ CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REQUIRE_SRI_FOR))) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "couldNotProcessUnknownDirective",
+ params, ArrayLength(params));
+ return nullptr;
+ }
+
+ // The directive 'reflected-xss' is part of CSP 1.1, see:
+ // http://www.w3.org/TR/2014/WD-CSP11-20140211/#reflected-xss
+ // Currently we are not supporting that directive, hence we log a
+ // warning to the console and ignore the directive including its values.
+ if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REFLECTED_XSS_DIRECTIVE)) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "notSupportingDirective",
+ params, ArrayLength(params));
+ return nullptr;
+ }
+
+ // Make sure the directive does not already exist
+ // (see http://www.w3.org/TR/CSP11/#parsing)
+ if (mPolicy->hasDirective(CSP_StringToCSPDirective(mCurToken))) {
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "duplicateDirective",
+ params, ArrayLength(params));
+ return nullptr;
+ }
+
+ // CSP delivered via meta tag should ignore the following directives:
+ // report-uri, frame-ancestors, and sandbox, see:
+ // http://www.w3.org/TR/CSP11/#delivery-html-meta-element
+ if (mDeliveredViaMetaTag &&
+ ((CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) ||
+ (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE)) ||
+ (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)))) {
+ // log to the console to indicate that meta CSP is ignoring the directive
+ const char16_t* params[] = { mCurToken.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag,
+ "ignoringSrcFromMetaCSP",
+ params, ArrayLength(params));
+ return nullptr;
+ }
+
+ // special case handling for block-all-mixed-content
+ if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT)) {
+ return new nsBlockAllMixedContentDirective(CSP_StringToCSPDirective(mCurToken));
+ }
+
+ // special case handling for upgrade-insecure-requests
+ if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) {
+ return new nsUpgradeInsecureDirective(CSP_StringToCSPDirective(mCurToken));
+ }
+
+ // child-src has it's own class to handle frame-src if necessary
+ if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE)) {
+ mChildSrc = new nsCSPChildSrcDirective(CSP_StringToCSPDirective(mCurToken));
+ return mChildSrc;
+ }
+
+ // if we have a frame-src, cache it so we can decide whether to use child-src
+ if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE)) {
+ const char16_t* params[] = { mCurToken.get(), NS_LITERAL_STRING("child-src").get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "deprecatedDirective",
+ params, ArrayLength(params));
+ mFrameSrc = new nsCSPDirective(CSP_StringToCSPDirective(mCurToken));
+ return mFrameSrc;
+ }
+
+ if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REQUIRE_SRI_FOR)) {
+ return new nsRequireSRIForDirective(CSP_StringToCSPDirective(mCurToken));
+ }
+
+ return new nsCSPDirective(CSP_StringToCSPDirective(mCurToken));
+}
+
+// directive = *WSP [ directive-name [ WSP directive-value ] ]
+void
+nsCSPParser::directive()
+{
+ // Set the directiveName to mCurToken
+ // Remember, the directive name is stored at index 0
+ mCurToken = mCurDir[0];
+
+ CSPPARSERLOG(("nsCSPParser::directive, mCurToken: %s, mCurValue: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get(),
+ NS_ConvertUTF16toUTF8(mCurValue).get()));
+
+ // Make sure that the directive-srcs-array contains at least
+ // one directive and one src.
+ if (mCurDir.Length() < 1) {
+ const char16_t* params[] = { u"directive missing" };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "failedToParseUnrecognizedSource",
+ params, ArrayLength(params));
+ return;
+ }
+
+ // Try to create a new CSPDirective
+ nsCSPDirective* cspDir = directiveName();
+ if (!cspDir) {
+ // if we can not create a CSPDirective, we can skip parsing the srcs for that array
+ return;
+ }
+
+ // special case handling for block-all-mixed-content, which is only specified
+ // by a directive name but does not include any srcs.
+ if (cspDir->equals(nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT)) {
+ if (mCurDir.Length() > 1) {
+ const char16_t* params[] = { u"block-all-mixed-content" };
+ logWarningErrorToConsole(nsIScriptError::warningFlag,
+ "ignoreSrcForDirective",
+ params, ArrayLength(params));
+ }
+ // add the directive and return
+ mPolicy->addDirective(cspDir);
+ return;
+ }
+
+ // special case handling for upgrade-insecure-requests, which is only specified
+ // by a directive name but does not include any srcs.
+ if (cspDir->equals(nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) {
+ if (mCurDir.Length() > 1) {
+ const char16_t* params[] = { u"upgrade-insecure-requests" };
+ logWarningErrorToConsole(nsIScriptError::warningFlag,
+ "ignoreSrcForDirective",
+ params, ArrayLength(params));
+ }
+ // add the directive and return
+ mPolicy->addUpgradeInsecDir(static_cast<nsUpgradeInsecureDirective*>(cspDir));
+ return;
+ }
+
+ // special case handling for require-sri-for, which has directive values that
+ // are well-defined tokens but are not sources
+ if (cspDir->equals(nsIContentSecurityPolicy::REQUIRE_SRI_FOR)) {
+ requireSRIForDirectiveValue(static_cast<nsRequireSRIForDirective*>(cspDir));
+ return;
+ }
+
+ // special case handling of the referrer directive (since it doesn't contain
+ // source lists)
+ if (cspDir->equals(nsIContentSecurityPolicy::REFERRER_DIRECTIVE)) {
+ referrerDirectiveValue(cspDir);
+ return;
+ }
+
+ // special case handling for report-uri directive (since it doesn't contain
+ // a valid source list but rather actual URIs)
+ if (CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
+ reportURIList(cspDir);
+ return;
+ }
+
+ // special case handling for sandbox directive (since it doe4sn't contain
+ // a valid source list but rather special sandbox flags)
+ if (CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) {
+ sandboxFlagList(cspDir);
+ return;
+ }
+
+ // make sure to reset cache variables when trying to invalidate unsafe-inline;
+ // unsafe-inline might not only appear in script-src, but also in default-src
+ mHasHashOrNonce = false;
+ mStrictDynamic = false;
+ mUnsafeInlineKeywordSrc = nullptr;
+
+ // Try to parse all the srcs by handing the array off to directiveValue
+ nsTArray<nsCSPBaseSrc*> srcs;
+ directiveValue(srcs);
+
+ // If we can not parse any srcs; we let the source expression be the empty set ('none')
+ // see, http://www.w3.org/TR/CSP11/#source-list-parsing
+ if (srcs.Length() == 0) {
+ nsCSPKeywordSrc *keyword = new nsCSPKeywordSrc(CSP_NONE);
+ srcs.AppendElement(keyword);
+ }
+
+ // If policy contains 'strict-dynamic' invalidate all srcs within script-src.
+ if (mStrictDynamic) {
+ MOZ_ASSERT(cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE),
+ "strict-dynamic only allowed within script-src");
+ for (uint32_t i = 0; i < srcs.Length(); i++) {
+ // Please note that nsCSPNonceSrc as well as nsCSPHashSrc overwrite invalidate(),
+ // so it's fine to just call invalidate() on all srcs. Please also note that
+ // nsCSPKeywordSrc() can not be invalidated and always returns false unless the
+ // keyword is 'strict-dynamic' in which case we allow the load if the script is
+ // not parser created!
+ srcs[i]->invalidate();
+ // Log a message to the console that src will be ignored.
+ nsAutoString srcStr;
+ srcs[i]->toString(srcStr);
+ // Even though we invalidate all of the srcs internally, we don't want to log
+ // messages for the srcs: (1) strict-dynamic, (2) unsafe-inline,
+ // (3) nonces, and (4) hashes
+ if (!srcStr.EqualsASCII(CSP_EnumToKeyword(CSP_STRICT_DYNAMIC)) &&
+ !srcStr.EqualsASCII(CSP_EnumToKeyword(CSP_UNSAFE_EVAL)) &&
+ !StringBeginsWith(NS_ConvertUTF16toUTF8(srcStr), NS_LITERAL_CSTRING("'nonce-")) &&
+ !StringBeginsWith(NS_ConvertUTF16toUTF8(srcStr), NS_LITERAL_CSTRING("'sha")))
+ {
+ const char16_t* params[] = { srcStr.get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringSrcForStrictDynamic",
+ params, ArrayLength(params));
+ }
+ }
+ // Log a warning that all scripts might be blocked because the policy contains
+ // 'strict-dynamic' but no valid nonce or hash.
+ if (!mHasHashOrNonce) {
+ const char16_t* params[] = { mCurDir[0].get() };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "strictDynamicButNoHashOrNonce",
+ params, ArrayLength(params));
+ }
+ }
+ else if (mHasHashOrNonce && mUnsafeInlineKeywordSrc &&
+ (cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) ||
+ cspDir->equals(nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE))) {
+ mUnsafeInlineKeywordSrc->invalidate();
+ // log to the console that unsafe-inline will be ignored
+ const char16_t* params[] = { u"'unsafe-inline'" };
+ logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringSrcWithinScriptStyleSrc",
+ params, ArrayLength(params));
+ }
+
+ // Add the newly created srcs to the directive and add the directive to the policy
+ cspDir->addSrcs(srcs);
+ mPolicy->addDirective(cspDir);
+}
+
+// policy = [ directive *( ";" [ directive ] ) ]
+nsCSPPolicy*
+nsCSPParser::policy()
+{
+ CSPPARSERLOG(("nsCSPParser::policy"));
+
+ mPolicy = new nsCSPPolicy();
+ for (uint32_t i = 0; i < mTokens.Length(); i++) {
+ // All input is already tokenized; set one tokenized array in the form of
+ // [ name, src, src, ... ]
+ // to mCurDir and call directive which processes the current directive.
+ mCurDir = mTokens[i];
+ directive();
+ }
+
+ if (mChildSrc && !mFrameSrc) {
+ // if we have a child-src, it handles frame-src too, unless frame-src is set
+ mChildSrc->setHandleFrameSrc();
+ }
+
+ return mPolicy;
+}
+
+nsCSPPolicy*
+nsCSPParser::parseContentSecurityPolicy(const nsAString& aPolicyString,
+ nsIURI *aSelfURI,
+ bool aReportOnly,
+ nsCSPContext* aCSPContext,
+ bool aDeliveredViaMetaTag)
+{
+ if (CSPPARSERLOGENABLED()) {
+ CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, policy: %s",
+ NS_ConvertUTF16toUTF8(aPolicyString).get()));
+ CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, selfURI: %s",
+ aSelfURI->GetSpecOrDefault().get()));
+ CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, reportOnly: %s",
+ (aReportOnly ? "true" : "false")));
+ CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, deliveredViaMetaTag: %s",
+ (aDeliveredViaMetaTag ? "true" : "false")));
+ }
+
+ NS_ASSERTION(aSelfURI, "Can not parseContentSecurityPolicy without aSelfURI");
+
+ // Separate all input into tokens and store them in the form of:
+ // [ [ name, src, src, ... ], [ name, src, src, ... ], ... ]
+ // The tokenizer itself can not fail; all eventual errors
+ // are detected in the parser itself.
+
+ nsTArray< nsTArray<nsString> > tokens;
+ nsCSPTokenizer::tokenizeCSPPolicy(aPolicyString, tokens);
+
+ nsCSPParser parser(tokens, aSelfURI, aCSPContext, aDeliveredViaMetaTag);
+
+ // Start the parser to generate a new CSPPolicy using the generated tokens.
+ nsCSPPolicy* policy = parser.policy();
+
+ // Check that report-only policies define a report-uri, otherwise log warning.
+ if (aReportOnly) {
+ policy->setReportOnlyFlag(true);
+ if (!policy->hasDirective(nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
+ nsAutoCString prePath;
+ nsresult rv = aSelfURI->GetPrePath(prePath);
+ NS_ENSURE_SUCCESS(rv, policy);
+ NS_ConvertUTF8toUTF16 unicodePrePath(prePath);
+ const char16_t* params[] = { unicodePrePath.get() };
+ parser.logWarningErrorToConsole(nsIScriptError::warningFlag, "reportURInotInReportOnlyHeader",
+ params, ArrayLength(params));
+ }
+ }
+
+ if (policy->getNumDirectives() == 0) {
+ // Individual errors were already reported in the parser, but if
+ // we do not have an enforcable directive at all, we return null.
+ delete policy;
+ return nullptr;
+ }
+
+ if (CSPPARSERLOGENABLED()) {
+ nsString parsedPolicy;
+ policy->toString(parsedPolicy);
+ CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, parsedPolicy: %s",
+ NS_ConvertUTF16toUTF8(parsedPolicy).get()));
+ }
+
+ return policy;
+}
diff --git a/dom/security/nsCSPParser.h b/dom/security/nsCSPParser.h
new file mode 100644
index 000000000..30954b10f
--- /dev/null
+++ b/dom/security/nsCSPParser.h
@@ -0,0 +1,262 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#ifndef nsCSPParser_h___
+#define nsCSPParser_h___
+
+#include "nsCSPUtils.h"
+#include "nsIURI.h"
+#include "nsString.h"
+
+/**
+ * How does the parsing work?
+ *
+ * We generate tokens by splitting the policy-string by whitespace and semicolon.
+ * Interally the tokens are represented as an array of string-arrays:
+ *
+ * [
+ * [ name, src, src, src, ... ],
+ * [ name, src, src, src, ... ],
+ * [ name, src, src, src, ... ]
+ * ]
+ *
+ * for example:
+ * [
+ * [ img-src, http://www.example.com, http:www.test.com ],
+ * [ default-src, 'self'],
+ * [ script-src, 'unsafe-eval', 'unsafe-inline' ],
+ * ]
+ *
+ * The first element of each array has to be a valid directive-name, otherwise we can
+ * ignore the remaining elements of the array. Also, if the
+ * directive already exists in the current policy, we can ignore
+ * the remaining elements of that array. (http://www.w3.org/TR/CSP/#parsing)
+ */
+
+typedef nsTArray< nsTArray<nsString> > cspTokens;
+
+class nsCSPTokenizer {
+
+ public:
+ static void tokenizeCSPPolicy(const nsAString &aPolicyString, cspTokens& outTokens);
+
+ private:
+ nsCSPTokenizer(const char16_t* aStart, const char16_t* aEnd);
+ ~nsCSPTokenizer();
+
+ inline bool atEnd()
+ {
+ return mCurChar >= mEndChar;
+ }
+
+ inline void skipWhiteSpace()
+ {
+ while (mCurChar < mEndChar &&
+ nsContentUtils::IsHTMLWhitespace(*mCurChar)) {
+ mCurToken.Append(*mCurChar++);
+ }
+ mCurToken.Truncate();
+ }
+
+ inline void skipWhiteSpaceAndSemicolon()
+ {
+ while (mCurChar < mEndChar && (*mCurChar == ';' ||
+ nsContentUtils::IsHTMLWhitespace(*mCurChar))){
+ mCurToken.Append(*mCurChar++);
+ }
+ mCurToken.Truncate();
+ }
+
+ inline bool accept(char16_t aChar)
+ {
+ NS_ASSERTION(mCurChar < mEndChar, "Trying to dereference mEndChar");
+ if (*mCurChar == aChar) {
+ mCurToken.Append(*mCurChar++);
+ return true;
+ }
+ return false;
+ }
+
+ void generateNextToken();
+ void generateTokens(cspTokens& outTokens);
+
+ const char16_t* mCurChar;
+ const char16_t* mEndChar;
+ nsString mCurToken;
+};
+
+
+class nsCSPParser {
+
+ public:
+ /**
+ * The CSP parser only has one publicly accessible function, which is parseContentSecurityPolicy.
+ * Internally the input string is separated into string tokens and policy() is called, which starts
+ * parsing the policy. The parser calls one function after the other according the the source-list
+ * from http://www.w3.org/TR/CSP11/#source-list. E.g., the parser can only call port() after the parser
+ * has already processed any possible host in host(), similar to a finite state machine.
+ */
+ static nsCSPPolicy* parseContentSecurityPolicy(const nsAString &aPolicyString,
+ nsIURI *aSelfURI,
+ bool aReportOnly,
+ nsCSPContext* aCSPContext,
+ bool aDeliveredViaMetaTag);
+
+ private:
+ nsCSPParser(cspTokens& aTokens,
+ nsIURI* aSelfURI,
+ nsCSPContext* aCSPContext,
+ bool aDeliveredViaMetaTag);
+
+ static bool sCSPExperimentalEnabled;
+ static bool sStrictDynamicEnabled;
+
+ ~nsCSPParser();
+
+
+ // Parsing the CSP using the source-list from http://www.w3.org/TR/CSP11/#source-list
+ nsCSPPolicy* policy();
+ void directive();
+ nsCSPDirective* directiveName();
+ void directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs);
+ void requireSRIForDirectiveValue(nsRequireSRIForDirective* aDir);
+ void referrerDirectiveValue(nsCSPDirective* aDir);
+ void reportURIList(nsCSPDirective* aDir);
+ void sandboxFlagList(nsCSPDirective* aDir);
+ void sourceList(nsTArray<nsCSPBaseSrc*>& outSrcs);
+ nsCSPBaseSrc* sourceExpression();
+ nsCSPSchemeSrc* schemeSource();
+ nsCSPHostSrc* hostSource();
+ nsCSPBaseSrc* keywordSource();
+ nsCSPNonceSrc* nonceSource();
+ nsCSPHashSrc* hashSource();
+ nsCSPHostSrc* appHost(); // helper function to support app specific hosts
+ nsCSPHostSrc* host();
+ bool hostChar();
+ bool schemeChar();
+ bool port();
+ bool path(nsCSPHostSrc* aCspHost);
+
+ bool subHost(); // helper function to parse subDomains
+ bool atValidUnreservedChar(); // helper function to parse unreserved
+ bool atValidSubDelimChar(); // helper function to parse sub-delims
+ bool atValidPctEncodedChar(); // helper function to parse pct-encoded
+ bool subPath(nsCSPHostSrc* aCspHost); // helper function to parse paths
+
+ inline bool atEnd()
+ {
+ return mCurChar >= mEndChar;
+ }
+
+ inline bool accept(char16_t aSymbol)
+ {
+ if (atEnd()) { return false; }
+ return (*mCurChar == aSymbol) && advance();
+ }
+
+ inline bool accept(bool (*aClassifier) (char16_t))
+ {
+ if (atEnd()) { return false; }
+ return (aClassifier(*mCurChar)) && advance();
+ }
+
+ inline bool peek(char16_t aSymbol)
+ {
+ if (atEnd()) { return false; }
+ return *mCurChar == aSymbol;
+ }
+
+ inline bool peek(bool (*aClassifier) (char16_t))
+ {
+ if (atEnd()) { return false; }
+ return aClassifier(*mCurChar);
+ }
+
+ inline bool advance()
+ {
+ if (atEnd()) { return false; }
+ mCurValue.Append(*mCurChar++);
+ return true;
+ }
+
+ inline void resetCurValue()
+ {
+ mCurValue.Truncate();
+ }
+
+ bool atEndOfPath();
+ bool atValidPathChar();
+
+ void resetCurChar(const nsAString& aToken);
+
+ void logWarningErrorToConsole(uint32_t aSeverityFlag,
+ const char* aProperty,
+ const char16_t* aParams[],
+ uint32_t aParamsLength);
+
+/**
+ * When parsing the policy, the parser internally uses the following helper
+ * variables/members which are used/reset during parsing. The following
+ * example explains how they are used.
+ * The tokenizer separats all input into arrays of arrays of strings, which
+ * are stored in mTokens, for example:
+ * mTokens = [ [ script-src, http://www.example.com, 'self' ], ... ]
+ *
+ * When parsing starts, mCurdir always holds the currently processed array of strings.
+ * In our example:
+ * mCurDir = [ script-src, http://www.example.com, 'self' ]
+ *
+ * During parsing, we process/consume one string at a time of that array.
+ * We set mCurToken to the string we are currently processing; in the first case
+ * that would be:
+ * mCurToken = script-src
+ * which allows to do simple string comparisons to see if mCurToken is a valid directive.
+ *
+ * Continuing parsing, the parser consumes the next string of that array, resetting:
+ * mCurToken = "http://www.example.com"
+ * ^ ^
+ * mCurChar mEndChar (points *after* the 'm')
+ * mCurValue = ""
+ *
+ * After calling advance() the first time, helpers would hold the following values:
+ * mCurToken = "http://www.example.com"
+ * ^ ^
+ * mCurChar mEndChar (points *after* the 'm')
+ * mCurValue = "h"
+ *
+ * We continue parsing till all strings of one directive are consumed, then we reset
+ * mCurDir to hold the next array of strings and start the process all over.
+ */
+
+ const char16_t* mCurChar;
+ const char16_t* mEndChar;
+ nsString mCurValue;
+ nsString mCurToken;
+ nsTArray<nsString> mCurDir;
+
+ // helpers to allow invalidation of srcs within script-src and style-src
+ // if either 'strict-dynamic' or at least a hash or nonce is present.
+ bool mHasHashOrNonce; // false, if no hash or nonce is defined
+ bool mStrictDynamic; // false, if 'strict-dynamic' is not defined
+ nsCSPKeywordSrc* mUnsafeInlineKeywordSrc; // null, otherwise invlidate()
+
+ // cache variables for child-src and frame-src directive handling.
+ // frame-src is deprecated in favor of child-src, however if we
+ // see a frame-src directive, it takes precedence for frames and iframes.
+ // At the end of parsing, if we have a child-src directive, we need to
+ // decide whether it will handle frames, or if there is a frame-src we
+ // should honor instead.
+ nsCSPChildSrcDirective* mChildSrc;
+ nsCSPDirective* mFrameSrc;
+
+ cspTokens mTokens;
+ nsIURI* mSelfURI;
+ nsCSPPolicy* mPolicy;
+ nsCSPContext* mCSPContext; // used for console logging
+ bool mDeliveredViaMetaTag;
+};
+
+#endif /* nsCSPParser_h___ */
diff --git a/dom/security/nsCSPService.cpp b/dom/security/nsCSPService.cpp
new file mode 100644
index 000000000..7344e19fa
--- /dev/null
+++ b/dom/security/nsCSPService.cpp
@@ -0,0 +1,336 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#include "mozilla/Logging.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+#include "nsIPrincipal.h"
+#include "nsIObserver.h"
+#include "nsIContent.h"
+#include "nsCSPService.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsError.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "mozilla/Preferences.h"
+#include "nsIScriptError.h"
+#include "nsContentUtils.h"
+#include "nsContentPolicyUtils.h"
+#include "nsPrincipal.h"
+
+using namespace mozilla;
+
+/* Keeps track of whether or not CSP is enabled */
+bool CSPService::sCSPEnabled = true;
+
+static LazyLogModule gCspPRLog("CSP");
+
+CSPService::CSPService()
+{
+ Preferences::AddBoolVarCache(&sCSPEnabled, "security.csp.enable");
+}
+
+CSPService::~CSPService()
+{
+ mAppStatusCache.Clear();
+}
+
+NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink)
+
+// Helper function to identify protocols and content types not subject to CSP.
+bool
+subjectToCSP(nsIURI* aURI, nsContentPolicyType aContentType) {
+ // These content types are not subject to CSP content policy checks:
+ // TYPE_CSP_REPORT -- csp can't block csp reports
+ // TYPE_REFRESH -- never passed to ShouldLoad (see nsIContentPolicy.idl)
+ // TYPE_DOCUMENT -- used for frame-ancestors
+ if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
+ aContentType == nsIContentPolicy::TYPE_REFRESH ||
+ aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
+ return false;
+ }
+
+ // The three protocols: data:, blob: and filesystem: share the same
+ // protocol flag (URI_IS_LOCAL_RESOURCE) with other protocols, like
+ // chrome:, resource:, moz-icon:, but those three protocols get
+ // special attention in CSP and are subject to CSP, hence we have
+ // to make sure those protocols are subject to CSP, see:
+ // http://www.w3.org/TR/CSP2/#source-list-guid-matching
+ bool match = false;
+ nsresult rv = aURI->SchemeIs("data", &match);
+ if (NS_SUCCEEDED(rv) && match) {
+ return true;
+ }
+ rv = aURI->SchemeIs("blob", &match);
+ if (NS_SUCCEEDED(rv) && match) {
+ return true;
+ }
+ rv = aURI->SchemeIs("filesystem", &match);
+ if (NS_SUCCEEDED(rv) && match) {
+ return true;
+ }
+ // finally we have to whitelist "about:" which does not fall in
+ // any of the two categories underneath but is not subject to CSP.
+ rv = aURI->SchemeIs("about", &match);
+ if (NS_SUCCEEDED(rv) && match) {
+ return false;
+ }
+
+ // Other protocols are not subject to CSP and can be whitelisted:
+ // * URI_IS_LOCAL_RESOURCE
+ // e.g. chrome:, data:, blob:, resource:, moz-icon:
+ // * URI_INHERITS_SECURITY_CONTEXT
+ // e.g. javascript:
+ //
+ // Please note that it should be possible for websites to
+ // whitelist their own protocol handlers with respect to CSP,
+ // hence we use protocol flags to accomplish that.
+ rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &match);
+ if (NS_SUCCEEDED(rv) && match) {
+ return false;
+ }
+ rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &match);
+ if (NS_SUCCEEDED(rv) && match) {
+ return false;
+ }
+ // all other protocols are subject To CSP.
+ return true;
+}
+
+/* nsIContentPolicy implementation */
+NS_IMETHODIMP
+CSPService::ShouldLoad(uint32_t aContentType,
+ nsIURI *aContentLocation,
+ nsIURI *aRequestOrigin,
+ nsISupports *aRequestContext,
+ const nsACString &aMimeTypeGuess,
+ nsISupports *aExtra,
+ nsIPrincipal *aRequestPrincipal,
+ int16_t *aDecision)
+{
+ if (!aContentLocation) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
+ MOZ_LOG(gCspPRLog, LogLevel::Debug,
+ ("CSPService::ShouldLoad called for %s",
+ aContentLocation->GetSpecOrDefault().get()));
+ }
+
+ // default decision, CSP can revise it if there's a policy to enforce
+ *aDecision = nsIContentPolicy::ACCEPT;
+
+ // No need to continue processing if CSP is disabled or if the protocol
+ // or type is *not* subject to CSP.
+ // Please note, the correct way to opt-out of CSP using a custom
+ // protocolHandler is to set one of the nsIProtocolHandler flags
+ // that are whitelistet in subjectToCSP()
+ if (!sCSPEnabled || !subjectToCSP(aContentLocation, aContentType)) {
+ return NS_OK;
+ }
+
+ // query the principal of the document; if no document is passed, then
+ // fall back to using the requestPrincipal (e.g. service workers do not
+ // pass a document).
+ nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
+ nsCOMPtr<nsIPrincipal> principal = node ? node->NodePrincipal()
+ : aRequestPrincipal;
+ if (!principal) {
+ // if we can't query a principal, then there is nothing to do.
+ return NS_OK;
+ }
+ nsresult rv = NS_OK;
+
+ // 1) Apply speculate CSP for preloads
+ bool isPreload = nsContentUtils::IsPreloadType(aContentType);
+
+ if (isPreload) {
+ nsCOMPtr<nsIContentSecurityPolicy> preloadCsp;
+ rv = principal->GetPreloadCsp(getter_AddRefs(preloadCsp));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (preloadCsp) {
+ // obtain the enforcement decision
+ // (don't pass aExtra, we use that slot for redirects)
+ rv = preloadCsp->ShouldLoad(aContentType,
+ aContentLocation,
+ aRequestOrigin,
+ aRequestContext,
+ aMimeTypeGuess,
+ nullptr, // aExtra
+ aDecision);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if the preload policy already denied the load, then there
+ // is no point in checking the real policy
+ if (NS_CP_REJECTED(*aDecision)) {
+ return NS_OK;
+ }
+ }
+ }
+
+ // 2) Apply actual CSP to all loads
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ rv = principal->GetCsp(getter_AddRefs(csp));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (csp) {
+ // obtain the enforcement decision
+ // (don't pass aExtra, we use that slot for redirects)
+ rv = csp->ShouldLoad(aContentType,
+ aContentLocation,
+ aRequestOrigin,
+ aRequestContext,
+ aMimeTypeGuess,
+ nullptr,
+ aDecision);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CSPService::ShouldProcess(uint32_t aContentType,
+ nsIURI *aContentLocation,
+ nsIURI *aRequestOrigin,
+ nsISupports *aRequestContext,
+ const nsACString &aMimeTypeGuess,
+ nsISupports *aExtra,
+ nsIPrincipal *aRequestPrincipal,
+ int16_t *aDecision)
+{
+ if (!aContentLocation) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
+ MOZ_LOG(gCspPRLog, LogLevel::Debug,
+ ("CSPService::ShouldProcess called for %s",
+ aContentLocation->GetSpecOrDefault().get()));
+ }
+
+ // ShouldProcess is only relevant to TYPE_OBJECT, so let's convert the
+ // internal contentPolicyType to the mapping external one.
+ // If it is not TYPE_OBJECT, we can return at this point.
+ // Note that we should still pass the internal contentPolicyType
+ // (aContentType) to ShouldLoad().
+ uint32_t policyType =
+ nsContentUtils::InternalContentPolicyTypeToExternal(aContentType);
+
+ if (policyType != nsIContentPolicy::TYPE_OBJECT) {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ }
+
+ return ShouldLoad(aContentType,
+ aContentLocation,
+ aRequestOrigin,
+ aRequestContext,
+ aMimeTypeGuess,
+ aExtra,
+ aRequestPrincipal,
+ aDecision);
+}
+
+/* nsIChannelEventSink implementation */
+NS_IMETHODIMP
+CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
+ nsIChannel *newChannel,
+ uint32_t flags,
+ nsIAsyncVerifyRedirectCallback *callback)
+{
+ net::nsAsyncRedirectAutoCallback autoCallback(callback);
+
+ nsCOMPtr<nsIURI> newUri;
+ nsresult rv = newChannel->GetURI(getter_AddRefs(newUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = oldChannel->GetLoadInfo();
+
+ // if no loadInfo on the channel, nothing for us to do
+ if (!loadInfo) {
+ return NS_OK;
+ }
+
+ // No need to continue processing if CSP is disabled or if the protocol
+ // is *not* subject to CSP.
+ // Please note, the correct way to opt-out of CSP using a custom
+ // protocolHandler is to set one of the nsIProtocolHandler flags
+ // that are whitelistet in subjectToCSP()
+ nsContentPolicyType policyType = loadInfo->InternalContentPolicyType();
+ if (!sCSPEnabled || !subjectToCSP(newUri, policyType)) {
+ return NS_OK;
+ }
+
+ /* Since redirecting channels don't call into nsIContentPolicy, we call our
+ * Content Policy implementation directly when redirects occur using the
+ * information set in the LoadInfo when channels are created.
+ *
+ * We check if the CSP permits this host for this type of load, if not,
+ * we cancel the load now.
+ */
+ nsCOMPtr<nsIURI> originalUri;
+ rv = oldChannel->GetOriginalURI(getter_AddRefs(originalUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isPreload = nsContentUtils::IsPreloadType(policyType);
+
+ /* On redirect, if the content policy is a preload type, rejecting the preload
+ * results in the load silently failing, so we convert preloads to the actual
+ * type. See Bug 1219453.
+ */
+ policyType =
+ nsContentUtils::InternalContentPolicyTypeToExternalOrWorker(policyType);
+
+ int16_t aDecision = nsIContentPolicy::ACCEPT;
+ // 1) Apply speculative CSP for preloads
+ if (isPreload) {
+ nsCOMPtr<nsIContentSecurityPolicy> preloadCsp;
+ loadInfo->LoadingPrincipal()->GetPreloadCsp(getter_AddRefs(preloadCsp));
+
+ if (preloadCsp) {
+ // Pass originalURI as aExtra to indicate the redirect
+ preloadCsp->ShouldLoad(policyType, // load type per nsIContentPolicy (uint32_t)
+ newUri, // nsIURI
+ nullptr, // nsIURI
+ nullptr, // nsISupports
+ EmptyCString(), // ACString - MIME guess
+ originalUri, // aExtra
+ &aDecision);
+
+ // if the preload policy already denied the load, then there
+ // is no point in checking the real policy
+ if (NS_CP_REJECTED(aDecision)) {
+ autoCallback.DontCallback();
+ return NS_BINDING_FAILED;
+ }
+ }
+ }
+
+ // 2) Apply actual CSP to all loads
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ loadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(csp));
+
+ if (csp) {
+ // Pass originalURI as aExtra to indicate the redirect
+ csp->ShouldLoad(policyType, // load type per nsIContentPolicy (uint32_t)
+ newUri, // nsIURI
+ nullptr, // nsIURI
+ nullptr, // nsISupports
+ EmptyCString(), // ACString - MIME guess
+ originalUri, // aExtra
+ &aDecision);
+ }
+
+ // if ShouldLoad doesn't accept the load, cancel the request
+ if (!NS_CP_ACCEPTED(aDecision)) {
+ autoCallback.DontCallback();
+ return NS_BINDING_FAILED;
+ }
+ return NS_OK;
+}
diff --git a/dom/security/nsCSPService.h b/dom/security/nsCSPService.h
new file mode 100644
index 000000000..0eb991233
--- /dev/null
+++ b/dom/security/nsCSPService.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#ifndef nsCSPService_h___
+#define nsCSPService_h___
+
+#include "nsXPCOM.h"
+#include "nsIContentPolicy.h"
+#include "nsIChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsDataHashtable.h"
+
+#define CSPSERVICE_CONTRACTID "@mozilla.org/cspservice;1"
+#define CSPSERVICE_CID \
+ { 0x8d2f40b2, 0x4875, 0x4c95, \
+ { 0x97, 0xd9, 0x3f, 0x7d, 0xca, 0x2c, 0xb4, 0x60 } }
+class CSPService : public nsIContentPolicy,
+ public nsIChannelEventSink
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPOLICY
+ NS_DECL_NSICHANNELEVENTSINK
+
+ CSPService();
+ static bool sCSPEnabled;
+
+protected:
+ virtual ~CSPService();
+
+private:
+ // Maps origins to app status.
+ nsDataHashtable<nsCStringHashKey, uint16_t> mAppStatusCache;
+};
+#endif /* nsCSPService_h___ */
diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp
new file mode 100644
index 000000000..63b4aae2c
--- /dev/null
+++ b/dom/security/nsCSPUtils.cpp
@@ -0,0 +1,1616 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#include "nsAttrValue.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsContentUtils.h"
+#include "nsCSPUtils.h"
+#include "nsDebug.h"
+#include "nsIConsoleService.h"
+#include "nsICryptoHash.h"
+#include "nsIScriptError.h"
+#include "nsIServiceManager.h"
+#include "nsIStringBundle.h"
+#include "nsIURL.h"
+#include "nsReadableUtils.h"
+#include "nsSandboxFlags.h"
+
+#define DEFAULT_PORT -1
+
+static mozilla::LogModule*
+GetCspUtilsLog()
+{
+ static mozilla::LazyLogModule gCspUtilsPRLog("CSPUtils");
+ return gCspUtilsPRLog;
+}
+
+#define CSPUTILSLOG(args) MOZ_LOG(GetCspUtilsLog(), mozilla::LogLevel::Debug, args)
+#define CSPUTILSLOGENABLED() MOZ_LOG_TEST(GetCspUtilsLog(), mozilla::LogLevel::Debug)
+
+void
+CSP_PercentDecodeStr(const nsAString& aEncStr, nsAString& outDecStr)
+{
+ outDecStr.Truncate();
+
+ // helper function that should not be visible outside this methods scope
+ struct local {
+ static inline char16_t convertHexDig(char16_t aHexDig) {
+ if (isNumberToken(aHexDig)) {
+ return aHexDig - '0';
+ }
+ if (aHexDig >= 'A' && aHexDig <= 'F') {
+ return aHexDig - 'A' + 10;
+ }
+ // must be a lower case character
+ // (aHexDig >= 'a' && aHexDig <= 'f')
+ return aHexDig - 'a' + 10;
+ }
+ };
+
+ const char16_t *cur, *end, *hexDig1, *hexDig2;
+ cur = aEncStr.BeginReading();
+ end = aEncStr.EndReading();
+
+ while (cur != end) {
+ // if it's not a percent sign then there is
+ // nothing to do for that character
+ if (*cur != PERCENT_SIGN) {
+ outDecStr.Append(*cur);
+ cur++;
+ continue;
+ }
+
+ // get the two hexDigs following the '%'-sign
+ hexDig1 = cur + 1;
+ hexDig2 = cur + 2;
+
+ // if there are no hexdigs after the '%' then
+ // there is nothing to do for us.
+ if (hexDig1 == end || hexDig2 == end ||
+ !isValidHexDig(*hexDig1) ||
+ !isValidHexDig(*hexDig2)) {
+ outDecStr.Append(PERCENT_SIGN);
+ cur++;
+ continue;
+ }
+
+ // decode "% hexDig1 hexDig2" into a character.
+ char16_t decChar = (local::convertHexDig(*hexDig1) << 4) +
+ local::convertHexDig(*hexDig2);
+ outDecStr.Append(decChar);
+
+ // increment 'cur' to after the second hexDig
+ cur = ++hexDig2;
+ }
+}
+
+void
+CSP_GetLocalizedStr(const char16_t* aName,
+ const char16_t** aParams,
+ uint32_t aLength,
+ char16_t** outResult)
+{
+ nsCOMPtr<nsIStringBundle> keyStringBundle;
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+
+ NS_ASSERTION(stringBundleService, "String bundle service must be present!");
+ stringBundleService->CreateBundle("chrome://global/locale/security/csp.properties",
+ getter_AddRefs(keyStringBundle));
+
+ NS_ASSERTION(keyStringBundle, "Key string bundle must be available!");
+
+ if (!keyStringBundle) {
+ return;
+ }
+ keyStringBundle->FormatStringFromName(aName, aParams, aLength, outResult);
+}
+
+void
+CSP_LogStrMessage(const nsAString& aMsg)
+{
+ nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
+
+ if (!console) {
+ return;
+ }
+ nsString msg = PromiseFlatString(aMsg);
+ console->LogStringMessage(msg.get());
+}
+
+void
+CSP_LogMessage(const nsAString& aMessage,
+ const nsAString& aSourceName,
+ const nsAString& aSourceLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aFlags,
+ const char *aCategory,
+ uint64_t aInnerWindowID)
+{
+ nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+
+ nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+
+ if (!console || !error) {
+ return;
+ }
+
+ // Prepending CSP to the outgoing console message
+ nsString cspMsg;
+ cspMsg.Append(NS_LITERAL_STRING("Content Security Policy: "));
+ cspMsg.Append(aMessage);
+
+ // Currently 'aSourceLine' is not logged to the console, because similar
+ // information is already included within the source link of the message.
+ // For inline violations however, the line and column number are 0 and
+ // information contained within 'aSourceLine' can be really useful for devs.
+ // E.g. 'aSourceLine' might be: 'onclick attribute on DIV element'.
+ // In such cases we append 'aSourceLine' directly to the error message.
+ if (!aSourceLine.IsEmpty()) {
+ cspMsg.Append(NS_LITERAL_STRING(" Source: "));
+ cspMsg.Append(aSourceLine);
+ cspMsg.Append(NS_LITERAL_STRING("."));
+ }
+
+ nsresult rv;
+ if (aInnerWindowID > 0) {
+ nsCString catStr;
+ catStr.AssignASCII(aCategory);
+ rv = error->InitWithWindowID(cspMsg, aSourceName,
+ aSourceLine, aLineNumber,
+ aColumnNumber, aFlags,
+ catStr, aInnerWindowID);
+ }
+ else {
+ rv = error->Init(cspMsg, aSourceName,
+ aSourceLine, aLineNumber,
+ aColumnNumber, aFlags,
+ aCategory);
+ }
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ console->LogMessage(error);
+}
+
+/**
+ * Combines CSP_LogMessage and CSP_GetLocalizedStr into one call.
+ */
+void
+CSP_LogLocalizedStr(const char16_t* aName,
+ const char16_t** aParams,
+ uint32_t aLength,
+ const nsAString& aSourceName,
+ const nsAString& aSourceLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aFlags,
+ const char* aCategory,
+ uint64_t aInnerWindowID)
+{
+ nsXPIDLString logMsg;
+ CSP_GetLocalizedStr(aName, aParams, aLength, getter_Copies(logMsg));
+ CSP_LogMessage(logMsg, aSourceName, aSourceLine,
+ aLineNumber, aColumnNumber, aFlags,
+ aCategory, aInnerWindowID);
+}
+
+/* ===== Helpers ============================ */
+CSPDirective
+CSP_ContentTypeToDirective(nsContentPolicyType aType)
+{
+ switch (aType) {
+ case nsIContentPolicy::TYPE_IMAGE:
+ case nsIContentPolicy::TYPE_IMAGESET:
+ return nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE;
+
+ // BLock XSLT as script, see bug 910139
+ case nsIContentPolicy::TYPE_XSLT:
+ case nsIContentPolicy::TYPE_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
+ return nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE;
+
+ case nsIContentPolicy::TYPE_STYLESHEET:
+ return nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE;
+
+ case nsIContentPolicy::TYPE_FONT:
+ return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE;
+
+ case nsIContentPolicy::TYPE_MEDIA:
+ return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE;
+
+ case nsIContentPolicy::TYPE_WEB_MANIFEST:
+ return nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE;
+
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER:
+ case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
+ case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
+ return nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE;
+
+ case nsIContentPolicy::TYPE_SUBDOCUMENT:
+ return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE;
+
+ case nsIContentPolicy::TYPE_WEBSOCKET:
+ case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
+ case nsIContentPolicy::TYPE_BEACON:
+ case nsIContentPolicy::TYPE_PING:
+ case nsIContentPolicy::TYPE_FETCH:
+ return nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE;
+
+ case nsIContentPolicy::TYPE_OBJECT:
+ case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST:
+ return nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE;
+
+ case nsIContentPolicy::TYPE_XBL:
+ case nsIContentPolicy::TYPE_DTD:
+ case nsIContentPolicy::TYPE_OTHER:
+ return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
+
+ // csp shold not block top level loads, e.g. in case
+ // of a redirect.
+ case nsIContentPolicy::TYPE_DOCUMENT:
+ // CSP can not block csp reports
+ case nsIContentPolicy::TYPE_CSP_REPORT:
+ return nsIContentSecurityPolicy::NO_DIRECTIVE;
+
+ // Fall through to error for all other directives
+ default:
+ MOZ_ASSERT(false, "Can not map nsContentPolicyType to CSPDirective");
+ }
+ return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
+}
+
+nsCSPHostSrc*
+CSP_CreateHostSrcFromURI(nsIURI* aURI)
+{
+ // Create the host first
+ nsCString host;
+ aURI->GetHost(host);
+ nsCSPHostSrc *hostsrc = new nsCSPHostSrc(NS_ConvertUTF8toUTF16(host));
+
+ // Add the scheme.
+ nsCString scheme;
+ aURI->GetScheme(scheme);
+ hostsrc->setScheme(NS_ConvertUTF8toUTF16(scheme));
+
+ int32_t port;
+ aURI->GetPort(&port);
+ // Only add port if it's not default port.
+ if (port > 0) {
+ nsAutoString portStr;
+ portStr.AppendInt(port);
+ hostsrc->setPort(portStr);
+ }
+ return hostsrc;
+}
+
+bool
+CSP_IsValidDirective(const nsAString& aDir)
+{
+ uint32_t numDirs = (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]));
+
+ for (uint32_t i = 0; i < numDirs; i++) {
+ if (aDir.LowerCaseEqualsASCII(CSPStrDirectives[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+bool
+CSP_IsDirective(const nsAString& aValue, CSPDirective aDir)
+{
+ return aValue.LowerCaseEqualsASCII(CSP_CSPDirectiveToString(aDir));
+}
+
+bool
+CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey)
+{
+ return aValue.LowerCaseEqualsASCII(CSP_EnumToKeyword(aKey));
+}
+
+bool
+CSP_IsQuotelessKeyword(const nsAString& aKey)
+{
+ nsString lowerKey = PromiseFlatString(aKey);
+ ToLowerCase(lowerKey);
+
+ static_assert(CSP_LAST_KEYWORD_VALUE ==
+ (sizeof(CSPStrKeywords) / sizeof(CSPStrKeywords[0])),
+ "CSP_LAST_KEYWORD_VALUE does not match length of CSPStrKeywords");
+
+ nsAutoString keyword;
+ for (uint32_t i = 0; i < CSP_LAST_KEYWORD_VALUE; i++) {
+ // skipping the leading ' and trimming the trailing '
+ keyword.AssignASCII(CSPStrKeywords[i] + 1);
+ keyword.Trim("'", false, true);
+ if (lowerKey.Equals(keyword)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * Checks whether the current directive permits a specific
+ * scheme. This function is called from nsCSPSchemeSrc() and
+ * also nsCSPHostSrc.
+ * @param aEnforcementScheme
+ * The scheme that this directive allows
+ * @param aUri
+ * The uri of the subresource load.
+ * @param aReportOnly
+ * Whether the enforced policy is report only or not.
+ * @param aUpgradeInsecure
+ * Whether the policy makes use of the directive
+ * 'upgrade-insecure-requests'.
+ */
+
+bool
+permitsScheme(const nsAString& aEnforcementScheme,
+ nsIURI* aUri,
+ bool aReportOnly,
+ bool aUpgradeInsecure)
+{
+ nsAutoCString scheme;
+ nsresult rv = aUri->GetScheme(scheme);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // no scheme to enforce, let's allow the load (e.g. script-src *)
+ if (aEnforcementScheme.IsEmpty()) {
+ return true;
+ }
+
+ // if the scheme matches, all good - allow the load
+ if (aEnforcementScheme.EqualsASCII(scheme.get())) {
+ return true;
+ }
+
+ // allow scheme-less sources where the protected resource is http
+ // and the load is https, see:
+ // http://www.w3.org/TR/CSP2/#match-source-expression
+ if (aEnforcementScheme.EqualsASCII("http") &&
+ scheme.EqualsASCII("https")) {
+ return true;
+ }
+
+ // Allow the load when enforcing upgrade-insecure-requests with the
+ // promise the request gets upgraded from http to https and ws to wss.
+ // See nsHttpChannel::Connect() and also WebSocket.cpp. Please note,
+ // the report only policies should not allow the load and report
+ // the error back to the page.
+ return ((aUpgradeInsecure && !aReportOnly) &&
+ ((scheme.EqualsASCII("http") && aEnforcementScheme.EqualsASCII("https")) ||
+ (scheme.EqualsASCII("ws") && aEnforcementScheme.EqualsASCII("wss"))));
+}
+
+/*
+ * A helper function for appending a CSP header to an existing CSP
+ * policy.
+ *
+ * @param aCsp the CSP policy
+ * @param aHeaderValue the header
+ * @param aReportOnly is this a report-only header?
+ */
+
+nsresult
+CSP_AppendCSPFromHeader(nsIContentSecurityPolicy* aCsp,
+ const nsAString& aHeaderValue,
+ bool aReportOnly)
+{
+ NS_ENSURE_ARG(aCsp);
+
+ // Need to tokenize the header value since multiple headers could be
+ // concatenated into one comma-separated list of policies.
+ // See RFC2616 section 4.2 (last paragraph)
+ nsresult rv = NS_OK;
+ nsCharSeparatedTokenizer tokenizer(aHeaderValue, ',');
+ while (tokenizer.hasMoreTokens()) {
+ const nsSubstring& policy = tokenizer.nextToken();
+ rv = aCsp->AppendPolicy(policy, aReportOnly, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ {
+ CSPUTILSLOG(("CSP refined with policy: \"%s\"",
+ NS_ConvertUTF16toUTF8(policy).get()));
+ }
+ }
+ return NS_OK;
+}
+
+/* ===== nsCSPSrc ============================ */
+
+nsCSPBaseSrc::nsCSPBaseSrc()
+ : mInvalidated(false)
+{
+}
+
+nsCSPBaseSrc::~nsCSPBaseSrc()
+{
+}
+
+// ::permits is only called for external load requests, therefore:
+// nsCSPKeywordSrc and nsCSPHashSource fall back to this base class
+// implementation which will never allow the load.
+bool
+nsCSPBaseSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
+ bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const
+{
+ if (CSPUTILSLOGENABLED()) {
+ CSPUTILSLOG(("nsCSPBaseSrc::permits, aUri: %s",
+ aUri->GetSpecOrDefault().get()));
+ }
+ return false;
+}
+
+// ::allows is only called for inlined loads, therefore:
+// nsCSPSchemeSrc, nsCSPHostSrc fall back
+// to this base class implementation which will never allow the load.
+bool
+nsCSPBaseSrc::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+ bool aParserCreated) const
+{
+ CSPUTILSLOG(("nsCSPBaseSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
+ aKeyword == CSP_HASH ? "hash" : CSP_EnumToKeyword(aKeyword),
+ NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
+ return false;
+}
+
+/* ====== nsCSPSchemeSrc ===================== */
+
+nsCSPSchemeSrc::nsCSPSchemeSrc(const nsAString& aScheme)
+ : mScheme(aScheme)
+{
+ ToLowerCase(mScheme);
+}
+
+nsCSPSchemeSrc::~nsCSPSchemeSrc()
+{
+}
+
+bool
+nsCSPSchemeSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
+ bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const
+{
+ if (CSPUTILSLOGENABLED()) {
+ CSPUTILSLOG(("nsCSPSchemeSrc::permits, aUri: %s",
+ aUri->GetSpecOrDefault().get()));
+ }
+ MOZ_ASSERT((!mScheme.EqualsASCII("")), "scheme can not be the empty string");
+ if (mInvalidated) {
+ return false;
+ }
+ return permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure);
+}
+
+bool
+nsCSPSchemeSrc::visit(nsCSPSrcVisitor* aVisitor) const
+{
+ return aVisitor->visitSchemeSrc(*this);
+}
+
+void
+nsCSPSchemeSrc::toString(nsAString& outStr) const
+{
+ outStr.Append(mScheme);
+ outStr.AppendASCII(":");
+}
+
+/* ===== nsCSPHostSrc ======================== */
+
+nsCSPHostSrc::nsCSPHostSrc(const nsAString& aHost)
+ : mHost(aHost)
+{
+ ToLowerCase(mHost);
+}
+
+nsCSPHostSrc::~nsCSPHostSrc()
+{
+}
+
+/*
+ * Checks whether the current directive permits a specific port.
+ * @param aEnforcementScheme
+ * The scheme that this directive allows
+ * (used to query the default port for that scheme)
+ * @param aEnforcementPort
+ * The port that this directive allows
+ * @param aResourceURI
+ * The uri of the subresource load
+ */
+bool
+permitsPort(const nsAString& aEnforcementScheme,
+ const nsAString& aEnforcementPort,
+ nsIURI* aResourceURI)
+{
+ // If enforcement port is the wildcard, don't block the load.
+ if (aEnforcementPort.EqualsASCII("*")) {
+ return true;
+ }
+
+ int32_t resourcePort;
+ nsresult rv = aResourceURI->GetPort(&resourcePort);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Avoid unnecessary string creation/manipulation and don't block the
+ // load if the resource to be loaded uses the default port for that
+ // scheme and there is no port to be enforced.
+ // Note, this optimization relies on scheme checks within permitsScheme().
+ if (resourcePort == DEFAULT_PORT && aEnforcementPort.IsEmpty()) {
+ return true;
+ }
+
+ // By now we know at that either the resourcePort does not use the default
+ // port or there is a port restriction to be enforced. A port value of -1
+ // corresponds to the protocol's default port (eg. -1 implies port 80 for
+ // http URIs), in such a case we have to query the default port of the
+ // resource to be loaded.
+ if (resourcePort == DEFAULT_PORT) {
+ nsAutoCString resourceScheme;
+ rv = aResourceURI->GetScheme(resourceScheme);
+ NS_ENSURE_SUCCESS(rv, false);
+ resourcePort = NS_GetDefaultPort(resourceScheme.get());
+ }
+
+ // If there is a port to be enforced and the ports match, then
+ // don't block the load.
+ nsString resourcePortStr;
+ resourcePortStr.AppendInt(resourcePort);
+ if (aEnforcementPort.Equals(resourcePortStr)) {
+ return true;
+ }
+
+ // If there is no port to be enforced, query the default port for the load.
+ nsString enforcementPort(aEnforcementPort);
+ if (enforcementPort.IsEmpty()) {
+ // For scheme less sources, our parser always generates a scheme
+ // which is the scheme of the protected resource.
+ MOZ_ASSERT(!aEnforcementScheme.IsEmpty(),
+ "need a scheme to query default port");
+ int32_t defaultEnforcementPort =
+ NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
+ enforcementPort.Truncate();
+ enforcementPort.AppendInt(defaultEnforcementPort);
+ }
+
+ // If default ports match, don't block the load
+ if (enforcementPort.Equals(resourcePortStr)) {
+ return true;
+ }
+
+ // Additional port matching where the regular URL matching algorithm
+ // treats insecure ports as matching their secure variants.
+ // default port for http is :80
+ // default port for https is :443
+ if (enforcementPort.EqualsLiteral("80") &&
+ resourcePortStr.EqualsLiteral("443")) {
+ return true;
+ }
+
+ // ports do not match, block the load.
+ return false;
+}
+
+bool
+nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
+ bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const
+{
+ if (CSPUTILSLOGENABLED()) {
+ CSPUTILSLOG(("nsCSPHostSrc::permits, aUri: %s",
+ aUri->GetSpecOrDefault().get()));
+ }
+
+ if (mInvalidated) {
+ return false;
+ }
+
+ // we are following the enforcement rules from the spec, see:
+ // http://www.w3.org/TR/CSP11/#match-source-expression
+
+ // 4.3) scheme matching: Check if the scheme matches.
+ if (!permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure)) {
+ return false;
+ }
+
+ // The host in nsCSpHostSrc should never be empty. In case we are enforcing
+ // just a specific scheme, the parser should generate a nsCSPSchemeSource.
+ NS_ASSERTION((!mHost.IsEmpty()), "host can not be the empty string");
+
+ // 2) host matching: Enforce a single *
+ if (mHost.EqualsASCII("*")) {
+ // The single ASTERISK character (*) does not match a URI's scheme of a type
+ // designating a globally unique identifier (such as blob:, data:, or filesystem:)
+ // At the moment firefox does not support filesystem; but for future compatibility
+ // we support it in CSP according to the spec, see: 4.2.2 Matching Source Expressions
+ // Note, that whitelisting any of these schemes would call nsCSPSchemeSrc::permits().
+ bool isBlobScheme =
+ (NS_SUCCEEDED(aUri->SchemeIs("blob", &isBlobScheme)) && isBlobScheme);
+ bool isDataScheme =
+ (NS_SUCCEEDED(aUri->SchemeIs("data", &isDataScheme)) && isDataScheme);
+ bool isFileScheme =
+ (NS_SUCCEEDED(aUri->SchemeIs("filesystem", &isFileScheme)) && isFileScheme);
+
+ if (isBlobScheme || isDataScheme || isFileScheme) {
+ return false;
+ }
+ return true;
+ }
+
+ // Before we can check if the host matches, we have to
+ // extract the host part from aUri.
+ nsAutoCString uriHost;
+ nsresult rv = aUri->GetHost(uriHost);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsString decodedUriHost;
+ CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost);
+
+ // 4.5) host matching: Check if the allowed host starts with a wilcard.
+ if (mHost.First() == '*') {
+ NS_ASSERTION(mHost[1] == '.', "Second character needs to be '.' whenever host starts with '*'");
+
+ // Eliminate leading "*", but keeping the FULL STOP (.) thereafter before checking
+ // if the remaining characters match
+ nsString wildCardHost = mHost;
+ wildCardHost = Substring(wildCardHost, 1, wildCardHost.Length() - 1);
+ if (!StringEndsWith(decodedUriHost, wildCardHost)) {
+ return false;
+ }
+ }
+ // 4.6) host matching: Check if hosts match.
+ else if (!mHost.Equals(decodedUriHost)) {
+ return false;
+ }
+
+ // Port matching: Check if the ports match.
+ if (!permitsPort(mScheme, mPort, aUri)) {
+ return false;
+ }
+
+ // 4.9) Path matching: If there is a path, we have to enforce
+ // path-level matching, unless the channel got redirected, see:
+ // http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects
+ if (!aWasRedirected && !mPath.IsEmpty()) {
+ // converting aUri into nsIURL so we can strip query and ref
+ // example.com/test#foo -> example.com/test
+ // example.com/test?val=foo -> example.com/test
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aUri);
+ if (!url) {
+ NS_ASSERTION(false, "can't QI into nsIURI");
+ return false;
+ }
+ nsAutoCString uriPath;
+ rv = url->GetFilePath(uriPath);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsString decodedUriPath;
+ CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriPath), decodedUriPath);
+
+ // check if the last character of mPath is '/'; if so
+ // we just have to check loading resource is within
+ // the allowed path.
+ if (mPath.Last() == '/') {
+ if (!StringBeginsWith(decodedUriPath, mPath)) {
+ return false;
+ }
+ }
+ // otherwise mPath whitelists a specific file, and we have to
+ // check if the loading resource matches that whitelisted file.
+ else {
+ if (!mPath.Equals(decodedUriPath)) {
+ return false;
+ }
+ }
+ }
+
+ // At the end: scheme, host, port and path match -> allow the load.
+ return true;
+}
+
+bool
+nsCSPHostSrc::visit(nsCSPSrcVisitor* aVisitor) const
+{
+ return aVisitor->visitHostSrc(*this);
+}
+
+void
+nsCSPHostSrc::toString(nsAString& outStr) const
+{
+ // If mHost is a single "*", we append the wildcard and return.
+ if (mHost.EqualsASCII("*") &&
+ mScheme.IsEmpty() &&
+ mPort.IsEmpty()) {
+ outStr.Append(mHost);
+ return;
+ }
+
+ // append scheme
+ outStr.Append(mScheme);
+
+ // append host
+ outStr.AppendASCII("://");
+ outStr.Append(mHost);
+
+ // append port
+ if (!mPort.IsEmpty()) {
+ outStr.AppendASCII(":");
+ outStr.Append(mPort);
+ }
+
+ // append path
+ outStr.Append(mPath);
+}
+
+void
+nsCSPHostSrc::setScheme(const nsAString& aScheme)
+{
+ mScheme = aScheme;
+ ToLowerCase(mScheme);
+}
+
+void
+nsCSPHostSrc::setPort(const nsAString& aPort)
+{
+ mPort = aPort;
+}
+
+void
+nsCSPHostSrc::appendPath(const nsAString& aPath)
+{
+ mPath.Append(aPath);
+}
+
+/* ===== nsCSPKeywordSrc ===================== */
+
+nsCSPKeywordSrc::nsCSPKeywordSrc(enum CSPKeyword aKeyword)
+ : mKeyword(aKeyword)
+{
+ NS_ASSERTION((aKeyword != CSP_SELF),
+ "'self' should have been replaced in the parser");
+}
+
+nsCSPKeywordSrc::~nsCSPKeywordSrc()
+{
+}
+
+bool
+nsCSPKeywordSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
+ bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const
+{
+ // no need to check for invalidated, this will always return false unless
+ // it is an nsCSPKeywordSrc for 'strict-dynamic', which should allow non
+ // parser created scripts.
+ return ((mKeyword == CSP_STRICT_DYNAMIC) && !aParserCreated);
+}
+
+bool
+nsCSPKeywordSrc::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+ bool aParserCreated) const
+{
+ CSPUTILSLOG(("nsCSPKeywordSrc::allows, aKeyWord: %s, aHashOrNonce: %s, mInvalidated: %s",
+ CSP_EnumToKeyword(aKeyword),
+ CSP_EnumToKeyword(mKeyword),
+ NS_ConvertUTF16toUTF8(aHashOrNonce).get(),
+ mInvalidated ? "yes" : "false"));
+
+ if (mInvalidated) {
+ // only 'self' and 'unsafe-inline' are keywords that can be ignored. Please note that
+ // the parser already translates 'self' into a uri (see assertion in constructor).
+ MOZ_ASSERT(mKeyword == CSP_UNSAFE_INLINE,
+ "should only invalidate unsafe-inline");
+ return false;
+ }
+ // either the keyword allows the load or the policy contains 'strict-dynamic', in which
+ // case we have to make sure the script is not parser created before allowing the load.
+ return ((mKeyword == aKeyword) ||
+ ((mKeyword == CSP_STRICT_DYNAMIC) && !aParserCreated));
+}
+
+bool
+nsCSPKeywordSrc::visit(nsCSPSrcVisitor* aVisitor) const
+{
+ return aVisitor->visitKeywordSrc(*this);
+}
+
+void
+nsCSPKeywordSrc::toString(nsAString& outStr) const
+{
+ outStr.AppendASCII(CSP_EnumToKeyword(mKeyword));
+}
+
+/* ===== nsCSPNonceSrc ==================== */
+
+nsCSPNonceSrc::nsCSPNonceSrc(const nsAString& aNonce)
+ : mNonce(aNonce)
+{
+}
+
+nsCSPNonceSrc::~nsCSPNonceSrc()
+{
+}
+
+bool
+nsCSPNonceSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
+ bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const
+{
+ if (CSPUTILSLOGENABLED()) {
+ CSPUTILSLOG(("nsCSPNonceSrc::permits, aUri: %s, aNonce: %s",
+ aUri->GetSpecOrDefault().get(),
+ NS_ConvertUTF16toUTF8(aNonce).get()));
+ }
+
+ // nonces can not be invalidated by strict-dynamic
+ return mNonce.Equals(aNonce);
+}
+
+bool
+nsCSPNonceSrc::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+ bool aParserCreated) const
+{
+ CSPUTILSLOG(("nsCSPNonceSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
+ CSP_EnumToKeyword(aKeyword), NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
+
+ if (aKeyword != CSP_NONCE) {
+ return false;
+ }
+ // nonces can not be invalidated by strict-dynamic
+ return mNonce.Equals(aHashOrNonce);
+}
+
+bool
+nsCSPNonceSrc::visit(nsCSPSrcVisitor* aVisitor) const
+{
+ return aVisitor->visitNonceSrc(*this);
+}
+
+void
+nsCSPNonceSrc::toString(nsAString& outStr) const
+{
+ outStr.AppendASCII(CSP_EnumToKeyword(CSP_NONCE));
+ outStr.Append(mNonce);
+ outStr.AppendASCII("'");
+}
+
+/* ===== nsCSPHashSrc ===================== */
+
+nsCSPHashSrc::nsCSPHashSrc(const nsAString& aAlgo, const nsAString& aHash)
+ : mAlgorithm(aAlgo)
+ , mHash(aHash)
+{
+ // Only the algo should be rewritten to lowercase, the hash must remain the same.
+ ToLowerCase(mAlgorithm);
+}
+
+nsCSPHashSrc::~nsCSPHashSrc()
+{
+}
+
+bool
+nsCSPHashSrc::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+ bool aParserCreated) const
+{
+ CSPUTILSLOG(("nsCSPHashSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
+ CSP_EnumToKeyword(aKeyword), NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
+
+ if (aKeyword != CSP_HASH) {
+ return false;
+ }
+
+ // hashes can not be invalidated by strict-dynamic
+
+ // Convert aHashOrNonce to UTF-8
+ NS_ConvertUTF16toUTF8 utf8_hash(aHashOrNonce);
+
+ nsresult rv;
+ nsCOMPtr<nsICryptoHash> hasher;
+ hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = hasher->InitWithString(NS_ConvertUTF16toUTF8(mAlgorithm));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = hasher->Update((uint8_t *)utf8_hash.get(), utf8_hash.Length());
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsAutoCString hash;
+ rv = hasher->Finish(true, hash);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // The NSS Base64 encoder automatically adds linebreaks "\r\n" every 64
+ // characters. We need to remove these so we can properly validate longer
+ // (SHA-512) base64-encoded hashes
+ hash.StripChars("\r\n");
+ return NS_ConvertUTF16toUTF8(mHash).Equals(hash);
+}
+
+bool
+nsCSPHashSrc::visit(nsCSPSrcVisitor* aVisitor) const
+{
+ return aVisitor->visitHashSrc(*this);
+}
+
+void
+nsCSPHashSrc::toString(nsAString& outStr) const
+{
+ outStr.AppendASCII("'");
+ outStr.Append(mAlgorithm);
+ outStr.AppendASCII("-");
+ outStr.Append(mHash);
+ outStr.AppendASCII("'");
+}
+
+/* ===== nsCSPReportURI ===================== */
+
+nsCSPReportURI::nsCSPReportURI(nsIURI *aURI)
+ :mReportURI(aURI)
+{
+}
+
+nsCSPReportURI::~nsCSPReportURI()
+{
+}
+
+bool
+nsCSPReportURI::visit(nsCSPSrcVisitor* aVisitor) const
+{
+ return false;
+}
+
+void
+nsCSPReportURI::toString(nsAString& outStr) const
+{
+ nsAutoCString spec;
+ nsresult rv = mReportURI->GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ outStr.AppendASCII(spec.get());
+}
+
+/* ===== nsCSPSandboxFlags ===================== */
+
+nsCSPSandboxFlags::nsCSPSandboxFlags(const nsAString& aFlags)
+ : mFlags(aFlags)
+{
+ ToLowerCase(mFlags);
+}
+
+nsCSPSandboxFlags::~nsCSPSandboxFlags()
+{
+}
+
+bool
+nsCSPSandboxFlags::visit(nsCSPSrcVisitor* aVisitor) const
+{
+ return false;
+}
+
+void
+nsCSPSandboxFlags::toString(nsAString& outStr) const
+{
+ outStr.Append(mFlags);
+}
+
+/* ===== nsCSPDirective ====================== */
+
+nsCSPDirective::nsCSPDirective(CSPDirective aDirective)
+{
+ mDirective = aDirective;
+}
+
+nsCSPDirective::~nsCSPDirective()
+{
+ for (uint32_t i = 0; i < mSrcs.Length(); i++) {
+ delete mSrcs[i];
+ }
+}
+
+bool
+nsCSPDirective::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
+ bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const
+{
+ if (CSPUTILSLOGENABLED()) {
+ CSPUTILSLOG(("nsCSPDirective::permits, aUri: %s",
+ aUri->GetSpecOrDefault().get()));
+ }
+
+ for (uint32_t i = 0; i < mSrcs.Length(); i++) {
+ if (mSrcs[i]->permits(aUri, aNonce, aWasRedirected, aReportOnly, aUpgradeInsecure, aParserCreated)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+nsCSPDirective::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+ bool aParserCreated) const
+{
+ CSPUTILSLOG(("nsCSPDirective::allows, aKeyWord: %s, a HashOrNonce: %s",
+ CSP_EnumToKeyword(aKeyword), NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
+
+ for (uint32_t i = 0; i < mSrcs.Length(); i++) {
+ if (mSrcs[i]->allows(aKeyword, aHashOrNonce, aParserCreated)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+nsCSPDirective::toString(nsAString& outStr) const
+{
+ // Append directive name
+ outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
+ outStr.AppendASCII(" ");
+
+ // Append srcs
+ uint32_t length = mSrcs.Length();
+ for (uint32_t i = 0; i < length; i++) {
+ mSrcs[i]->toString(outStr);
+ if (i != (length - 1)) {
+ outStr.AppendASCII(" ");
+ }
+ }
+}
+
+void
+nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const
+{
+ mozilla::dom::Sequence<nsString> srcs;
+ nsString src;
+ for (uint32_t i = 0; i < mSrcs.Length(); i++) {
+ src.Truncate();
+ mSrcs[i]->toString(src);
+ srcs.AppendElement(src, mozilla::fallible);
+ }
+
+ switch(mDirective) {
+ case nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE:
+ outCSP.mDefault_src.Construct();
+ outCSP.mDefault_src.Value() = mozilla::Move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE:
+ outCSP.mScript_src.Construct();
+ outCSP.mScript_src.Value() = mozilla::Move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE:
+ outCSP.mObject_src.Construct();
+ outCSP.mObject_src.Value() = mozilla::Move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE:
+ outCSP.mStyle_src.Construct();
+ outCSP.mStyle_src.Value() = mozilla::Move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE:
+ outCSP.mImg_src.Construct();
+ outCSP.mImg_src.Value() = mozilla::Move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE:
+ outCSP.mMedia_src.Construct();
+ outCSP.mMedia_src.Value() = mozilla::Move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE:
+ outCSP.mFrame_src.Construct();
+ outCSP.mFrame_src.Value() = mozilla::Move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE:
+ outCSP.mFont_src.Construct();
+ outCSP.mFont_src.Value() = mozilla::Move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE:
+ outCSP.mConnect_src.Construct();
+ outCSP.mConnect_src.Value() = mozilla::Move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE:
+ outCSP.mReport_uri.Construct();
+ outCSP.mReport_uri.Value() = mozilla::Move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE:
+ outCSP.mFrame_ancestors.Construct();
+ outCSP.mFrame_ancestors.Value() = mozilla::Move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE:
+ outCSP.mManifest_src.Construct();
+ outCSP.mManifest_src.Value() = mozilla::Move(srcs);
+ return;
+ // not supporting REFLECTED_XSS_DIRECTIVE
+
+ case nsIContentSecurityPolicy::BASE_URI_DIRECTIVE:
+ outCSP.mBase_uri.Construct();
+ outCSP.mBase_uri.Value() = mozilla::Move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE:
+ outCSP.mForm_action.Construct();
+ outCSP.mForm_action.Value() = mozilla::Move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT:
+ outCSP.mBlock_all_mixed_content.Construct();
+ // does not have any srcs
+ return;
+
+ case nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE:
+ outCSP.mUpgrade_insecure_requests.Construct();
+ // does not have any srcs
+ return;
+
+ case nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE:
+ outCSP.mChild_src.Construct();
+ outCSP.mChild_src.Value() = mozilla::Move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::SANDBOX_DIRECTIVE:
+ outCSP.mSandbox.Construct();
+ outCSP.mSandbox.Value() = mozilla::Move(srcs);
+ return;
+
+ // REFERRER_DIRECTIVE and REQUIRE_SRI_FOR are handled in nsCSPPolicy::toDomCSPStruct()
+
+ default:
+ NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
+ }
+}
+
+
+bool
+nsCSPDirective::restrictsContentType(nsContentPolicyType aContentType) const
+{
+ // make sure we do not check for the default src before any other sources
+ if (isDefaultDirective()) {
+ return false;
+ }
+ return mDirective == CSP_ContentTypeToDirective(aContentType);
+}
+
+void
+nsCSPDirective::getReportURIs(nsTArray<nsString> &outReportURIs) const
+{
+ NS_ASSERTION((mDirective == nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE), "not a report-uri directive");
+
+ // append uris
+ nsString tmpReportURI;
+ for (uint32_t i = 0; i < mSrcs.Length(); i++) {
+ tmpReportURI.Truncate();
+ mSrcs[i]->toString(tmpReportURI);
+ outReportURIs.AppendElement(tmpReportURI);
+ }
+}
+
+bool
+nsCSPDirective::visitSrcs(nsCSPSrcVisitor* aVisitor) const
+{
+ for (uint32_t i = 0; i < mSrcs.Length(); i++) {
+ if (!mSrcs[i]->visit(aVisitor)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool nsCSPDirective::equals(CSPDirective aDirective) const
+{
+ return (mDirective == aDirective);
+}
+
+/* =============== nsCSPChildSrcDirective ============= */
+
+nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective)
+ : nsCSPDirective(aDirective)
+ , mHandleFrameSrc(false)
+{
+}
+
+nsCSPChildSrcDirective::~nsCSPChildSrcDirective()
+{
+}
+
+void nsCSPChildSrcDirective::setHandleFrameSrc()
+{
+ mHandleFrameSrc = true;
+}
+
+bool nsCSPChildSrcDirective::restrictsContentType(nsContentPolicyType aContentType) const
+{
+ if (aContentType == nsIContentPolicy::TYPE_SUBDOCUMENT) {
+ return mHandleFrameSrc;
+ }
+
+ return (aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER
+ || aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER
+ || aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER
+ );
+}
+
+bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const
+{
+ if (aDirective == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) {
+ return mHandleFrameSrc;
+ }
+
+ return (aDirective == nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE);
+}
+
+/* =============== nsBlockAllMixedContentDirective ============= */
+
+nsBlockAllMixedContentDirective::nsBlockAllMixedContentDirective(CSPDirective aDirective)
+: nsCSPDirective(aDirective)
+{
+}
+
+nsBlockAllMixedContentDirective::~nsBlockAllMixedContentDirective()
+{
+}
+
+void
+nsBlockAllMixedContentDirective::toString(nsAString& outStr) const
+{
+ outStr.AppendASCII(CSP_CSPDirectiveToString(
+ nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
+}
+
+/* =============== nsUpgradeInsecureDirective ============= */
+
+nsUpgradeInsecureDirective::nsUpgradeInsecureDirective(CSPDirective aDirective)
+: nsCSPDirective(aDirective)
+{
+}
+
+nsUpgradeInsecureDirective::~nsUpgradeInsecureDirective()
+{
+}
+
+void
+nsUpgradeInsecureDirective::toString(nsAString& outStr) const
+{
+ outStr.AppendASCII(CSP_CSPDirectiveToString(
+ nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
+}
+
+/* ===== nsRequireSRIForDirective ========================= */
+
+nsRequireSRIForDirective::nsRequireSRIForDirective(CSPDirective aDirective)
+: nsCSPDirective(aDirective)
+{
+}
+
+nsRequireSRIForDirective::~nsRequireSRIForDirective()
+{
+}
+
+void
+nsRequireSRIForDirective::toString(nsAString &outStr) const
+{
+ outStr.AppendASCII(CSP_CSPDirectiveToString(
+ nsIContentSecurityPolicy::REQUIRE_SRI_FOR));
+ for (uint32_t i = 0; i < mTypes.Length(); i++) {
+ if (mTypes[i] == nsIContentPolicy::TYPE_SCRIPT) {
+ outStr.AppendASCII(" script");
+ }
+ else if (mTypes[i] == nsIContentPolicy::TYPE_STYLESHEET) {
+ outStr.AppendASCII(" style");
+ }
+ }
+}
+
+bool
+nsRequireSRIForDirective::hasType(nsContentPolicyType aType) const
+{
+ for (uint32_t i = 0; i < mTypes.Length(); i++) {
+ if (mTypes[i] == aType) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+nsRequireSRIForDirective::restrictsContentType(const nsContentPolicyType aType) const
+{
+ return this->hasType(aType);
+}
+
+bool
+nsRequireSRIForDirective::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+ bool aParserCreated) const
+{
+ // can only disallow CSP_REQUIRE_SRI_FOR.
+ return (aKeyword != CSP_REQUIRE_SRI_FOR);
+}
+
+/* ===== nsCSPPolicy ========================= */
+
+nsCSPPolicy::nsCSPPolicy()
+ : mUpgradeInsecDir(nullptr)
+ , mReportOnly(false)
+{
+ CSPUTILSLOG(("nsCSPPolicy::nsCSPPolicy"));
+}
+
+nsCSPPolicy::~nsCSPPolicy()
+{
+ CSPUTILSLOG(("nsCSPPolicy::~nsCSPPolicy"));
+
+ for (uint32_t i = 0; i < mDirectives.Length(); i++) {
+ delete mDirectives[i];
+ }
+}
+
+bool
+nsCSPPolicy::permits(CSPDirective aDir,
+ nsIURI* aUri,
+ bool aSpecific) const
+{
+ nsString outp;
+ return this->permits(aDir, aUri, EmptyString(), false, aSpecific, false, outp);
+}
+
+bool
+nsCSPPolicy::permits(CSPDirective aDir,
+ nsIURI* aUri,
+ const nsAString& aNonce,
+ bool aWasRedirected,
+ bool aSpecific,
+ bool aParserCreated,
+ nsAString& outViolatedDirective) const
+{
+ if (CSPUTILSLOGENABLED()) {
+ CSPUTILSLOG(("nsCSPPolicy::permits, aUri: %s, aDir: %d, aSpecific: %s",
+ aUri->GetSpecOrDefault().get(), aDir,
+ aSpecific ? "true" : "false"));
+ }
+
+ NS_ASSERTION(aUri, "permits needs an uri to perform the check!");
+ outViolatedDirective.Truncate();
+
+ nsCSPDirective* defaultDir = nullptr;
+
+ // Try to find a relevant directive
+ // These directive arrays are short (1-5 elements), not worth using a hashtable.
+ for (uint32_t i = 0; i < mDirectives.Length(); i++) {
+ if (mDirectives[i]->equals(aDir)) {
+ if (!mDirectives[i]->permits(aUri, aNonce, aWasRedirected, mReportOnly,
+ mUpgradeInsecDir, aParserCreated)) {
+ mDirectives[i]->toString(outViolatedDirective);
+ return false;
+ }
+ return true;
+ }
+ if (mDirectives[i]->isDefaultDirective()) {
+ defaultDir = mDirectives[i];
+ }
+ }
+
+ // If the above loop runs through, we haven't found a matching directive.
+ // Avoid relooping, just store the result of default-src while looping.
+ if (!aSpecific && defaultDir) {
+ if (!defaultDir->permits(aUri, aNonce, aWasRedirected, mReportOnly,
+ mUpgradeInsecDir, aParserCreated)) {
+ defaultDir->toString(outViolatedDirective);
+ return false;
+ }
+ return true;
+ }
+
+ // Nothing restricts this, so we're allowing the load
+ // See bug 764937
+ return true;
+}
+
+bool
+nsCSPPolicy::allows(nsContentPolicyType aContentType,
+ enum CSPKeyword aKeyword,
+ const nsAString& aHashOrNonce,
+ bool aParserCreated) const
+{
+ CSPUTILSLOG(("nsCSPPolicy::allows, aKeyWord: %s, a HashOrNonce: %s",
+ CSP_EnumToKeyword(aKeyword), NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
+
+ nsCSPDirective* defaultDir = nullptr;
+
+ // Try to find a matching directive
+ for (uint32_t i = 0; i < mDirectives.Length(); i++) {
+ if (mDirectives[i]->restrictsContentType(aContentType)) {
+ if (mDirectives[i]->allows(aKeyword, aHashOrNonce, aParserCreated)) {
+ return true;
+ }
+ return false;
+ }
+ if (mDirectives[i]->isDefaultDirective()) {
+ defaultDir = mDirectives[i];
+ }
+ }
+
+ // {nonce,hash}-source should not consult default-src:
+ // * return false if default-src is specified
+ // * but allow the load if default-src is *not* specified (Bug 1198422)
+ if (aKeyword == CSP_NONCE || aKeyword == CSP_HASH) {
+ if (!defaultDir) {
+ return true;
+ }
+ return false;
+ }
+
+ // If the above loop runs through, we haven't found a matching directive.
+ // Avoid relooping, just store the result of default-src while looping.
+ if (defaultDir) {
+ return defaultDir->allows(aKeyword, aHashOrNonce, aParserCreated);
+ }
+
+ // Allowing the load; see Bug 885433
+ // a) inline scripts (also unsafe eval) should only be blocked
+ // if there is a [script-src] or [default-src]
+ // b) inline styles should only be blocked
+ // if there is a [style-src] or [default-src]
+ return true;
+}
+
+bool
+nsCSPPolicy::allows(nsContentPolicyType aContentType,
+ enum CSPKeyword aKeyword) const
+{
+ return allows(aContentType, aKeyword, NS_LITERAL_STRING(""), false);
+}
+
+void
+nsCSPPolicy::toString(nsAString& outStr) const
+{
+ uint32_t length = mDirectives.Length();
+ for (uint32_t i = 0; i < length; ++i) {
+
+ if (mDirectives[i]->equals(nsIContentSecurityPolicy::REFERRER_DIRECTIVE)) {
+ outStr.AppendASCII(CSP_CSPDirectiveToString(nsIContentSecurityPolicy::REFERRER_DIRECTIVE));
+ outStr.AppendASCII(" ");
+ outStr.Append(mReferrerPolicy);
+ } else {
+ mDirectives[i]->toString(outStr);
+ }
+ if (i != (length - 1)) {
+ outStr.AppendASCII("; ");
+ }
+ }
+}
+
+void
+nsCSPPolicy::toDomCSPStruct(mozilla::dom::CSP& outCSP) const
+{
+ outCSP.mReport_only = mReportOnly;
+
+ for (uint32_t i = 0; i < mDirectives.Length(); ++i) {
+ if (mDirectives[i]->equals(nsIContentSecurityPolicy::REFERRER_DIRECTIVE)) {
+ mozilla::dom::Sequence<nsString> srcs;
+ srcs.AppendElement(mReferrerPolicy, mozilla::fallible);
+ outCSP.mReferrer.Construct();
+ outCSP.mReferrer.Value() = srcs;
+ } else {
+ mDirectives[i]->toDomCSPStruct(outCSP);
+ }
+ }
+}
+
+bool
+nsCSPPolicy::hasDirective(CSPDirective aDir) const
+{
+ for (uint32_t i = 0; i < mDirectives.Length(); i++) {
+ if (mDirectives[i]->equals(aDir)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * Use this function only after ::allows() returned 'false'. Most and
+ * foremost it's used to get the violated directive before sending reports.
+ * The parameter outDirective is the equivalent of 'outViolatedDirective'
+ * for the ::permits() function family.
+ */
+void
+nsCSPPolicy::getDirectiveStringForContentType(nsContentPolicyType aContentType,
+ nsAString& outDirective) const
+{
+ nsCSPDirective* defaultDir = nullptr;
+ for (uint32_t i = 0; i < mDirectives.Length(); i++) {
+ if (mDirectives[i]->restrictsContentType(aContentType)) {
+ mDirectives[i]->toString(outDirective);
+ return;
+ }
+ if (mDirectives[i]->isDefaultDirective()) {
+ defaultDir = mDirectives[i];
+ }
+ }
+ // if we haven't found a matching directive yet,
+ // the contentType must be restricted by the default directive
+ if (defaultDir) {
+ defaultDir->toString(outDirective);
+ return;
+ }
+ NS_ASSERTION(false, "Can not query directive string for contentType!");
+ outDirective.AppendASCII("couldNotQueryViolatedDirective");
+}
+
+void
+nsCSPPolicy::getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const
+{
+ for (uint32_t i = 0; i < mDirectives.Length(); i++) {
+ if (mDirectives[i]->equals(aDir)) {
+ mDirectives[i]->toString(outDirective);
+ return;
+ }
+ }
+}
+
+/*
+ * Helper function that returns the underlying bit representation of sandbox
+ * flags. The function returns SANDBOXED_NONE if there are no sandbox
+ * directives.
+ */
+uint32_t
+nsCSPPolicy::getSandboxFlags() const
+{
+ for (uint32_t i = 0; i < mDirectives.Length(); i++) {
+ if (mDirectives[i]->equals(nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) {
+ nsAutoString flags;
+ mDirectives[i]->toString(flags);
+
+ if (flags.IsEmpty()) {
+ return SANDBOX_ALL_FLAGS;
+ }
+
+ nsAttrValue attr;
+ attr.ParseAtomArray(flags);
+
+ return nsContentUtils::ParseSandboxAttributeToFlags(&attr);
+ }
+ }
+
+ return SANDBOXED_NONE;
+}
+
+void
+nsCSPPolicy::getReportURIs(nsTArray<nsString>& outReportURIs) const
+{
+ for (uint32_t i = 0; i < mDirectives.Length(); i++) {
+ if (mDirectives[i]->equals(nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
+ mDirectives[i]->getReportURIs(outReportURIs);
+ return;
+ }
+ }
+}
+
+bool
+nsCSPPolicy::visitDirectiveSrcs(CSPDirective aDir, nsCSPSrcVisitor* aVisitor) const
+{
+ for (uint32_t i = 0; i < mDirectives.Length(); i++) {
+ if (mDirectives[i]->equals(aDir)) {
+ return mDirectives[i]->visitSrcs(aVisitor);
+ }
+ }
+ return false;
+}
+
+bool
+nsCSPPolicy::requireSRIForType(nsContentPolicyType aContentType)
+{
+ for (uint32_t i = 0; i < mDirectives.Length(); i++) {
+ if (mDirectives[i]->equals(nsIContentSecurityPolicy::REQUIRE_SRI_FOR)) {
+ return static_cast<nsRequireSRIForDirective*>(mDirectives[i])->hasType(aContentType);
+ }
+ }
+ return false;
+}
diff --git a/dom/security/nsCSPUtils.h b/dom/security/nsCSPUtils.h
new file mode 100644
index 000000000..b33c8932a
--- /dev/null
+++ b/dom/security/nsCSPUtils.h
@@ -0,0 +1,642 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#ifndef nsCSPUtils_h___
+#define nsCSPUtils_h___
+
+#include "nsCOMPtr.h"
+#include "nsIContentPolicy.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+namespace dom {
+ struct CSP;
+} // namespace dom
+} // namespace mozilla
+
+/* =============== Logging =================== */
+
+void CSP_LogLocalizedStr(const char16_t* aName,
+ const char16_t** aParams,
+ uint32_t aLength,
+ const nsAString& aSourceName,
+ const nsAString& aSourceLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aFlags,
+ const char* aCategory,
+ uint64_t aInnerWindowID);
+
+void CSP_GetLocalizedStr(const char16_t* aName,
+ const char16_t** aParams,
+ uint32_t aLength,
+ char16_t** outResult);
+
+void CSP_LogStrMessage(const nsAString& aMsg);
+
+void CSP_LogMessage(const nsAString& aMessage,
+ const nsAString& aSourceName,
+ const nsAString& aSourceLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aFlags,
+ const char* aCategory,
+ uint64_t aInnerWindowID);
+
+
+/* =============== Constant and Type Definitions ================== */
+
+#define INLINE_STYLE_VIOLATION_OBSERVER_TOPIC "violated base restriction: Inline Stylesheets will not apply"
+#define INLINE_SCRIPT_VIOLATION_OBSERVER_TOPIC "violated base restriction: Inline Scripts will not execute"
+#define EVAL_VIOLATION_OBSERVER_TOPIC "violated base restriction: Code will not be created from strings"
+#define SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC "Inline Script had invalid nonce"
+#define STYLE_NONCE_VIOLATION_OBSERVER_TOPIC "Inline Style had invalid nonce"
+#define SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC "Inline Script had invalid hash"
+#define STYLE_HASH_VIOLATION_OBSERVER_TOPIC "Inline Style had invalid hash"
+#define REQUIRE_SRI_SCRIPT_VIOLATION_OBSERVER_TOPIC "Missing required Subresource Integrity for Script"
+#define REQUIRE_SRI_STYLE_VIOLATION_OBSERVER_TOPIC "Missing required Subresource Integrity for Style"
+
+// these strings map to the CSPDirectives in nsIContentSecurityPolicy
+// NOTE: When implementing a new directive, you will need to add it here but also
+// add a corresponding entry to the constants in nsIContentSecurityPolicy.idl
+// and also create an entry for the new directive in
+// nsCSPDirective::toDomCSPStruct() and add it to CSPDictionaries.webidl.
+// Order of elements below important! Make sure it matches the order as in
+// nsIContentSecurityPolicy.idl
+static const char* CSPStrDirectives[] = {
+ "-error-", // NO_DIRECTIVE
+ "default-src", // DEFAULT_SRC_DIRECTIVE
+ "script-src", // SCRIPT_SRC_DIRECTIVE
+ "object-src", // OBJECT_SRC_DIRECTIVE
+ "style-src", // STYLE_SRC_DIRECTIVE
+ "img-src", // IMG_SRC_DIRECTIVE
+ "media-src", // MEDIA_SRC_DIRECTIVE
+ "frame-src", // FRAME_SRC_DIRECTIVE
+ "font-src", // FONT_SRC_DIRECTIVE
+ "connect-src", // CONNECT_SRC_DIRECTIVE
+ "report-uri", // REPORT_URI_DIRECTIVE
+ "frame-ancestors", // FRAME_ANCESTORS_DIRECTIVE
+ "reflected-xss", // REFLECTED_XSS_DIRECTIVE
+ "base-uri", // BASE_URI_DIRECTIVE
+ "form-action", // FORM_ACTION_DIRECTIVE
+ "referrer", // REFERRER_DIRECTIVE
+ "manifest-src", // MANIFEST_SRC_DIRECTIVE
+ "upgrade-insecure-requests", // UPGRADE_IF_INSECURE_DIRECTIVE
+ "child-src", // CHILD_SRC_DIRECTIVE
+ "block-all-mixed-content", // BLOCK_ALL_MIXED_CONTENT
+ "require-sri-for", // REQUIRE_SRI_FOR
+ "sandbox" // SANDBOX_DIRECTIVE
+};
+
+inline const char* CSP_CSPDirectiveToString(CSPDirective aDir)
+{
+ return CSPStrDirectives[static_cast<uint32_t>(aDir)];
+}
+
+inline CSPDirective CSP_StringToCSPDirective(const nsAString& aDir)
+{
+ nsString lowerDir = PromiseFlatString(aDir);
+ ToLowerCase(lowerDir);
+
+ uint32_t numDirs = (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]));
+ for (uint32_t i = 1; i < numDirs; i++) {
+ if (lowerDir.EqualsASCII(CSPStrDirectives[i])) {
+ return static_cast<CSPDirective>(i);
+ }
+ }
+ NS_ASSERTION(false, "Can not convert unknown Directive to Integer");
+ return nsIContentSecurityPolicy::NO_DIRECTIVE;
+}
+
+// Please add any new enum items not only to CSPKeyword, but also add
+// a string version for every enum >> using the same index << to
+// CSPStrKeywords underneath.
+enum CSPKeyword {
+ CSP_SELF = 0,
+ CSP_UNSAFE_INLINE,
+ CSP_UNSAFE_EVAL,
+ CSP_NONE,
+ CSP_NONCE,
+ CSP_REQUIRE_SRI_FOR,
+ CSP_STRICT_DYNAMIC,
+ // CSP_LAST_KEYWORD_VALUE always needs to be the last element in the enum
+ // because we use it to calculate the size for the char* array.
+ CSP_LAST_KEYWORD_VALUE,
+ // Putting CSP_HASH after the delimitor, because CSP_HASH is not a valid
+ // keyword (hash uses e.g. sha256, sha512) but we use CSP_HASH internally
+ // to identify allowed hashes in ::allows.
+ CSP_HASH
+ };
+
+static const char* CSPStrKeywords[] = {
+ "'self'", // CSP_SELF = 0
+ "'unsafe-inline'", // CSP_UNSAFE_INLINE
+ "'unsafe-eval'", // CSP_UNSAFE_EVAL
+ "'none'", // CSP_NONE
+ "'nonce-", // CSP_NONCE
+ "require-sri-for", // CSP_REQUIRE_SRI_FOR
+ "'strict-dynamic'" // CSP_STRICT_DYNAMIC
+ // Remember: CSP_HASH is not supposed to be used
+};
+
+inline const char* CSP_EnumToKeyword(enum CSPKeyword aKey)
+{
+ // Make sure all elements in enum CSPKeyword got added to CSPStrKeywords.
+ static_assert((sizeof(CSPStrKeywords) / sizeof(CSPStrKeywords[0]) ==
+ static_cast<uint32_t>(CSP_LAST_KEYWORD_VALUE)),
+ "CSP_LAST_KEYWORD_VALUE does not match length of CSPStrKeywords");
+
+ if (static_cast<uint32_t>(aKey) < static_cast<uint32_t>(CSP_LAST_KEYWORD_VALUE)) {
+ return CSPStrKeywords[static_cast<uint32_t>(aKey)];
+ }
+ return "error: invalid keyword in CSP_EnumToKeyword";
+}
+
+inline CSPKeyword CSP_KeywordToEnum(const nsAString& aKey)
+{
+ nsString lowerKey = PromiseFlatString(aKey);
+ ToLowerCase(lowerKey);
+
+ static_assert(CSP_LAST_KEYWORD_VALUE ==
+ (sizeof(CSPStrKeywords) / sizeof(CSPStrKeywords[0])),
+ "CSP_LAST_KEYWORD_VALUE does not match length of CSPStrKeywords");
+
+ for (uint32_t i = 0; i < CSP_LAST_KEYWORD_VALUE; i++) {
+ if (lowerKey.EqualsASCII(CSPStrKeywords[i])) {
+ return static_cast<CSPKeyword>(i);
+ }
+ }
+ NS_ASSERTION(false, "Can not convert unknown Keyword to Enum");
+ return CSP_LAST_KEYWORD_VALUE;
+}
+
+nsresult CSP_AppendCSPFromHeader(nsIContentSecurityPolicy* aCsp,
+ const nsAString& aHeaderValue,
+ bool aReportOnly);
+
+/* =============== Helpers ================== */
+
+class nsCSPHostSrc;
+
+nsCSPHostSrc* CSP_CreateHostSrcFromURI(nsIURI* aURI);
+bool CSP_IsValidDirective(const nsAString& aDir);
+bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir);
+bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey);
+bool CSP_IsQuotelessKeyword(const nsAString& aKey);
+CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType);
+
+class nsCSPSrcVisitor;
+
+void CSP_PercentDecodeStr(const nsAString& aEncStr, nsAString& outDecStr);
+
+/* =============== nsCSPSrc ================== */
+
+class nsCSPBaseSrc {
+ public:
+ nsCSPBaseSrc();
+ virtual ~nsCSPBaseSrc();
+
+ virtual bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
+ bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const;
+ virtual bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+ bool aParserCreated) const;
+ virtual bool visit(nsCSPSrcVisitor* aVisitor) const = 0;
+ virtual void toString(nsAString& outStr) const = 0;
+
+ virtual void invalidate() const
+ { mInvalidated = true; }
+
+ protected:
+ // invalidate srcs if 'script-dynamic' is present or also invalidate
+ // unsafe-inline' if nonce- or hash-source specified
+ mutable bool mInvalidated;
+
+};
+
+/* =============== nsCSPSchemeSrc ============ */
+
+class nsCSPSchemeSrc : public nsCSPBaseSrc {
+ public:
+ explicit nsCSPSchemeSrc(const nsAString& aScheme);
+ virtual ~nsCSPSchemeSrc();
+
+ bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
+ bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const;
+ bool visit(nsCSPSrcVisitor* aVisitor) const;
+ void toString(nsAString& outStr) const;
+
+ inline void getScheme(nsAString& outStr) const
+ { outStr.Assign(mScheme); };
+
+ private:
+ nsString mScheme;
+};
+
+/* =============== nsCSPHostSrc ============== */
+
+class nsCSPHostSrc : public nsCSPBaseSrc {
+ public:
+ explicit nsCSPHostSrc(const nsAString& aHost);
+ virtual ~nsCSPHostSrc();
+
+ bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
+ bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const;
+ bool visit(nsCSPSrcVisitor* aVisitor) const;
+ void toString(nsAString& outStr) const;
+
+ void setScheme(const nsAString& aScheme);
+ void setPort(const nsAString& aPort);
+ void appendPath(const nsAString &aPath);
+
+ inline void getScheme(nsAString& outStr) const
+ { outStr.Assign(mScheme); };
+
+ inline void getHost(nsAString& outStr) const
+ { outStr.Assign(mHost); };
+
+ inline void getPort(nsAString& outStr) const
+ { outStr.Assign(mPort); };
+
+ inline void getPath(nsAString& outStr) const
+ { outStr.Assign(mPath); };
+
+ private:
+ nsString mScheme;
+ nsString mHost;
+ nsString mPort;
+ nsString mPath;
+};
+
+/* =============== nsCSPKeywordSrc ============ */
+
+class nsCSPKeywordSrc : public nsCSPBaseSrc {
+ public:
+ explicit nsCSPKeywordSrc(CSPKeyword aKeyword);
+ virtual ~nsCSPKeywordSrc();
+
+ bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+ bool aParserCreated) const;
+ bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
+ bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const;
+ bool visit(nsCSPSrcVisitor* aVisitor) const;
+ void toString(nsAString& outStr) const;
+
+ inline CSPKeyword getKeyword() const
+ { return mKeyword; };
+
+ inline void invalidate() const
+ {
+ // keywords that need to invalidated
+ if (mKeyword == CSP_SELF || mKeyword == CSP_UNSAFE_INLINE) {
+ mInvalidated = true;
+ }
+ }
+
+ private:
+ CSPKeyword mKeyword;
+};
+
+/* =============== nsCSPNonceSource =========== */
+
+class nsCSPNonceSrc : public nsCSPBaseSrc {
+ public:
+ explicit nsCSPNonceSrc(const nsAString& aNonce);
+ virtual ~nsCSPNonceSrc();
+
+ bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
+ bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const;
+ bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+ bool aParserCreated) const;
+ bool visit(nsCSPSrcVisitor* aVisitor) const;
+ void toString(nsAString& outStr) const;
+
+ inline void getNonce(nsAString& outStr) const
+ { outStr.Assign(mNonce); };
+
+ inline void invalidate() const
+ {
+ // overwrite nsCSPBaseSRC::invalidate() and explicitily
+ // do *not* invalidate, because 'strict-dynamic' should
+ // not invalidate nonces.
+ }
+
+ private:
+ nsString mNonce;
+};
+
+/* =============== nsCSPHashSource ============ */
+
+class nsCSPHashSrc : public nsCSPBaseSrc {
+ public:
+ nsCSPHashSrc(const nsAString& algo, const nsAString& hash);
+ virtual ~nsCSPHashSrc();
+
+ bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+ bool aParserCreated) const;
+ void toString(nsAString& outStr) const;
+ bool visit(nsCSPSrcVisitor* aVisitor) const;
+
+ inline void getAlgorithm(nsAString& outStr) const
+ { outStr.Assign(mAlgorithm); };
+
+ inline void getHash(nsAString& outStr) const
+ { outStr.Assign(mHash); };
+
+ inline void invalidate() const
+ {
+ // overwrite nsCSPBaseSRC::invalidate() and explicitily
+ // do *not* invalidate, because 'strict-dynamic' should
+ // not invalidate hashes.
+ }
+
+ private:
+ nsString mAlgorithm;
+ nsString mHash;
+};
+
+/* =============== nsCSPReportURI ============ */
+
+class nsCSPReportURI : public nsCSPBaseSrc {
+ public:
+ explicit nsCSPReportURI(nsIURI* aURI);
+ virtual ~nsCSPReportURI();
+
+ bool visit(nsCSPSrcVisitor* aVisitor) const;
+ void toString(nsAString& outStr) const;
+
+ private:
+ nsCOMPtr<nsIURI> mReportURI;
+};
+
+/* =============== nsCSPSandboxFlags ================== */
+
+class nsCSPSandboxFlags : public nsCSPBaseSrc {
+ public:
+ explicit nsCSPSandboxFlags(const nsAString& aFlags);
+ virtual ~nsCSPSandboxFlags();
+
+ bool visit(nsCSPSrcVisitor* aVisitor) const;
+ void toString(nsAString& outStr) const;
+
+ private:
+ nsString mFlags;
+};
+
+/* =============== nsCSPSrcVisitor ================== */
+
+class nsCSPSrcVisitor {
+ public:
+ virtual bool visitSchemeSrc(const nsCSPSchemeSrc& src) = 0;
+
+ virtual bool visitHostSrc(const nsCSPHostSrc& src) = 0;
+
+ virtual bool visitKeywordSrc(const nsCSPKeywordSrc& src) = 0;
+
+ virtual bool visitNonceSrc(const nsCSPNonceSrc& src) = 0;
+
+ virtual bool visitHashSrc(const nsCSPHashSrc& src) = 0;
+
+ protected:
+ explicit nsCSPSrcVisitor() {};
+ virtual ~nsCSPSrcVisitor() {};
+};
+
+/* =============== nsCSPDirective ============= */
+
+class nsCSPDirective {
+ public:
+ explicit nsCSPDirective(CSPDirective aDirective);
+ virtual ~nsCSPDirective();
+
+ virtual bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
+ bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const;
+ virtual bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+ bool aParserCreated) const;
+ virtual void toString(nsAString& outStr) const;
+ void toDomCSPStruct(mozilla::dom::CSP& outCSP) const;
+
+ virtual void addSrcs(const nsTArray<nsCSPBaseSrc*>& aSrcs)
+ { mSrcs = aSrcs; }
+
+ virtual bool restrictsContentType(nsContentPolicyType aContentType) const;
+
+ inline bool isDefaultDirective() const
+ { return mDirective == nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; }
+
+ virtual bool equals(CSPDirective aDirective) const;
+
+ void getReportURIs(nsTArray<nsString> &outReportURIs) const;
+
+ bool visitSrcs(nsCSPSrcVisitor* aVisitor) const;
+
+ private:
+ CSPDirective mDirective;
+ nsTArray<nsCSPBaseSrc*> mSrcs;
+};
+
+/* =============== nsCSPChildSrcDirective ============= */
+
+/*
+ * In CSP 2, the child-src directive covers both workers and
+ * subdocuments (i.e., frames and iframes). Workers were removed
+ * from script-src, but frames can be controlled by either child-src
+ * or frame-src directives, so child-src needs to know whether it should
+ * also restrict frames. When both are present the frame-src directive
+ * takes precedent.
+ */
+class nsCSPChildSrcDirective : public nsCSPDirective {
+ public:
+ explicit nsCSPChildSrcDirective(CSPDirective aDirective);
+ virtual ~nsCSPChildSrcDirective();
+
+ void setHandleFrameSrc();
+
+ virtual bool restrictsContentType(nsContentPolicyType aContentType) const;
+
+ virtual bool equals(CSPDirective aDirective) const;
+
+ private:
+ bool mHandleFrameSrc;
+};
+
+/* =============== nsBlockAllMixedContentDirective === */
+
+class nsBlockAllMixedContentDirective : public nsCSPDirective {
+ public:
+ explicit nsBlockAllMixedContentDirective(CSPDirective aDirective);
+ ~nsBlockAllMixedContentDirective();
+
+ bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
+ bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const
+ { return false; }
+
+ bool permits(nsIURI* aUri) const
+ { return false; }
+
+ bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+ bool aParserCreated) const
+ { return false; }
+
+ void toString(nsAString& outStr) const;
+
+ void addSrcs(const nsTArray<nsCSPBaseSrc*>& aSrcs)
+ { MOZ_ASSERT(false, "block-all-mixed-content does not hold any srcs"); }
+};
+
+/* =============== nsUpgradeInsecureDirective === */
+
+/*
+ * Upgrading insecure requests includes the following actors:
+ * (1) CSP:
+ * The CSP implementation whitelists the http-request
+ * in case the policy is executed in enforcement mode.
+ * The CSP implementation however does not allow http
+ * requests to succeed if executed in report-only mode.
+ * In such a case the CSP implementation reports the
+ * error back to the page.
+ *
+ * (2) MixedContent:
+ * The evalution of MixedContent whitelists all http
+ * requests with the promise that the http requests
+ * gets upgraded to https before any data is fetched
+ * from the network.
+ *
+ * (3) CORS:
+ * Does not consider the http request to be of a
+ * different origin in case the scheme is the only
+ * difference in otherwise matching URIs.
+ *
+ * (4) nsHttpChannel:
+ * Before connecting, the channel gets redirected
+ * to use https.
+ *
+ * (5) WebSocketChannel:
+ * Similar to the httpChannel, the websocketchannel
+ * gets upgraded from ws to wss.
+ */
+class nsUpgradeInsecureDirective : public nsCSPDirective {
+ public:
+ explicit nsUpgradeInsecureDirective(CSPDirective aDirective);
+ ~nsUpgradeInsecureDirective();
+
+ bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
+ bool aReportOnly, bool aUpgradeInsecure, bool aParserCreated) const
+ { return false; }
+
+ bool permits(nsIURI* aUri) const
+ { return false; }
+
+ bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+ bool aParserCreated) const
+ { return false; }
+
+ void toString(nsAString& outStr) const;
+
+ void addSrcs(const nsTArray<nsCSPBaseSrc*>& aSrcs)
+ { MOZ_ASSERT(false, "upgrade-insecure-requests does not hold any srcs"); }
+};
+
+/* ===== nsRequireSRIForDirective ========================= */
+
+class nsRequireSRIForDirective : public nsCSPDirective {
+ public:
+ explicit nsRequireSRIForDirective(CSPDirective aDirective);
+ ~nsRequireSRIForDirective();
+
+ void toString(nsAString& outStr) const;
+
+ void addType(nsContentPolicyType aType)
+ { mTypes.AppendElement(aType); }
+ bool hasType(nsContentPolicyType aType) const;
+ bool restrictsContentType(nsContentPolicyType aType) const;
+ bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce,
+ bool aParserCreated) const;
+
+ private:
+ nsTArray<nsContentPolicyType> mTypes;
+};
+
+/* =============== nsCSPPolicy ================== */
+
+class nsCSPPolicy {
+ public:
+ nsCSPPolicy();
+ virtual ~nsCSPPolicy();
+
+ bool permits(CSPDirective aDirective,
+ nsIURI* aUri,
+ const nsAString& aNonce,
+ bool aWasRedirected,
+ bool aSpecific,
+ bool aParserCreated,
+ nsAString& outViolatedDirective) const;
+ bool permits(CSPDirective aDir,
+ nsIURI* aUri,
+ bool aSpecific) const;
+ bool allows(nsContentPolicyType aContentType,
+ enum CSPKeyword aKeyword,
+ const nsAString& aHashOrNonce,
+ bool aParserCreated) const;
+ bool allows(nsContentPolicyType aContentType,
+ enum CSPKeyword aKeyword) const;
+ void toString(nsAString& outStr) const;
+ void toDomCSPStruct(mozilla::dom::CSP& outCSP) const;
+
+ inline void addDirective(nsCSPDirective* aDir)
+ { mDirectives.AppendElement(aDir); }
+
+ inline void addUpgradeInsecDir(nsUpgradeInsecureDirective* aDir)
+ {
+ mUpgradeInsecDir = aDir;
+ addDirective(aDir);
+ }
+
+ bool hasDirective(CSPDirective aDir) const;
+
+ inline void setReportOnlyFlag(bool aFlag)
+ { mReportOnly = aFlag; }
+
+ inline bool getReportOnlyFlag() const
+ { return mReportOnly; }
+
+ inline void setReferrerPolicy(const nsAString* aValue)
+ {
+ mReferrerPolicy = *aValue;
+ ToLowerCase(mReferrerPolicy);
+ }
+
+ inline void getReferrerPolicy(nsAString& outPolicy) const
+ { outPolicy.Assign(mReferrerPolicy); }
+
+ void getReportURIs(nsTArray<nsString> &outReportURIs) const;
+
+ void getDirectiveStringForContentType(nsContentPolicyType aContentType,
+ nsAString& outDirective) const;
+
+ void getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const;
+
+ uint32_t getSandboxFlags() const;
+
+ bool requireSRIForType(nsContentPolicyType aContentType);
+
+ inline uint32_t getNumDirectives() const
+ { return mDirectives.Length(); }
+
+ bool visitDirectiveSrcs(CSPDirective aDir, nsCSPSrcVisitor* aVisitor) const;
+
+ private:
+ nsUpgradeInsecureDirective* mUpgradeInsecDir;
+ nsTArray<nsCSPDirective*> mDirectives;
+ bool mReportOnly;
+ nsString mReferrerPolicy;
+};
+
+#endif /* nsCSPUtils_h___ */
diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp
new file mode 100644
index 000000000..c4e1ed8e1
--- /dev/null
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -0,0 +1,695 @@
+#include "nsContentSecurityManager.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIStreamListener.h"
+#include "nsILoadInfo.h"
+#include "nsContentUtils.h"
+#include "nsCORSListenerProxy.h"
+#include "nsIStreamListener.h"
+#include "nsIDocument.h"
+#include "nsMixedContentBlocker.h"
+
+#include "mozilla/dom/Element.h"
+
+NS_IMPL_ISUPPORTS(nsContentSecurityManager,
+ nsIContentSecurityManager,
+ nsIChannelEventSink)
+
+static nsresult
+ValidateSecurityFlags(nsILoadInfo* aLoadInfo)
+{
+ nsSecurityFlags securityMode = aLoadInfo->GetSecurityMode();
+
+ if (securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS &&
+ securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED &&
+ securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS &&
+ securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
+ securityMode != nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) {
+ MOZ_ASSERT(false, "need one securityflag from nsILoadInfo to perform security checks");
+ return NS_ERROR_FAILURE;
+ }
+
+ // all good, found the right security flags
+ return NS_OK;
+}
+
+static bool SchemeIs(nsIURI* aURI, const char* aScheme)
+{
+ nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI);
+ NS_ENSURE_TRUE(baseURI, false);
+
+ bool isScheme = false;
+ return NS_SUCCEEDED(baseURI->SchemeIs(aScheme, &isScheme)) && isScheme;
+}
+
+
+static bool IsImageLoadInEditorAppType(nsILoadInfo* aLoadInfo)
+{
+ // Editor apps get special treatment here, editors can load images
+ // from anywhere. This allows editor to insert images from file://
+ // into documents that are being edited.
+ nsContentPolicyType type = aLoadInfo->InternalContentPolicyType();
+ if (type != nsIContentPolicy::TYPE_INTERNAL_IMAGE &&
+ type != nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD &&
+ type != nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON &&
+ type != nsIContentPolicy::TYPE_IMAGESET) {
+ return false;
+ }
+
+ uint32_t appType = nsIDocShell::APP_TYPE_UNKNOWN;
+ nsINode* node = aLoadInfo->LoadingNode();
+ if (!node) {
+ return false;
+ }
+ nsIDocument* doc = node->OwnerDoc();
+ if (!doc) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = doc->GetDocShell();
+ if (!docShellTreeItem) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ docShellTreeItem->GetRootTreeItem(getter_AddRefs(root));
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(root));
+ if (!docShell || NS_FAILED(docShell->GetAppType(&appType))) {
+ appType = nsIDocShell::APP_TYPE_UNKNOWN;
+ }
+
+ return appType == nsIDocShell::APP_TYPE_EDITOR;
+}
+
+static nsresult
+DoCheckLoadURIChecks(nsIURI* aURI, nsILoadInfo* aLoadInfo)
+{
+ // Bug 1228117: determine the correct security policy for DTD loads
+ if (aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DTD) {
+ return NS_OK;
+ }
+
+ if (IsImageLoadInEditorAppType(aLoadInfo)) {
+ return NS_OK;
+ }
+
+ uint32_t flags = nsIScriptSecurityManager::STANDARD;
+ if (aLoadInfo->GetAllowChrome()) {
+ flags |= nsIScriptSecurityManager::ALLOW_CHROME;
+ }
+ if (aLoadInfo->GetDisallowScript()) {
+ flags |= nsIScriptSecurityManager::DISALLOW_SCRIPT;
+ }
+
+ // Only call CheckLoadURIWithPrincipal() using the TriggeringPrincipal and not
+ // the LoadingPrincipal when SEC_ALLOW_CROSS_ORIGIN_* security flags are set,
+ // to allow, e.g. user stylesheets to load chrome:// URIs.
+ return nsContentUtils::GetSecurityManager()->
+ CheckLoadURIWithPrincipal(aLoadInfo->TriggeringPrincipal(),
+ aURI,
+ flags);
+}
+
+static bool
+URIHasFlags(nsIURI* aURI, uint32_t aURIFlags)
+{
+ bool hasFlags;
+ nsresult rv = NS_URIChainHasFlags(aURI, aURIFlags, &hasFlags);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return hasFlags;
+}
+
+static nsresult
+DoSOPChecks(nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel* aChannel)
+{
+ if (aLoadInfo->GetAllowChrome() &&
+ (URIHasFlags(aURI, nsIProtocolHandler::URI_IS_UI_RESOURCE) ||
+ SchemeIs(aURI, "moz-safe-about"))) {
+ // UI resources are allowed.
+ return DoCheckLoadURIChecks(aURI, aLoadInfo);
+ }
+
+ NS_ENSURE_FALSE(NS_HasBeenCrossOrigin(aChannel, true),
+ NS_ERROR_DOM_BAD_URI);
+
+ return NS_OK;
+}
+
+static nsresult
+DoCORSChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo,
+ nsCOMPtr<nsIStreamListener>& aInAndOutListener)
+{
+ MOZ_RELEASE_ASSERT(aInAndOutListener, "can not perform CORS checks without a listener");
+
+ // No need to set up CORS if TriggeringPrincipal is the SystemPrincipal.
+ // For example, allow user stylesheets to load XBL from external files
+ // without requiring CORS.
+ if (nsContentUtils::IsSystemPrincipal(aLoadInfo->TriggeringPrincipal())) {
+ return NS_OK;
+ }
+
+ nsIPrincipal* loadingPrincipal = aLoadInfo->LoadingPrincipal();
+ RefPtr<nsCORSListenerProxy> corsListener =
+ new nsCORSListenerProxy(aInAndOutListener,
+ loadingPrincipal,
+ aLoadInfo->GetCookiePolicy() ==
+ nsILoadInfo::SEC_COOKIES_INCLUDE);
+ // XXX: @arg: DataURIHandling::Allow
+ // lets use DataURIHandling::Allow for now and then decide on callsite basis. see also:
+ // http://mxr.mozilla.org/mozilla-central/source/dom/security/nsCORSListenerProxy.h#33
+ nsresult rv = corsListener->Init(aChannel, DataURIHandling::Allow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aInAndOutListener = corsListener;
+ return NS_OK;
+}
+
+static nsresult
+DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsContentPolicyType contentPolicyType =
+ aLoadInfo->GetExternalContentPolicyType();
+ nsContentPolicyType internalContentPolicyType =
+ aLoadInfo->InternalContentPolicyType();
+ nsCString mimeTypeGuess;
+ nsCOMPtr<nsINode> requestingContext = nullptr;
+
+#ifdef DEBUG
+ // Don't enforce TYPE_DOCUMENT assertions for loads
+ // initiated by javascript tests.
+ bool skipContentTypeCheck = false;
+ skipContentTypeCheck = Preferences::GetBool("network.loadinfo.skip_type_assertion");
+#endif
+
+ switch(contentPolicyType) {
+ case nsIContentPolicy::TYPE_OTHER: {
+ mimeTypeGuess = EmptyCString();
+ requestingContext = aLoadInfo->LoadingNode();
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_SCRIPT: {
+ mimeTypeGuess = NS_LITERAL_CSTRING("application/javascript");
+ requestingContext = aLoadInfo->LoadingNode();
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_IMAGE: {
+ mimeTypeGuess = EmptyCString();
+ requestingContext = aLoadInfo->LoadingNode();
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_STYLESHEET: {
+ mimeTypeGuess = NS_LITERAL_CSTRING("text/css");
+ requestingContext = aLoadInfo->LoadingNode();
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_OBJECT: {
+ mimeTypeGuess = EmptyCString();
+ requestingContext = aLoadInfo->LoadingNode();
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_DOCUMENT: {
+ MOZ_ASSERT(skipContentTypeCheck || false, "contentPolicyType not supported yet");
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_SUBDOCUMENT: {
+ mimeTypeGuess = NS_LITERAL_CSTRING("text/html");
+ requestingContext = aLoadInfo->LoadingNode();
+ MOZ_ASSERT(!requestingContext ||
+ requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE,
+ "type_subdocument requires requestingContext of type Document");
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_REFRESH: {
+ MOZ_ASSERT(false, "contentPolicyType not supported yet");
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_XBL: {
+ mimeTypeGuess = EmptyCString();
+ requestingContext = aLoadInfo->LoadingNode();
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_PING: {
+ mimeTypeGuess = EmptyCString();
+ requestingContext = aLoadInfo->LoadingNode();
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_XMLHTTPREQUEST: {
+ // alias nsIContentPolicy::TYPE_DATAREQUEST:
+ requestingContext = aLoadInfo->LoadingNode();
+ MOZ_ASSERT(!requestingContext ||
+ requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE,
+ "type_xml requires requestingContext of type Document");
+
+ // We're checking for the external TYPE_XMLHTTPREQUEST here in case
+ // an addon creates a request with that type.
+ if (internalContentPolicyType ==
+ nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST ||
+ internalContentPolicyType ==
+ nsIContentPolicy::TYPE_XMLHTTPREQUEST) {
+ mimeTypeGuess = EmptyCString();
+ }
+ else {
+ MOZ_ASSERT(internalContentPolicyType ==
+ nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE,
+ "can not set mime type guess for unexpected internal type");
+ mimeTypeGuess = NS_LITERAL_CSTRING(TEXT_EVENT_STREAM);
+ }
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST: {
+ mimeTypeGuess = EmptyCString();
+ requestingContext = aLoadInfo->LoadingNode();
+ MOZ_ASSERT(!requestingContext ||
+ requestingContext->NodeType() == nsIDOMNode::ELEMENT_NODE,
+ "type_subrequest requires requestingContext of type Element");
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_DTD: {
+ mimeTypeGuess = EmptyCString();
+ requestingContext = aLoadInfo->LoadingNode();
+ MOZ_ASSERT(!requestingContext ||
+ requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE,
+ "type_dtd requires requestingContext of type Document");
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_FONT: {
+ mimeTypeGuess = EmptyCString();
+ requestingContext = aLoadInfo->LoadingNode();
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_MEDIA: {
+ if (internalContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_TRACK) {
+ mimeTypeGuess = NS_LITERAL_CSTRING("text/vtt");
+ }
+ else {
+ mimeTypeGuess = EmptyCString();
+ }
+ requestingContext = aLoadInfo->LoadingNode();
+ MOZ_ASSERT(!requestingContext ||
+ requestingContext->NodeType() == nsIDOMNode::ELEMENT_NODE,
+ "type_media requires requestingContext of type Element");
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_WEBSOCKET: {
+ // Websockets have to use the proxied URI:
+ // ws:// instead of http:// for CSP checks
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal
+ = do_QueryInterface(aChannel);
+ MOZ_ASSERT(httpChannelInternal);
+ if (httpChannelInternal) {
+ httpChannelInternal->GetProxyURI(getter_AddRefs(uri));
+ }
+ mimeTypeGuess = EmptyCString();
+ requestingContext = aLoadInfo->LoadingNode();
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_CSP_REPORT: {
+ mimeTypeGuess = EmptyCString();
+ requestingContext = aLoadInfo->LoadingNode();
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_XSLT: {
+ mimeTypeGuess = NS_LITERAL_CSTRING("application/xml");
+ requestingContext = aLoadInfo->LoadingNode();
+ MOZ_ASSERT(!requestingContext ||
+ requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE,
+ "type_xslt requires requestingContext of type Document");
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_BEACON: {
+ mimeTypeGuess = EmptyCString();
+ requestingContext = aLoadInfo->LoadingNode();
+ MOZ_ASSERT(!requestingContext ||
+ requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE,
+ "type_beacon requires requestingContext of type Document");
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_FETCH: {
+ mimeTypeGuess = EmptyCString();
+ requestingContext = aLoadInfo->LoadingNode();
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_IMAGESET: {
+ mimeTypeGuess = EmptyCString();
+ requestingContext = aLoadInfo->LoadingNode();
+ break;
+ }
+
+ case nsIContentPolicy::TYPE_WEB_MANIFEST: {
+ mimeTypeGuess = NS_LITERAL_CSTRING("application/manifest+json");
+ requestingContext = aLoadInfo->LoadingNode();
+ break;
+ }
+
+ default:
+ // nsIContentPolicy::TYPE_INVALID
+ MOZ_ASSERT(false, "can not perform security check without a valid contentType");
+ }
+
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ rv = NS_CheckContentLoadPolicy(internalContentPolicyType,
+ uri,
+ aLoadInfo->LoadingPrincipal(),
+ requestingContext,
+ mimeTypeGuess,
+ nullptr, //extra,
+ &shouldLoad,
+ nsContentUtils::GetContentPolicy(),
+ nsContentUtils::GetSecurityManager());
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (NS_CP_REJECTED(shouldLoad)) {
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+
+ if (nsMixedContentBlocker::sSendHSTSPriming) {
+ rv = nsMixedContentBlocker::MarkLoadInfoForPriming(uri,
+ requestingContext,
+ aLoadInfo);
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+/*
+ * Based on the security flags provided in the loadInfo of the channel,
+ * doContentSecurityCheck() performs the following content security checks
+ * before opening the channel:
+ *
+ * (1) Same Origin Policy Check (if applicable)
+ * (2) Allow Cross Origin but perform sanity checks whether a principal
+ * is allowed to access the following URL.
+ * (3) Perform CORS check (if applicable)
+ * (4) ContentPolicy checks (Content-Security-Policy, Mixed Content, ...)
+ *
+ * @param aChannel
+ * The channel to perform the security checks on.
+ * @param aInAndOutListener
+ * The streamListener that is passed to channel->AsyncOpen2() that is now potentially
+ * wrappend within nsCORSListenerProxy() and becomes the corsListener that now needs
+ * to be set as new streamListener on the channel.
+ */
+nsresult
+nsContentSecurityManager::doContentSecurityCheck(nsIChannel* aChannel,
+ nsCOMPtr<nsIStreamListener>& aInAndOutListener)
+{
+ NS_ENSURE_ARG(aChannel);
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+
+ if (!loadInfo) {
+ MOZ_ASSERT(false, "channel needs to have loadInfo to perform security checks");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // if dealing with a redirected channel then we have already installed
+ // streamlistener and redirect proxies and so we are done.
+ if (loadInfo->GetInitialSecurityCheckDone()) {
+ return NS_OK;
+ }
+
+ // make sure that only one of the five security flags is set in the loadinfo
+ // e.g. do not require same origin and allow cross origin at the same time
+ nsresult rv = ValidateSecurityFlags(loadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // since aChannel was openend using asyncOpen2() we have to make sure
+ // that redirects of that channel also get openend using asyncOpen2()
+ // please note that some implementations of ::AsyncOpen2 might already
+ // have set that flag to true (e.g. nsViewSourceChannel) in which case
+ // we just set the flag again.
+ loadInfo->SetEnforceSecurity(true);
+
+ if (loadInfo->GetSecurityMode() == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) {
+ rv = DoCORSChecks(aChannel, loadInfo, aInAndOutListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = CheckChannel(aChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Perform all ContentPolicy checks (MixedContent, CSP, ...)
+ rv = DoContentSecurityChecks(aChannel, loadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // now lets set the initalSecurityFlag for subsequent calls
+ loadInfo->SetInitialSecurityCheckDone(true);
+
+ // all security checks passed - lets allow the load
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentSecurityManager::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aRedirFlags,
+ nsIAsyncVerifyRedirectCallback *aCb)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo = aOldChannel->GetLoadInfo();
+ // Are we enforcing security using LoadInfo?
+ if (loadInfo && loadInfo->GetEnforceSecurity()) {
+ nsresult rv = CheckChannel(aNewChannel);
+ if (NS_FAILED(rv)) {
+ aOldChannel->Cancel(rv);
+ return rv;
+ }
+ }
+
+ // Also verify that the redirecting server is allowed to redirect to the
+ // given URI
+ nsCOMPtr<nsIPrincipal> oldPrincipal;
+ nsContentUtils::GetSecurityManager()->
+ GetChannelResultPrincipal(aOldChannel, getter_AddRefs(oldPrincipal));
+
+ nsCOMPtr<nsIURI> newURI;
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+ nsCOMPtr<nsIURI> newOriginalURI;
+ aNewChannel->GetOriginalURI(getter_AddRefs(newOriginalURI));
+
+ NS_ENSURE_STATE(oldPrincipal && newURI && newOriginalURI);
+
+ const uint32_t flags =
+ nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT |
+ nsIScriptSecurityManager::DISALLOW_SCRIPT;
+ nsresult rv = nsContentUtils::GetSecurityManager()->
+ CheckLoadURIWithPrincipal(oldPrincipal, newURI, flags);
+ if (NS_SUCCEEDED(rv) && newOriginalURI != newURI) {
+ rv = nsContentUtils::GetSecurityManager()->
+ CheckLoadURIWithPrincipal(oldPrincipal, newOriginalURI, flags);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aCb->OnRedirectVerifyCallback(NS_OK);
+ return NS_OK;
+}
+
+static void
+AddLoadFlags(nsIRequest *aRequest, nsLoadFlags aNewFlags)
+{
+ nsLoadFlags flags;
+ aRequest->GetLoadFlags(&flags);
+ flags |= aNewFlags;
+ aRequest->SetLoadFlags(flags);
+}
+
+/*
+ * Check that this channel passes all security checks. Returns an error code
+ * if this requesst should not be permitted.
+ */
+nsresult
+nsContentSecurityManager::CheckChannel(nsIChannel* aChannel)
+{
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+ MOZ_ASSERT(loadInfo);
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Handle cookie policies
+ uint32_t cookiePolicy = loadInfo->GetCookiePolicy();
+ if (cookiePolicy == nsILoadInfo::SEC_COOKIES_SAME_ORIGIN) {
+
+ // We shouldn't have the SEC_COOKIES_SAME_ORIGIN flag for top level loads
+ MOZ_ASSERT(loadInfo->GetExternalContentPolicyType() !=
+ nsIContentPolicy::TYPE_DOCUMENT);
+ nsIPrincipal* loadingPrincipal = loadInfo->LoadingPrincipal();
+
+ // It doesn't matter what we pass for the third, data-inherits, argument.
+ // Any protocol which inherits won't pay attention to cookies anyway.
+ rv = loadingPrincipal->CheckMayLoad(uri, false, false);
+ if (NS_FAILED(rv)) {
+ AddLoadFlags(aChannel, nsIRequest::LOAD_ANONYMOUS);
+ }
+ }
+ else if (cookiePolicy == nsILoadInfo::SEC_COOKIES_OMIT) {
+ AddLoadFlags(aChannel, nsIRequest::LOAD_ANONYMOUS);
+ }
+
+ nsSecurityFlags securityMode = loadInfo->GetSecurityMode();
+
+ // CORS mode is handled by nsCORSListenerProxy
+ if (securityMode == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) {
+ if (NS_HasBeenCrossOrigin(aChannel)) {
+ loadInfo->MaybeIncreaseTainting(LoadTainting::CORS);
+ }
+ return NS_OK;
+ }
+
+ // Allow subresource loads if TriggeringPrincipal is the SystemPrincipal.
+ // For example, allow user stylesheets to load XBL from external files.
+ if (nsContentUtils::IsSystemPrincipal(loadInfo->TriggeringPrincipal()) &&
+ loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT &&
+ loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_SUBDOCUMENT) {
+ return NS_OK;
+ }
+
+ // if none of the REQUIRE_SAME_ORIGIN flags are set, then SOP does not apply
+ if ((securityMode == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS) ||
+ (securityMode == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED)) {
+ rv = DoSOPChecks(uri, loadInfo, aChannel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if ((securityMode == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS) ||
+ (securityMode == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL)) {
+ if (NS_HasBeenCrossOrigin(aChannel)) {
+ loadInfo->MaybeIncreaseTainting(LoadTainting::Opaque);
+ }
+ // Please note that DoCheckLoadURIChecks should only be enforced for
+ // cross origin requests. If the flag SEC_REQUIRE_CORS_DATA_INHERITS is set
+ // within the loadInfo, then then CheckLoadURIWithPrincipal is performed
+ // within nsCorsListenerProxy
+ rv = DoCheckLoadURIChecks(uri, loadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+// ==== nsIContentSecurityManager implementation =====
+
+NS_IMETHODIMP
+nsContentSecurityManager::PerformSecurityCheck(nsIChannel* aChannel,
+ nsIStreamListener* aStreamListener,
+ nsIStreamListener** outStreamListener)
+{
+ nsCOMPtr<nsIStreamListener> inAndOutListener = aStreamListener;
+ nsresult rv = doContentSecurityCheck(aChannel, inAndOutListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ inAndOutListener.forget(outStreamListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentSecurityManager::IsOriginPotentiallyTrustworthy(nsIPrincipal* aPrincipal,
+ bool* aIsTrustWorthy)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aIsTrustWorthy);
+
+ if (aPrincipal->GetIsSystemPrincipal()) {
+ *aIsTrustWorthy = true;
+ return NS_OK;
+ }
+
+ // The following implements:
+ // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
+
+ *aIsTrustWorthy = false;
+
+ if (aPrincipal->GetIsNullPrincipal()) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aPrincipal->GetIsCodebasePrincipal(),
+ "Nobody is expected to call us with an nsIExpandedPrincipal");
+
+ nsCOMPtr<nsIURI> uri;
+ aPrincipal->GetURI(getter_AddRefs(uri));
+
+ nsAutoCString scheme;
+ nsresult rv = uri->GetScheme(scheme);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ // Blobs are expected to inherit their principal so we don't expect to have
+ // a codebase principal with scheme 'blob' here. We can't assert that though
+ // since someone could mess with a non-blob URI to give it that scheme.
+ NS_WARNING_ASSERTION(!scheme.EqualsLiteral("blob"),
+ "IsOriginPotentiallyTrustworthy ignoring blob scheme");
+
+ // According to the specification, the user agent may choose to extend the
+ // trust to other, vendor-specific URL schemes. We use this for "resource:",
+ // which is technically a substituting protocol handler that is not limited to
+ // local resource mapping, but in practice is never mapped remotely as this
+ // would violate assumptions a lot of code makes.
+ if (scheme.EqualsLiteral("https") ||
+ scheme.EqualsLiteral("file") ||
+ scheme.EqualsLiteral("resource") ||
+ scheme.EqualsLiteral("app") ||
+ scheme.EqualsLiteral("moz-extension") ||
+ scheme.EqualsLiteral("wss")) {
+ *aIsTrustWorthy = true;
+ return NS_OK;
+ }
+
+ nsAutoCString host;
+ rv = uri->GetHost(host);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ if (host.Equals("127.0.0.1") ||
+ host.Equals("localhost") ||
+ host.Equals("::1")) {
+ *aIsTrustWorthy = true;
+ return NS_OK;
+ }
+
+ // If a host is not considered secure according to the default algorithm, then
+ // check to see if it has been whitelisted by the user. We only apply this
+ // whitelist for network resources, i.e., those with scheme "http" or "ws".
+ // The pref should contain a comma-separated list of hostnames.
+ if (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("ws")) {
+ nsAdoptingCString whitelist = Preferences::GetCString("dom.securecontext.whitelist");
+ if (whitelist) {
+ nsCCharSeparatedTokenizer tokenizer(whitelist, ',');
+ while (tokenizer.hasMoreTokens()) {
+ const nsCSubstring& allowedHost = tokenizer.nextToken();
+ if (host.Equals(allowedHost)) {
+ *aIsTrustWorthy = true;
+ return NS_OK;
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/dom/security/nsContentSecurityManager.h b/dom/security/nsContentSecurityManager.h
new file mode 100644
index 000000000..912c0e89f
--- /dev/null
+++ b/dom/security/nsContentSecurityManager.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#ifndef nsContentSecurityManager_h___
+#define nsContentSecurityManager_h___
+
+#include "nsIContentSecurityManager.h"
+#include "nsIChannel.h"
+#include "nsIChannelEventSink.h"
+
+class nsIStreamListener;
+
+#define NS_CONTENTSECURITYMANAGER_CONTRACTID "@mozilla.org/contentsecuritymanager;1"
+// cdcc1ab8-3cea-4e6c-a294-a651fa35227f
+#define NS_CONTENTSECURITYMANAGER_CID \
+{ 0xcdcc1ab8, 0x3cea, 0x4e6c, \
+ { 0xa2, 0x94, 0xa6, 0x51, 0xfa, 0x35, 0x22, 0x7f } }
+
+class nsContentSecurityManager : public nsIContentSecurityManager
+ , public nsIChannelEventSink
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTSECURITYMANAGER
+ NS_DECL_NSICHANNELEVENTSINK
+
+ nsContentSecurityManager() {}
+
+ static nsresult doContentSecurityCheck(nsIChannel* aChannel,
+ nsCOMPtr<nsIStreamListener>& aInAndOutListener);
+
+private:
+ static nsresult CheckChannel(nsIChannel* aChannel);
+
+ virtual ~nsContentSecurityManager() {}
+
+};
+
+#endif /* nsContentSecurityManager_h___ */
diff --git a/dom/security/nsMixedContentBlocker.cpp b/dom/security/nsMixedContentBlocker.cpp
new file mode 100644
index 000000000..a9aca5333
--- /dev/null
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -0,0 +1,1191 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#include "nsMixedContentBlocker.h"
+
+#include "nsContentPolicyUtils.h"
+#include "nsCSPContext.h"
+#include "nsThreadUtils.h"
+#include "nsINode.h"
+#include "nsCOMPtr.h"
+#include "nsIDocShell.h"
+#include "nsISecurityEventSink.h"
+#include "nsIWebProgressListener.h"
+#include "nsContentUtils.h"
+#include "nsIRequest.h"
+#include "nsIDocument.h"
+#include "nsIContentViewer.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIParentChannel.h"
+#include "mozilla/Preferences.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsISecureBrowserUI.h"
+#include "nsIDocumentLoader.h"
+#include "nsIWebNavigation.h"
+#include "nsLoadGroup.h"
+#include "nsIScriptError.h"
+#include "nsIURI.h"
+#include "nsIChannelEventSink.h"
+#include "nsAsyncRedirectVerifyHelper.h"
+#include "mozilla/LoadInfo.h"
+#include "nsISiteSecurityService.h"
+
+#include "mozilla/Logging.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/ipc/URIUtils.h"
+
+
+using namespace mozilla;
+
+enum nsMixedContentBlockerMessageType {
+ eBlocked = 0x00,
+ eUserOverride = 0x01
+};
+
+// Is mixed script blocking (fonts, plugin content, scripts, stylesheets,
+// iframes, websockets, XHR) enabled?
+bool nsMixedContentBlocker::sBlockMixedScript = false;
+
+// Is mixed display content blocking (images, audio, video, <a ping>) enabled?
+bool nsMixedContentBlocker::sBlockMixedDisplay = false;
+
+// Do we move HSTS before mixed-content
+bool nsMixedContentBlocker::sUseHSTS = false;
+// Do we send an HSTS priming request
+bool nsMixedContentBlocker::sSendHSTSPriming = false;
+// Default HSTS Priming failure timeout to 7 days, in seconds
+uint32_t nsMixedContentBlocker::sHSTSPrimingCacheTimeout = (60 * 24 * 7);
+
+// Fired at the document that attempted to load mixed content. The UI could
+// handle this event, for example, by displaying an info bar that offers the
+// choice to reload the page with mixed content permitted.
+class nsMixedContentEvent : public Runnable
+{
+public:
+ nsMixedContentEvent(nsISupports *aContext, MixedContentTypes aType, bool aRootHasSecureConnection)
+ : mContext(aContext), mType(aType), mRootHasSecureConnection(aRootHasSecureConnection)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ NS_ASSERTION(mContext,
+ "You can't call this runnable without a requesting context");
+
+ // To update the security UI in the tab with the blocked mixed content, call
+ // nsISecurityEventSink::OnSecurityChange. You can get to the event sink by
+ // calling NS_CP_GetDocShellFromContext on the context, and QI'ing to
+ // nsISecurityEventSink.
+
+
+ // Mixed content was allowed and is about to load; get the document and
+ // set the approriate flag to true if we are about to load Mixed Active
+ // Content.
+ nsCOMPtr<nsIDocShell> docShell = NS_CP_GetDocShellFromContext(mContext);
+ if (!docShell) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
+ docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
+ NS_ASSERTION(sameTypeRoot, "No document shell root tree item from document shell tree item!");
+
+ // now get the document from sameTypeRoot
+ nsCOMPtr<nsIDocument> rootDoc = sameTypeRoot->GetDocument();
+ NS_ASSERTION(rootDoc, "No root document from document shell root tree item.");
+
+ // Get eventSink and the current security state from the docShell
+ nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell);
+ NS_ASSERTION(eventSink, "No eventSink from docShell.");
+ nsCOMPtr<nsIDocShell> rootShell = do_GetInterface(sameTypeRoot);
+ NS_ASSERTION(rootShell, "No root docshell from document shell root tree item.");
+ uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN;
+ nsCOMPtr<nsISecureBrowserUI> securityUI;
+ rootShell->GetSecurityUI(getter_AddRefs(securityUI));
+ // If there is no securityUI, document doesn't have a security state to
+ // update. But we still want to set the document flags, so we don't return
+ // early.
+ nsresult stateRV = NS_ERROR_FAILURE;
+ if (securityUI) {
+ stateRV = securityUI->GetState(&state);
+ }
+
+ if (mType == eMixedScript) {
+ // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
+ if (rootDoc->GetHasMixedActiveContentLoaded()) {
+ return NS_OK;
+ }
+ rootDoc->SetHasMixedActiveContentLoaded(true);
+
+ // Update the security UI in the tab with the allowed mixed active content
+ if (securityUI) {
+ // Bug 1182551 - before changing the security state to broken, check
+ // that the root is actually secure.
+ if (mRootHasSecureConnection) {
+ // reset state security flag
+ state = state >> 4 << 4;
+ // set state security flag to broken, since there is mixed content
+ state |= nsIWebProgressListener::STATE_IS_BROKEN;
+
+ // If mixed display content is loaded, make sure to include that in the state.
+ if (rootDoc->GetHasMixedDisplayContentLoaded()) {
+ state |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
+ }
+
+ eventSink->OnSecurityChange(mContext,
+ (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+ } else {
+ // root not secure, mixed active content loaded in an https subframe
+ if (NS_SUCCEEDED(stateRV)) {
+ eventSink->OnSecurityChange(mContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+ }
+ }
+ }
+
+ } else if (mType == eMixedDisplay) {
+ // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
+ if (rootDoc->GetHasMixedDisplayContentLoaded()) {
+ return NS_OK;
+ }
+ rootDoc->SetHasMixedDisplayContentLoaded(true);
+
+ // Update the security UI in the tab with the allowed mixed display content.
+ if (securityUI) {
+ // Bug 1182551 - before changing the security state to broken, check
+ // that the root is actually secure.
+ if (mRootHasSecureConnection) {
+ // reset state security flag
+ state = state >> 4 << 4;
+ // set state security flag to broken, since there is mixed content
+ state |= nsIWebProgressListener::STATE_IS_BROKEN;
+
+ // If mixed active content is loaded, make sure to include that in the state.
+ if (rootDoc->GetHasMixedActiveContentLoaded()) {
+ state |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
+ }
+
+ eventSink->OnSecurityChange(mContext,
+ (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+ } else {
+ // root not secure, mixed display content loaded in an https subframe
+ if (NS_SUCCEEDED(stateRV)) {
+ eventSink->OnSecurityChange(mContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+ }
+private:
+ // The requesting context for the content load. Generally, a DOM node from
+ // the document that caused the load.
+ nsCOMPtr<nsISupports> mContext;
+
+ // The type of mixed content detected, e.g. active or display
+ const MixedContentTypes mType;
+
+ // Indicates whether the top level load is https or not.
+ bool mRootHasSecureConnection;
+};
+
+
+nsMixedContentBlocker::nsMixedContentBlocker()
+{
+ // Cache the pref for mixed script blocking
+ Preferences::AddBoolVarCache(&sBlockMixedScript,
+ "security.mixed_content.block_active_content");
+
+ // Cache the pref for mixed display blocking
+ Preferences::AddBoolVarCache(&sBlockMixedDisplay,
+ "security.mixed_content.block_display_content");
+
+ // Cache the pref for HSTS
+ Preferences::AddBoolVarCache(&sUseHSTS,
+ "security.mixed_content.use_hsts");
+
+ // Cache the pref for sending HSTS priming
+ Preferences::AddBoolVarCache(&sSendHSTSPriming,
+ "security.mixed_content.send_hsts_priming");
+
+ // Cache the pref for HSTS priming failure cache time
+ Preferences::AddUintVarCache(&sHSTSPrimingCacheTimeout,
+ "security.mixed_content.hsts_priming_cache_timeout");
+}
+
+nsMixedContentBlocker::~nsMixedContentBlocker()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsMixedContentBlocker, nsIContentPolicy, nsIChannelEventSink)
+
+static void
+LogMixedContentMessage(MixedContentTypes aClassification,
+ nsIURI* aContentLocation,
+ nsIDocument* aRootDoc,
+ nsMixedContentBlockerMessageType aMessageType)
+{
+ nsAutoCString messageCategory;
+ uint32_t severityFlag;
+ nsAutoCString messageLookupKey;
+
+ if (aMessageType == eBlocked) {
+ severityFlag = nsIScriptError::errorFlag;
+ messageCategory.AssignLiteral("Mixed Content Blocker");
+ if (aClassification == eMixedDisplay) {
+ messageLookupKey.AssignLiteral("BlockMixedDisplayContent");
+ } else {
+ messageLookupKey.AssignLiteral("BlockMixedActiveContent");
+ }
+ } else {
+ severityFlag = nsIScriptError::warningFlag;
+ messageCategory.AssignLiteral("Mixed Content Message");
+ if (aClassification == eMixedDisplay) {
+ messageLookupKey.AssignLiteral("LoadingMixedDisplayContent2");
+ } else {
+ messageLookupKey.AssignLiteral("LoadingMixedActiveContent2");
+ }
+ }
+
+ NS_ConvertUTF8toUTF16 locationSpecUTF16(aContentLocation->GetSpecOrDefault());
+ const char16_t* strings[] = { locationSpecUTF16.get() };
+ nsContentUtils::ReportToConsole(severityFlag, messageCategory, aRootDoc,
+ nsContentUtils::eSECURITY_PROPERTIES,
+ messageLookupKey.get(), strings, ArrayLength(strings));
+}
+
+/* nsIChannelEventSink implementation
+ * This code is called when a request is redirected.
+ * We check the channel associated with the new uri is allowed to load
+ * in the current context
+ */
+NS_IMETHODIMP
+nsMixedContentBlocker::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aFlags,
+ nsIAsyncVerifyRedirectCallback* aCallback)
+{
+ nsAsyncRedirectAutoCallback autoCallback(aCallback);
+
+ if (!aOldChannel) {
+ NS_ERROR("No channel when evaluating mixed content!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we are in the parent process in e10s, we don't have access to the
+ // document node, and hence ShouldLoad will fail when we try to get
+ // the docShell. If that's the case, ignore mixed content checks
+ // on redirects in the parent. Let the child check for mixed content.
+ nsCOMPtr<nsIParentChannel> is_ipc_channel;
+ NS_QueryNotificationCallbacks(aNewChannel, is_ipc_channel);
+ if (is_ipc_channel) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIURI> oldUri;
+ rv = aOldChannel->GetURI(getter_AddRefs(oldUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> newUri;
+ rv = aNewChannel->GetURI(getter_AddRefs(newUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the loading Info from the old channel
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ rv = aOldChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!loadInfo) {
+ // XXX: We want to have a loadInfo on all channels, but we don't yet.
+ // If an addon creates a channel, they may not set loadinfo. If that
+ // channel redirects from one page to another page, we would get caught
+ // in this code path. Hence, we have to return NS_OK. Once we have more
+ // confidence that all channels have loadinfo, we can change this to
+ // a failure. See bug 1077201.
+ return NS_OK;
+ }
+
+ nsContentPolicyType contentPolicyType = loadInfo->InternalContentPolicyType();
+ nsCOMPtr<nsIPrincipal> requestingPrincipal = loadInfo->LoadingPrincipal();
+
+ // Since we are calling shouldLoad() directly on redirects, we don't go through the code
+ // in nsContentPolicyUtils::NS_CheckContentLoadPolicy(). Hence, we have to
+ // duplicate parts of it here.
+ nsCOMPtr<nsIURI> requestingLocation;
+ if (requestingPrincipal) {
+ // We check to see if the loadingPrincipal is systemPrincipal and return
+ // early if it is
+ if (nsContentUtils::IsSystemPrincipal(requestingPrincipal)) {
+ return NS_OK;
+ }
+ // We set the requestingLocation from the RequestingPrincipal.
+ rv = requestingPrincipal->GetURI(getter_AddRefs(requestingLocation));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsISupports> requestingContext = loadInfo->LoadingNode();
+
+ int16_t decision = REJECT_REQUEST;
+ rv = ShouldLoad(contentPolicyType,
+ newUri,
+ requestingLocation,
+ requestingContext,
+ EmptyCString(), // aMimeGuess
+ nullptr, // aExtra
+ requestingPrincipal,
+ &decision);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (nsMixedContentBlocker::sSendHSTSPriming) {
+ // The LoadInfo passed in is for the original channel, HSTS priming needs to
+ // be set on the new channel, if required. If the redirect changes
+ // http->https, or vice-versa, the need for priming may change.
+ nsCOMPtr<nsILoadInfo> newLoadInfo;
+ rv = aNewChannel->GetLoadInfo(getter_AddRefs(newLoadInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsMixedContentBlocker::MarkLoadInfoForPriming(newUri,
+ requestingContext,
+ newLoadInfo);
+ if (NS_FAILED(rv)) {
+ decision = REJECT_REQUEST;
+ newLoadInfo->ClearHSTSPriming();
+ }
+ }
+
+ // If the channel is about to load mixed content, abort the channel
+ if (!NS_CP_ACCEPTED(decision)) {
+ autoCallback.DontCallback();
+ return NS_BINDING_FAILED;
+ }
+
+ return NS_OK;
+}
+
+/* This version of ShouldLoad() is non-static and called by the Content Policy
+ * API and AsyncOnChannelRedirect(). See nsIContentPolicy::ShouldLoad()
+ * for detailed description of the parameters.
+ */
+NS_IMETHODIMP
+nsMixedContentBlocker::ShouldLoad(uint32_t aContentType,
+ nsIURI* aContentLocation,
+ nsIURI* aRequestingLocation,
+ nsISupports* aRequestingContext,
+ const nsACString& aMimeGuess,
+ nsISupports* aExtra,
+ nsIPrincipal* aRequestPrincipal,
+ int16_t* aDecision)
+{
+ // We pass in false as the first parameter to ShouldLoad(), because the
+ // callers of this method don't know whether the load went through cached
+ // image redirects. This is handled by direct callers of the static
+ // ShouldLoad.
+ nsresult rv = ShouldLoad(false, // aHadInsecureImageRedirect
+ aContentType,
+ aContentLocation,
+ aRequestingLocation,
+ aRequestingContext,
+ aMimeGuess,
+ aExtra,
+ aRequestPrincipal,
+ aDecision);
+ return rv;
+}
+
+/* Static version of ShouldLoad() that contains all the Mixed Content Blocker
+ * logic. Called from non-static ShouldLoad().
+ */
+nsresult
+nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect,
+ uint32_t aContentType,
+ nsIURI* aContentLocation,
+ nsIURI* aRequestingLocation,
+ nsISupports* aRequestingContext,
+ const nsACString& aMimeGuess,
+ nsISupports* aExtra,
+ nsIPrincipal* aRequestPrincipal,
+ int16_t* aDecision)
+{
+ // Asserting that we are on the main thread here and hence do not have to lock
+ // and unlock sBlockMixedScript and sBlockMixedDisplay before reading/writing
+ // to them.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ bool isPreload = nsContentUtils::IsPreloadType(aContentType);
+
+ // The content policy type that we receive may be an internal type for
+ // scripts. Let's remember if we have seen a worker type, and reset it to the
+ // external type in all cases right now.
+ bool isWorkerType = aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+ aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER ||
+ aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER;
+ aContentType = nsContentUtils::InternalContentPolicyTypeToExternal(aContentType);
+
+ // Assume active (high risk) content and blocked by default
+ MixedContentTypes classification = eMixedScript;
+ // Make decision to block/reject by default
+ *aDecision = REJECT_REQUEST;
+
+ // Notes on non-obvious decisions:
+ //
+ // TYPE_DTD: A DTD can contain entity definitions that expand to scripts.
+ //
+ // TYPE_FONT: The TrueType hinting mechanism is basically a scripting
+ // language that gets interpreted by the operating system's font rasterizer.
+ // Mixed content web fonts are relatively uncommon, and we can can fall back
+ // to built-in fonts with minimal disruption in almost all cases.
+ //
+ // TYPE_OBJECT_SUBREQUEST could actually be either active content (e.g. a
+ // script that a plugin will execute) or display content (e.g. Flash video
+ // content). Until we have a way to determine active vs passive content
+ // from plugin requests (bug 836352), we will treat this as passive content.
+ // This is to prevent false positives from causing users to become
+ // desensitized to the mixed content blocker.
+ //
+ // TYPE_CSP_REPORT: High-risk because they directly leak information about
+ // the content of the page, and because blocking them does not have any
+ // negative effect on the page loading.
+ //
+ // TYPE_PING: Ping requests are POSTS, not GETs like images and media.
+ // Also, PING requests have no bearing on the rendering or operation of
+ // the page when used as designed, so even though they are lower risk than
+ // scripts, blocking them is basically risk-free as far as compatibility is
+ // concerned.
+ //
+ // TYPE_STYLESHEET: XSLT stylesheets can insert scripts. CSS positioning
+ // and other advanced CSS features can possibly be exploited to cause
+ // spoofing attacks (e.g. make a "grant permission" button look like a
+ // "refuse permission" button).
+ //
+ // TYPE_BEACON: Beacon requests are similar to TYPE_PING, and are blocked by
+ // default.
+ //
+ // TYPE_WEBSOCKET: The Websockets API requires browsers to
+ // reject mixed-content websockets: "If secure is false but the origin of
+ // the entry script has a scheme component that is itself a secure protocol,
+ // e.g. HTTPS, then throw a SecurityError exception." We already block mixed
+ // content websockets within the websockets implementation, so we don't need
+ // to do any blocking here, nor do we need to provide a way to undo or
+ // override the blocking. Websockets without TLS are very flaky anyway in the
+ // face of many HTTP-aware proxies. Compared to passive content, there is
+ // additional risk that the script using WebSockets will disclose sensitive
+ // information from the HTTPS page and/or eval (directly or indirectly)
+ // received data.
+ //
+ // TYPE_XMLHTTPREQUEST: XHR requires either same origin or CORS, so most
+ // mixed-content XHR will already be blocked by that check. This will also
+ // block HTTPS-to-HTTP XHR with CORS. The same security concerns mentioned
+ // above for WebSockets apply to XHR, and XHR should have the same security
+ // properties as WebSockets w.r.t. mixed content. XHR's handling of redirects
+ // amplifies these concerns.
+
+
+ static_assert(TYPE_DATAREQUEST == TYPE_XMLHTTPREQUEST,
+ "TYPE_DATAREQUEST is not a synonym for "
+ "TYPE_XMLHTTPREQUEST");
+
+ switch (aContentType) {
+ // The top-level document cannot be mixed content by definition
+ case TYPE_DOCUMENT:
+ *aDecision = ACCEPT;
+ return NS_OK;
+ // Creating insecure websocket connections in a secure page is blocked already
+ // in the websocket constructor. We don't need to check the blocking here
+ // and we don't want to un-block
+ case TYPE_WEBSOCKET:
+ *aDecision = ACCEPT;
+ return NS_OK;
+
+ // Static display content is considered moderate risk for mixed content so
+ // these will be blocked according to the mixed display preference
+ case TYPE_IMAGE:
+ case TYPE_MEDIA:
+ case TYPE_OBJECT_SUBREQUEST:
+ classification = eMixedDisplay;
+ break;
+
+ // Active content (or content with a low value/risk-of-blocking ratio)
+ // that has been explicitly evaluated; listed here for documentation
+ // purposes and to avoid the assertion and warning for the default case.
+ case TYPE_BEACON:
+ case TYPE_CSP_REPORT:
+ case TYPE_DTD:
+ case TYPE_FETCH:
+ case TYPE_FONT:
+ case TYPE_IMAGESET:
+ case TYPE_OBJECT:
+ case TYPE_SCRIPT:
+ case TYPE_STYLESHEET:
+ case TYPE_SUBDOCUMENT:
+ case TYPE_PING:
+ case TYPE_WEB_MANIFEST:
+ case TYPE_XBL:
+ case TYPE_XMLHTTPREQUEST:
+ case TYPE_XSLT:
+ case TYPE_OTHER:
+ break;
+
+
+ // This content policy works as a whitelist.
+ default:
+ MOZ_ASSERT(false, "Mixed content of unknown type");
+ }
+
+ // Make sure to get the URI the load started with. No need to check
+ // outer schemes because all the wrapping pseudo protocols inherit the
+ // security properties of the actual network request represented
+ // by the innerMost URL.
+ nsCOMPtr<nsIURI> innerContentLocation = NS_GetInnermostURI(aContentLocation);
+ if (!innerContentLocation) {
+ NS_ERROR("Can't get innerURI from aContentLocation");
+ *aDecision = REJECT_REQUEST;
+ return NS_OK;
+ }
+
+ /* Get the scheme of the sub-document resource to be requested. If it is
+ * a safe to load in an https context then mixed content doesn't apply.
+ *
+ * Check Protocol Flags to determine if scheme is safe to load:
+ * URI_DOES_NOT_RETURN_DATA - e.g.
+ * "mailto"
+ * URI_IS_LOCAL_RESOURCE - e.g.
+ * "data",
+ * "resource",
+ * "moz-icon"
+ * URI_INHERITS_SECURITY_CONTEXT - e.g.
+ * "javascript"
+ * URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT - e.g.
+ * "https",
+ * "moz-safe-about"
+ *
+ */
+ bool schemeLocal = false;
+ bool schemeNoReturnData = false;
+ bool schemeInherits = false;
+ bool schemeSecure = false;
+ if (NS_FAILED(NS_URIChainHasFlags(innerContentLocation, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE , &schemeLocal)) ||
+ NS_FAILED(NS_URIChainHasFlags(innerContentLocation, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, &schemeNoReturnData)) ||
+ NS_FAILED(NS_URIChainHasFlags(innerContentLocation, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &schemeInherits)) ||
+ NS_FAILED(NS_URIChainHasFlags(innerContentLocation, nsIProtocolHandler::URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT, &schemeSecure))) {
+ *aDecision = REJECT_REQUEST;
+ return NS_ERROR_FAILURE;
+ }
+ // TYPE_IMAGE redirects are cached based on the original URI, not the final
+ // destination and hence cache hits for images may not have the correct
+ // innerContentLocation. Check if the cached hit went through an http redirect,
+ // and if it did, we can't treat this as a secure subresource.
+ if (!aHadInsecureImageRedirect &&
+ (schemeLocal || schemeNoReturnData || schemeInherits || schemeSecure)) {
+ *aDecision = ACCEPT;
+ return NS_OK;
+ }
+
+ // Since there are cases where aRequestingLocation and aRequestPrincipal are
+ // definitely not the owning document, we try to ignore them by extracting the
+ // requestingLocation in the following order:
+ // 1) from the aRequestingContext, either extracting
+ // a) the node's principal, or the
+ // b) script object's principal.
+ // 2) if aRequestingContext yields a principal but no location, we check
+ // if its the system principal. If it is, allow the load.
+ // 3) Special case handling for:
+ // a) speculative loads, where shouldLoad is called twice (bug 839235)
+ // and the first speculative load does not include a context.
+ // In this case we use aRequestingLocation to set requestingLocation.
+ // b) TYPE_CSP_REPORT which does not provide a context. In this case we
+ // use aRequestingLocation to set requestingLocation.
+ // c) content scripts from addon code that do not provide aRequestingContext
+ // or aRequestingLocation, but do provide aRequestPrincipal.
+ // If aRequestPrincipal is an expanded principal, we allow the load.
+ // 4) If we still end up not having a requestingLocation, we reject the load.
+
+ nsCOMPtr<nsIPrincipal> principal;
+ // 1a) Try to get the principal if aRequestingContext is a node.
+ nsCOMPtr<nsINode> node = do_QueryInterface(aRequestingContext);
+ if (node) {
+ principal = node->NodePrincipal();
+ }
+
+ // 1b) Try using the window's script object principal if it's not a node.
+ if (!principal) {
+ nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrin = do_QueryInterface(aRequestingContext);
+ if (scriptObjPrin) {
+ principal = scriptObjPrin->GetPrincipal();
+ }
+ }
+
+ nsCOMPtr<nsIURI> requestingLocation;
+ if (principal) {
+ principal->GetURI(getter_AddRefs(requestingLocation));
+ }
+
+ // 2) if aRequestingContext yields a principal but no location, we check if its a system principal.
+ if (principal && !requestingLocation) {
+ if (nsContentUtils::IsSystemPrincipal(principal)) {
+ *aDecision = ACCEPT;
+ return NS_OK;
+ }
+ }
+
+ // 3a,b) Special case handling for speculative loads and TYPE_CSP_REPORT. In
+ // such cases, aRequestingContext doesn't exist, so we use aRequestingLocation.
+ // Unfortunately we can not distinguish between speculative and normal loads here,
+ // otherwise we could special case this assignment.
+ if (!requestingLocation) {
+ requestingLocation = aRequestingLocation;
+ }
+
+ // 3c) Special case handling for content scripts from addons code, which only
+ // provide a aRequestPrincipal; aRequestingContext and aRequestingLocation are
+ // both null; if the aRequestPrincipal is an expandedPrincipal, we allow the load.
+ if (!principal && !requestingLocation && aRequestPrincipal) {
+ nsCOMPtr<nsIExpandedPrincipal> expanded = do_QueryInterface(aRequestPrincipal);
+ if (expanded) {
+ *aDecision = ACCEPT;
+ return NS_OK;
+ }
+ }
+
+ // 4) Giving up. We still don't have a requesting location, therefore we can't tell
+ // if this is a mixed content load. Deny to be safe.
+ if (!requestingLocation) {
+ *aDecision = REJECT_REQUEST;
+ return NS_OK;
+ }
+
+ // Check the parent scheme. If it is not an HTTPS page then mixed content
+ // restrictions do not apply.
+ bool parentIsHttps;
+ nsCOMPtr<nsIURI> innerRequestingLocation = NS_GetInnermostURI(requestingLocation);
+ if (!innerRequestingLocation) {
+ NS_ERROR("Can't get innerURI from requestingLocation");
+ *aDecision = REJECT_REQUEST;
+ return NS_OK;
+ }
+
+ nsresult rv = innerRequestingLocation->SchemeIs("https", &parentIsHttps);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("requestingLocation->SchemeIs failed");
+ *aDecision = REJECT_REQUEST;
+ return NS_OK;
+ }
+ if (!parentIsHttps) {
+ *aDecision = ACCEPT;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = NS_CP_GetDocShellFromContext(aRequestingContext);
+ NS_ENSURE_TRUE(docShell, NS_OK);
+
+ // Disallow mixed content loads for workers, shared workers and service
+ // workers.
+ if (isWorkerType) {
+ // For workers, we can assume that we're mixed content at this point, since
+ // the parent is https, and the protocol associated with innerContentLocation
+ // doesn't map to the secure URI flags checked above. Assert this for
+ // sanity's sake
+#ifdef DEBUG
+ bool isHttpsScheme = false;
+ rv = innerContentLocation->SchemeIs("https", &isHttpsScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(!isHttpsScheme);
+#endif
+ *aDecision = REJECT_REQUEST;
+ return NS_OK;
+ }
+
+ // The page might have set the CSP directive 'upgrade-insecure-requests'. In such
+ // a case allow the http: load to succeed with the promise that the channel will
+ // get upgraded to https before fetching any data from the netwerk.
+ // Please see: nsHttpChannel::Connect()
+ //
+ // Please note that the CSP directive 'upgrade-insecure-requests' only applies to
+ // http: and ws: (for websockets). Websockets are not subject to mixed content
+ // blocking since insecure websockets are not allowed within secure pages. Hence,
+ // we only have to check against http: here. Skip mixed content blocking if the
+ // subresource load uses http: and the CSP directive 'upgrade-insecure-requests'
+ // is present on the page.
+ bool isHttpScheme = false;
+ rv = innerContentLocation->SchemeIs("http", &isHttpScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsIDocument* document = docShell->GetDocument();
+ MOZ_ASSERT(document, "Expected a document");
+ if (isHttpScheme && document->GetUpgradeInsecureRequests(isPreload)) {
+ *aDecision = ACCEPT;
+ return NS_OK;
+ }
+
+ // The page might have set the CSP directive 'block-all-mixed-content' which
+ // should block not only active mixed content loads but in fact all mixed content
+ // loads, see https://www.w3.org/TR/mixed-content/#strict-checking
+ // Block all non secure loads in case the CSP directive is present. Please note
+ // that at this point we already know, based on |schemeSecure| that the load is
+ // not secure, so we can bail out early at this point.
+ if (document->GetBlockAllMixedContent(isPreload)) {
+ // log a message to the console before returning.
+ nsAutoCString spec;
+ rv = aContentLocation->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ConvertUTF8toUTF16 reportSpec(spec);
+
+ const char16_t* params[] = { reportSpec.get()};
+ CSP_LogLocalizedStr(u"blockAllMixedContent",
+ params, ArrayLength(params),
+ EmptyString(), // aSourceFile
+ EmptyString(), // aScriptSample
+ 0, // aLineNumber
+ 0, // aColumnNumber
+ nsIScriptError::errorFlag, "CSP",
+ document->InnerWindowID());
+ *aDecision = REJECT_REQUEST;
+ return NS_OK;
+ }
+
+ // Determine if the rootDoc is https and if the user decided to allow Mixed Content
+ bool rootHasSecureConnection = false;
+ bool allowMixedContent = false;
+ bool isRootDocShell = false;
+ rv = docShell->GetAllowMixedContentAndConnectionData(&rootHasSecureConnection, &allowMixedContent, &isRootDocShell);
+ if (NS_FAILED(rv)) {
+ *aDecision = REJECT_REQUEST;
+ return rv;
+ }
+
+ // Get the sameTypeRoot tree item from the docshell
+ nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
+ docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot));
+ NS_ASSERTION(sameTypeRoot, "No root tree item from docshell!");
+
+ // When navigating an iframe, the iframe may be https
+ // but its parents may not be. Check the parents to see if any of them are https.
+ // If none of the parents are https, allow the load.
+ if (aContentType == TYPE_SUBDOCUMENT && !rootHasSecureConnection) {
+
+ bool httpsParentExists = false;
+
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ parentTreeItem = docShell;
+
+ while(!httpsParentExists && parentTreeItem) {
+ nsCOMPtr<nsIWebNavigation> parentAsNav(do_QueryInterface(parentTreeItem));
+ NS_ASSERTION(parentAsNav, "No web navigation object from parent's docshell tree item");
+ nsCOMPtr<nsIURI> parentURI;
+
+ parentAsNav->GetCurrentURI(getter_AddRefs(parentURI));
+ if (!parentURI) {
+ // if getting the URI fails, assume there is a https parent and break.
+ httpsParentExists = true;
+ break;
+ }
+
+ nsCOMPtr<nsIURI> innerParentURI = NS_GetInnermostURI(parentURI);
+ if (!innerParentURI) {
+ NS_ERROR("Can't get innerURI from parentURI");
+ *aDecision = REJECT_REQUEST;
+ return NS_OK;
+ }
+
+ if (NS_FAILED(innerParentURI->SchemeIs("https", &httpsParentExists))) {
+ // if getting the scheme fails, assume there is a https parent and break.
+ httpsParentExists = true;
+ break;
+ }
+
+ // When the parent and the root are the same, we have traversed all the way up
+ // the same type docshell tree. Break out of the while loop.
+ if(sameTypeRoot == parentTreeItem) {
+ break;
+ }
+
+ // update the parent to the grandparent.
+ nsCOMPtr<nsIDocShellTreeItem> newParentTreeItem;
+ parentTreeItem->GetSameTypeParent(getter_AddRefs(newParentTreeItem));
+ parentTreeItem = newParentTreeItem;
+ } // end while loop.
+
+ if (!httpsParentExists) {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ }
+ }
+
+ // Get the root document from the sameTypeRoot
+ nsCOMPtr<nsIDocument> rootDoc = sameTypeRoot->GetDocument();
+ NS_ASSERTION(rootDoc, "No root document from document shell root tree item.");
+
+ // Get eventSink and the current security state from the docShell
+ nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell);
+ NS_ASSERTION(eventSink, "No eventSink from docShell.");
+ nsCOMPtr<nsIDocShell> rootShell = do_GetInterface(sameTypeRoot);
+ NS_ASSERTION(rootShell, "No root docshell from document shell root tree item.");
+ uint32_t state = nsIWebProgressListener::STATE_IS_BROKEN;
+ nsCOMPtr<nsISecureBrowserUI> securityUI;
+ rootShell->GetSecurityUI(getter_AddRefs(securityUI));
+ // If there is no securityUI, document doesn't have a security state.
+ // Allow load and return early.
+ if (!securityUI) {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ }
+ nsresult stateRV = securityUI->GetState(&state);
+
+ bool doHSTSPriming = false;
+ if (isHttpScheme) {
+ bool hsts = false;
+ bool cached = false;
+ nsCOMPtr<nsISiteSecurityService> sss =
+ do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aContentLocation,
+ 0, &cached, &hsts);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (hsts && sUseHSTS) {
+ // assume we will be upgraded later
+ *aDecision = ACCEPT;
+ return NS_OK;
+ }
+
+ // Send a priming request if the result is not already cached and priming
+ // requests are allowed
+ if (!cached && sSendHSTSPriming) {
+ // add this URI as a priming location
+ doHSTSPriming = true;
+ document->AddHSTSPrimingLocation(innerContentLocation,
+ HSTSPrimingState::eHSTS_PRIMING_ALLOW);
+ *aDecision = ACCEPT;
+ }
+ }
+
+ // At this point we know that the request is mixed content, and the only
+ // question is whether we block it. Record telemetry at this point as to
+ // whether HSTS would have fixed things by making the content location
+ // into an HTTPS URL.
+ //
+ // Note that we count this for redirects as well as primary requests. This
+ // will cause some degree of double-counting, especially when mixed content
+ // is not blocked (e.g., for images). For more detail, see:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1198572#c19
+ //
+ // We do not count requests aHadInsecureImageRedirect=true, since these are
+ // just an artifact of the image caching system.
+ bool active = (classification == eMixedScript);
+ if (!aHadInsecureImageRedirect) {
+ if (XRE_IsParentProcess()) {
+ AccumulateMixedContentHSTS(innerContentLocation, active, doHSTSPriming);
+ } else {
+ // Ask the parent process to do the same call
+ mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
+ if (cc) {
+ mozilla::ipc::URIParams uri;
+ SerializeURI(innerContentLocation, uri);
+ cc->SendAccumulateMixedContentHSTS(uri, active, doHSTSPriming);
+ }
+ }
+ }
+
+ // set hasMixedContentObjectSubrequest on this object if necessary
+ if (aContentType == TYPE_OBJECT_SUBREQUEST) {
+ rootDoc->SetHasMixedContentObjectSubrequest(true);
+ }
+
+ // If the content is display content, and the pref says display content should be blocked, block it.
+ if (sBlockMixedDisplay && classification == eMixedDisplay) {
+ if (allowMixedContent) {
+ LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride);
+ *aDecision = nsIContentPolicy::ACCEPT;
+ // See if mixed display content has already loaded on the page or if the state needs to be updated here.
+ // If mixed display hasn't loaded previously, then we need to call OnSecurityChange() to update the UI.
+ if (rootDoc->GetHasMixedDisplayContentLoaded()) {
+ return NS_OK;
+ }
+ rootDoc->SetHasMixedDisplayContentLoaded(true);
+
+ if (rootHasSecureConnection) {
+ // reset state security flag
+ state = state >> 4 << 4;
+ // set state security flag to broken, since there is mixed content
+ state |= nsIWebProgressListener::STATE_IS_BROKEN;
+
+ // If mixed active content is loaded, make sure to include that in the state.
+ if (rootDoc->GetHasMixedActiveContentLoaded()) {
+ state |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
+ }
+
+ eventSink->OnSecurityChange(aRequestingContext,
+ (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+ } else {
+ // User has overriden the pref and the root is not https;
+ // mixed display content was allowed on an https subframe.
+ if (NS_SUCCEEDED(stateRV)) {
+ eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
+ }
+ }
+ } else {
+ if (doHSTSPriming) {
+ document->AddHSTSPrimingLocation(innerContentLocation,
+ HSTSPrimingState::eHSTS_PRIMING_BLOCK);
+ *aDecision = nsIContentPolicy::ACCEPT;
+ } else {
+ *aDecision = nsIContentPolicy::REJECT_REQUEST;
+ }
+ LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked);
+ if (!rootDoc->GetHasMixedDisplayContentBlocked() && NS_SUCCEEDED(stateRV)) {
+ rootDoc->SetHasMixedDisplayContentBlocked(true);
+ eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT));
+ }
+ }
+ return NS_OK;
+
+ } else if (sBlockMixedScript && classification == eMixedScript) {
+ // If the content is active content, and the pref says active content should be blocked, block it
+ // unless the user has choosen to override the pref
+ if (allowMixedContent) {
+ LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride);
+ *aDecision = nsIContentPolicy::ACCEPT;
+ // See if the state will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
+ if (rootDoc->GetHasMixedActiveContentLoaded()) {
+ return NS_OK;
+ }
+ rootDoc->SetHasMixedActiveContentLoaded(true);
+
+ if (rootHasSecureConnection) {
+ // reset state security flag
+ state = state >> 4 << 4;
+ // set state security flag to broken, since there is mixed content
+ state |= nsIWebProgressListener::STATE_IS_BROKEN;
+
+ // If mixed display content is loaded, make sure to include that in the state.
+ if (rootDoc->GetHasMixedDisplayContentLoaded()) {
+ state |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
+ }
+
+ eventSink->OnSecurityChange(aRequestingContext,
+ (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+
+ return NS_OK;
+ } else {
+ // User has already overriden the pref and the root is not https;
+ // mixed active content was allowed on an https subframe.
+ if (NS_SUCCEEDED(stateRV)) {
+ eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
+ }
+ return NS_OK;
+ }
+ } else {
+ //User has not overriden the pref by Disabling protection. Reject the request and update the security state.
+ if (doHSTSPriming) {
+ document->AddHSTSPrimingLocation(innerContentLocation,
+ HSTSPrimingState::eHSTS_PRIMING_BLOCK);
+ *aDecision = nsIContentPolicy::ACCEPT;
+ } else {
+ *aDecision = nsIContentPolicy::REJECT_REQUEST;
+ }
+ LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked);
+ // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
+ if (rootDoc->GetHasMixedActiveContentBlocked()) {
+ return NS_OK;
+ }
+ rootDoc->SetHasMixedActiveContentBlocked(true);
+
+ // The user has not overriden the pref, so make sure they still have an option by calling eventSink
+ // which will invoke the doorhanger
+ if (NS_SUCCEEDED(stateRV)) {
+ eventSink->OnSecurityChange(aRequestingContext, (state | nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT));
+ }
+ return NS_OK;
+ }
+ } else {
+ // The content is not blocked by the mixed content prefs.
+
+ // Log a message that we are loading mixed content.
+ LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride);
+
+ // Fire the event from a script runner as it is unsafe to run script
+ // from within ShouldLoad
+ nsContentUtils::AddScriptRunner(
+ new nsMixedContentEvent(aRequestingContext, classification, rootHasSecureConnection));
+ *aDecision = ACCEPT;
+ return NS_OK;
+ }
+}
+
+NS_IMETHODIMP
+nsMixedContentBlocker::ShouldProcess(uint32_t aContentType,
+ nsIURI* aContentLocation,
+ nsIURI* aRequestingLocation,
+ nsISupports* aRequestingContext,
+ const nsACString& aMimeGuess,
+ nsISupports* aExtra,
+ nsIPrincipal* aRequestPrincipal,
+ int16_t* aDecision)
+{
+ aContentType = nsContentUtils::InternalContentPolicyTypeToExternal(aContentType);
+
+ if (!aContentLocation) {
+ // aContentLocation may be null when a plugin is loading without an associated URI resource
+ if (aContentType == TYPE_OBJECT) {
+ *aDecision = ACCEPT;
+ return NS_OK;
+ } else {
+ *aDecision = REJECT_REQUEST;
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return ShouldLoad(aContentType, aContentLocation, aRequestingLocation,
+ aRequestingContext, aMimeGuess, aExtra, aRequestPrincipal,
+ aDecision);
+}
+
+enum MixedContentHSTSState {
+ MCB_HSTS_PASSIVE_NO_HSTS = 0,
+ MCB_HSTS_PASSIVE_WITH_HSTS = 1,
+ MCB_HSTS_ACTIVE_NO_HSTS = 2,
+ MCB_HSTS_ACTIVE_WITH_HSTS = 3
+};
+
+// Similar to the existing mixed-content HSTS, except MCB_HSTS_*_NO_HSTS is
+// broken into two distinct states, indicating whether we plan to send a priming
+// request or not. If we decided not go send a priming request, it could be
+// because it is a type we do not support, or because we cached a previous
+// negative response.
+enum MixedContentHSTSPrimingState {
+ eMCB_HSTS_PASSIVE_WITH_HSTS = 0,
+ eMCB_HSTS_ACTIVE_WITH_HSTS = 1,
+ eMCB_HSTS_PASSIVE_NO_PRIMING = 2,
+ eMCB_HSTS_PASSIVE_DO_PRIMING = 3,
+ eMCB_HSTS_ACTIVE_NO_PRIMING = 4,
+ eMCB_HSTS_ACTIVE_DO_PRIMING = 5
+};
+
+// Record information on when HSTS would have made mixed content not mixed
+// content (regardless of whether it was actually blocked)
+void
+nsMixedContentBlocker::AccumulateMixedContentHSTS(nsIURI* aURI, bool aActive, bool aHasHSTSPriming)
+{
+ // This method must only be called in the parent, because
+ // nsSiteSecurityService is only available in the parent
+ if (!XRE_IsParentProcess()) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ bool hsts;
+ nsresult rv;
+ nsCOMPtr<nsISiteSecurityService> sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, 0, nullptr, &hsts);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // states: would upgrade, would prime, hsts info cached
+ // active, passive
+ //
+ if (!aActive) {
+ if (!hsts) {
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
+ MCB_HSTS_PASSIVE_NO_HSTS);
+ if (aHasHSTSPriming) {
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+ eMCB_HSTS_PASSIVE_DO_PRIMING);
+ } else {
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+ eMCB_HSTS_PASSIVE_NO_PRIMING);
+ }
+ }
+ else {
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
+ MCB_HSTS_PASSIVE_WITH_HSTS);
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+ eMCB_HSTS_PASSIVE_WITH_HSTS);
+ }
+ } else {
+ if (!hsts) {
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
+ MCB_HSTS_ACTIVE_NO_HSTS);
+ if (aHasHSTSPriming) {
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+ eMCB_HSTS_ACTIVE_DO_PRIMING);
+ } else {
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+ eMCB_HSTS_ACTIVE_NO_PRIMING);
+ }
+ }
+ else {
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS,
+ MCB_HSTS_ACTIVE_WITH_HSTS);
+ Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING,
+ eMCB_HSTS_ACTIVE_WITH_HSTS);
+ }
+ }
+}
+
+//static
+nsresult
+nsMixedContentBlocker::MarkLoadInfoForPriming(nsIURI* aURI,
+ nsISupports* aRequestingContext,
+ nsILoadInfo* aLoadInfo)
+{
+ nsresult rv;
+ bool sendPriming = false;
+ bool mixedContentWouldBlock = false;
+ rv = GetHSTSPrimingFromRequestingContext(aURI,
+ aRequestingContext,
+ &sendPriming,
+ &mixedContentWouldBlock);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (sendPriming) {
+ aLoadInfo->SetHSTSPriming(mixedContentWouldBlock);
+ }
+
+ return NS_OK;
+}
+
+//static
+nsresult
+nsMixedContentBlocker::GetHSTSPrimingFromRequestingContext(nsIURI* aURI,
+ nsISupports* aRequestingContext,
+ bool* aSendPrimingRequest,
+ bool* aMixedContentWouldBlock)
+{
+ *aSendPrimingRequest = false;
+ *aMixedContentWouldBlock = false;
+ // If we marked for priming, we used the innermost URI, so get that
+ nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
+ if (!innerURI) {
+ NS_ERROR("Can't get innerURI from aContentLocation");
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+
+ bool isHttp = false;
+ innerURI->SchemeIs("http", &isHttp);
+ if (!isHttp) {
+ // there is nothign to do
+ return NS_OK;
+ }
+
+ // If the DocShell was marked for HSTS priming, propagate that to the LoadInfo
+ nsCOMPtr<nsIDocShell> docShell = NS_CP_GetDocShellFromContext(aRequestingContext);
+ if (!docShell) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDocument> document = docShell->GetDocument();
+ if (!document) {
+ return NS_OK;
+ }
+
+ HSTSPrimingState status = document->GetHSTSPrimingStateForLocation(innerURI);
+ if (status != HSTSPrimingState::eNO_HSTS_PRIMING) {
+ *aSendPrimingRequest = (status != HSTSPrimingState::eNO_HSTS_PRIMING);
+ *aMixedContentWouldBlock = (status == HSTSPrimingState::eHSTS_PRIMING_BLOCK);
+ }
+
+ return NS_OK;
+}
diff --git a/dom/security/nsMixedContentBlocker.h b/dom/security/nsMixedContentBlocker.h
new file mode 100644
index 000000000..539c3ebbb
--- /dev/null
+++ b/dom/security/nsMixedContentBlocker.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#ifndef nsMixedContentBlocker_h___
+#define nsMixedContentBlocker_h___
+
+#define NS_MIXEDCONTENTBLOCKER_CONTRACTID "@mozilla.org/mixedcontentblocker;1"
+/* daf1461b-bf29-4f88-8d0e-4bcdf332c862 */
+#define NS_MIXEDCONTENTBLOCKER_CID \
+{ 0xdaf1461b, 0xbf29, 0x4f88, \
+ { 0x8d, 0x0e, 0x4b, 0xcd, 0xf3, 0x32, 0xc8, 0x62 } }
+
+// This enum defines type of content that is detected when an
+// nsMixedContentEvent fires
+enum MixedContentTypes {
+ // "Active" content, such as fonts, plugin content, JavaScript, stylesheets,
+ // iframes, WebSockets, and XHR
+ eMixedScript,
+ // "Display" content, such as images, audio, video, and <a ping>
+ eMixedDisplay
+};
+
+#include "nsIContentPolicy.h"
+#include "nsIChannel.h"
+#include "nsIChannelEventSink.h"
+#include "imgRequest.h"
+
+class nsILoadInfo; // forward declaration
+
+class nsMixedContentBlocker : public nsIContentPolicy,
+ public nsIChannelEventSink
+{
+private:
+ virtual ~nsMixedContentBlocker();
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPOLICY
+ NS_DECL_NSICHANNELEVENTSINK
+
+ nsMixedContentBlocker();
+
+ /* Static version of ShouldLoad() that contains all the Mixed Content Blocker
+ * logic. Called from non-static ShouldLoad().
+ * Called directly from imageLib when an insecure redirect exists in a cached
+ * image load.
+ * @param aHadInsecureImageRedirect
+ * boolean flag indicating that an insecure redirect through http
+ * occured when this image was initially loaded and cached.
+ * Remaining parameters are from nsIContentPolicy::ShouldLoad().
+ */
+ static nsresult ShouldLoad(bool aHadInsecureImageRedirect,
+ uint32_t aContentType,
+ nsIURI* aContentLocation,
+ nsIURI* aRequestingLocation,
+ nsISupports* aRequestingContext,
+ const nsACString& aMimeGuess,
+ nsISupports* aExtra,
+ nsIPrincipal* aRequestPrincipal,
+ int16_t* aDecision);
+ static void AccumulateMixedContentHSTS(nsIURI* aURI,
+ bool aActive,
+ bool aHasHSTSPriming);
+ /* If the document associated with aRequestingContext requires priming for
+ * aURI, propagate that to the LoadInfo so the HttpChannel will find out about
+ * it.
+ *
+ * @param aURI The URI associated with the load
+ * @param aRequestingContext the requesting context passed to ShouldLoad
+ * @param aLoadInfo the LoadInfo for the load
+ */
+ static nsresult MarkLoadInfoForPriming(nsIURI* aURI,
+ nsISupports* aRequestingContext,
+ nsILoadInfo* aLoadInfo);
+
+ /* Given a context, return whether HSTS was marked on the document associated
+ * with the load for the given URI. This is used by MarkLoadInfoForPriming and
+ * directly by the image loader to determine whether to allow a load to occur
+ * from the cache.
+ *
+ * @param aURI The URI associated with the load
+ * @param aRequestingContext the requesting context passed to ShouldLoad
+ * @param aSendPrimingRequest out true if priming is required on the channel
+ * @param aMixedContentWouldBlock out true if mixed content would block
+ */
+ static nsresult GetHSTSPrimingFromRequestingContext(nsIURI* aURI,
+ nsISupports* aRequestingContext,
+ bool* aSendPrimingRequest,
+ bool* aMixedContentWouldBlock);
+
+
+ static bool sBlockMixedScript;
+ static bool sBlockMixedDisplay;
+ // Do we move HSTS before mixed-content
+ static bool sUseHSTS;
+ // Do we send an HSTS priming request
+ static bool sSendHSTSPriming;
+ // Default HSTS Priming failure timeout in seconds
+ static uint32_t sHSTSPrimingCacheTimeout;
+};
+
+#endif /* nsMixedContentBlocker_h___ */
diff --git a/dom/security/test/contentverifier/browser.ini b/dom/security/test/contentverifier/browser.ini
new file mode 100644
index 000000000..c41c2e8e8
--- /dev/null
+++ b/dom/security/test/contentverifier/browser.ini
@@ -0,0 +1,19 @@
+[DEFAULT]
+support-files =
+ file_contentserver.sjs
+ file_about_newtab.html
+ file_about_newtab_bad.html
+ file_about_newtab_bad_csp.html
+ file_about_newtab_bad_csp_signature
+ file_about_newtab_good_signature
+ file_about_newtab_bad_signature
+ file_about_newtab_broken_signature
+ file_about_newtab_sri.html
+ file_about_newtab_sri_signature
+ goodChain.pem
+ head.js
+ script.js
+ style.css
+
+[browser_verify_content_about_newtab.js]
+[browser_verify_content_about_newtab2.js]
diff --git a/dom/security/test/contentverifier/browser_verify_content_about_newtab.js b/dom/security/test/contentverifier/browser_verify_content_about_newtab.js
new file mode 100644
index 000000000..f63726ef8
--- /dev/null
+++ b/dom/security/test/contentverifier/browser_verify_content_about_newtab.js
@@ -0,0 +1,20 @@
+
+const TESTS = [
+ // { newtab (aboutURI) or regular load (url) : url,
+ // testStrings : expected strings in the loaded page }
+ { "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
+ { "aboutURI" : URI_ERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
+ { "url" : URI_BAD_FILE_CACHED, "testStrings" : [BAD_ABOUT_STRING] },
+ { "aboutURI" : URI_BAD_FILE_CACHED, "testStrings" : [ABOUT_BLANK] },
+ { "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
+ { "aboutURI" : URI_SRI, "testStrings" : [
+ STYLESHEET_WITHOUT_SRI_BLOCKED,
+ STYLESHEET_WITH_SRI_LOADED,
+ SCRIPT_WITHOUT_SRI_BLOCKED,
+ SCRIPT_WITH_SRI_LOADED,
+ ]},
+ { "aboutURI" : URI_BAD_CSP, "testStrings" : [CSP_TEST_SUCCESS_STRING] },
+ { "url" : URI_CLEANUP, "testStrings" : [CLEANUP_DONE] },
+];
+
+add_task(runTests);
diff --git a/dom/security/test/contentverifier/browser_verify_content_about_newtab2.js b/dom/security/test/contentverifier/browser_verify_content_about_newtab2.js
new file mode 100644
index 000000000..a35ff6660
--- /dev/null
+++ b/dom/security/test/contentverifier/browser_verify_content_about_newtab2.js
@@ -0,0 +1,19 @@
+
+const TESTS = [
+ // { newtab (aboutURI) or regular load (url) : url,
+ // testStrings : expected strings in the loaded page }
+ { "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
+ { "aboutURI" : URI_ERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
+ { "aboutURI" : URI_KEYERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
+ { "aboutURI" : URI_SIGERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
+ { "aboutURI" : URI_NO_HEADER, "testStrings" : [ABOUT_BLANK] },
+ { "aboutURI" : URI_BAD_SIG, "testStrings" : [ABOUT_BLANK] },
+ { "aboutURI" : URI_BROKEN_SIG, "testStrings" : [ABOUT_BLANK] },
+ { "aboutURI" : URI_BAD_X5U, "testStrings" : [ABOUT_BLANK] },
+ { "aboutURI" : URI_HTTP_X5U, "testStrings" : [ABOUT_BLANK] },
+ { "aboutURI" : URI_BAD_FILE, "testStrings" : [ABOUT_BLANK] },
+ { "aboutURI" : URI_BAD_ALL, "testStrings" : [ABOUT_BLANK] },
+ { "url" : URI_CLEANUP, "testStrings" : [CLEANUP_DONE] },
+];
+
+add_task(runTests);
diff --git a/dom/security/test/contentverifier/file_about_newtab.html b/dom/security/test/contentverifier/file_about_newtab.html
new file mode 100644
index 000000000..f274743b9
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1226928 -->
+<head>
+ <meta charset="utf-8">
+ <title>Testpage for bug 1226928</title>
+</head>
+<body>
+ Just a fully good testpage for Bug 1226928<br/>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/security/test/contentverifier/file_about_newtab_bad.html b/dom/security/test/contentverifier/file_about_newtab_bad.html
new file mode 100644
index 000000000..45899f4f4
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_bad.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1226928 -->
+<head>
+ <meta charset="utf-8">
+ <title>Testpage for bug 1226928</title>
+</head>
+<body>
+ Just a bad testpage for Bug 1226928<br/>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/security/test/contentverifier/file_about_newtab_bad_csp.html b/dom/security/test/contentverifier/file_about_newtab_bad_csp.html
new file mode 100644
index 000000000..f8044b73f
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_bad_csp.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Testpage for CSP violation (inline script)</title>
+</head>
+<body>
+ CSP violation test succeeded.
+ <script>
+ // This inline script would override the success string if loaded.
+ document.body.innerHTML = "CSP violation test failed.";
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/security/test/contentverifier/file_about_newtab_bad_csp_signature b/dom/security/test/contentverifier/file_about_newtab_bad_csp_signature
new file mode 100644
index 000000000..ded42dc9f
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_bad_csp_signature
@@ -0,0 +1 @@
+oiypz3lb-IyJsmKNsnlp2zDrqncste8yONn9WUE6ksgJWMhSEQ9lp8vRqN0W3JPwJb6uSk16RI-tDv7uy0jxon5jL1BZpqlqIpvimg7FCQEedMKoHZwtE9an-e95sOTd \ No newline at end of file
diff --git a/dom/security/test/contentverifier/file_about_newtab_bad_signature b/dom/security/test/contentverifier/file_about_newtab_bad_signature
new file mode 100644
index 000000000..73a3c1e34
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_bad_signature
@@ -0,0 +1 @@
+KirX94omQL7lKfWGhc777t8U29enDg0O0UcJLH3PRXcvWGO8KA6mmLS3yNCFnGiTjP3vNnVtm-sUkXr4ix8WTkKABkU4fEAi77sNOkLCKw40M9sDJOesmYInS_J2AuXX \ No newline at end of file
diff --git a/dom/security/test/contentverifier/file_about_newtab_broken_signature b/dom/security/test/contentverifier/file_about_newtab_broken_signature
new file mode 100644
index 000000000..468a167ff
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_broken_signature
@@ -0,0 +1 @@
+MGUCMFwSs3o95ukwBWXN1WbLgnpJ_uHWFiQROPm9zjrSqzlfiSMyLwJwIZzldWo_pBJtOwIxAJIfhXIiMVfl5NkFEJUUMxzu6FuxOJl5DCpG2wHLy9AhayLUzm4X4SpwZ6QBPapdTg \ No newline at end of file
diff --git a/dom/security/test/contentverifier/file_about_newtab_good_signature b/dom/security/test/contentverifier/file_about_newtab_good_signature
new file mode 100644
index 000000000..d826d49c3
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_good_signature
@@ -0,0 +1 @@
+HUndgHvxHNMiAe1SXoeyOOraUJCdxHqWkAYTu0Cq1KpAHcWZEVelNTvyXGbTLWj8btsmqNLAm08UlyK43q_2oO9DQfez3Fo8DhsKvm7TqgSXCkhUoxsYNanxWXhqw-Jw \ No newline at end of file
diff --git a/dom/security/test/contentverifier/file_about_newtab_sri.html b/dom/security/test/contentverifier/file_about_newtab_sri.html
new file mode 100644
index 000000000..4415c533a
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_sri.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1235572 -->
+<head>
+ <meta charset="utf-8">
+ <title>Testpage for bug 1235572</title>
+ <script>
+ function loaded(resource) {
+ document.getElementById("result").innerHTML += resource + " loaded\n";
+ }
+ function blocked(resource) {
+ document.getElementById("result").innerHTML += resource + " blocked\n";
+ }
+ </script>
+</head>
+<body>
+ Testing script loading without SRI for Bug 1235572<br/>
+ <div id="result"></div>
+
+ <!-- use css1 and css2 to make urls different to avoid the resource being cached-->
+ <link rel="stylesheet" href="https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?resource=css1"
+ onload="loaded('Stylesheet without SRI')"
+ onerror="blocked('Stylesheet without SRI')">
+ <link rel="stylesheet" href="https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?resource=css2"
+ integrity="sha384-/6Tvxh7SX39y62qePcvYoi5Vrf0lK8Ix3wJFLCYKI5KNJ5wIlCR8UsFC1OXwmwgd"
+ onload="loaded('Stylesheet with SRI')"
+ onerror="blocked('Stylesheet with SRI')">
+ <script src="https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?resource=script"
+ onload="loaded('Script without SRI')"
+ onerror="blocked('Script without SRI')"></script>
+ <script src="https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?resource=script"
+ integrity="sha384-zDCkvKOHXk8mM6Nk07oOGXGME17PA4+ydFw+hq0r9kgF6ZDYFWK3fLGPEy7FoOAo"
+ onload="loaded('Script with SRI')"
+ onerror="blocked('Script with SRI')"></script>
+</body>
+</html>
diff --git a/dom/security/test/contentverifier/file_about_newtab_sri_signature b/dom/security/test/contentverifier/file_about_newtab_sri_signature
new file mode 100644
index 000000000..b7ac17944
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_sri_signature
@@ -0,0 +1 @@
+yoIyAYiiEzdP1zpkRy3KaqdsjUy62Notku89cytwVwcH0x6fKsMCdM-df1wbk9N28CSTaIOW5kcSenFy5K3nU-zPIoqZDjQo6aSjF8hF6lrw1a1xbhfl9K3g4YJsuWsO \ No newline at end of file
diff --git a/dom/security/test/contentverifier/file_contentserver.sjs b/dom/security/test/contentverifier/file_contentserver.sjs
new file mode 100644
index 000000000..3ea49cdde
--- /dev/null
+++ b/dom/security/test/contentverifier/file_contentserver.sjs
@@ -0,0 +1,261 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form 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/. */
+// sjs for remote about:newtab (bug 1226928)
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.importGlobalProperties(["URLSearchParams"]);
+
+const path = "browser/dom/security/test/contentverifier/";
+
+const goodFileName = "file_about_newtab.html";
+const goodFileBase = path + goodFileName;
+const goodFile = FileUtils.getDir("TmpD", [], true);
+goodFile.append(goodFileName);
+const goodSignature = path + "file_about_newtab_good_signature";
+const goodX5UString = "\"https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=default\"";
+
+const scriptFileName = "script.js";
+const cssFileName = "style.css";
+const badFile = path + "file_about_newtab_bad.html";
+const brokenSignature = path + "file_about_newtab_broken_signature";
+const badSignature = path + "file_about_newtab_bad_signature";
+const badX5UString = "\"https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=bad\"";
+const httpX5UString = "\"http://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=default\"";
+
+const sriFile = path + "file_about_newtab_sri.html";
+const sriSignature = path + "file_about_newtab_sri_signature";
+
+const badCspFile = path + "file_about_newtab_bad_csp.html";
+const badCspSignature = path + "file_about_newtab_bad_csp_signature";
+
+// This cert chain is copied from
+// security/manager/ssl/tests/unit/test_content_signing/
+// using the certificates
+// * content_signing_remote_newtab_ee.pem
+// * content_signing_int.pem
+// * content_signing_root.pem
+const goodCertChainPath = path + "goodChain.pem";
+
+const tempFileNames = [goodFileName, scriptFileName, cssFileName];
+
+// we copy the file to serve as newtab to a temp directory because
+// we modify it during tests.
+setupTestFiles();
+
+function setupTestFiles() {
+ for (let fileName of tempFileNames) {
+ let tempFile = FileUtils.getDir("TmpD", [], true);
+ tempFile.append(fileName);
+ if (!tempFile.exists()) {
+ let fileIn = getFileName(path + fileName, "CurWorkD");
+ fileIn.copyTo(FileUtils.getDir("TmpD", [], true), "");
+ }
+ }
+}
+
+function getFileName(filePath, dir) {
+ // Since it's relative to the cwd of the test runner, we start there and
+ // append to get to the actual path of the file.
+ let testFile =
+ Cc["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get(dir, Components.interfaces.nsILocalFile);
+ let dirs = filePath.split("/");
+ for (let i = 0; i < dirs.length; i++) {
+ testFile.append(dirs[i]);
+ }
+ return testFile;
+}
+
+function loadFile(file) {
+ // Load a file to return it.
+ let testFileStream =
+ Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Components.interfaces.nsIFileInputStream);
+ testFileStream.init(file, -1, 0, 0);
+ return NetUtil.readInputStreamToString(testFileStream,
+ testFileStream.available());
+}
+
+function appendToFile(aFile, content) {
+ try {
+ let file = FileUtils.openFileOutputStream(aFile, FileUtils.MODE_APPEND |
+ FileUtils.MODE_WRONLY);
+ file.write(content, content.length);
+ file.close();
+ } catch (e) {
+ dump(">>> Error in appendToFile "+e);
+ return "Error";
+ }
+ return "Done";
+}
+
+function truncateFile(aFile, length) {
+ let fileIn = loadFile(aFile);
+ fileIn = fileIn.slice(0, -length);
+
+ try {
+ let file = FileUtils.openFileOutputStream(aFile, FileUtils.MODE_WRONLY |
+ FileUtils.MODE_TRUNCATE);
+ file.write(fileIn, fileIn.length);
+ file.close();
+ } catch (e) {
+ dump(">>> Error in truncateFile "+e);
+ return "Error";
+ }
+ return "Done";
+}
+
+function cleanupTestFiles() {
+ for (let fileName of tempFileNames) {
+ let tempFile = FileUtils.getDir("TmpD", [], true);
+ tempFile.append(fileName);
+ tempFile.remove(true);
+ }
+}
+
+/*
+ * handle requests of the following form:
+ * sig=good&key=good&file=good&header=good&cached=no to serve pages with
+ * content signatures
+ *
+ * it further handles invalidateFile=yep and validateFile=yep to change the
+ * served file
+ */
+function handleRequest(request, response) {
+ let params = new URLSearchParams(request.queryString);
+ let x5uType = params.get("x5u");
+ let signatureType = params.get("sig");
+ let fileType = params.get("file");
+ let headerType = params.get("header");
+ let cached = params.get("cached");
+ let invalidateFile = params.get("invalidateFile");
+ let validateFile = params.get("validateFile");
+ let resource = params.get("resource");
+ let x5uParam = params.get("x5u");
+
+ if (params.get("cleanup")) {
+ cleanupTestFiles();
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("Done");
+ return;
+ }
+
+ if (resource) {
+ if (resource == "script") {
+ response.setHeader("Content-Type", "application/javascript", false);
+ response.write(loadFile(getFileName(scriptFileName, "TmpD")));
+ } else { // resource == "css1" || resource == "css2"
+ response.setHeader("Content-Type", "text/css", false);
+ response.write(loadFile(getFileName(cssFileName, "TmpD")));
+ }
+ return;
+ }
+
+ // if invalidateFile is set, this doesn't actually return a newtab page
+ // but changes the served file to invalidate the signature
+ // NOTE: make sure to make the file valid again afterwards!
+ if (invalidateFile) {
+ let r = "Done";
+ for (let fileName of tempFileNames) {
+ if (appendToFile(getFileName(fileName, "TmpD"), "!") != "Done") {
+ r = "Error";
+ }
+ }
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(r);
+ return;
+ }
+
+ // if validateFile is set, this doesn't actually return a newtab page
+ // but changes the served file to make the signature valid again
+ if (validateFile) {
+ let r = "Done";
+ for (let fileName of tempFileNames) {
+ if (truncateFile(getFileName(fileName, "TmpD"), 1) != "Done") {
+ r = "Error";
+ }
+ }
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(r);
+ return;
+ }
+
+ // we have to return the certificate chain on request for the x5u parameter
+ if (x5uParam && x5uParam == "default") {
+ response.setHeader("Cache-Control", "max-age=216000", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(loadFile(getFileName(goodCertChainPath, "CurWorkD")));
+ return;
+ }
+
+ // avoid confusing cache behaviours
+ if (!cached) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ } else {
+ response.setHeader("Cache-Control", "max-age=3600", false);
+ }
+
+ // send HTML to test allowed/blocked behaviours
+ response.setHeader("Content-Type", "text/html", false);
+
+ // set signature header and key for Content-Signature header
+ /* By default a good content-signature header is returned. Any broken return
+ * value has to be indicated in the url.
+ */
+ let csHeader = "";
+ let x5uString = goodX5UString;
+ let signature = goodSignature;
+ let file = goodFile;
+ if (x5uType == "bad") {
+ x5uString = badX5UString;
+ } else if (x5uType == "http") {
+ x5uString = httpX5UString;
+ }
+ if (signatureType == "bad") {
+ signature = badSignature;
+ } else if (signatureType == "broken") {
+ signature = brokenSignature;
+ } else if (signatureType == "sri") {
+ signature = sriSignature;
+ } else if (signatureType == "bad-csp") {
+ signature = badCspSignature;
+ }
+ if (fileType == "bad") {
+ file = getFileName(badFile, "CurWorkD");
+ } else if (fileType == "sri") {
+ file = getFileName(sriFile, "CurWorkD");
+ } else if (fileType == "bad-csp") {
+ file = getFileName(badCspFile, "CurWorkD");
+ }
+
+ if (headerType == "good") {
+ // a valid content-signature header
+ csHeader = "x5u=" + x5uString + ";p384ecdsa=" +
+ loadFile(getFileName(signature, "CurWorkD"));
+ } else if (headerType == "error") {
+ // this content-signature header is missing ; before p384ecdsa
+ csHeader = "x5u=" + x5uString + "p384ecdsa=" +
+ loadFile(getFileName(signature, "CurWorkD"));
+ } else if (headerType == "errorInX5U") {
+ // this content-signature header is missing the keyid directive
+ csHeader = "x6u=" + x5uString + ";p384ecdsa=" +
+ loadFile(getFileName(signature, "CurWorkD"));
+ } else if (headerType == "errorInSignature") {
+ // this content-signature header is missing the p384ecdsa directive
+ csHeader = "x5u=" + x5uString + ";p385ecdsa=" +
+ loadFile(getFileName(signature, "CurWorkD"));
+ }
+
+ if (csHeader) {
+ response.setHeader("Content-Signature", csHeader, false);
+ }
+ let result = loadFile(file);
+
+ response.write(result);
+}
diff --git a/dom/security/test/contentverifier/goodChain.pem b/dom/security/test/contentverifier/goodChain.pem
new file mode 100644
index 000000000..d5047cfc2
--- /dev/null
+++ b/dom/security/test/contentverifier/goodChain.pem
@@ -0,0 +1,51 @@
+-----BEGIN CERTIFICATE-----
+MIICUzCCAT2gAwIBAgIUJ1BtYqWRwUsVaZCGPp9eTHIC04QwCwYJKoZIhvcNAQEL
+MBExDzANBgNVBAMMBmludC1DQTAiGA8yMDE1MTEyODAwMDAwMFoYDzIwMTgwMjA1
+MDAwMDAwWjAUMRIwEAYDVQQDDAllZS1pbnQtQ0EwdjAQBgcqhkjOPQIBBgUrgQQA
+IgNiAAShaHJDNitcexiJ83kVRhWhxz+0je6GPgIpFdtgjiUt5LcTLajOmOgxU05q
+nAwLCcjWOa3oMgbluoE0c6EfozDgXajJbkOD/ieHPalxA74oiM/wAvBa9xof3cyD
+dKpuqc6jTjBMMBMGA1UdJQQMMAoGCCsGAQUFBwMDMDUGA1UdEQQuMCyCKnJlbW90
+ZW5ld3RhYi5jb250ZW50LXNpZ25hdHVyZS5tb3ppbGxhLm9yZzALBgkqhkiG9w0B
+AQsDggEBALiLck6k50ok9ahVq45P3feY1PeUXcIYZkJd8aPDYM+0kfg5+JyJBykA
+mtHWPE1QQjs7VRMfaLfu04E4UJMI2V1AON1qtgR9BQLctW85KFACg2omfiCKwJh0
+5Q8cxBFx9BpNMayqLJwHttB6oluxZFTB8CL/hfpbYpHz1bMEDCVSRP588YBrc8mV
+OLqzQK+k3ewwGvfD6SvXmTny37MxqwxdTPFJNnpqzKAsQIvz8Skic9BkA1NFk0Oq
+lsKKoiibbOCmwS9XY/laAkBaC3winuhciYAC0ImAopZ4PBCU0AOHGrNbhZXWYQxt
+uHBj34FqvIRCqgM06JCEwN0ULgix4kI=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC0TCCAbugAwIBAgIUPcKbBQpKwTzrrlqzM+d3z5DWiNUwCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTUxMTI4MDAwMDAwWhgPMjAxODAyMDUwMDAw
+MDBaMBExDzANBgNVBAMMBmludC1DQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALqIUahEjhbWQf1utogGNhA9PBPZ6uQ1SrTs9WhXbCR7wcclqODYH72x
+nAabbhqG8mvir1p1a2pkcQh6pVqnRYf3HNUknAJ+zUP8HmnQOCApk6sgw0nk27lM
+wmtsDu0Vgg/xfq1pGrHTAjqLKkHup3DgDw2N/WYLK7AkkqR9uYhheZCxV5A90jvF
+4LhIH6g304hD7ycW2FW3ZlqqfgKQLzp7EIAGJMwcbJetlmFbt+KWEsB1MaMMkd20
+yvf8rR0l0wnvuRcOp2jhs3svIm9p47SKlWEd7ibWJZ2rkQhONsscJAQsvxaLL+Xx
+j5kXMbiz/kkj+nJRxDHVA6zaGAo17Y0CAwEAAaMlMCMwDAYDVR0TBAUwAwEB/zAT
+BgNVHSUEDDAKBggrBgEFBQcDAzALBgkqhkiG9w0BAQsDggEBADDPjITgz8joxLRW
+wpLxELKSgO/KQ6iAXztjMHq9ovT7Fy0fqBnQ1mMVFr+sBXLgtUCM45aip6PjhUXc
+zs5Dq5STg+kz7qtmAjEQvOPcyictbgdu/K7+uMhXQhlzhOgyW88Uk5vrAezNTc/e
+TvSmWp1FcgVAfaeMN/90nzD1KIHoUt7zqZIz9ub8jXPVzQNZq4vh33smZhmbdTdV
+DaHUyef5cR1VTEGB+L1qzUIQqpHmD4UkMNP1nYedWfauiQhRt6Ql3rJSCRuEvsOA
+iBTJlwai/EFwfyfHkOV2GNgv+A5wHHEjBtF5c4PCxQEL5Vw+mfZHLsDVqF3279ZY
+lQ6jQ9g=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIICzTCCAbegAwIBAgIUKRLJoCmk0A6PHrNc8CxFn//4BYcwCwYJKoZIhvcNAQEL
+MA0xCzAJBgNVBAMMAmNhMCIYDzIwMTUxMTI4MDAwMDAwWhgPMjAxODAyMDUwMDAw
+MDBaMA0xCzAJBgNVBAMMAmNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAuohRqESOFtZB/W62iAY2ED08E9nq5DVKtOz1aFdsJHvBxyWo4NgfvbGcBptu
+Gobya+KvWnVramRxCHqlWqdFh/cc1SScAn7NQ/weadA4ICmTqyDDSeTbuUzCa2wO
+7RWCD/F+rWkasdMCOosqQe6ncOAPDY39ZgsrsCSSpH25iGF5kLFXkD3SO8XguEgf
+qDfTiEPvJxbYVbdmWqp+ApAvOnsQgAYkzBxsl62WYVu34pYSwHUxowyR3bTK9/yt
+HSXTCe+5Fw6naOGzey8ib2njtIqVYR3uJtYlnauRCE42yxwkBCy/Fosv5fGPmRcx
+uLP+SSP6clHEMdUDrNoYCjXtjQIDAQABoyUwIzAMBgNVHRMEBTADAQH/MBMGA1Ud
+JQQMMAoGCCsGAQUFBwMDMAsGCSqGSIb3DQEBCwOCAQEAABgMK6EyVIXTjD5qaxPO
+DWz6yREACmAQBcowKWvfhwgi27DPSXyFGDbzTPEo+7RrIcXJkVAhLouGT51fCwTZ
+zb6Sgf6ztX7VSppY9AT4utvlZKP1xQ5WhIYsMtdHCHLHIkRjeWyoBEfUx50UXNLK
+Snl+A02GKYWiX+TLLg2DPN2s7v/mm8NLMQNgUlL7KakB2FHFyPa8otPpL4llg7UJ
+iBTVQ0c3JoiVbwZaY1Z8QinfMXUrTK9egUC4BAcId1dE8glzA5RRlw1fTLWpGApt
+hUmbDnl9N2a9NhGX323ypNzIATexafipzWe7bc4u/+bFdrUqnKUoEka73pZBdHdA
+FQ==
+-----END CERTIFICATE-----
diff --git a/dom/security/test/contentverifier/head.js b/dom/security/test/contentverifier/head.js
new file mode 100644
index 000000000..d9637d18b
--- /dev/null
+++ b/dom/security/test/contentverifier/head.js
@@ -0,0 +1,210 @@
+/*
+ * Test Content-Signature for remote about:newtab
+ * - Bug 1226928 - allow about:newtab to load remote content
+ *
+ * This tests content-signature verification on remote about:newtab in the
+ * following cases (see TESTS, all failed loads display about:blank fallback):
+ * - good case (signature should verify and correct page is displayed)
+ * - reload of newtab when the siganture was invalidated after the last correct
+ * load
+ * - malformed content-signature header
+ * - malformed keyid directive
+ * - malformed p384ecdsa directive
+ * - wrong signature (this is not a siganture for the delivered document)
+ * - invalid signature (this is not even a signature)
+ * - loading a file that doesn't fit the key or signature
+ * - cache poisoning (load a malicious remote page not in newtab, subsequent
+ * newtab load has to load the fallback)
+ */
+
+const ABOUT_NEWTAB_URI = "about:newtab";
+
+const BASE = "https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?";
+const URI_GOOD = BASE + "sig=good&x5u=good&file=good&header=good";
+
+const INVALIDATE_FILE = BASE + "invalidateFile=yep";
+const VALIDATE_FILE = BASE + "validateFile=yep";
+
+const URI_HEADER_BASE = BASE + "sig=good&x5u=good&file=good&header=";
+const URI_ERROR_HEADER = URI_HEADER_BASE + "error";
+const URI_KEYERROR_HEADER = URI_HEADER_BASE + "errorInX5U";
+const URI_SIGERROR_HEADER = URI_HEADER_BASE + "errorInSignature";
+const URI_NO_HEADER = URI_HEADER_BASE + "noHeader";
+
+const URI_BAD_SIG = BASE + "sig=bad&x5u=good&file=good&header=good";
+const URI_BROKEN_SIG = BASE + "sig=broken&x5u=good&file=good&header=good";
+const URI_BAD_X5U = BASE + "sig=good&x5u=bad&file=good&header=good";
+const URI_HTTP_X5U = BASE + "sig=good&x5u=http&file=good&header=good";
+const URI_BAD_FILE = BASE + "sig=good&x5u=good&file=bad&header=good";
+const URI_BAD_ALL = BASE + "sig=bad&x5u=bad&file=bad&header=bad";
+const URI_BAD_CSP = BASE + "sig=bad-csp&x5u=good&file=bad-csp&header=good";
+
+const URI_BAD_FILE_CACHED = BASE + "sig=good&x5u=good&file=bad&header=good&cached=true";
+
+const GOOD_ABOUT_STRING = "Just a fully good testpage for Bug 1226928";
+const BAD_ABOUT_STRING = "Just a bad testpage for Bug 1226928";
+const ABOUT_BLANK = "<head></head><body></body>";
+
+const URI_CLEANUP = BASE + "cleanup=true";
+const CLEANUP_DONE = "Done";
+
+const URI_SRI = BASE + "sig=sri&x5u=good&file=sri&header=good";
+const STYLESHEET_WITHOUT_SRI_BLOCKED = "Stylesheet without SRI blocked";
+const STYLESHEET_WITH_SRI_BLOCKED = "Stylesheet with SRI blocked";
+const STYLESHEET_WITH_SRI_LOADED = "Stylesheet with SRI loaded";
+const SCRIPT_WITHOUT_SRI_BLOCKED = "Script without SRI blocked";
+const SCRIPT_WITH_SRI_BLOCKED = "Script with SRI blocked";
+const SCRIPT_WITH_SRI_LOADED = "Script with SRI loaded";
+
+const CSP_TEST_SUCCESS_STRING = "CSP violation test succeeded.";
+
+// Needs to sync with pref "security.signed_content.CSP.default".
+const SIGNED_CONTENT_CSP = `{"csp-policies":[{"report-only":false,"script-src":["https://example.com","'unsafe-inline'"],"style-src":["https://example.com"]}]}`;
+
+var browser = null;
+var aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
+ .getService(Ci.nsIAboutNewTabService);
+
+function pushPrefs(...aPrefs) {
+ return new Promise((resolve) => {
+ SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve);
+ });
+}
+
+/*
+ * run tests with input from TESTS
+ */
+function doTest(aExpectedStrings, reload, aUrl, aNewTabPref) {
+ // set about:newtab location for this test if it's a newtab test
+ if (aNewTabPref) {
+ aboutNewTabService.newTabURL = aNewTabPref;
+ }
+
+ // set prefs
+ yield pushPrefs(
+ ["browser.newtabpage.remote.content-signing-test", true],
+ ["browser.newtabpage.remote", true],
+ ["security.content.signature.root_hash",
+ "CC:BE:04:87:74:B2:98:24:4A:C6:7A:71:BC:6F:DB:D6:C0:48:17:29:57:51:96:47:38:CC:24:C8:E4:F9:DD:CB"]);
+
+ if (aNewTabPref === URI_BAD_CSP) {
+ // Use stricter CSP to test CSP violation.
+ yield pushPrefs(["security.signed_content.CSP.default", "script-src 'self'; style-src 'self'"]);
+ } else {
+ // Use weaker CSP to test normal content.
+ yield pushPrefs(["security.signed_content.CSP.default", "script-src 'self' 'unsafe-inline'; style-src 'self'"]);
+ }
+
+ // start the test
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: aUrl,
+ },
+ function * (browser) {
+ // check if everything's set correct for testing
+ ok(Services.prefs.getBoolPref(
+ "browser.newtabpage.remote.content-signing-test"),
+ "sanity check: remote newtab signing test should be used");
+ ok(Services.prefs.getBoolPref("browser.newtabpage.remote"),
+ "sanity check: remote newtab should be used");
+ // we only check this if we really do a newtab test
+ if (aNewTabPref) {
+ ok(aboutNewTabService.overridden,
+ "sanity check: default URL for about:newtab should be overriden");
+ is(aboutNewTabService.newTabURL, aNewTabPref,
+ "sanity check: default URL for about:newtab should return the new URL");
+ }
+
+ // Every valid remote newtab page must have built-in CSP.
+ let shouldHaveCSP = ((aUrl === ABOUT_NEWTAB_URI) &&
+ (aNewTabPref === URI_GOOD || aNewTabPref === URI_SRI));
+
+ if (shouldHaveCSP) {
+ is(browser.contentDocument.nodePrincipal.cspJSON, SIGNED_CONTENT_CSP,
+ "Valid remote newtab page must have built-in CSP.");
+ }
+
+ yield ContentTask.spawn(
+ browser, aExpectedStrings, function * (aExpectedStrings) {
+ for (let expectedString of aExpectedStrings) {
+ ok(content.document.documentElement.innerHTML.includes(expectedString),
+ "Expect the following value in the result\n" + expectedString +
+ "\nand got " + content.document.documentElement.innerHTML);
+ }
+ });
+
+ // for good test cases we check if a reload fails if the remote page
+ // changed from valid to invalid in the meantime
+ if (reload) {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: INVALIDATE_FILE,
+ },
+ function * (browser2) {
+ yield ContentTask.spawn(browser2, null, function * () {
+ ok(content.document.documentElement.innerHTML.includes("Done"),
+ "Expect the following value in the result\n" + "Done" +
+ "\nand got " + content.document.documentElement.innerHTML);
+ });
+ }
+ );
+
+ browser.reload();
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ let expectedStrings = [ABOUT_BLANK];
+ if (aNewTabPref == URI_SRI) {
+ expectedStrings = [
+ STYLESHEET_WITHOUT_SRI_BLOCKED,
+ STYLESHEET_WITH_SRI_BLOCKED,
+ SCRIPT_WITHOUT_SRI_BLOCKED,
+ SCRIPT_WITH_SRI_BLOCKED
+ ];
+ }
+ yield ContentTask.spawn(browser, expectedStrings,
+ function * (expectedStrings) {
+ for (let expectedString of expectedStrings) {
+ ok(content.document.documentElement.innerHTML.includes(expectedString),
+ "Expect the following value in the result\n" + expectedString +
+ "\nand got " + content.document.documentElement.innerHTML);
+ }
+ }
+ );
+
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: VALIDATE_FILE,
+ },
+ function * (browser2) {
+ yield ContentTask.spawn(browser2, null, function * () {
+ ok(content.document.documentElement.innerHTML.includes("Done"),
+ "Expect the following value in the result\n" + "Done" +
+ "\nand got " + content.document.documentElement.innerHTML);
+ });
+ }
+ );
+ }
+ }
+ );
+}
+
+function runTests() {
+ // run tests from TESTS
+ for (let i = 0; i < TESTS.length; i++) {
+ let testCase = TESTS[i];
+ let url = "", aNewTabPref = "";
+ let reload = false;
+ var aExpectedStrings = testCase.testStrings;
+ if (testCase.aboutURI) {
+ url = ABOUT_NEWTAB_URI;
+ aNewTabPref = testCase.aboutURI;
+ if (aNewTabPref == URI_GOOD || aNewTabPref == URI_SRI) {
+ reload = true;
+ }
+ } else {
+ url = testCase.url;
+ }
+
+ yield doTest(aExpectedStrings, reload, url, aNewTabPref);
+ }
+}
diff --git a/dom/security/test/contentverifier/script.js b/dom/security/test/contentverifier/script.js
new file mode 100644
index 000000000..8fd8f96b2
--- /dev/null
+++ b/dom/security/test/contentverifier/script.js
@@ -0,0 +1 @@
+var load=true;
diff --git a/dom/security/test/contentverifier/signature.der b/dom/security/test/contentverifier/signature.der
new file mode 100644
index 000000000..011b94142
--- /dev/null
+++ b/dom/security/test/contentverifier/signature.der
Binary files differ
diff --git a/dom/security/test/contentverifier/sk.pem b/dom/security/test/contentverifier/sk.pem
new file mode 100644
index 000000000..2ed514b9f
--- /dev/null
+++ b/dom/security/test/contentverifier/sk.pem
@@ -0,0 +1,9 @@
+-----BEGIN EC PARAMETERS-----
+BgUrgQQAIg==
+-----END EC PARAMETERS-----
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDAzX2TrGOr0WE92AbAl+nqnpqh25pKCLYNMTV2hJHztrkVPWOp8w0mh
+scIodK8RMpagBwYFK4EEACKhZANiAATiTcWYbt0Wg63dO7OXvpptNG0ryxv+v+Js
+JJ5Upr3pFus5fZyKxzP9NPzB+oFhL/xw3jMx7X5/vBGaQ2sJSiNlHVkqZgzYF6JQ
+4yUyiqTY7v67CyfUPA1BJg/nxOS9m3o=
+-----END EC PRIVATE KEY-----
diff --git a/dom/security/test/contentverifier/style.css b/dom/security/test/contentverifier/style.css
new file mode 100644
index 000000000..c7ab9ecff
--- /dev/null
+++ b/dom/security/test/contentverifier/style.css
@@ -0,0 +1,3 @@
+#red-text {
+ color: red;
+}
diff --git a/dom/security/test/cors/file_CrossSiteXHR_cache_server.sjs b/dom/security/test/cors/file_CrossSiteXHR_cache_server.sjs
new file mode 100644
index 000000000..8ee4ddbf5
--- /dev/null
+++ b/dom/security/test/cors/file_CrossSiteXHR_cache_server.sjs
@@ -0,0 +1,49 @@
+function handleRequest(request, response)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var [name, value] = val.split('=');
+ query[name] = unescape(value);
+ });
+
+ if ("setState" in query) {
+ setState("test/dom/security/test_CrossSiteXHR_cache:secData",
+ query.setState);
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write("hi");
+
+ return;
+ }
+
+ var isPreflight = request.method == "OPTIONS";
+
+ // Send response
+
+ secData =
+ eval(getState("test/dom/security/test_CrossSiteXHR_cache:secData"));
+
+ if (secData.allowOrigin)
+ response.setHeader("Access-Control-Allow-Origin", secData.allowOrigin);
+
+ if (secData.withCred)
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+
+ if (isPreflight) {
+ if (secData.allowHeaders)
+ response.setHeader("Access-Control-Allow-Headers", secData.allowHeaders);
+
+ if (secData.allowMethods)
+ response.setHeader("Access-Control-Allow-Methods", secData.allowMethods);
+
+ if (secData.cacheTime)
+ response.setHeader("Access-Control-Max-Age", secData.cacheTime.toString());
+
+ return;
+ }
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "application/xml", false);
+ response.write("<res>hello pass</res>\n");
+}
diff --git a/dom/security/test/cors/file_CrossSiteXHR_inner.html b/dom/security/test/cors/file_CrossSiteXHR_inner.html
new file mode 100644
index 000000000..9268f0ed7
--- /dev/null
+++ b/dom/security/test/cors/file_CrossSiteXHR_inner.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML>
+<!--
+ NOTE! The content of this file is duplicated in file_CrossSiteXHR_inner.jar
+ and file_CrossSiteXHR_inner_data.sjs
+ Please update those files if you update this one.
+-->
+
+<html>
+<head>
+<script>
+function trimString(stringValue) {
+ return stringValue.replace(/^\s+|\s+$/g, '');
+};
+
+window.addEventListener("message", function(e) {
+
+ sendData = null;
+
+ req = eval(e.data);
+ var res = {
+ didFail: false,
+ events: [],
+ progressEvents: 0,
+ status: 0,
+ responseText: "",
+ statusText: "",
+ responseXML: null,
+ sendThrew: false
+ };
+
+ var xhr = new XMLHttpRequest();
+ for (type of ["load", "abort", "error", "loadstart", "loadend"]) {
+ xhr.addEventListener(type, function(e) {
+ res.events.push(e.type);
+ }, false);
+ }
+ xhr.addEventListener("readystatechange", function(e) {
+ res.events.push("rs" + xhr.readyState);
+ }, false);
+ xhr.addEventListener("progress", function(e) {
+ res.progressEvents++;
+ }, false);
+ if (req.uploadProgress) {
+ xhr.upload.addEventListener(req.uploadProgress, function(e) {
+ res.progressEvents++;
+ }, false);
+ }
+ xhr.onerror = function(e) {
+ res.didFail = true;
+ };
+ xhr.onloadend = function (event) {
+ res.status = xhr.status;
+ try {
+ res.statusText = xhr.statusText;
+ } catch (e) {
+ delete(res.statusText);
+ }
+ res.responseXML = xhr.responseXML ?
+ (new XMLSerializer()).serializeToString(xhr.responseXML) :
+ null;
+ res.responseText = xhr.responseText;
+
+ res.responseHeaders = {};
+ for (responseHeader in req.responseHeaders) {
+ res.responseHeaders[responseHeader] =
+ xhr.getResponseHeader(responseHeader);
+ }
+ res.allResponseHeaders = {};
+ var splitHeaders = xhr.getAllResponseHeaders().split("\r\n");
+ for (var i = 0; i < splitHeaders.length; i++) {
+ var headerValuePair = splitHeaders[i].split(":");
+ if(headerValuePair[1] != null) {
+ var headerName = trimString(headerValuePair[0]);
+ var headerValue = trimString(headerValuePair[1]);
+ res.allResponseHeaders[headerName] = headerValue;
+ }
+ }
+ post(e, res);
+ }
+
+ if (req.withCred)
+ xhr.withCredentials = true;
+ if (req.body)
+ sendData = req.body;
+
+ res.events.push("opening");
+ // Allow passign in falsy usernames/passwords so we can test them
+ try {
+ xhr.open(req.method, req.url, true,
+ ("username" in req) ? req.username : "",
+ ("password" in req) ? req.password : "aa");
+ } catch (ex) {
+ res.didFail = true;
+ post(e, res);
+ }
+
+ for (header in req.headers) {
+ xhr.setRequestHeader(header, req.headers[header]);
+ }
+
+ res.events.push("sending");
+ try {
+ xhr.send(sendData);
+ } catch (ex) {
+ res.didFail = true;
+ res.sendThrew = true;
+ post(e, res);
+ }
+
+}, false);
+
+function post(e, res) {
+ e.source.postMessage(res.toSource(), "http://mochi.test:8888");
+}
+
+</script>
+</head>
+<body>
+Inner page
+</body>
+</html>
diff --git a/dom/security/test/cors/file_CrossSiteXHR_inner.jar b/dom/security/test/cors/file_CrossSiteXHR_inner.jar
new file mode 100644
index 000000000..bdb0eb440
--- /dev/null
+++ b/dom/security/test/cors/file_CrossSiteXHR_inner.jar
Binary files differ
diff --git a/dom/security/test/cors/file_CrossSiteXHR_inner_data.sjs b/dom/security/test/cors/file_CrossSiteXHR_inner_data.sjs
new file mode 100644
index 000000000..2908921e2
--- /dev/null
+++ b/dom/security/test/cors/file_CrossSiteXHR_inner_data.sjs
@@ -0,0 +1,103 @@
+var data = '<!DOCTYPE HTML>\n\
+<html>\n\
+<head>\n\
+<script>\n\
+window.addEventListener("message", function(e) {\n\
+\n\
+ sendData = null;\n\
+\n\
+ req = eval(e.data);\n\
+ var res = {\n\
+ didFail: false,\n\
+ events: [],\n\
+ progressEvents: 0\n\
+ };\n\
+ \n\
+ var xhr = new XMLHttpRequest();\n\
+ for (type of ["load", "abort", "error", "loadstart", "loadend"]) {\n\
+ xhr.addEventListener(type, function(e) {\n\
+ res.events.push(e.type);\n\
+ }, false);\n\
+ }\n\
+ xhr.addEventListener("readystatechange", function(e) {\n\
+ res.events.push("rs" + xhr.readyState);\n\
+ }, false);\n\
+ xhr.addEventListener("progress", function(e) {\n\
+ res.progressEvents++;\n\
+ }, false);\n\
+ if (req.uploadProgress) {\n\
+ xhr.upload.addEventListener(req.uploadProgress, function(e) {\n\
+ res.progressEvents++;\n\
+ }, false);\n\
+ }\n\
+ xhr.onerror = function(e) {\n\
+ res.didFail = true;\n\
+ };\n\
+ xhr.onloadend = function (event) {\n\
+ res.status = xhr.status;\n\
+ try {\n\
+ res.statusText = xhr.statusText;\n\
+ } catch (e) {\n\
+ delete(res.statusText);\n\
+ }\n\
+ res.responseXML = xhr.responseXML ?\n\
+ (new XMLSerializer()).serializeToString(xhr.responseXML) :\n\
+ null;\n\
+ res.responseText = xhr.responseText;\n\
+\n\
+ res.responseHeaders = {};\n\
+ for (responseHeader in req.responseHeaders) {\n\
+ res.responseHeaders[responseHeader] =\n\
+ xhr.getResponseHeader(responseHeader);\n\
+ }\n\
+ res.allResponseHeaders = {};\n\
+ var splitHeaders = xhr.getAllResponseHeaders().split("\\r\\n");\n\
+ for (var i = 0; i < splitHeaders.length; i++) {\n\
+ var headerValuePair = splitHeaders[i].split(":");\n\
+ if(headerValuePair[1] != null){\n\
+ var headerName = trimString(headerValuePair[0]);\n\
+ var headerValue = trimString(headerValuePair[1]); \n\
+ res.allResponseHeaders[headerName] = headerValue;\n\
+ }\n\
+ }\n\
+ post(e, res);\n\
+ }\n\
+\n\
+ if (req.withCred)\n\
+ xhr.withCredentials = true;\n\
+ if (req.body)\n\
+ sendData = req.body;\n\
+\n\
+ res.events.push("opening");\n\
+ xhr.open(req.method, req.url, true);\n\
+\n\
+ for (header in req.headers) {\n\
+ xhr.setRequestHeader(header, req.headers[header]);\n\
+ }\n\
+\n\
+ res.events.push("sending");\n\
+ xhr.send(sendData);\n\
+\n\
+}, false);\n\
+\n\
+function post(e, res) {\n\
+ e.source.postMessage(res.toSource(), "*");\n\
+}\n\
+function trimString(stringValue) {\n\
+ return stringValue.replace("/^\s+|\s+$/g","");\n\
+};\n\
+\n\
+</script>\n\
+</head>\n\
+<body>\n\
+Inner page\n\
+</body>\n\
+</html>'
+
+function handleRequest(request, response)
+{
+ response.setStatusLine(null, 302, "Follow me");
+ response.setHeader("Location", "data:text/html," + escape(data));
+ response.setHeader("Content-Type", "text/plain");
+ response.write("Follow that guy!");
+}
diff --git a/dom/security/test/cors/file_CrossSiteXHR_server.sjs b/dom/security/test/cors/file_CrossSiteXHR_server.sjs
new file mode 100644
index 000000000..66d110468
--- /dev/null
+++ b/dom/security/test/cors/file_CrossSiteXHR_server.sjs
@@ -0,0 +1,179 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var [name, value] = val.split('=');
+ query[name] = unescape(value);
+ });
+
+ var isPreflight = request.method == "OPTIONS";
+
+ var bodyStream = new BinaryInputStream(request.bodyInputStream);
+ var bodyBytes = [];
+ while ((bodyAvail = bodyStream.available()) > 0)
+ Array.prototype.push.apply(bodyBytes, bodyStream.readByteArray(bodyAvail));
+
+ var body = decodeURIComponent(
+ escape(String.fromCharCode.apply(null, bodyBytes)));
+
+ if (query.hop) {
+ query.hop = parseInt(query.hop, 10);
+ hops = eval(query.hops);
+ var curHop = hops[query.hop - 1];
+ query.allowOrigin = curHop.allowOrigin;
+ query.allowHeaders = curHop.allowHeaders;
+ query.allowMethods = curHop.allowMethods;
+ query.allowCred = curHop.allowCred;
+ query.noAllowPreflight = curHop.noAllowPreflight;
+ if (curHop.setCookie) {
+ query.setCookie = unescape(curHop.setCookie);
+ }
+ if (curHop.cookie) {
+ query.cookie = unescape(curHop.cookie);
+ }
+ query.noCookie = curHop.noCookie;
+ }
+
+ // Check that request was correct
+
+ if (!isPreflight && query.body && body != query.body) {
+ sendHttp500(response, "Wrong body. Expected " + query.body + " got " +
+ body);
+ return;
+ }
+
+ if (!isPreflight && "headers" in query) {
+ headers = eval(query.headers);
+ for(headerName in headers) {
+ // Content-Type is changed if there was a body
+ if (!(headerName == "Content-Type" && body) &&
+ (!request.hasHeader(headerName) ||
+ request.getHeader(headerName) != headers[headerName])) {
+ var actual = request.hasHeader(headerName) ? request.getHeader(headerName)
+ : "<missing header>";
+ sendHttp500(response,
+ "Header " + headerName + " had wrong value. Expected " +
+ headers[headerName] + " got " + actual);
+ return;
+ }
+ }
+ }
+
+ if (isPreflight && "requestHeaders" in query &&
+ request.getHeader("Access-Control-Request-Headers") != query.requestHeaders) {
+ sendHttp500(response,
+ "Access-Control-Request-Headers had wrong value. Expected " +
+ query.requestHeaders + " got " +
+ request.getHeader("Access-Control-Request-Headers"));
+ return;
+ }
+
+ if (isPreflight && "requestMethod" in query &&
+ request.getHeader("Access-Control-Request-Method") != query.requestMethod) {
+ sendHttp500(response,
+ "Access-Control-Request-Method had wrong value. Expected " +
+ query.requestMethod + " got " +
+ request.getHeader("Access-Control-Request-Method"));
+ return;
+ }
+
+ if ("origin" in query && request.getHeader("Origin") != query.origin) {
+ sendHttp500(response,
+ "Origin had wrong value. Expected " + query.origin + " got " +
+ request.getHeader("Origin"));
+ return;
+ }
+
+ if ("cookie" in query) {
+ cookies = {};
+ request.getHeader("Cookie").split(/ *; */).forEach(function (val) {
+ var [name, value] = val.split('=');
+ cookies[name] = unescape(value);
+ });
+
+ query.cookie.split(",").forEach(function (val) {
+ var [name, value] = val.split('=');
+ if (cookies[name] != value) {
+ sendHttp500(response,
+ "Cookie " + name + " had wrong value. Expected " + value +
+ " got " + cookies[name]);
+ return;
+ }
+ });
+ }
+
+ if (query.noCookie && request.hasHeader("Cookie")) {
+ sendHttp500(response,
+ "Got cookies when didn't expect to: " + request.getHeader("Cookie"));
+ return;
+ }
+
+ // Send response
+
+ if (!isPreflight && query.status) {
+ response.setStatusLine(null, query.status, query.statusMessage);
+ }
+ if (isPreflight && query.preflightStatus) {
+ response.setStatusLine(null, query.preflightStatus, "preflight status");
+ }
+
+ if (query.allowOrigin && (!isPreflight || !query.noAllowPreflight))
+ response.setHeader("Access-Control-Allow-Origin", query.allowOrigin);
+
+ if (query.allowCred)
+ response.setHeader("Access-Control-Allow-Credentials", "true");
+
+ if (query.setCookie)
+ response.setHeader("Set-Cookie", query.setCookie + "; path=/");
+
+ if (isPreflight) {
+ if (query.allowHeaders)
+ response.setHeader("Access-Control-Allow-Headers", query.allowHeaders);
+
+ if (query.allowMethods)
+ response.setHeader("Access-Control-Allow-Methods", query.allowMethods);
+ }
+ else {
+ if (query.responseHeaders) {
+ let responseHeaders = eval(query.responseHeaders);
+ for (let responseHeader in responseHeaders) {
+ response.setHeader(responseHeader, responseHeaders[responseHeader]);
+ }
+ }
+
+ if (query.exposeHeaders)
+ response.setHeader("Access-Control-Expose-Headers", query.exposeHeaders);
+ }
+
+ if (!isPreflight && query.hop && query.hop < hops.length) {
+ newURL = hops[query.hop].server +
+ "/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?" +
+ "hop=" + (query.hop + 1) + "&hops=" + escape(query.hops);
+ if ("headers" in query) {
+ newURL += "&headers=" + escape(query.headers);
+ }
+ response.setStatusLine(null, 307, "redirect");
+ response.setHeader("Location", newURL);
+
+ return;
+ }
+
+ // Send response body
+ if (!isPreflight && request.method != "HEAD") {
+ response.setHeader("Content-Type", "application/xml", false);
+ response.write("<res>hello pass</res>\n");
+ }
+ if (isPreflight && "preflightBody" in query) {
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(query.preflightBody);
+ }
+}
+
+function sendHttp500(response, text) {
+ response.setStatusLine(null, 500, text);
+}
diff --git a/dom/security/test/cors/mochitest.ini b/dom/security/test/cors/mochitest.ini
new file mode 100644
index 000000000..095b0d09d
--- /dev/null
+++ b/dom/security/test/cors/mochitest.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ file_CrossSiteXHR_cache_server.sjs
+ file_CrossSiteXHR_inner.html
+ file_CrossSiteXHR_inner.jar
+ file_CrossSiteXHR_inner_data.sjs
+ file_CrossSiteXHR_server.sjs
+
+[test_CrossSiteXHR.html]
+[test_CrossSiteXHR_cache.html]
+[test_CrossSiteXHR_origin.html]
diff --git a/dom/security/test/cors/test_CrossSiteXHR.html b/dom/security/test/cors/test_CrossSiteXHR.html
new file mode 100644
index 000000000..b3cda3b87
--- /dev/null
+++ b/dom/security/test/cors/test_CrossSiteXHR.html
@@ -0,0 +1,1461 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
+ <title>Test for Cross Site XMLHttpRequest</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="initTest()">
+<p id="display">
+<iframe id=loader></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.8">
+
+const runPreflightTests = 1;
+const runCookieTests = 1;
+const runRedirectTests = 1;
+
+var gen;
+
+function initTest() {
+ SimpleTest.waitForExplicitFinish();
+ // Allow all cookies, then do the actual test initialization
+ SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0]]}, initTestCallback);
+}
+
+function initTestCallback() {
+ window.addEventListener("message", function(e) {
+ gen.send(e.data);
+ }, false);
+
+ gen = runTest();
+
+ gen.next()
+}
+
+function runTest() {
+ var loader = document.getElementById('loader');
+ var loaderWindow = loader.contentWindow;
+ loader.onload = function () { gen.next() };
+
+ // Test preflight-less requests
+ basePath = "/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?"
+ baseURL = "http://mochi.test:8888" + basePath;
+
+ // Test preflighted requests
+ loader.src = "http://example.org/tests/dom/security/test/cors/file_CrossSiteXHR_inner.html";
+ origin = "http://example.org";
+ yield undefined;
+
+ tests = [// Plain request
+ { pass: 1,
+ method: "GET",
+ noAllowPreflight: 1,
+ },
+
+ // undefined username
+ { pass: 1,
+ method: "GET",
+ noAllowPreflight: 1,
+ username: undefined
+ },
+
+ // undefined username and password
+ { pass: 1,
+ method: "GET",
+ noAllowPreflight: 1,
+ username: undefined,
+ password: undefined
+ },
+
+ // nonempty username
+ { pass: 0,
+ method: "GET",
+ noAllowPreflight: 1,
+ username: "user",
+ },
+
+ // nonempty password
+ // XXXbz this passes for now, because we ignore passwords
+ // without usernames in most cases.
+ { pass: 1,
+ method: "GET",
+ noAllowPreflight: 1,
+ password: "password",
+ },
+
+ // Default allowed headers
+ { pass: 1,
+ method: "GET",
+ headers: { "Content-Type": "text/plain",
+ "Accept": "foo/bar",
+ "Accept-Language": "sv-SE" },
+ noAllowPreflight: 1,
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "Content-Type": "foo/bar",
+ "Accept": "foo/bar",
+ "Accept-Language": "sv-SE" },
+ noAllowPreflight: 1,
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "Content-Type": "foo/bar, text/plain" },
+ noAllowPreflight: 1,
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "Content-Type": "foo/bar, text/plain, garbage" },
+ noAllowPreflight: 1,
+ },
+
+ // Custom headers
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "X-My-Header",
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue",
+ "long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header": "secondValue" },
+ allowHeaders: "x-my-header, long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header-long-header",
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my%-header": "myValue" },
+ allowHeaders: "x-my%-header",
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "" },
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "",
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "y-my-header",
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header y-my-header",
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header, y-my-header z",
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header, y-my-he(ader",
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "myheader": "" },
+ allowMethods: "myheader",
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "User-Agent": "myValue" },
+ allowHeaders: "User-Agent",
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "User-Agent": "myValue" },
+ },
+
+ // Multiple custom headers
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue",
+ "second-header": "secondValue",
+ "third-header": "thirdValue" },
+ allowHeaders: "x-my-header, second-header, third-header",
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue",
+ "second-header": "secondValue",
+ "third-header": "thirdValue" },
+ allowHeaders: "x-my-header,second-header,third-header",
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue",
+ "second-header": "secondValue",
+ "third-header": "thirdValue" },
+ allowHeaders: "x-my-header ,second-header ,third-header",
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue",
+ "second-header": "secondValue",
+ "third-header": "thirdValue" },
+ allowHeaders: "x-my-header , second-header , third-header",
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue",
+ "second-header": "secondValue" },
+ allowHeaders: ", x-my-header, , ,, second-header, , ",
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue",
+ "second-header": "secondValue" },
+ allowHeaders: "x-my-header, second-header, unused-header",
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue",
+ "y-my-header": "secondValue" },
+ allowHeaders: "x-my-header",
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "",
+ "y-my-header": "" },
+ allowHeaders: "x-my-header",
+ },
+
+ // HEAD requests
+ { pass: 1,
+ method: "HEAD",
+ noAllowPreflight: 1,
+ },
+
+ // HEAD with safe headers
+ { pass: 1,
+ method: "HEAD",
+ headers: { "Content-Type": "text/plain",
+ "Accept": "foo/bar",
+ "Accept-Language": "sv-SE" },
+ noAllowPreflight: 1,
+ },
+ { pass: 0,
+ method: "HEAD",
+ headers: { "Content-Type": "foo/bar",
+ "Accept": "foo/bar",
+ "Accept-Language": "sv-SE" },
+ noAllowPreflight: 1,
+ },
+ { pass: 0,
+ method: "HEAD",
+ headers: { "Content-Type": "foo/bar, text/plain" },
+ noAllowPreflight: 1,
+ },
+ { pass: 0,
+ method: "HEAD",
+ headers: { "Content-Type": "foo/bar, text/plain, garbage" },
+ noAllowPreflight: 1,
+ },
+
+ // HEAD with custom headers
+ { pass: 1,
+ method: "HEAD",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ },
+ { pass: 0,
+ method: "HEAD",
+ headers: { "x-my-header": "myValue" },
+ },
+ { pass: 0,
+ method: "HEAD",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "",
+ },
+ { pass: 0,
+ method: "HEAD",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "y-my-header",
+ },
+ { pass: 0,
+ method: "HEAD",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header y-my-header",
+ },
+
+ // POST tests
+ { pass: 1,
+ method: "POST",
+ body: "hi there",
+ noAllowPreflight: 1,
+ },
+ { pass: 1,
+ method: "POST",
+ },
+ { pass: 1,
+ method: "POST",
+ noAllowPreflight: 1,
+ },
+
+ // POST with standard headers
+ { pass: 1,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain" },
+ noAllowPreflight: 1,
+ },
+ { pass: 1,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "multipart/form-data" },
+ noAllowPreflight: 1,
+ },
+ { pass: 1,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
+ noAllowPreflight: 1,
+ },
+ { pass: 0,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "foo/bar" },
+ },
+ { pass: 0,
+ method: "POST",
+ headers: { "Content-Type": "foo/bar" },
+ },
+ { pass: 1,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain",
+ "Accept": "foo/bar",
+ "Accept-Language": "sv-SE" },
+ noAllowPreflight: 1,
+ },
+ { pass: 0,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "foo/bar, text/plain" },
+ noAllowPreflight: 1,
+ },
+ { pass: 0,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "foo/bar, text/plain, garbage" },
+ noAllowPreflight: 1,
+ },
+
+ // POST with custom headers
+ { pass: 1,
+ method: "POST",
+ body: "hi there",
+ headers: { "Accept": "foo/bar",
+ "Accept-Language": "sv-SE",
+ "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ },
+ { pass: 1,
+ method: "POST",
+ headers: { "Content-Type": "text/plain",
+ "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ },
+ { pass: 1,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain",
+ "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ },
+ { pass: 1,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "foo/bar",
+ "x-my-header": "myValue" },
+ allowHeaders: "x-my-header, content-type",
+ },
+ { pass: 0,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "foo/bar" },
+ noAllowPreflight: 1,
+ },
+ { pass: 0,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "foo/bar",
+ "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ },
+ { pass: 1,
+ method: "POST",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ },
+ { pass: 1,
+ method: "POST",
+ body: "hi there",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header, $_%",
+ },
+
+ // Other methods
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "DELETE",
+ },
+ { pass: 0,
+ method: "DELETE",
+ allowHeaders: "DELETE",
+ },
+ { pass: 0,
+ method: "DELETE",
+ },
+ { pass: 0,
+ method: "DELETE",
+ allowMethods: "",
+ },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "POST, PUT, DELETE",
+ },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "POST, DELETE, PUT",
+ },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "DELETE, POST, PUT",
+ },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "POST ,PUT ,DELETE",
+ },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "POST,PUT,DELETE",
+ },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "POST , PUT , DELETE",
+ },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: " ,, PUT ,, , , DELETE , ,",
+ },
+ { pass: 0,
+ method: "DELETE",
+ allowMethods: "PUT",
+ },
+ { pass: 0,
+ method: "DELETE",
+ allowMethods: "DELETEZ",
+ },
+ { pass: 0,
+ method: "DELETE",
+ allowMethods: "DELETE PUT",
+ },
+ { pass: 0,
+ method: "DELETE",
+ allowMethods: "DELETE, PUT Z",
+ },
+ { pass: 0,
+ method: "DELETE",
+ allowMethods: "DELETE, PU(T",
+ },
+ { pass: 0,
+ method: "DELETE",
+ allowMethods: "PUT DELETE",
+ },
+ { pass: 0,
+ method: "DELETE",
+ allowMethods: "PUT Z, DELETE",
+ },
+ { pass: 0,
+ method: "DELETE",
+ allowMethods: "PU(T, DELETE",
+ },
+ { pass: 0,
+ method: "MYMETHOD",
+ allowMethods: "myMethod",
+ },
+ { pass: 0,
+ method: "PUT",
+ allowMethods: "put",
+ },
+
+ // Progress events
+ { pass: 1,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain" },
+ uploadProgress: "progress",
+ },
+ { pass: 0,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain" },
+ uploadProgress: "progress",
+ noAllowPreflight: 1,
+ },
+
+ // Status messages
+ { pass: 1,
+ method: "GET",
+ noAllowPreflight: 1,
+ status: 404,
+ statusMessage: "nothin' here",
+ },
+ { pass: 1,
+ method: "GET",
+ noAllowPreflight: 1,
+ status: 401,
+ statusMessage: "no can do",
+ },
+ { pass: 1,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "foo/bar" },
+ allowHeaders: "content-type",
+ status: 500,
+ statusMessage: "server boo",
+ },
+ { pass: 1,
+ method: "GET",
+ noAllowPreflight: 1,
+ status: 200,
+ statusMessage: "Yes!!",
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "header value" },
+ allowHeaders: "x-my-header",
+ preflightStatus: 400
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "header value" },
+ allowHeaders: "x-my-header",
+ preflightStatus: 200
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "header value" },
+ allowHeaders: "x-my-header",
+ preflightStatus: 204
+ },
+
+ // exposed headers
+ { pass: 1,
+ method: "GET",
+ responseHeaders: { "x-my-header": "x header" },
+ exposeHeaders: "x-my-header",
+ expectedResponseHeaders: ["x-my-header"],
+ },
+ { pass: 0,
+ method: "GET",
+ origin: "http://invalid",
+ responseHeaders: { "x-my-header": "x header" },
+ exposeHeaders: "x-my-header",
+ expectedResponseHeaders: [],
+ },
+ { pass: 1,
+ method: "GET",
+ responseHeaders: { "x-my-header": "x header" },
+ expectedResponseHeaders: [],
+ },
+ { pass: 1,
+ method: "GET",
+ responseHeaders: { "x-my-header": "x header" },
+ exposeHeaders: "x-my-header y",
+ expectedResponseHeaders: [],
+ },
+ { pass: 1,
+ method: "GET",
+ responseHeaders: { "x-my-header": "x header" },
+ exposeHeaders: "y x-my-header",
+ expectedResponseHeaders: [],
+ },
+ { pass: 1,
+ method: "GET",
+ responseHeaders: { "x-my-header": "x header" },
+ exposeHeaders: "x-my-header, y-my-header z",
+ expectedResponseHeaders: [],
+ },
+ { pass: 1,
+ method: "GET",
+ responseHeaders: { "x-my-header": "x header" },
+ exposeHeaders: "x-my-header, y-my-hea(er",
+ expectedResponseHeaders: [],
+ },
+ { pass: 1,
+ method: "GET",
+ responseHeaders: { "x-my-header": "x header",
+ "y-my-header": "y header" },
+ exposeHeaders: " , ,,y-my-header,z-my-header, ",
+ expectedResponseHeaders: ["y-my-header"],
+ },
+ { pass: 1,
+ method: "GET",
+ responseHeaders: { "Cache-Control": "cacheControl header",
+ "Content-Language": "contentLanguage header",
+ "Expires":"expires header",
+ "Last-Modified":"lastModified header",
+ "Pragma":"pragma header",
+ "Unexpected":"unexpected header" },
+ expectedResponseHeaders: ["Cache-Control","Content-Language","Content-Type","Expires","Last-Modified","Pragma"],
+ },
+ // Check that sending a body in the OPTIONS response works
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "DELETE",
+ preflightBody: "I'm a preflight response body",
+ },
+ ];
+
+ if (!runPreflightTests) {
+ tests = [];
+ }
+
+ for (test of tests) {
+ var req = {
+ url: baseURL + "allowOrigin=" + escape(test.origin || origin),
+ method: test.method,
+ headers: test.headers,
+ uploadProgress: test.uploadProgress,
+ body: test.body,
+ responseHeaders: test.responseHeaders,
+ };
+
+ if (test.pass) {
+ req.url += "&origin=" + escape(origin) +
+ "&requestMethod=" + test.method;
+ }
+
+ if ("username" in test) {
+ req.username = test.username;
+ }
+
+ if ("password" in test) {
+ req.password = test.password;
+ }
+
+ if (test.noAllowPreflight)
+ req.url += "&noAllowPreflight";
+
+ if (test.pass && "headers" in test) {
+ function isUnsafeHeader(name) {
+ lName = name.toLowerCase();
+ return lName != "accept" &&
+ lName != "accept-language" &&
+ (lName != "content-type" ||
+ ["text/plain",
+ "multipart/form-data",
+ "application/x-www-form-urlencoded"]
+ .indexOf(test.headers[name].toLowerCase()) == -1);
+ }
+ req.url += "&headers=" + escape(test.headers.toSource());
+ reqHeaders =
+ escape(Object.keys(test.headers)
+ .filter(isUnsafeHeader)
+ .map(String.toLowerCase)
+ .sort()
+ .join(","));
+ req.url += reqHeaders ? "&requestHeaders=" + reqHeaders : "";
+ }
+ if ("allowHeaders" in test)
+ req.url += "&allowHeaders=" + escape(test.allowHeaders);
+ if ("allowMethods" in test)
+ req.url += "&allowMethods=" + escape(test.allowMethods);
+ if (test.body)
+ req.url += "&body=" + escape(test.body);
+ if (test.status) {
+ req.url += "&status=" + test.status;
+ req.url += "&statusMessage=" + escape(test.statusMessage);
+ }
+ if (test.preflightStatus)
+ req.url += "&preflightStatus=" + test.preflightStatus;
+ if (test.responseHeaders)
+ req.url += "&responseHeaders=" + escape(test.responseHeaders.toSource());
+ if (test.exposeHeaders)
+ req.url += "&exposeHeaders=" + escape(test.exposeHeaders);
+ if (test.preflightBody)
+ req.url += "&preflightBody=" + escape(test.preflightBody);
+
+ loaderWindow.postMessage(req.toSource(), origin);
+ res = eval(yield);
+
+ if (test.pass) {
+ is(res.didFail, false,
+ "shouldn't have failed in test for " + test.toSource());
+ if (test.status) {
+ is(res.status, test.status, "wrong status in test for " + test.toSource());
+ is(res.statusText, test.statusMessage, "wrong status text for " + test.toSource());
+ }
+ else {
+ is(res.status, 200, "wrong status in test for " + test.toSource());
+ is(res.statusText, "OK", "wrong status text for " + test.toSource());
+ }
+ if (test.method !== "HEAD") {
+ is(res.responseXML, "<res>hello pass</res>",
+ "wrong responseXML in test for " + test.toSource());
+ is(res.responseText, "<res>hello pass</res>\n",
+ "wrong responseText in test for " + test.toSource());
+ is(res.events.join(","),
+ "opening,rs1,sending,loadstart,rs2,rs3,rs4,load,loadend",
+ "wrong responseText in test for " + test.toSource());
+ }
+ else {
+ is(res.responseXML, null,
+ "wrong responseXML in test for " + test.toSource());
+ is(res.responseText, "",
+ "wrong responseText in test for " + test.toSource());
+ is(res.events.join(","),
+ "opening,rs1,sending,loadstart,rs2,rs4,load,loadend",
+ "wrong responseText in test for " + test.toSource());
+ }
+ if (test.responseHeaders) {
+ for (header in test.responseHeaders) {
+ if (test.expectedResponseHeaders.indexOf(header) == -1) {
+ is(res.responseHeaders[header], null,
+ "|xhr.getResponseHeader()|wrong response header (" + header + ") in test for " +
+ test.toSource());
+ is(res.allResponseHeaders[header], undefined,
+ "|xhr.getAllResponseHeaderss()|wrong response header (" + header + ") in test for " +
+ test.toSource());
+ }
+ else {
+ is(res.responseHeaders[header], test.responseHeaders[header],
+ "|xhr.getResponseHeader()|wrong response header (" + header + ") in test for " +
+ test.toSource());
+ is(res.allResponseHeaders[header], test.responseHeaders[header],
+ "|xhr.getAllResponseHeaderss()|wrong response header (" + header + ") in test for " +
+ test.toSource());
+ }
+ }
+ }
+ }
+ else {
+ is(res.didFail, true,
+ "should have failed in test for " + test.toSource());
+ is(res.status, 0, "wrong status in test for " + test.toSource());
+ is(res.statusText, "", "wrong status text for " + test.toSource());
+ is(res.responseXML, null,
+ "wrong responseXML in test for " + test.toSource());
+ is(res.responseText, "",
+ "wrong responseText in test for " + test.toSource());
+ if (!res.sendThrew) {
+ if (test.username) {
+ is(res.events.join(","),
+ "opening,rs1,sending,loadstart,rs4,error,loadend",
+ "wrong events in test for " + test.toSource());
+ } else {
+ is(res.events.join(","),
+ "opening,rs1,sending,loadstart,rs2,rs4,error,loadend",
+ "wrong events in test for " + test.toSource());
+ }
+ }
+ is(res.progressEvents, 0,
+ "wrong events in test for " + test.toSource());
+ if (test.responseHeaders) {
+ for (header in test.responseHeaders) {
+ is(res.responseHeaders[header], null,
+ "wrong response header (" + header + ") in test for " +
+ test.toSource());
+ }
+ }
+ }
+ }
+
+ // Test cookie behavior
+ tests = [{ pass: 1,
+ method: "GET",
+ withCred: 1,
+ allowCred: 1,
+ },
+ { pass: 0,
+ method: "GET",
+ withCred: 1,
+ allowCred: 0,
+ },
+ { pass: 0,
+ method: "GET",
+ withCred: 1,
+ allowCred: 1,
+ origin: "*",
+ },
+ { pass: 1,
+ method: "GET",
+ withCred: 0,
+ allowCred: 1,
+ origin: "*",
+ },
+ { pass: 1,
+ method: "GET",
+ setCookie: "a=1",
+ withCred: 1,
+ allowCred: 1,
+ },
+ { pass: 1,
+ method: "GET",
+ cookie: "a=1",
+ withCred: 1,
+ allowCred: 1,
+ },
+ { pass: 1,
+ method: "GET",
+ noCookie: 1,
+ withCred: 0,
+ allowCred: 1,
+ },
+ { pass: 0,
+ method: "GET",
+ noCookie: 1,
+ withCred: 1,
+ allowCred: 1,
+ },
+ { pass: 1,
+ method: "GET",
+ setCookie: "a=2",
+ withCred: 0,
+ allowCred: 1,
+ },
+ { pass: 1,
+ method: "GET",
+ cookie: "a=1",
+ withCred: 1,
+ allowCred: 1,
+ },
+ { pass: 1,
+ method: "GET",
+ setCookie: "a=2",
+ withCred: 1,
+ allowCred: 1,
+ },
+ { pass: 1,
+ method: "GET",
+ cookie: "a=2",
+ withCred: 1,
+ allowCred: 1,
+ },
+ ];
+
+ if (!runCookieTests) {
+ tests = [];
+ }
+
+ for (test of tests) {
+ req = {
+ url: baseURL + "allowOrigin=" + escape(test.origin || origin),
+ method: test.method,
+ headers: test.headers,
+ withCred: test.withCred,
+ };
+
+ if (test.allowCred)
+ req.url += "&allowCred";
+
+ if (test.setCookie)
+ req.url += "&setCookie=" + escape(test.setCookie);
+ if (test.cookie)
+ req.url += "&cookie=" + escape(test.cookie);
+ if (test.noCookie)
+ req.url += "&noCookie";
+
+ if ("allowHeaders" in test)
+ req.url += "&allowHeaders=" + escape(test.allowHeaders);
+ if ("allowMethods" in test)
+ req.url += "&allowMethods=" + escape(test.allowMethods);
+
+ loaderWindow.postMessage(req.toSource(), origin);
+
+ res = eval(yield);
+ if (test.pass) {
+ is(res.didFail, false,
+ "shouldn't have failed in test for " + test.toSource());
+ is(res.status, 200, "wrong status in test for " + test.toSource());
+ is(res.statusText, "OK", "wrong status text for " + test.toSource());
+ is(res.responseXML, "<res>hello pass</res>",
+ "wrong responseXML in test for " + test.toSource());
+ is(res.responseText, "<res>hello pass</res>\n",
+ "wrong responseText in test for " + test.toSource());
+ is(res.events.join(","),
+ "opening,rs1,sending,loadstart,rs2,rs3,rs4,load,loadend",
+ "wrong responseText in test for " + test.toSource());
+ }
+ else {
+ is(res.didFail, true,
+ "should have failed in test for " + test.toSource());
+ is(res.status, 0, "wrong status in test for " + test.toSource());
+ is(res.statusText, "", "wrong status text for " + test.toSource());
+ is(res.responseXML, null,
+ "wrong responseXML in test for " + test.toSource());
+ is(res.responseText, "",
+ "wrong responseText in test for " + test.toSource());
+ is(res.events.join(","),
+ "opening,rs1,sending,loadstart,rs2,rs4,error,loadend",
+ "wrong events in test for " + test.toSource());
+ is(res.progressEvents, 0,
+ "wrong events in test for " + test.toSource());
+ }
+ }
+
+ // Make sure to clear cookies to avoid affecting other tests
+ document.cookie = "a=; path=/; expires=Thu, 01-Jan-1970 00:00:01 GMT"
+ is(document.cookie, "", "No cookies should be left over");
+
+
+ // Test redirects
+ is(loader.src, "http://example.org/tests/dom/security/test/cors/file_CrossSiteXHR_inner.html");
+ is(origin, "http://example.org");
+
+ tests = [{ pass: 1,
+ method: "GET",
+ hops: [{ server: "http://example.com",
+ allowOrigin: origin
+ },
+ ],
+ },
+ { pass: 0,
+ method: "GET",
+ hops: [{ server: "http://example.com",
+ allowOrigin: origin
+ },
+ { server: "http://example.org",
+ allowOrigin: origin
+ },
+ ],
+ },
+ { pass: 1,
+ method: "GET",
+ hops: [{ server: "http://example.com",
+ allowOrigin: origin
+ },
+ { server: "http://example.org",
+ allowOrigin: "*"
+ },
+ ],
+ },
+ { pass: 0,
+ method: "GET",
+ hops: [{ server: "http://example.com",
+ allowOrigin: origin
+ },
+ { server: "http://example.org",
+ },
+ ],
+ },
+ { pass: 1,
+ method: "GET",
+ hops: [{ server: "http://example.org",
+ },
+ { server: "http://example.org",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin
+ },
+ ],
+ },
+ { pass: 0,
+ method: "GET",
+ hops: [{ server: "http://example.org",
+ },
+ { server: "http://example.org",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin
+ },
+ { server: "http://example.org",
+ },
+ ],
+ },
+ { pass: 0,
+ method: "GET",
+ hops: [{ server: "http://example.com",
+ allowOrigin: origin
+ },
+ { server: "http://test2.example.org:8000",
+ allowOrigin: origin
+ },
+ { server: "http://sub2.xn--lt-uia.example.org",
+ allowOrigin: origin
+ },
+ { server: "http://sub1.test1.example.org",
+ allowOrigin: origin
+ },
+ ],
+ },
+ { pass: 0,
+ method: "GET",
+ hops: [{ server: "http://example.com",
+ allowOrigin: origin
+ },
+ { server: "http://test2.example.org:8000",
+ allowOrigin: origin
+ },
+ { server: "http://sub2.xn--lt-uia.example.org",
+ allowOrigin: "*"
+ },
+ { server: "http://sub1.test1.example.org",
+ allowOrigin: "*"
+ },
+ ],
+ },
+ { pass: 1,
+ method: "GET",
+ hops: [{ server: "http://example.com",
+ allowOrigin: origin
+ },
+ { server: "http://test2.example.org:8000",
+ allowOrigin: "*"
+ },
+ { server: "http://sub2.xn--lt-uia.example.org",
+ allowOrigin: "*"
+ },
+ { server: "http://sub1.test1.example.org",
+ allowOrigin: "*"
+ },
+ ],
+ },
+ { pass: 0,
+ method: "GET",
+ hops: [{ server: "http://example.com",
+ allowOrigin: origin
+ },
+ { server: "http://test2.example.org:8000",
+ allowOrigin: origin
+ },
+ { server: "http://sub2.xn--lt-uia.example.org",
+ allowOrigin: "x"
+ },
+ { server: "http://sub1.test1.example.org",
+ allowOrigin: origin
+ },
+ ],
+ },
+ { pass: 0,
+ method: "GET",
+ hops: [{ server: "http://example.com",
+ allowOrigin: origin
+ },
+ { server: "http://test2.example.org:8000",
+ allowOrigin: origin
+ },
+ { server: "http://sub2.xn--lt-uia.example.org",
+ allowOrigin: "*"
+ },
+ { server: "http://sub1.test1.example.org",
+ allowOrigin: origin
+ },
+ ],
+ },
+ { pass: 0,
+ method: "GET",
+ hops: [{ server: "http://example.com",
+ allowOrigin: origin
+ },
+ { server: "http://test2.example.org:8000",
+ allowOrigin: origin
+ },
+ { server: "http://sub2.xn--lt-uia.example.org",
+ allowOrigin: "*"
+ },
+ { server: "http://sub1.test1.example.org",
+ },
+ ],
+ },
+ { pass: 1,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain" },
+ hops: [{ server: "http://example.org",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ },
+ ],
+ },
+ { pass: 1,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain",
+ "my-header": "myValue",
+ },
+ hops: [{ server: "http://example.org",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ allowHeaders: "my-header",
+ },
+ ],
+ },
+ { pass: 0,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain",
+ "my-header": "myValue",
+ },
+ hops: [{ server: "http://example.org",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ allowHeaders: "my-header",
+ },
+ { server: "http://sub1.test1.example.org",
+ allowOrigin: origin,
+ allowHeaders: "my-header",
+ },
+ ],
+ },
+ { pass: 0,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain",
+ "my-header": "myValue",
+ },
+ hops: [{ server: "http://example.org",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ allowHeaders: "my-header",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ allowHeaders: "my-header",
+ },
+ ],
+ },
+ { pass: 0,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain",
+ "my-header": "myValue",
+ },
+ hops: [{ server: "http://example.org",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ allowHeaders: "my-header",
+ },
+ { server: "http://example.org",
+ allowOrigin: origin,
+ allowHeaders: "my-header",
+ },
+ ],
+ },
+ { pass: 0,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain",
+ "my-header": "myValue",
+ },
+ hops: [{ server: "http://example.org",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ noAllowPreflight: 1,
+ },
+ ],
+ },
+ { pass: 1,
+ method: "DELETE",
+ hops: [{ server: "http://example.org",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ allowMethods: "DELETE",
+ },
+ ],
+ },
+ { pass: 0,
+ method: "DELETE",
+ hops: [{ server: "http://example.org",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ allowMethods: "DELETE",
+ },
+ { server: "http://sub1.test1.example.org",
+ allowOrigin: origin,
+ allowMethods: "DELETE",
+ },
+ ],
+ },
+ { pass: 0,
+ method: "DELETE",
+ hops: [{ server: "http://example.org",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ allowMethods: "DELETE",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ allowMethods: "DELETE",
+ },
+ ],
+ },
+ { pass: 0,
+ method: "DELETE",
+ hops: [{ server: "http://example.org",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ allowMethods: "DELETE",
+ },
+ { server: "http://example.org",
+ allowOrigin: origin,
+ allowMethods: "DELETE",
+ },
+ ],
+ },
+ { pass: 0,
+ method: "DELETE",
+ hops: [{ server: "http://example.org",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ allowMethods: "DELETE",
+ noAllowPreflight: 1,
+ },
+ ],
+ },
+ { pass: 0,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain",
+ "my-header": "myValue",
+ },
+ hops: [{ server: "http://example.com",
+ allowOrigin: origin,
+ },
+ { server: "http://sub1.test1.example.org",
+ allowOrigin: origin,
+ },
+ ],
+ },
+ { pass: 0,
+ method: "DELETE",
+ hops: [{ server: "http://example.com",
+ allowOrigin: origin,
+ },
+ { server: "http://sub1.test1.example.org",
+ allowOrigin: origin,
+ },
+ ],
+ },
+ { pass: 0,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain",
+ "my-header": "myValue",
+ },
+ hops: [{ server: "http://example.com",
+ },
+ { server: "http://sub1.test1.example.org",
+ allowOrigin: origin,
+ allowHeaders: "my-header",
+ },
+ ],
+ },
+ { pass: 1,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain" },
+ hops: [{ server: "http://example.org",
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ },
+ ],
+ },
+ { pass: 0,
+ method: "POST",
+ body: "hi there",
+ headers: { "Content-Type": "text/plain",
+ "my-header": "myValue",
+ },
+ hops: [{ server: "http://example.com",
+ allowOrigin: origin,
+ allowHeaders: "my-header",
+ },
+ { server: "http://example.org",
+ allowOrigin: origin,
+ allowHeaders: "my-header",
+ },
+ ],
+ },
+
+ // test redirects with different credentials settings
+ {
+ // Initialize by setting a cookies for same- and cross- origins.
+ pass: 1,
+ method: "GET",
+ hops: [{ server: origin,
+ setCookie: escape("a=1"),
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ allowCred: 1,
+ setCookie: escape("a=2"),
+ },
+ ],
+ withCred: 1,
+ },
+ { pass: 1,
+ method: "GET",
+ hops: [{ server: origin,
+ cookie: escape("a=1"),
+ },
+ { server: origin,
+ cookie: escape("a=1"),
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ noCookie: 1,
+ },
+ ],
+ withCred: 0,
+ },
+ { pass: 1,
+ method: "GET",
+ hops: [{ server: origin,
+ cookie: escape("a=1"),
+ },
+ { server: origin,
+ cookie: escape("a=1"),
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ allowCred: 1,
+ cookie: escape("a=2"),
+ },
+ ],
+ withCred: 1,
+ },
+ // expected fail because allow-credentials CORS header is not set
+ { pass: 0,
+ method: "GET",
+ hops: [{ server: origin,
+ cookie: escape("a=1"),
+ },
+ { server: origin,
+ cookie: escape("a=1"),
+ },
+ { server: "http://example.com",
+ allowOrigin: origin,
+ cookie: escape("a=2"),
+ },
+ ],
+ withCred: 1,
+ },
+ { pass: 1,
+ method: "GET",
+ hops: [{ server: origin,
+ cookie: escape("a=1"),
+ },
+ { server: origin,
+ cookie: escape("a=1"),
+ },
+ { server: "http://example.com",
+ allowOrigin: '*',
+ noCookie: 1,
+ },
+ ],
+ withCred: 0,
+ },
+ { pass: 0,
+ method: "GET",
+ hops: [{ server: origin,
+ cookie: escape("a=1"),
+ },
+ { server: origin,
+ cookie: escape("a=1"),
+ },
+ { server: "http://example.com",
+ allowOrigin: '*',
+ allowCred: 1,
+ cookie: escape("a=2"),
+ },
+ ],
+ withCred: 1,
+ },
+ ];
+
+ if (!runRedirectTests) {
+ tests = [];
+ }
+
+ for (test of tests) {
+ req = {
+ url: test.hops[0].server + basePath + "hop=1&hops=" +
+ escape(test.hops.toSource()),
+ method: test.method,
+ headers: test.headers,
+ body: test.body,
+ withCred: test.withCred,
+ };
+
+ if (test.pass) {
+ if (test.body)
+ req.url += "&body=" + escape(test.body);
+ }
+
+ loaderWindow.postMessage(req.toSource(), origin);
+
+ res = eval(yield);
+ if (test.pass) {
+ is(res.didFail, false,
+ "shouldn't have failed in test for " + test.toSource());
+ is(res.status, 200, "wrong status in test for " + test.toSource());
+ is(res.statusText, "OK", "wrong status text for " + test.toSource());
+ is(res.responseXML, "<res>hello pass</res>",
+ "wrong responseXML in test for " + test.toSource());
+ is(res.responseText, "<res>hello pass</res>\n",
+ "wrong responseText in test for " + test.toSource());
+ is(res.events.join(","),
+ "opening,rs1,sending,loadstart,rs2,rs3,rs4,load,loadend",
+ "wrong responseText in test for " + test.toSource());
+ }
+ else {
+ is(res.didFail, true,
+ "should have failed in test for " + test.toSource());
+ is(res.status, 0, "wrong status in test for " + test.toSource());
+ is(res.statusText, "", "wrong status text for " + test.toSource());
+ is(res.responseXML, null,
+ "wrong responseXML in test for " + test.toSource());
+ is(res.responseText, "",
+ "wrong responseText in test for " + test.toSource());
+ is(res.events.join(","),
+ "opening,rs1,sending,loadstart,rs2,rs4,error,loadend",
+ "wrong events in test for " + test.toSource());
+ is(res.progressEvents, 0,
+ "wrong progressevents in test for " + test.toSource());
+ }
+ }
+
+
+ SimpleTest.finish();
+
+ yield undefined;
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/cors/test_CrossSiteXHR_cache.html b/dom/security/test/cors/test_CrossSiteXHR_cache.html
new file mode 100644
index 000000000..3252eff4a
--- /dev/null
+++ b/dom/security/test/cors/test_CrossSiteXHR_cache.html
@@ -0,0 +1,587 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
+ <title>Test for Cross Site XMLHttpRequest</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="gen.next()">
+<p id="display">
+<iframe id=loader></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.7">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("This test needs to generate artificial pauses, hence it uses timeouts. There is no way around it, unfortunately. :(");
+
+window.addEventListener("message", function(e) {
+ gen.send(e.data);
+}, false);
+
+gen = runTest();
+
+function runTest() {
+ var loader = document.getElementById('loader');
+ var loaderWindow = loader.contentWindow;
+ loader.onload = function () { gen.next() };
+
+ loader.src = "http://example.org/tests/dom/security/test/cors/file_CrossSiteXHR_inner.html";
+ origin = "http://example.org";
+ yield undefined;
+
+ tests = [{ pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ cacheTime: 3600
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue",
+ "y-my-header": "second" },
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "y-my-header": "hello" },
+ allowHeaders: "y-my-header",
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "y-my-header": "hello" },
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "y-my-header": "hello" },
+ allowHeaders: "y-my-header,x-my-header",
+ cacheTime: 3600,
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue",
+ "y-my-header": "second" },
+ },
+ { newTest: "*******" },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ cacheTime: 2
+ },
+ { pause: 2.1 },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header, y-my-header",
+ cacheTime: 3600
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "y-my-header": "myValue" },
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "z-my-header": "myValue" },
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ cacheTime: "\t 3600 \t ",
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ cacheTime: "3600 3",
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ cacheTime: "asdf",
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "GET",
+ headers: { "first-header": "myValue" },
+ allowHeaders: "first-header",
+ cacheTime: 2,
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "second-header": "myValue" },
+ allowHeaders: "second-header",
+ cacheTime: 3600,
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "third-header": "myValue" },
+ allowHeaders: "third-header",
+ cacheTime: 2,
+ },
+ { pause: 2.1 },
+ { pass: 1,
+ method: "GET",
+ headers: { "second-header": "myValue" },
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "first-header": "myValue" },
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "GET",
+ headers: { "first-header": "myValue" },
+ allowHeaders: "first-header",
+ cacheTime: 2,
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "second-header": "myValue" },
+ allowHeaders: "second-header",
+ cacheTime: 3600,
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "third-header": "myValue" },
+ allowHeaders: "third-header",
+ cacheTime: 2,
+ },
+ { pause: 2.1 },
+ { pass: 1,
+ method: "GET",
+ headers: { "second-header": "myValue" },
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "third-header": "myValue" },
+ },
+ { newTest: "*******" },
+ { pass: 0,
+ method: "DELETE",
+ },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "DELETE",
+ cacheTime: 3600
+ },
+ { pass: 1,
+ method: "DELETE",
+ },
+ { pass: 1,
+ method: "DELETE",
+ },
+ { pass: 0,
+ method: "PATCH",
+ },
+ { pass: 1,
+ method: "PATCH",
+ allowMethods: "PATCH",
+ },
+ { pass: 0,
+ method: "PATCH",
+ },
+ { pass: 1,
+ method: "PATCH",
+ allowMethods: "PATCH",
+ cacheTime: 3600,
+ },
+ { pass: 1,
+ method: "PATCH",
+ },
+ { pass: 0,
+ method: "DELETE",
+ },
+ { pass: 0,
+ method: "PUT",
+ },
+ { newTest: "*******" },
+ { pass: 0,
+ method: "DELETE",
+ },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "DELETE",
+ cacheTime: 2
+ },
+ { pause: 2.1 },
+ { pass: 0,
+ method: "DELETE",
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "DELETE, PUT",
+ cacheTime: 3600
+ },
+ { pass: 1,
+ method: "DELETE",
+ },
+ { pass: 1,
+ method: "PUT",
+ },
+ { pass: 0,
+ method: "PATCH",
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "FIRST",
+ allowMethods: "FIRST",
+ cacheTime: 2,
+ },
+ { pass: 1,
+ method: "SECOND",
+ allowMethods: "SECOND",
+ cacheTime: 3600,
+ },
+ { pass: 1,
+ method: "THIRD",
+ allowMethods: "THIRD",
+ cacheTime: 2,
+ },
+ { pause: 2.1 },
+ { pass: 1,
+ method: "SECOND",
+ },
+ { pass: 0,
+ method: "FIRST",
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "FIRST",
+ allowMethods: "FIRST",
+ cacheTime: 2,
+ },
+ { pass: 1,
+ method: "SECOND",
+ allowMethods: "SECOND",
+ cacheTime: 3600,
+ },
+ { pass: 1,
+ method: "THIRD",
+ allowMethods: "THIRD",
+ cacheTime: 2,
+ },
+ { pause: 2.1 },
+ { pass: 1,
+ method: "SECOND",
+ },
+ { pass: 0,
+ method: "THIRD",
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "x-value" },
+ allowHeaders: "x-my-header",
+ cacheTime: 3600,
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "x-value" }
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "y-my-header": "y-value" }
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "x-value" }
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "x-value" },
+ allowHeaders: "x-my-header",
+ cacheTime: 3600,
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "x-value" },
+ },
+ { pass: 0,
+ method: "PUT",
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "x-value" },
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "x-value" },
+ allowHeaders: "x-my-header",
+ cacheTime: 3600,
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "x-value" },
+ },
+ { pass: 0,
+ method: "GET",
+ noOrigin: 1,
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "x-value" },
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "DELETE",
+ cacheTime: 3600,
+ },
+ { pass: 1,
+ method: "DELETE"
+ },
+ { pass: 0,
+ method: "PUT"
+ },
+ { pass: 0,
+ method: "DELETE"
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "DELETE",
+ cacheTime: 3600,
+ },
+ { pass: 1,
+ method: "DELETE"
+ },
+ { pass: 0,
+ method: "DELETE",
+ headers: { "my-header": "value" },
+ },
+ { pass: 0,
+ method: "DELETE"
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "DELETE",
+ cacheTime: 3600,
+ },
+ { pass: 1,
+ method: "DELETE"
+ },
+ { pass: 0,
+ method: "GET",
+ noOrigin: 1,
+ },
+ { pass: 0,
+ method: "DELETE"
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "GET",
+ withCred: true,
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ cacheTime: 3600
+ },
+ { pass: 1,
+ method: "GET",
+ withCred: true,
+ headers: { "x-my-header": "myValue" },
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "GET",
+ withCred: true,
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ cacheTime: 3600
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "y-my-header": "myValue" },
+ allowHeaders: "y-my-header",
+ cacheTime: 2
+ },
+ { pass: 1,
+ method: "GET",
+ headers: { "y-my-header": "myValue" },
+ },
+ { pass: 1,
+ method: "GET",
+ withCred: true,
+ headers: { "x-my-header": "myValue" },
+ },
+ { pause: 2.1 },
+ { pass: 1,
+ method: "GET",
+ withCred: true,
+ headers: { "x-my-header": "myValue" },
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "y-my-header": "myValue" },
+ },
+ { pass: 0,
+ method: "GET",
+ withCred: true,
+ headers: { "y-my-header": "myValue" },
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "DELETE",
+ cacheTime: 3600
+ },
+ { pass: 0,
+ method: "GET",
+ headers: { "DELETE": "myvalue" },
+ },
+ { newTest: "*******" },
+ { pass: 1,
+ method: "GET",
+ headers: { "x-my-header": "myValue" },
+ allowHeaders: "x-my-header",
+ cacheTime: 3600
+ },
+ { pass: 0,
+ method: "3600",
+ headers: { "x-my-header": "myvalue" },
+ },
+ ];
+
+ for (let i = 0; i < 110; i++) {
+ tests.push({ newTest: "*******" },
+ { pass: 1,
+ method: "DELETE",
+ allowMethods: "DELETE",
+ cacheTime: 3600,
+ });
+ }
+
+ baseURL = "http://mochi.test:8888/tests/dom/security/test/cors/" +
+ "file_CrossSiteXHR_cache_server.sjs?";
+ setStateURL = baseURL + "setState=";
+
+ var unique = Date.now();
+ for (test of tests) {
+ if (test.newTest) {
+ unique++;
+ continue;
+ }
+ if (test.pause) {
+ setTimeout(function() { gen.next() }, test.pause * 1000);
+ yield undefined;
+ continue;
+ }
+
+ req = {
+ url: baseURL + "c=" + unique,
+ method: test.method,
+ headers: test.headers,
+ withCred: test.withCred,
+ };
+
+ sec = { allowOrigin: test.noOrigin ? "" : origin,
+ allowHeaders: test.allowHeaders,
+ allowMethods: test.allowMethods,
+ cacheTime: test.cacheTime,
+ withCred: test.withCred };
+ xhr = new XMLHttpRequest();
+ xhr.open("POST", setStateURL + escape(sec.toSource()), true);
+ xhr.onloadend = function() { gen.next(); }
+ xhr.send();
+ yield undefined;
+
+ loaderWindow.postMessage(req.toSource(), origin);
+
+ res = eval(yield);
+
+ testName = test.toSource() + " (index " + tests.indexOf(test) + ")";
+
+ if (test.pass) {
+ is(res.didFail, false,
+ "shouldn't have failed in test for " + testName);
+ is(res.status, 200, "wrong status in test for " + testName);
+ is(res.responseXML, "<res>hello pass</res>",
+ "wrong responseXML in test for " + testName);
+ is(res.responseText, "<res>hello pass</res>\n",
+ "wrong responseText in test for " + testName);
+ is(res.events.join(","),
+ "opening,rs1,sending,loadstart,rs2,rs3,rs4,load,loadend",
+ "wrong events in test for " + testName);
+ }
+ else {
+ is(res.didFail, true,
+ "should have failed in test for " + testName);
+ is(res.status, 0, "wrong status in test for " + testName);
+ is(res.responseXML, null,
+ "wrong responseXML in test for " + testName);
+ is(res.responseText, "",
+ "wrong responseText in test for " + testName);
+ is(res.events.join(","),
+ "opening,rs1,sending,loadstart,rs2,rs4,error,loadend",
+ "wrong events in test for " + testName);
+ is(res.progressEvents, 0,
+ "wrong events in test for " + testName);
+ }
+ }
+
+ SimpleTest.finish();
+
+ yield undefined;
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/cors/test_CrossSiteXHR_origin.html b/dom/security/test/cors/test_CrossSiteXHR_origin.html
new file mode 100644
index 000000000..1012ae593
--- /dev/null
+++ b/dom/security/test/cors/test_CrossSiteXHR_origin.html
@@ -0,0 +1,174 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
+ <title>Test for Cross Site XMLHttpRequest</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display">
+<iframe id=loader></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.8">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(2);
+
+var origins =
+ [{ server: 'http://example.org' },
+ { server: 'http://example.org:80',
+ origin: 'http://example.org'
+ },
+ { server: 'http://sub1.test1.example.org' },
+ { server: 'http://test2.example.org:8000' },
+ { server: 'http://sub1.\xe4lt.example.org:8000',
+ origin: 'http://sub1.xn--lt-uia.example.org:8000'
+ },
+ { server: 'http://sub2.\xe4lt.example.org',
+ origin: 'http://sub2.xn--lt-uia.example.org'
+ },
+ { server: 'http://ex\xe4mple.test',
+ origin: 'http://xn--exmple-cua.test'
+ },
+ { server: 'http://xn--exmple-cua.test' },
+ { server: 'http://\u03c0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3\u03bc\u03b1.\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae',
+ origin: 'http://xn--hxajbheg2az3al.xn--jxalpdlp'
+ },
+ { origin: 'http://example.org',
+ file: 'jar:http://example.org/tests/dom/security/test/cors/file_CrossSiteXHR_inner.jar!/file_CrossSiteXHR_inner.html'
+ },
+ { origin: 'null',
+ file: 'http://example.org/tests/dom/security/test/cors/file_CrossSiteXHR_inner_data.sjs'
+ },
+ ];
+
+ //['https://example.com:443'],
+ //['https://sub1.test1.example.com:443'],
+
+window.addEventListener("message", function(e) {
+ gen.send(e.data);
+}, false);
+
+gen = runTest();
+
+function runTest() {
+ var loader = document.getElementById('loader');
+ var loaderWindow = loader.contentWindow;
+ loader.onload = function () { gen.next() };
+
+ // Test preflight-less requests
+ basePath = "/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?"
+ baseURL = "http://mochi.test:8888" + basePath;
+
+ for (originEntry of origins) {
+ origin = originEntry.origin || originEntry.server;
+
+ loader.src = originEntry.file ||
+ (originEntry.server + "/tests/dom/security/test/cors/file_CrossSiteXHR_inner.html");
+ yield undefined;
+
+ var isNullOrigin = origin == "null";
+
+ port = /:\d+/;
+ passTests = [
+ origin,
+ "*",
+ " \t " + origin + "\t \t",
+ "\t \t* \t ",
+ ];
+ failTests = [
+ "",
+ " ",
+ port.test(origin) ? origin.replace(port, "")
+ : origin + ":1234",
+ port.test(origin) ? origin.replace(port, ":")
+ : origin + ":",
+ origin + ".",
+ origin + "/",
+ origin + "#",
+ origin + "?",
+ origin + "\\",
+ origin + "%",
+ origin + "@",
+ origin + "/hello",
+ "foo:bar@" + origin,
+ "* " + origin,
+ origin + " " + origin,
+ "allow <" + origin + ">",
+ "<" + origin + ">",
+ "<*>",
+ origin.substr(0, 5) == "https" ? origin.replace("https", "http")
+ : origin.replace("http", "https"),
+ origin.replace("://", "://www."),
+ origin.replace("://", ":// "),
+ origin.replace(/\/[^.]+\./, "/"),
+ ];
+
+ if (isNullOrigin) {
+ passTests = ["*", "\t \t* \t ", "null"];
+ failTests = failTests.filter(function(v) { return v != origin });
+ }
+
+ for (allowOrigin of passTests) {
+ req = {
+ url: baseURL +
+ "allowOrigin=" + escape(allowOrigin) +
+ "&origin=" + escape(origin),
+ method: "GET",
+ };
+ loaderWindow.postMessage(req.toSource(), isNullOrigin ? "*" : origin);
+
+ res = eval(yield);
+ is(res.didFail, false, "shouldn't have failed for " + allowOrigin);
+ is(res.status, 200, "wrong status for " + allowOrigin);
+ is(res.statusText, "OK", "wrong status text for " + allowOrigin);
+ is(res.responseXML,
+ "<res>hello pass</res>",
+ "wrong responseXML in test for " + allowOrigin);
+ is(res.responseText, "<res>hello pass</res>\n",
+ "wrong responseText in test for " + allowOrigin);
+ is(res.events.join(","),
+ "opening,rs1,sending,loadstart,rs2,rs3,rs4,load,loadend",
+ "wrong responseText in test for " + allowOrigin);
+ }
+
+ for (allowOrigin of failTests) {
+ req = {
+ url: baseURL + "allowOrigin=" + escape(allowOrigin),
+ method: "GET",
+ };
+ loaderWindow.postMessage(req.toSource(), isNullOrigin ? "*" : origin);
+
+ res = eval(yield);
+ is(res.didFail, true, "should have failed for " + allowOrigin);
+ is(res.responseText, "", "should have no text for " + allowOrigin);
+ is(res.status, 0, "should have no status for " + allowOrigin);
+ is(res.statusText, "", "wrong status text for " + allowOrigin);
+ is(res.responseXML, null, "should have no XML for " + allowOrigin);
+ is(res.events.join(","),
+ "opening,rs1,sending,loadstart,rs2,rs4,error,loadend",
+ "wrong events in test for " + allowOrigin);
+ is(res.progressEvents, 0,
+ "wrong events in test for " + allowOrigin);
+ }
+ }
+
+ SimpleTest.finish();
+
+ yield undefined;
+}
+
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set": [["network.jar.block-remote-files", false]]}, function() {
+ gen.next();
+ });
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/browser.ini b/dom/security/test/csp/browser.ini
new file mode 100644
index 000000000..0846e87fe
--- /dev/null
+++ b/dom/security/test/csp/browser.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+support-files =
+ !/dom/security/test/csp/file_testserver.sjs
+ !/dom/security/test/csp/file_web_manifest.html
+ !/dom/security/test/csp/file_web_manifest.json
+ !/dom/security/test/csp/file_web_manifest.json^headers^
+ !/dom/security/test/csp/file_web_manifest_https.html
+ !/dom/security/test/csp/file_web_manifest_https.json
+ !/dom/security/test/csp/file_web_manifest_mixed_content.html
+ !/dom/security/test/csp/file_web_manifest_remote.html
+[browser_test_web_manifest.js]
+[browser_test_web_manifest_mixed_content.js]
+[browser_manifest-src-override-default-src.js]
diff --git a/dom/security/test/csp/browser_manifest-src-override-default-src.js b/dom/security/test/csp/browser_manifest-src-override-default-src.js
new file mode 100644
index 000000000..0c4c7b7bc
--- /dev/null
+++ b/dom/security/test/csp/browser_manifest-src-override-default-src.js
@@ -0,0 +1,108 @@
+/*
+ * Description of the tests:
+ * Tests check that default-src can be overridden by manifest-src.
+ */
+/*globals Cu, is, ok*/
+"use strict";
+const {
+ ManifestObtainer
+} = Cu.import("resource://gre/modules/ManifestObtainer.jsm", {});
+const path = "/tests/dom/security/test/csp/";
+const testFile = `${path}file_web_manifest.html`;
+const mixedContentFile = `${path}file_web_manifest_mixed_content.html`;
+const server = `${path}file_testserver.sjs`;
+const defaultURL = new URL(`http://example.org${server}`);
+const mixedURL = new URL(`http://mochi.test:8888${server}`);
+const tests = [
+ // Check interaction with default-src and another origin,
+ // CSP allows fetching from example.org, so manifest should load.
+ {
+ expected: `CSP manifest-src overrides default-src of elsewhere.com`,
+ get tabURL() {
+ const url = new URL(defaultURL);
+ url.searchParams.append("file", testFile);
+ url.searchParams.append("cors", "*");
+ url.searchParams.append("csp", "default-src http://elsewhere.com; manifest-src http://example.org");
+ return url.href;
+ },
+ run(manifest) {
+ is(manifest.name, "loaded", this.expected);
+ }
+ },
+ // Check interaction with default-src none,
+ // CSP allows fetching manifest from example.org, so manifest should load.
+ {
+ expected: `CSP manifest-src overrides default-src`,
+ get tabURL() {
+ const url = new URL(mixedURL);
+ url.searchParams.append("file", mixedContentFile);
+ url.searchParams.append("cors", "http://test:80");
+ url.searchParams.append("csp", "default-src 'self'; manifest-src http://test:80");
+ return url.href;
+ },
+ run(manifest) {
+ is(manifest.name, "loaded", this.expected);
+ }
+ },
+];
+
+//jscs:disable
+add_task(function* () {
+ //jscs:enable
+ const testPromises = tests.map((test) => {
+ const tabOptions = {
+ gBrowser,
+ url: test.tabURL,
+ skipAnimation: true,
+ };
+ return BrowserTestUtils.withNewTab(tabOptions, (browser) => testObtainingManifest(browser, test));
+ });
+ yield Promise.all(testPromises);
+});
+
+function* testObtainingManifest(aBrowser, aTest) {
+ const expectsBlocked = aTest.expected.includes("block");
+ const observer = (expectsBlocked) ? createNetObserver(aTest) : null;
+ // Expect an exception (from promise rejection) if there a content policy
+ // that is violated.
+ try {
+ const manifest = yield ManifestObtainer.browserObtainManifest(aBrowser);
+ aTest.run(manifest);
+ } catch (e) {
+ const wasBlocked = e.message.includes("NetworkError when attempting to fetch resource");
+ ok(wasBlocked,`Expected promise rejection obtaining ${aTest.tabURL}: ${e.message}`);
+ if (observer) {
+ yield observer.untilFinished;
+ }
+ }
+}
+
+// Helper object used to observe policy violations. It waits 1 seconds
+// for a response, and then times out causing its associated test to fail.
+function createNetObserver(test) {
+ let finishedTest;
+ let success = false;
+ const finished = new Promise((resolver) => {
+ finishedTest = resolver;
+ });
+ const timeoutId = setTimeout(() => {
+ if (!success) {
+ test.run("This test timed out.");
+ finishedTest();
+ }
+ }, 1000);
+ var observer = {
+ get untilFinished(){
+ return finished;
+ },
+ observe(subject, topic) {
+ SpecialPowers.removeObserver(observer, "csp-on-violate-policy");
+ test.run(topic);
+ finishedTest();
+ clearTimeout(timeoutId);
+ success = true;
+ },
+ };
+ SpecialPowers.addObserver(observer, "csp-on-violate-policy", false);
+ return observer;
+}
diff --git a/dom/security/test/csp/browser_test_web_manifest.js b/dom/security/test/csp/browser_test_web_manifest.js
new file mode 100644
index 000000000..df23770ba
--- /dev/null
+++ b/dom/security/test/csp/browser_test_web_manifest.js
@@ -0,0 +1,224 @@
+/*
+ * Description of the tests:
+ * These tests check for conformance to the CSP spec as they relate to Web Manifests.
+ *
+ * In particular, the tests check that default-src and manifest-src directives are
+ * are respected by the ManifestObtainer.
+ */
+/*globals Cu, is, ok*/
+"use strict";
+const {
+ ManifestObtainer
+} = Cu.import("resource://gre/modules/ManifestObtainer.jsm", {});
+const path = "/tests/dom/security/test/csp/";
+const testFile = `${path}file_web_manifest.html`;
+const remoteFile = `${path}file_web_manifest_remote.html`;
+const httpsManifest = `${path}file_web_manifest_https.html`;
+const server = `${path}file_testserver.sjs`;
+const defaultURL = new URL(`http://example.org${server}`);
+const secureURL = new URL(`https://example.com:443${server}`);
+const tests = [
+ // CSP block everything, so trying to load a manifest
+ // will result in a policy violation.
+ {
+ expected: "default-src 'none' blocks fetching manifest.",
+ get tabURL() {
+ const url = new URL(defaultURL);
+ url.searchParams.append("file", testFile);
+ url.searchParams.append("csp", "default-src 'none'");
+ return url.href;
+ },
+ run(topic) {
+ is(topic, "csp-on-violate-policy", this.expected);
+ }
+ },
+ // CSP allows fetching only from mochi.test:8888,
+ // so trying to load a manifest from same origin
+ // triggers a CSP violation.
+ {
+ expected: "default-src mochi.test:8888 blocks manifest fetching.",
+ get tabURL() {
+ const url = new URL(defaultURL);
+ url.searchParams.append("file", testFile);
+ url.searchParams.append("csp", "default-src mochi.test:8888");
+ return url.href;
+ },
+ run(topic) {
+ is(topic, "csp-on-violate-policy", this.expected);
+ }
+ },
+ // CSP restricts fetching to 'self', so allowing the manifest
+ // to load. The name of the manifest is then checked.
+ {
+ expected: "CSP default-src 'self' allows fetch of manifest.",
+ get tabURL() {
+ const url = new URL(defaultURL);
+ url.searchParams.append("file", testFile);
+ url.searchParams.append("csp", "default-src 'self'");
+ return url.href;
+ },
+ run(manifest) {
+ is(manifest.name, "loaded", this.expected);
+ }
+ },
+ // CSP only allows fetching from mochi.test:8888 and remoteFile
+ // requests a manifest from that origin, so manifest should load.
+ {
+ expected: "CSP default-src mochi.test:8888 allows fetching manifest.",
+ get tabURL() {
+ const url = new URL(defaultURL);
+ url.searchParams.append("file", remoteFile);
+ url.searchParams.append("csp", "default-src http://mochi.test:8888");
+ return url.href;
+ },
+ run(manifest) {
+ is(manifest.name, "loaded", this.expected);
+ }
+ },
+ // default-src blocks everything, so any attempt to
+ // fetch a manifest from another origin will trigger a
+ // policy violation.
+ {
+ expected: "default-src 'none' blocks mochi.test:8888",
+ get tabURL() {
+ const url = new URL(defaultURL);
+ url.searchParams.append("file", remoteFile);
+ url.searchParams.append("csp", "default-src 'none'");
+ return url.href;
+ },
+ run(topic) {
+ is(topic, "csp-on-violate-policy", this.expected);
+ }
+ },
+ // CSP allows fetching from self, so manifest should load.
+ {
+ expected: "CSP manifest-src allows self",
+ get tabURL() {
+ const url = new URL(defaultURL);
+ url.searchParams.append("file", testFile);
+ url.searchParams.append("csp", "manifest-src 'self'");
+ return url.href;
+ },
+ run(manifest) {
+ is(manifest.name, "loaded", this.expected);
+ }
+ },
+ // CSP allows fetching from example.org, so manifest should load.
+ {
+ expected: "CSP manifest-src allows http://example.org",
+ get tabURL() {
+ const url = new URL(defaultURL);
+ url.searchParams.append("file", testFile);
+ url.searchParams.append("csp", "manifest-src http://example.org");
+ return url.href;
+ },
+ run(manifest) {
+ is(manifest.name, "loaded", this.expected);
+ }
+ }, {
+ expected: "CSP manifest-src allows mochi.test:8888",
+ get tabURL() {
+ const url = new URL(defaultURL);
+ url.searchParams.append("file", remoteFile);
+ url.searchParams.append("cors", "*");
+ url.searchParams.append("csp", "default-src *; manifest-src http://mochi.test:8888");
+ return url.href;
+ },
+ run(manifest) {
+ is(manifest.name, "loaded", this.expected);
+ }
+ },
+ // CSP restricts fetching to mochi.test:8888, but the test
+ // file is at example.org. Hence, a policy violation is
+ // triggered.
+ {
+ expected: "CSP blocks manifest fetching from example.org.",
+ get tabURL() {
+ const url = new URL(defaultURL);
+ url.searchParams.append("file", testFile);
+ url.searchParams.append("csp", "manifest-src mochi.test:8888");
+ return url.href;
+ },
+ run(topic) {
+ is(topic, "csp-on-violate-policy", this.expected);
+ }
+ },
+ // CSP is set to only allow manifest to be loaded from same origin,
+ // but the remote file attempts to load from a different origin. Thus
+ // this causes a CSP violation.
+ {
+ expected: "CSP manifest-src 'self' blocks cross-origin fetch.",
+ get tabURL() {
+ const url = new URL(defaultURL);
+ url.searchParams.append("file", remoteFile);
+ url.searchParams.append("csp", "manifest-src 'self'");
+ return url.href;
+ },
+ run(topic) {
+ is(topic, "csp-on-violate-policy", this.expected);
+ }
+ },
+ // CSP allows fetching over TLS from example.org, so manifest should load.
+ {
+ expected: "CSP manifest-src allows example.com over TLS",
+ get tabURL() {
+ // secureURL loads https://example.com:443
+ // and gets manifest from https://example.org:443
+ const url = new URL(secureURL);
+ url.searchParams.append("file", httpsManifest);
+ url.searchParams.append("cors", "*");
+ url.searchParams.append("csp", "manifest-src https://example.com:443");
+ return url.href;
+ },
+ run(manifest) {
+ is(manifest.name, "loaded", this.expected);
+ }
+ },
+];
+
+//jscs:disable
+add_task(function* () {
+ //jscs:enable
+ const testPromises = tests.map((test) => {
+ const tabOptions = {
+ gBrowser,
+ url: test.tabURL,
+ skipAnimation: true,
+ };
+ return BrowserTestUtils.withNewTab(tabOptions, (browser) => testObtainingManifest(browser, test));
+ });
+ yield Promise.all(testPromises);
+});
+
+function* testObtainingManifest(aBrowser, aTest) {
+ const waitForObserver = waitForNetObserver(aTest);
+ // Expect an exception (from promise rejection) if there a content policy
+ // that is violated.
+ try {
+ const manifest = yield ManifestObtainer.browserObtainManifest(aBrowser);
+ aTest.run(manifest);
+ } catch (e) {
+ const wasBlocked = e.message.includes("NetworkError when attempting to fetch resource");
+ ok(wasBlocked, `Expected promise rejection obtaining ${aTest.tabURL}: ${e.message}`);
+ } finally {
+ yield waitForObserver;
+ }
+}
+
+// Helper object used to observe policy violations when blocking is expected.
+function waitForNetObserver(aTest) {
+ return new Promise((resolve) => {
+ // We don't need to wait for violation, so just resolve
+ if (!aTest.expected.includes("block")){
+ return resolve();
+ }
+ const observer = {
+ observe(subject, topic) {
+ SpecialPowers.removeObserver(observer, "csp-on-violate-policy");
+ aTest.run(topic);
+ resolve();
+ },
+ };
+ SpecialPowers.addObserver(observer, "csp-on-violate-policy", false);
+ });
+}
diff --git a/dom/security/test/csp/browser_test_web_manifest_mixed_content.js b/dom/security/test/csp/browser_test_web_manifest_mixed_content.js
new file mode 100644
index 000000000..9238acbcd
--- /dev/null
+++ b/dom/security/test/csp/browser_test_web_manifest_mixed_content.js
@@ -0,0 +1,53 @@
+/*
+ * Description of the test:
+ * Check that mixed content blocker works prevents fetches of
+ * mixed content manifests.
+ */
+/*globals Cu, ok*/
+"use strict";
+const {
+ ManifestObtainer
+} = Cu.import("resource://gre/modules/ManifestObtainer.jsm", {});
+const path = "/tests/dom/security/test/csp/";
+const mixedContent = `${path}file_web_manifest_mixed_content.html`;
+const server = `${path}file_testserver.sjs`;
+const secureURL = new URL(`https://example.com${server}`);
+const tests = [
+ // Trying to load mixed content in file_web_manifest_mixed_content.html
+ // needs to result in an error.
+ {
+ expected: "Mixed Content Blocker prevents fetching manifest.",
+ get tabURL() {
+ const url = new URL(secureURL);
+ url.searchParams.append("file", mixedContent);
+ return url.href;
+ },
+ run(error) {
+ // Check reason for error.
+ const check = /NetworkError when attempting to fetch resource/.test(error.message);
+ ok(check, this.expected);
+ }
+ }
+];
+
+//jscs:disable
+add_task(function* () {
+ //jscs:enable
+ const testPromises = tests.map((test) => {
+ const tabOptions = {
+ gBrowser,
+ url: test.tabURL,
+ skipAnimation: true,
+ };
+ return BrowserTestUtils.withNewTab(tabOptions, (browser) => testObtainingManifest(browser, test));
+ });
+ yield Promise.all(testPromises);
+});
+
+function* testObtainingManifest(aBrowser, aTest) {
+ try {
+ yield ManifestObtainer.browserObtainManifest(aBrowser);
+ } catch (e) {
+ aTest.run(e);
+ }
+}
diff --git a/dom/security/test/csp/file_CSP.css b/dom/security/test/csp/file_CSP.css
new file mode 100644
index 000000000..6835c4d4a
--- /dev/null
+++ b/dom/security/test/csp/file_CSP.css
@@ -0,0 +1,20 @@
+/*
+ * Moved this CSS from an inline stylesheet to an external file when we added
+ * inline-style blocking in bug 763879.
+ * This test may hang if the load for this .css file is blocked due to a
+ * malfunction of CSP, but should pass if the style_good test passes.
+ */
+
+/* CSS font embedding tests */
+@font-face {
+ font-family: "arbitrary_good";
+ src: url('file_CSP.sjs?testid=font_good&type=application/octet-stream');
+}
+@font-face {
+ font-family: "arbitrary_bad";
+ src: url('http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=font_bad&type=application/octet-stream');
+}
+
+.div_arbitrary_good { font-family: "arbitrary_good"; }
+.div_arbitrary_bad { font-family: "arbitrary_bad"; }
+
diff --git a/dom/security/test/csp/file_CSP.sjs b/dom/security/test/csp/file_CSP.sjs
new file mode 100644
index 000000000..85c2df3ba
--- /dev/null
+++ b/dom/security/test/csp/file_CSP.sjs
@@ -0,0 +1,26 @@
+// SJS file for CSP mochitests
+
+function handleRequest(request, response)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var [name, value] = val.split('=');
+ query[name] = unescape(value);
+ });
+
+ var isPreflight = request.method == "OPTIONS";
+
+
+ //avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ if ("type" in query) {
+ response.setHeader("Content-Type", unescape(query['type']), false);
+ } else {
+ response.setHeader("Content-Type", "text/html", false);
+ }
+
+ if ("content" in query) {
+ response.write(unescape(query['content']));
+ }
+}
diff --git a/dom/security/test/csp/file_allow_https_schemes.html b/dom/security/test/csp/file_allow_https_schemes.html
new file mode 100644
index 000000000..787e683e8
--- /dev/null
+++ b/dom/security/test/csp/file_allow_https_schemes.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 826805 - CSP: Allow http and https for scheme-less sources</title>
+ </head>
+ <body>
+ <div id="testdiv">blocked</div>
+ <!--
+ We resue file_path_matching.js which just updates the contents of 'testdiv' to contain allowed.
+ Note, that we are loading the file_path_matchting.js using a scheme of 'https'.
+ -->
+ <script src="https://example.com/tests/dom/security/test/csp/file_path_matching.js#foo"></script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_base_uri_server.sjs b/dom/security/test/csp/file_base_uri_server.sjs
new file mode 100644
index 000000000..dfba2a061
--- /dev/null
+++ b/dom/security/test/csp/file_base_uri_server.sjs
@@ -0,0 +1,61 @@
+// Custom *.sjs file specifically for the needs of
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1263286
+
+"use strict";
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+const PRE_BASE = `
+ <!DOCTYPE HTML>
+ <html>
+ <head>
+ <title>Bug 1045897 - Test CSP base-uri directive</title>`;
+
+const REGULAR_POST_BASE =`
+ </head>
+ <body onload='window.parent.postMessage({result: document.baseURI}, "*");'>
+ <!-- just making use of the 'base' tag for this test -->
+ </body>
+ </html>`;
+
+const SCRIPT_POST_BASE = `
+ </head>
+ <body>
+ <script>
+ document.getElementById("base1").removeAttribute("href");
+ window.parent.postMessage({result: document.baseURI}, "*");
+ </script>
+ </body>
+ </html>`;
+
+function handleRequest(request, response) {
+ const query = new URLSearchParams(request.queryString);
+
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // Deliver the CSP policy encoded in the URL
+ response.setHeader("Content-Security-Policy", query.get("csp"), false);
+
+ // Send HTML to test allowed/blocked behaviors
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(PRE_BASE);
+ var base1 =
+ "<base id=\"base1\" href=\"" + query.get("base1") + "\">";
+ var base2 =
+ "<base id=\"base2\" href=\"" + query.get("base2") + "\">";
+ response.write(base1 + base2);
+
+ if (query.get("action") === "enforce-csp") {
+ response.write(REGULAR_POST_BASE);
+ return;
+ }
+
+ if (query.get("action") === "remove-base1") {
+ response.write(SCRIPT_POST_BASE);
+ return;
+ }
+
+ // we should never get here, but just in case
+ // return something unexpected
+ response.write("do'h");
+}
diff --git a/dom/security/test/csp/file_blob_data_schemes.html b/dom/security/test/csp/file_blob_data_schemes.html
new file mode 100644
index 000000000..0a4a49160
--- /dev/null
+++ b/dom/security/test/csp/file_blob_data_schemes.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1086999 - Wildcard should not match blob:, data:</title>
+</head>
+<body>
+<script type="text/javascript">
+
+var base64data =
+"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+"P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
+
+
+// construct an image element using *data:*
+var data_src = "data:image/png;base64," + base64data;
+var data_img = document.createElement('img');
+data_img.onload = function() {
+ window.parent.postMessage({scheme: "data", result: "allowed"}, "*");
+}
+data_img.onerror = function() {
+ window.parent.postMessage({scheme: "data", result: "blocked"}, "*");
+}
+data_img.src = data_src;
+document.body.appendChild(data_img);
+
+
+// construct an image element using *blob:*
+var byteCharacters = atob(base64data);
+var byteNumbers = new Array(byteCharacters.length);
+for (var i = 0; i < byteCharacters.length; i++) {
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
+}
+var byteArray = new Uint8Array(byteNumbers);
+var blob = new Blob([byteArray], {type: "image/png"});
+var imageUrl = URL.createObjectURL( blob );
+
+var blob_img = document.createElement('img');
+blob_img.onload = function() {
+ window.parent.postMessage({scheme: "blob", result: "allowed"}, "*");
+}
+blob_img.onerror = function() {
+ window.parent.postMessage({scheme: "blob", result: "blocked"}, "*");
+}
+blob_img.src = imageUrl;
+document.body.appendChild(blob_img);
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_block_all_mcb.sjs b/dom/security/test/csp/file_block_all_mcb.sjs
new file mode 100644
index 000000000..731553dd7
--- /dev/null
+++ b/dom/security/test/csp/file_block_all_mcb.sjs
@@ -0,0 +1,76 @@
+// custom *.sjs for Bug 1122236
+// CSP: 'block-all-mixed-content'
+
+const HEAD =
+ "<!DOCTYPE HTML>" +
+ "<html><head><meta charset=\"utf-8\">" +
+ "<title>Bug 1122236 - CSP: Implement block-all-mixed-content</title>" +
+ "</head>";
+
+const CSP_ALLOW =
+ "<meta http-equiv=\"Content-Security-Policy\" content=\"img-src *\">";
+
+const CSP_BLOCK =
+ "<meta http-equiv=\"Content-Security-Policy\" content=\"block-all-mixed-content\">";
+
+const BODY =
+ "<body>" +
+ "<img id=\"testimage\" src=\"http://mochi.test:8888/tests/image/test/mochitest/blue.png\"></img>" +
+ "<script type=\"application/javascript\">" +
+ " var myImg = document.getElementById(\"testimage\");" +
+ " myImg.onload = function(e) {" +
+ " window.parent.postMessage({result: \"img-loaded\"}, \"*\");" +
+ " };" +
+ " myImg.onerror = function(e) {" +
+ " window.parent.postMessage({result: \"img-blocked\"}, \"*\");" +
+ " };" +
+ "</script>" +
+ "</body>" +
+ "</html>";
+
+// We have to use this special code fragment, in particular '?nocache' to trigger an
+// actual network load rather than loading the image from the cache.
+const BODY_CSPRO =
+ "<body>" +
+ "<img id=\"testimage\" src=\"http://mochi.test:8888/tests/image/test/mochitest/blue.png?nocache\"></img>" +
+ "<script type=\"application/javascript\">" +
+ " var myImg = document.getElementById(\"testimage\");" +
+ " myImg.onload = function(e) {" +
+ " window.parent.postMessage({result: \"img-loaded\"}, \"*\");" +
+ " };" +
+ " myImg.onerror = function(e) {" +
+ " window.parent.postMessage({result: \"img-blocked\"}, \"*\");" +
+ " };" +
+ "</script>" +
+ "</body>" +
+ "</html>";
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ var queryString = request.queryString;
+
+ if (queryString === "csp-block") {
+ response.write(HEAD + CSP_BLOCK + BODY);
+ return;
+ }
+ if (queryString === "csp-allow") {
+ response.write(HEAD + CSP_ALLOW + BODY);
+ return;
+ }
+ if (queryString === "no-csp") {
+ response.write(HEAD + BODY);
+ return;
+ }
+ if (queryString === "cspro-block") {
+ // CSP RO is not supported in meta tag, let's use the header
+ response.setHeader("Content-Security-Policy-Report-Only", "block-all-mixed-content", false);
+ response.write(HEAD + BODY_CSPRO);
+ return;
+ }
+ // we should never get here but just in case return something unexpected
+ response.write("do'h");
+
+}
diff --git a/dom/security/test/csp/file_block_all_mixed_content_frame_navigation1.html b/dom/security/test/csp/file_block_all_mixed_content_frame_navigation1.html
new file mode 100644
index 000000000..fdc1ae87a
--- /dev/null
+++ b/dom/security/test/csp/file_block_all_mixed_content_frame_navigation1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="Content-Security-Policy" content="block-all-mixed-content">
+ <title>Bug 1122236 - CSP: Implement block-all-mixed-content</title>
+</head>
+<body>
+<b>user clicks and navigates from https://b.com to http://c.com</b>
+
+<a id="navlink" href="http://example.com/tests/dom/security/test/csp/file_block_all_mixed_content_frame_navigation2.html">foo</a>
+
+<script class="testbody" type="text/javascript">
+ // click the link to start the frame navigation
+ document.getElementById("navlink").click();
+</script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_block_all_mixed_content_frame_navigation2.html b/dom/security/test/csp/file_block_all_mixed_content_frame_navigation2.html
new file mode 100644
index 000000000..4c4084e9e
--- /dev/null
+++ b/dom/security/test/csp/file_block_all_mixed_content_frame_navigation2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1122236 - CSP: Implement block-all-mixed-content</title>
+</head>
+<body>
+<b>http://c.com loaded, let's tell the parent</b>
+
+<script class="testbody" type="text/javascript">
+ window.parent.postMessage({result: "frame-navigated"}, "*");
+</script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_bug1229639.html b/dom/security/test/csp/file_bug1229639.html
new file mode 100644
index 000000000..1e6152ead
--- /dev/null
+++ b/dom/security/test/csp/file_bug1229639.html
@@ -0,0 +1,7 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <!-- this should be allowed -->
+ <script src="http://mochi.test:8888/tests/dom/security/test/csp/%24.js"> </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_bug1229639.html^headers^ b/dom/security/test/csp/file_bug1229639.html^headers^
new file mode 100644
index 000000000..0177de7a3
--- /dev/null
+++ b/dom/security/test/csp/file_bug1229639.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: "default-src 'self'; script-src http://mochi.test:8888/tests/dom/security/test/csp/%24.js \ No newline at end of file
diff --git a/dom/security/test/csp/file_bug1312272.html b/dom/security/test/csp/file_bug1312272.html
new file mode 100644
index 000000000..18e0e5589
--- /dev/null
+++ b/dom/security/test/csp/file_bug1312272.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>marquee inline script tests for Bug 1312272</title>
+</head>
+<body>
+<marquee id="m" onstart="parent.postMessage('csp-violation-marquee-onstart', '*')">bug 1312272</marquee>
+<script src="file_bug1312272.js"></script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_bug1312272.html^headers^ b/dom/security/test/csp/file_bug1312272.html^headers^
new file mode 100644
index 000000000..25a9483ea
--- /dev/null
+++ b/dom/security/test/csp/file_bug1312272.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src *; script-src * 'unsafe-eval'
diff --git a/dom/security/test/csp/file_bug1312272.js b/dom/security/test/csp/file_bug1312272.js
new file mode 100644
index 000000000..01c03e43f
--- /dev/null
+++ b/dom/security/test/csp/file_bug1312272.js
@@ -0,0 +1,8 @@
+var m = document.getElementById("m");
+m.addEventListener("click", function() {
+ // this will trigger after onstart, obviously.
+ parent.postMessage('finish', '*');
+});
+console.log("finish-handler setup");
+m.click();
+console.log("clicked");
diff --git a/dom/security/test/csp/file_bug663567.xsl b/dom/security/test/csp/file_bug663567.xsl
new file mode 100644
index 000000000..b12b0d3b1
--- /dev/null
+++ b/dom/security/test/csp/file_bug663567.xsl
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!-- Edited by XMLSpy® -->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+<xsl:template match="/">
+ <html>
+ <body>
+ <h2 id="xsltheader">this xml file should be formatted using an xsl file(lower iframe should contain xml dump)!</h2>
+ <table border="1">
+ <tr bgcolor="#990099">
+ <th>Title</th>
+ <th>Artist</th>
+ <th>Price</th>
+ </tr>
+ <xsl:for-each select="catalog/cd">
+ <tr>
+ <td><xsl:value-of select="title"/></td>
+ <td><xsl:value-of select="artist"/></td>
+ <td><xsl:value-of select="price"/></td>
+ </tr>
+ </xsl:for-each>
+ </table>
+ </body>
+ </html>
+</xsl:template>
+</xsl:stylesheet>
+
diff --git a/dom/security/test/csp/file_bug663567_allows.xml b/dom/security/test/csp/file_bug663567_allows.xml
new file mode 100644
index 000000000..93d345103
--- /dev/null
+++ b/dom/security/test/csp/file_bug663567_allows.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml-stylesheet type="text/xsl" href="file_bug663567.xsl"?>
+<catalog>
+ <cd>
+ <title>Empire Burlesque</title>
+ <artist>Bob Dylan</artist>
+ <country>USA</country>
+ <company>Columbia</company>
+ <price>10.90</price>
+ <year>1985</year>
+ </cd>
+ <cd>
+ <title>Hide your heart</title>
+ <artist>Bonnie Tyler</artist>
+ <country>UK</country>
+ <company>CBS Records</company>
+ <price>9.90</price>
+ <year>1988</year>
+ </cd>
+ <cd>
+ <title>Greatest Hits</title>
+ <artist>Dolly Parton</artist>
+ <country>USA</country>
+ <company>RCA</company>
+ <price>9.90</price>
+ <year>1982</year>
+ </cd>
+</catalog>
diff --git a/dom/security/test/csp/file_bug663567_allows.xml^headers^ b/dom/security/test/csp/file_bug663567_allows.xml^headers^
new file mode 100644
index 000000000..4c6fa3c26
--- /dev/null
+++ b/dom/security/test/csp/file_bug663567_allows.xml^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self'
diff --git a/dom/security/test/csp/file_bug663567_blocks.xml b/dom/security/test/csp/file_bug663567_blocks.xml
new file mode 100644
index 000000000..93d345103
--- /dev/null
+++ b/dom/security/test/csp/file_bug663567_blocks.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml-stylesheet type="text/xsl" href="file_bug663567.xsl"?>
+<catalog>
+ <cd>
+ <title>Empire Burlesque</title>
+ <artist>Bob Dylan</artist>
+ <country>USA</country>
+ <company>Columbia</company>
+ <price>10.90</price>
+ <year>1985</year>
+ </cd>
+ <cd>
+ <title>Hide your heart</title>
+ <artist>Bonnie Tyler</artist>
+ <country>UK</country>
+ <company>CBS Records</company>
+ <price>9.90</price>
+ <year>1988</year>
+ </cd>
+ <cd>
+ <title>Greatest Hits</title>
+ <artist>Dolly Parton</artist>
+ <country>USA</country>
+ <company>RCA</company>
+ <price>9.90</price>
+ <year>1982</year>
+ </cd>
+</catalog>
diff --git a/dom/security/test/csp/file_bug663567_blocks.xml^headers^ b/dom/security/test/csp/file_bug663567_blocks.xml^headers^
new file mode 100644
index 000000000..baf7f3c6a
--- /dev/null
+++ b/dom/security/test/csp/file_bug663567_blocks.xml^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src *.example.com
diff --git a/dom/security/test/csp/file_bug802872.html b/dom/security/test/csp/file_bug802872.html
new file mode 100644
index 000000000..dc7129b0c
--- /dev/null
+++ b/dom/security/test/csp/file_bug802872.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 802872</title>
+ <!-- Including SimpleTest.js so we can use AddLoadEvent !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <script src='file_bug802872.js'></script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_bug802872.html^headers^ b/dom/security/test/csp/file_bug802872.html^headers^
new file mode 100644
index 000000000..4c6fa3c26
--- /dev/null
+++ b/dom/security/test/csp/file_bug802872.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self'
diff --git a/dom/security/test/csp/file_bug802872.js b/dom/security/test/csp/file_bug802872.js
new file mode 100644
index 000000000..5df8086cc
--- /dev/null
+++ b/dom/security/test/csp/file_bug802872.js
@@ -0,0 +1,43 @@
+/*
+ * The policy for this test is:
+ * Content-Security-Policy: default-src 'self'
+ */
+
+function createAllowedEvent() {
+ /*
+ * Creates a new EventSource using 'http://mochi.test:8888'. Since all mochitests run on
+ * 'http://mochi.test', a default-src of 'self' allows this request.
+ */
+ var src_event = new EventSource("http://mochi.test:8888/tests/dom/security/test/csp/file_bug802872.sjs");
+
+ src_event.onmessage = function(e) {
+ src_event.close();
+ parent.dispatchEvent(new Event('allowedEventSrcCallbackOK'));
+ }
+
+ src_event.onerror = function(e) {
+ src_event.close();
+ parent.dispatchEvent(new Event('allowedEventSrcCallbackFailed'));
+ }
+}
+
+function createBlockedEvent() {
+ /*
+ * creates a new EventSource using 'http://example.com'. This domain is not whitelisted by the
+ * CSP of this page, therefore the CSP blocks this request.
+ */
+ var src_event = new EventSource("http://example.com/tests/dom/security/test/csp/file_bug802872.sjs");
+
+ src_event.onmessage = function(e) {
+ src_event.close();
+ parent.dispatchEvent(new Event('blockedEventSrcCallbackOK'));
+ }
+
+ src_event.onerror = function(e) {
+ src_event.close();
+ parent.dispatchEvent(new Event('blockedEventSrcCallbackFailed'));
+ }
+}
+
+addLoadEvent(createAllowedEvent);
+addLoadEvent(createBlockedEvent);
diff --git a/dom/security/test/csp/file_bug802872.sjs b/dom/security/test/csp/file_bug802872.sjs
new file mode 100644
index 000000000..b3e3f7024
--- /dev/null
+++ b/dom/security/test/csp/file_bug802872.sjs
@@ -0,0 +1,7 @@
+function handleRequest(request, response)
+{
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/event-stream", false);
+ response.write("data: eventsource response from server!");
+ response.write("\n\n");
+}
diff --git a/dom/security/test/csp/file_bug836922_npolicies.html b/dom/security/test/csp/file_bug836922_npolicies.html
new file mode 100644
index 000000000..6a728813a
--- /dev/null
+++ b/dom/security/test/csp/file_bug836922_npolicies.html
@@ -0,0 +1,12 @@
+<html>
+ <head>
+ <link rel='stylesheet' type='text/css'
+ href='/tests/dom/security/test/csp/file_CSP.sjs?testid=css_self&type=text/css' />
+
+ </head>
+ <body>
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img_self&type=img/png"> </img>
+ <script src='/tests/dom/security/test/csp/file_CSP.sjs?testid=script_self&type=text/javascript'></script>
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_bug836922_npolicies.html^headers^ b/dom/security/test/csp/file_bug836922_npolicies.html^headers^
new file mode 100644
index 000000000..ec6ba8c4a
--- /dev/null
+++ b/dom/security/test/csp/file_bug836922_npolicies.html^headers^
@@ -0,0 +1,2 @@
+content-security-policy: default-src 'self'; img-src 'none'; report-uri http://mochi.test:8888/tests/dom/security/test/csp/file_bug836922_npolicies_violation.sjs
+content-security-policy-report-only: default-src *; img-src 'self'; script-src 'none'; report-uri http://mochi.test:8888/tests/dom/security/test/csp/file_bug836922_npolicies_ro_violation.sjs
diff --git a/dom/security/test/csp/file_bug836922_npolicies_ro_violation.sjs b/dom/security/test/csp/file_bug836922_npolicies_ro_violation.sjs
new file mode 100644
index 000000000..3e7603421
--- /dev/null
+++ b/dom/security/test/csp/file_bug836922_npolicies_ro_violation.sjs
@@ -0,0 +1,53 @@
+// SJS file that receives violation reports and then responds with nothing.
+
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+const STATE_KEY = "bug836922_ro_violations";
+
+function handleRequest(request, response)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var [name, value] = val.split('=');
+ query[name] = unescape(value);
+ });
+
+ if ('results' in query) {
+ // if asked for the received data, send it.
+ response.setHeader("Content-Type", "text/javascript", false);
+ if (getState(STATE_KEY)) {
+ response.write(getState(STATE_KEY));
+ } else {
+ // no state has been recorded.
+ response.write(JSON.stringify({}));
+ }
+ } else if ('reset' in query) {
+ //clear state
+ setState(STATE_KEY, JSON.stringify(null));
+ } else {
+ // ... otherwise, just respond "ok".
+ response.write("null");
+
+ var bodystream = new BinaryInputStream(request.bodyInputStream);
+ var avail;
+ var bytes = [];
+ while ((avail = bodystream.available()) > 0)
+ Array.prototype.push.apply(bytes, bodystream.readByteArray(avail));
+
+ var data = String.fromCharCode.apply(null, bytes);
+
+ // figure out which test was violating a policy
+ var testpat = new RegExp("testid=([a-z0-9_]+)");
+ var testid = testpat.exec(data)[1];
+
+ // store the violation in the persistent state
+ var s = JSON.parse(getState(STATE_KEY) || "{}");
+ s[testid] ? s[testid]++ : s[testid] = 1;
+ setState(STATE_KEY, JSON.stringify(s));
+ }
+}
+
+
diff --git a/dom/security/test/csp/file_bug836922_npolicies_violation.sjs b/dom/security/test/csp/file_bug836922_npolicies_violation.sjs
new file mode 100644
index 000000000..15e4958af
--- /dev/null
+++ b/dom/security/test/csp/file_bug836922_npolicies_violation.sjs
@@ -0,0 +1,59 @@
+// SJS file that receives violation reports and then responds with nothing.
+
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+const STATE = "bug836922_violations";
+
+function handleRequest(request, response)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var [name, value] = val.split('=');
+ query[name] = unescape(value);
+ });
+
+
+ if ('results' in query) {
+ // if asked for the received data, send it.
+ response.setHeader("Content-Type", "text/javascript", false);
+ if (getState(STATE)) {
+ response.write(getState(STATE));
+ } else {
+ // no state has been recorded.
+ response.write(JSON.stringify({}));
+ }
+ } else if ('reset' in query) {
+ //clear state
+ setState(STATE, JSON.stringify(null));
+ } else {
+ // ... otherwise, just respond "ok".
+ response.write("null");
+
+ var bodystream = new BinaryInputStream(request.bodyInputStream);
+ var avail;
+ var bytes = [];
+ while ((avail = bodystream.available()) > 0)
+ Array.prototype.push.apply(bytes, bodystream.readByteArray(avail));
+
+ var data = String.fromCharCode.apply(null, bytes);
+
+ // figure out which test was violating a policy
+ var testpat = new RegExp("testid=([a-z0-9_]+)");
+ var testid = testpat.exec(data)[1];
+
+ // store the violation in the persistent state
+ var s = getState(STATE);
+ if (!s) s = "{}";
+ s = JSON.parse(s);
+ if (!s) s = {};
+
+ if (!s[testid]) s[testid] = 0;
+ s[testid]++;
+ setState(STATE, JSON.stringify(s));
+ }
+}
+
+
diff --git a/dom/security/test/csp/file_bug885433_allows.html b/dom/security/test/csp/file_bug885433_allows.html
new file mode 100644
index 000000000..5d7aacbda
--- /dev/null
+++ b/dom/security/test/csp/file_bug885433_allows.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<!--
+The Content-Security-Policy header for this file is:
+
+ Content-Security-Policy: img-src 'self';
+
+It does not include any of the default-src, script-src, or style-src
+directives. It should allow the use of unsafe-inline and unsafe-eval on
+scripts, and unsafe-inline on styles, because no directives related to scripts
+or styles are specified.
+-->
+<html>
+<body>
+ <ol>
+ <li id="unsafe-inline-script-allowed">Inline script allowed (this text should be green)</li>
+ <li id="unsafe-eval-script-allowed">Eval script allowed (this text should be green)</li>
+ <li id="unsafe-inline-style-allowed">Inline style allowed (this text should be green)</li>
+ </ol>
+
+ <script>
+ // Use inline script to set a style attribute
+ document.getElementById("unsafe-inline-script-allowed").style.color = "green";
+
+ // Use eval to set a style attribute
+ // try/catch is used because CSP causes eval to throw an exception when it
+ // is blocked, which would derail the rest of the tests in this file.
+ try {
+ eval('document.getElementById("unsafe-eval-script-allowed").style.color = "green";');
+ } catch (e) {}
+ </script>
+
+ <style>
+ li#unsafe-inline-style-allowed {
+ color: green;
+ }
+ </style>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_bug885433_allows.html^headers^ b/dom/security/test/csp/file_bug885433_allows.html^headers^
new file mode 100644
index 000000000..767b9ca92
--- /dev/null
+++ b/dom/security/test/csp/file_bug885433_allows.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: img-src 'self';
diff --git a/dom/security/test/csp/file_bug885433_blocks.html b/dom/security/test/csp/file_bug885433_blocks.html
new file mode 100644
index 000000000..2279b33e4
--- /dev/null
+++ b/dom/security/test/csp/file_bug885433_blocks.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<!--
+The Content-Security-Policy header for this file is:
+
+ Content-Security-Policy: default-src 'self';
+
+The Content-Security-Policy header for this file includes the default-src
+directive, which triggers the default behavior of blocking unsafe-inline and
+unsafe-eval on scripts, and unsafe-inline on styles.
+-->
+<html>
+<body>
+ <ol>
+ <li id="unsafe-inline-script-blocked">Inline script blocked (this text should be black)</li>
+ <li id="unsafe-eval-script-blocked">Eval script blocked (this text should be black)</li>
+ <li id="unsafe-inline-style-blocked">Inline style blocked (this text should be black)</li>
+ </ol>
+
+ <script>
+ // Use inline script to set a style attribute
+ document.getElementById("unsafe-inline-script-blocked").style.color = "green";
+
+ // Use eval to set a style attribute
+ // try/catch is used because CSP causes eval to throw an exception when it
+ // is blocked, which would derail the rest of the tests in this file.
+ try {
+ eval('document.getElementById("unsafe-eval-script-blocked").style.color = "green";');
+ } catch (e) {}
+ </script>
+
+ <style>
+ li#unsafe-inline-style-blocked {
+ color: green;
+ }
+ </style>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_bug885433_blocks.html^headers^ b/dom/security/test/csp/file_bug885433_blocks.html^headers^
new file mode 100644
index 000000000..f82598b67
--- /dev/null
+++ b/dom/security/test/csp/file_bug885433_blocks.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self';
diff --git a/dom/security/test/csp/file_bug886164.html b/dom/security/test/csp/file_bug886164.html
new file mode 100644
index 000000000..ec8c9e7e9
--- /dev/null
+++ b/dom/security/test/csp/file_bug886164.html
@@ -0,0 +1,15 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <!-- sandbox="allow-same-origin" -->
+ <!-- Content-Security-Policy: default-src 'self' -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img_bad&type=img/png"> </img>
+
+ <!-- these should load ok -->
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img_good&type=img/png" />
+ <script src='/tests/dom/security/test/csp/file_CSP.sjs?testid=scripta_bad&type=text/javascript'></script>
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_bug886164.html^headers^ b/dom/security/test/csp/file_bug886164.html^headers^
new file mode 100644
index 000000000..4c6fa3c26
--- /dev/null
+++ b/dom/security/test/csp/file_bug886164.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self'
diff --git a/dom/security/test/csp/file_bug886164_2.html b/dom/security/test/csp/file_bug886164_2.html
new file mode 100644
index 000000000..83d36c55a
--- /dev/null
+++ b/dom/security/test/csp/file_bug886164_2.html
@@ -0,0 +1,14 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <!-- sandbox -->
+ <!-- Content-Security-Policy: default-src 'self' -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img2_bad&type=img/png"> </img>
+
+ <!-- these should load ok -->
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img2a_good&type=img/png" />
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_bug886164_2.html^headers^ b/dom/security/test/csp/file_bug886164_2.html^headers^
new file mode 100644
index 000000000..4c6fa3c26
--- /dev/null
+++ b/dom/security/test/csp/file_bug886164_2.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self'
diff --git a/dom/security/test/csp/file_bug886164_3.html b/dom/security/test/csp/file_bug886164_3.html
new file mode 100644
index 000000000..8b4313000
--- /dev/null
+++ b/dom/security/test/csp/file_bug886164_3.html
@@ -0,0 +1,12 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <!-- sandbox -->
+ <!-- Content-Security-Policy: default-src 'none' -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img3_bad&type=img/png"> </img>
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img3a_bad&type=img/png" />
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_bug886164_3.html^headers^ b/dom/security/test/csp/file_bug886164_3.html^headers^
new file mode 100644
index 000000000..6581fd425
--- /dev/null
+++ b/dom/security/test/csp/file_bug886164_3.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'none'
diff --git a/dom/security/test/csp/file_bug886164_4.html b/dom/security/test/csp/file_bug886164_4.html
new file mode 100644
index 000000000..41137ea01
--- /dev/null
+++ b/dom/security/test/csp/file_bug886164_4.html
@@ -0,0 +1,12 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <!-- sandbox -->
+ <!-- Content-Security-Policy: default-src 'none' -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img4_bad&type=img/png"> </img>
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img4a_bad&type=img/png" />
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_bug886164_4.html^headers^ b/dom/security/test/csp/file_bug886164_4.html^headers^
new file mode 100644
index 000000000..6581fd425
--- /dev/null
+++ b/dom/security/test/csp/file_bug886164_4.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'none'
diff --git a/dom/security/test/csp/file_bug886164_5.html b/dom/security/test/csp/file_bug886164_5.html
new file mode 100644
index 000000000..ae65171a5
--- /dev/null
+++ b/dom/security/test/csp/file_bug886164_5.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head> <meta charset="utf-8"> </head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+ }
+
+ function doStuff() {
+ ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
+ }
+</script>
+<script src='file_iframe_sandbox_pass.js'></script>
+<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
+ I am sandboxed but with only inline "allow-scripts"
+
+ <!-- sandbox="allow-scripts" -->
+ <!-- Content-Security-Policy: default-src 'none' 'unsafe-inline'-->
+
+ <!-- these should be stopped by CSP -->
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img5_bad&type=img/png" />
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img5a_bad&type=img/png"> </img>
+ <script src='/tests/dom/security/test/csp/file_CSP.sjs?testid=script5_bad&type=text/javascript'></script>
+ <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script5a_bad&type=text/javascript'></script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_bug886164_5.html^headers^ b/dom/security/test/csp/file_bug886164_5.html^headers^
new file mode 100644
index 000000000..3abc19055
--- /dev/null
+++ b/dom/security/test/csp/file_bug886164_5.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'none' 'unsafe-inline';
diff --git a/dom/security/test/csp/file_bug886164_6.html b/dom/security/test/csp/file_bug886164_6.html
new file mode 100644
index 000000000..f985ec8ce
--- /dev/null
+++ b/dom/security/test/csp/file_bug886164_6.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+ }
+
+ function doStuff() {
+ ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
+
+ document.getElementById('a_form').submit();
+
+ // trigger the javascript: url test
+ sendMouseEvent({type:'click'}, 'a_link');
+ }
+</script>
+<script src='file_iframe_sandbox_pass.js'></script>
+<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
+ I am sandboxed but with "allow-scripts"
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img6_bad&type=img/png"> </img>
+ <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script6_bad&type=text/javascript'></script>
+
+ <form method="get" action="file_iframe_sandbox_form_fail.html" id="a_form">
+ First name: <input type="text" name="firstname">
+ Last name: <input type="text" name="lastname">
+ <input type="submit" onclick="doSubmit()" id="a_button">
+ </form>
+
+ <a href = 'javascript:ok(true, "documents sandboxed with allow-scripts should be able to run script from javascript: URLs");' id='a_link'>click me</a>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_bug886164_6.html^headers^ b/dom/security/test/csp/file_bug886164_6.html^headers^
new file mode 100644
index 000000000..6f9fc3f25
--- /dev/null
+++ b/dom/security/test/csp/file_bug886164_6.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self' 'unsafe-inline';
diff --git a/dom/security/test/csp/file_bug888172.html b/dom/security/test/csp/file_bug888172.html
new file mode 100644
index 000000000..27cf9b00a
--- /dev/null
+++ b/dom/security/test/csp/file_bug888172.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html>
+ <body>
+ <ol>
+ <li id="unsafe-inline-script">Inline script (green if allowed, black if blocked)</li>
+ <li id="unsafe-eval-script">Eval script (green if allowed, black if blocked)</li>
+ <li id="unsafe-inline-style">Inline style (green if allowed, black if blocked)</li>
+ </ol>
+
+ <script>
+ // Use inline script to set a style attribute
+ document.getElementById("unsafe-inline-script").style.color = "green";
+
+ // Use eval to set a style attribute
+ // try/catch is used because CSP causes eval to throw an exception when it
+ // is blocked, which would derail the rest of the tests in this file.
+ try {
+ eval('document.getElementById("unsafe-eval-script").style.color = "green";');
+ } catch (e) {}
+ </script>
+
+ <style>
+ li#unsafe-inline-style {
+ color: green;
+ }
+ </style>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_bug888172.sjs b/dom/security/test/csp/file_bug888172.sjs
new file mode 100644
index 000000000..03309610f
--- /dev/null
+++ b/dom/security/test/csp/file_bug888172.sjs
@@ -0,0 +1,43 @@
+// SJS file for CSP mochitests
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function loadHTMLFromFile(path) {
+ // Load the HTML to return in the response from file.
+ // Since it's relative to the cwd of the test runner, we start there and
+ // append to get to the actual path of the file.
+ var testHTMLFile =
+ Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("CurWorkD", Components.interfaces.nsILocalFile);
+ var dirs = path.split("/");
+ for (var i = 0; i < dirs.length; i++) {
+ testHTMLFile.append(dirs[i]);
+ }
+ var testHTMLFileStream =
+ Components.classes["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ testHTMLFileStream.init(testHTMLFile, -1, 0, 0);
+ var testHTML = NetUtil.readInputStreamToString(testHTMLFileStream, testHTMLFileStream.available());
+ return testHTML;
+}
+
+function handleRequest(request, response)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var [name, value] = val.split('=');
+ query[name] = unescape(value);
+ });
+
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // Deliver the CSP policy encoded in the URI
+ if (query['csp'])
+ response.setHeader("Content-Security-Policy", unescape(query['csp']), false);
+
+ // Send HTML to test allowed/blocked behaviors
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(loadHTMLFromFile("tests/dom/security/test/csp/file_bug888172.html"));
+}
diff --git a/dom/security/test/csp/file_bug909029_none.html b/dom/security/test/csp/file_bug909029_none.html
new file mode 100644
index 000000000..0d4934a4a
--- /dev/null
+++ b/dom/security/test/csp/file_bug909029_none.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <!-- file_CSP.sjs mocks a resource load -->
+ <link rel='stylesheet' type='text/css'
+ href='file_CSP.sjs?testid=noneExternalStylesBlocked&type=text/css' />
+ </head>
+ <body>
+ <p id="inline-style">This should be green</p>
+ <p id="inline-script">This should be black</p>
+ <style>
+ p#inline-style { color:rgb(0, 128, 0); }
+ </style>
+ <script>
+ // Use inline script to set a style attribute
+ document.getElementById("inline-script").style.color = "rgb(0, 128, 0)";
+ </script>
+ <img src="file_CSP.sjs?testid=noneExternalImgLoaded&type=img/png" />
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_bug909029_none.html^headers^ b/dom/security/test/csp/file_bug909029_none.html^headers^
new file mode 100644
index 000000000..ecb345875
--- /dev/null
+++ b/dom/security/test/csp/file_bug909029_none.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src * ; style-src 'none' 'unsafe-inline';
diff --git a/dom/security/test/csp/file_bug909029_star.html b/dom/security/test/csp/file_bug909029_star.html
new file mode 100644
index 000000000..bcb907a96
--- /dev/null
+++ b/dom/security/test/csp/file_bug909029_star.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<html>
+ <head>
+ <link rel='stylesheet' type='text/css'
+ href='file_CSP.sjs?testid=starExternalStylesLoaded&type=text/css' />
+ </head>
+ <body>
+ <p id="inline-style">This should be green</p>
+ <p id="inline-script">This should be black</p>
+ <style>
+ p#inline-style { color:rgb(0, 128, 0); }
+ </style>
+ <script>
+ // Use inline script to set a style attribute
+ document.getElementById("inline-script").style.color = "rgb(0, 128, 0)";
+ </script>
+ <img src="file_CSP.sjs?testid=starExternalImgLoaded&type=img/png" />
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_bug909029_star.html^headers^ b/dom/security/test/csp/file_bug909029_star.html^headers^
new file mode 100644
index 000000000..eccc1c011
--- /dev/null
+++ b/dom/security/test/csp/file_bug909029_star.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src *; style-src * 'unsafe-inline';
diff --git a/dom/security/test/csp/file_bug910139.sjs b/dom/security/test/csp/file_bug910139.sjs
new file mode 100644
index 000000000..172cc09c9
--- /dev/null
+++ b/dom/security/test/csp/file_bug910139.sjs
@@ -0,0 +1,52 @@
+// Server side js file for bug 910139, see file test_bug910139.html for details.
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function loadResponseFromFile(path) {
+ var testHTMLFile =
+ Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("CurWorkD", Components.interfaces.nsILocalFile);
+ var dirs = path.split("/");
+ for (var i = 0; i < dirs.length; i++) {
+ testHTMLFile.append(dirs[i]);
+ }
+ var testHTMLFileStream =
+ Components.classes["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ testHTMLFileStream.init(testHTMLFile, -1, 0, 0);
+ var testHTML = NetUtil.readInputStreamToString(testHTMLFileStream, testHTMLFileStream.available());
+ return testHTML;
+}
+
+var policies = [
+ "default-src 'self'; script-src 'self'", // CSP for checkAllowed
+ "default-src 'self'; script-src *.example.com" // CSP for checkBlocked
+]
+
+function getPolicy() {
+ var index;
+ // setState only accepts strings as arguments
+ if (!getState("counter")) {
+ index = 0;
+ setState("counter", index.toString());
+ }
+ else {
+ index = parseInt(getState("counter"));
+ ++index;
+ setState("counter", index.toString());
+ }
+ return policies[index];
+}
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // set the required CSP
+ response.setHeader("Content-Security-Policy", getPolicy(), false);
+
+ // return the requested XML file.
+ response.write(loadResponseFromFile("tests/dom/security/test/csp/file_bug910139.xml"));
+}
diff --git a/dom/security/test/csp/file_bug910139.xml b/dom/security/test/csp/file_bug910139.xml
new file mode 100644
index 000000000..29feba941
--- /dev/null
+++ b/dom/security/test/csp/file_bug910139.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<?xml-stylesheet type="text/xsl" href="file_bug910139.xsl"?>
+<catalog>
+ <cd>
+ <title>Empire Burlesque</title>
+ <artist>Bob Dylan</artist>
+ <country>USA</country>
+ <company>Columbia</company>
+ <price>10.90</price>
+ <year>1985</year>
+ </cd>
+ <cd>
+ <title>Hide your heart</title>
+ <artist>Bonnie Tyler</artist>
+ <country>UK</country>
+ <company>CBS Records</company>
+ <price>9.90</price>
+ <year>1988</year>
+ </cd>
+ <cd>
+ <title>Greatest Hits</title>
+ <artist>Dolly Parton</artist>
+ <country>USA</country>
+ <company>RCA</company>
+ <price>9.90</price>
+ <year>1982</year>
+ </cd>
+</catalog>
diff --git a/dom/security/test/csp/file_bug910139.xsl b/dom/security/test/csp/file_bug910139.xsl
new file mode 100644
index 000000000..b99abca09
--- /dev/null
+++ b/dom/security/test/csp/file_bug910139.xsl
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!-- Edited by XMLSpy® -->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+<xsl:template match="/">
+ <html>
+ <body>
+ <h2 id="xsltheader">this xml file should be formatted using an xsl file(lower iframe should contain xml dump)!</h2>
+ <table border="1">
+ <tr bgcolor="#990099">
+ <th>Title</th>
+ <th>Artist</th>
+ <th>Price</th>
+ </tr>
+ <xsl:for-each select="catalog/cd">
+ <tr>
+ <td><xsl:value-of select="title"/></td>
+ <td><xsl:value-of select="artist"/></td>
+ <td><xsl:value-of select="price"/></td>
+ </tr>
+ </xsl:for-each>
+ </table>
+ </body>
+ </html>
+</xsl:template>
+</xsl:stylesheet>
+
diff --git a/dom/security/test/csp/file_bug941404.html b/dom/security/test/csp/file_bug941404.html
new file mode 100644
index 000000000..3a2e636e0
--- /dev/null
+++ b/dom/security/test/csp/file_bug941404.html
@@ -0,0 +1,27 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+
+ <!-- this should be allowed (no CSP)-->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img_good&type=img/png"> </img>
+
+
+ <script type="text/javascript">
+ var req = new XMLHttpRequest();
+ req.onload = function() {
+ //this should be allowed (no CSP)
+ try {
+ var img = document.createElement("img");
+ img.src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img2_good&type=img/png";
+ document.body.appendChild(img);
+ } catch(e) {
+ console.log("yo: "+e);
+ }
+ };
+ req.open("get", "file_bug941404_xhr.html", true);
+ req.responseType = "document";
+ req.send();
+ </script>
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_bug941404_xhr.html b/dom/security/test/csp/file_bug941404_xhr.html
new file mode 100644
index 000000000..22e176f20
--- /dev/null
+++ b/dom/security/test/csp/file_bug941404_xhr.html
@@ -0,0 +1,5 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_bug941404_xhr.html^headers^ b/dom/security/test/csp/file_bug941404_xhr.html^headers^
new file mode 100644
index 000000000..1e5f70cc3
--- /dev/null
+++ b/dom/security/test/csp/file_bug941404_xhr.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'none' 'unsafe-inline' 'unsafe-eval'
diff --git a/dom/security/test/csp/file_child-src_iframe.html b/dom/security/test/csp/file_child-src_iframe.html
new file mode 100644
index 000000000..3534aa329
--- /dev/null
+++ b/dom/security/test/csp/file_child-src_iframe.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1045891</title>
+ </head>
+ <body>
+ <iframe id="testframe"> </iframe>
+ <script type="text/javascript">
+ page_id = window.location.hash.substring(1);
+
+ function executeTest(ev) {
+ testframe = document.getElementById('testframe');
+ testframe.contentWindow.postMessage({id:page_id, message:"execute"}, 'http://mochi.test:8888');
+ }
+
+ function reportError(ev) {
+ window.parent.postMessage({id:page_id, message:"blocked"}, 'http://mochi.test:8888');
+ cleanup();
+ }
+
+ function recvMessage(ev) {
+ if (ev.data.id == page_id) {
+ window.parent.postMessage({id:ev.data.id, message:ev.data.message}, 'http://mochi.test:8888');
+ cleanup();
+ }
+ }
+
+ function cleanup() {
+ testframe = document.getElementById('testframe');
+ window.removeEventListener('message', recvMessage);
+ testframe.removeEventListener('load', executeTest);
+ testframe.removeEventListener('error', reportError);
+ }
+
+
+ window.addEventListener('message', recvMessage, false);
+
+ try {
+ // Please note that file_testserver.sjs?foo does not return a response.
+ // For testing purposes this is not necessary because we only want to check
+ // whether CSP allows or blocks the load.
+ src = "file_testserver.sjs";
+ src += "?file=" + escape("tests/dom/security/test/csp/file_child-src_inner_frame.html");
+ src += "#" + escape(page_id);
+ testframe = document.getElementById('testframe');
+
+ testframe.addEventListener('load', executeTest, false);
+ testframe.addEventListener('error', reportError, false);
+
+ testframe.src = src;
+ }
+ catch (e) {
+ if (e.message.match(/Failed to load script/)) {
+ window.parent.postMessage({id:page_id, message:"blocked"}, 'http://mochi.test:8888');
+ } else {
+ window.parent.postMessage({id:page_id, message:"exception"}, 'http://mochi.test:8888');
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_child-src_inner_frame.html b/dom/security/test/csp/file_child-src_inner_frame.html
new file mode 100644
index 000000000..e42102430
--- /dev/null
+++ b/dom/security/test/csp/file_child-src_inner_frame.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1045891</title>
+ </head>
+ <body>
+ <iframe id="innermosttestframe"> </iframe>
+ <script type="text/javascript">
+ page_id = window.location.hash.substring(1);
+
+ function recvMessage(ev) {
+ if (ev.data.id == page_id) {
+ window.parent.postMessage({id:ev.data.id, message:'allowed'}, 'http://mochi.test:8888');
+ window.removeEventListener('message', recvMessage);
+ }
+ }
+
+ window.addEventListener('message', recvMessage, false);
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_child-src_service_worker.html b/dom/security/test/csp/file_child-src_service_worker.html
new file mode 100644
index 000000000..b291a4a4e
--- /dev/null
+++ b/dom/security/test/csp/file_child-src_service_worker.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1045891</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ page_id = window.location.hash.substring(1);
+ try {
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.register(
+ 'file_child-src_service_worker.js',
+ { scope: './' + page_id + '/' }
+ ).then(function(reg)
+ {
+ // registration worked
+ reg.unregister().then(function() {
+ window.parent.postMessage({id:page_id, message:"allowed"}, 'http://mochi.test:8888');
+ });
+ }).catch(function(error) {
+ // registration failed
+ window.parent.postMessage({id:page_id, message:"blocked"}, 'http://mochi.test:8888');
+ });
+ };
+ } catch(ex) {
+ window.parent.postMessage({id:page_id, message:"exception"}, 'http://mochi.test:8888');
+ }
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_child-src_service_worker.js b/dom/security/test/csp/file_child-src_service_worker.js
new file mode 100644
index 000000000..53f768707
--- /dev/null
+++ b/dom/security/test/csp/file_child-src_service_worker.js
@@ -0,0 +1,3 @@
+this.addEventListener('install', function(event) {
+ close();
+});
diff --git a/dom/security/test/csp/file_child-src_shared_worker-redirect.html b/dom/security/test/csp/file_child-src_shared_worker-redirect.html
new file mode 100644
index 000000000..313915302
--- /dev/null
+++ b/dom/security/test/csp/file_child-src_shared_worker-redirect.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1045891</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ page_id = window.location.hash.substring(1);
+ var redir = 'none';
+
+ page_id.split('_').forEach(function (val) {
+ var [name, value] = val.split('-');
+ if (name == 'redir') {
+ redir = unescape(value);
+ }
+ });
+
+ try {
+ worker = new SharedWorker('file_redirect_worker.sjs?path='
+ + escape("/tests/dom/security/test/csp/file_child-src_shared_worker.js")
+ + "&redir=" + redir
+ + "&page_id=" + page_id,
+ page_id);
+ worker.port.start();
+
+ worker.onerror = function(evt) {
+ evt.preventDefault();
+ window.parent.postMessage({id:page_id, message:"blocked"},
+ 'http://mochi.test:8888');
+ }
+
+ worker.port.onmessage = function(ev) {
+ window.parent.postMessage({id:page_id, message:"allowed"}, 'http://mochi.test:8888');
+ };
+
+ worker.onerror = function() {
+ window.parent.postMessage({id:page_id, message:"blocked"}, 'http://mochi.test:8888');
+ };
+
+ worker.port.postMessage('foo');
+ }
+ catch (e) {
+ window.parent.postMessage({id:page_id, message:"blocked"}, 'http://mochi.test:8888');
+ }
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_child-src_shared_worker.html b/dom/security/test/csp/file_child-src_shared_worker.html
new file mode 100644
index 000000000..0e9a56a29
--- /dev/null
+++ b/dom/security/test/csp/file_child-src_shared_worker.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1045891</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ page_id = window.location.hash.substring(1);
+ try {
+ worker = new SharedWorker(
+ 'file_testserver.sjs?file='+
+ escape("tests/dom/security/test/csp/file_child-src_shared_worker.js"),
+ page_id);
+ worker.port.start();
+
+ worker.onerror = function(evt) {
+ evt.preventDefault();
+ window.parent.postMessage({id:page_id, message:"blocked"},
+ 'http://mochi.test:8888');
+ }
+
+ worker.port.onmessage = function(ev) {
+ window.parent.postMessage({id:page_id, message:"allowed"},
+ 'http://mochi.test:8888');
+ };
+ worker.port.postMessage('foo');
+ }
+ catch (e) {
+ window.parent.postMessage({id:page_id, message:"blocked"},
+ 'http://mochi.test:8888');
+ }
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_child-src_shared_worker.js b/dom/security/test/csp/file_child-src_shared_worker.js
new file mode 100644
index 000000000..0fca1394c
--- /dev/null
+++ b/dom/security/test/csp/file_child-src_shared_worker.js
@@ -0,0 +1,8 @@
+onconnect = function(e) {
+ var port = e.ports[0];
+ port.addEventListener('message', function(e) {
+ port.postMessage('success');
+ });
+
+ port.start();
+}
diff --git a/dom/security/test/csp/file_child-src_shared_worker_data.html b/dom/security/test/csp/file_child-src_shared_worker_data.html
new file mode 100644
index 000000000..a4befe4ca
--- /dev/null
+++ b/dom/security/test/csp/file_child-src_shared_worker_data.html
@@ -0,0 +1,37 @@
+
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1045891</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ var page_id = window.location.hash.substring(1);
+ var shared_worker = "onconnect = function(e) { " +
+ "var port = e.ports[0];" +
+ "port.addEventListener('message'," +
+ "function(e) { port.postMessage('success'); });" +
+ "port.start(); }";
+
+ try {
+ var worker = new SharedWorker('data:application/javascript;charset=UTF-8,'+
+ escape(shared_worker), page_id);
+ worker.port.start();
+
+ worker.onerror = function(evt) {
+ evt.preventDefault();
+ window.parent.postMessage({id:page_id, message:"blocked"}, 'http://mochi.test:8888');
+ }
+
+ worker.port.onmessage = function(ev) {
+ window.parent.postMessage({id:page_id, message:"allowed"}, 'http://mochi.test:8888');
+ };
+
+ worker.port.postMessage('foo');
+ }
+ catch (e) {
+ window.parent.postMessage({id:page_id, message:"blocked"}, 'http://mochi.test:8888');
+ }
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_child-src_worker-redirect.html b/dom/security/test/csp/file_child-src_worker-redirect.html
new file mode 100644
index 000000000..188f173b8
--- /dev/null
+++ b/dom/security/test/csp/file_child-src_worker-redirect.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1045891</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ var page_id = window.location.hash.substring(1);
+ var redir = 'none';
+
+ page_id.split('_').forEach(function (val) {
+ var [name, value] = val.split('-');
+ if (name == 'redir') {
+ redir = unescape(value);
+ }
+ });
+
+ try {
+ worker = new Worker('file_redirect_worker.sjs?path='
+ + escape("/tests/dom/security/test/csp/file_child-src_worker.js")
+ + "&redir=" + redir
+ + "&page_id=" + page_id
+ );
+
+ worker.onerror = function(error) {
+ var msg = error.message;
+ if (msg.match(/^NetworkError/) || msg.match(/Failed to load worker script/)) {
+ // this means CSP blocked it
+ msg = "blocked";
+ }
+ window.parent.postMessage({id:page_id, message:msg}, 'http://mochi.test:8888');
+ error.preventDefault();
+ };
+
+ worker.onmessage = function(ev) {
+ window.parent.postMessage({id:page_id, message:"allowed"}, 'http://mochi.test:8888');
+
+ };
+ worker.postMessage('foo');
+ }
+ catch (e) {
+ if (e.message.match(/Failed to load script/)) {
+ window.parent.postMessage({id:page_id, message:"blocked"}, 'http://mochi.test:8888');
+ } else {
+ window.parent.postMessage({id:page_id, message:"exception"}, 'http://mochi.test:8888');
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_child-src_worker.html b/dom/security/test/csp/file_child-src_worker.html
new file mode 100644
index 000000000..9300d3f61
--- /dev/null
+++ b/dom/security/test/csp/file_child-src_worker.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1045891</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ page_id = window.location.hash.substring(1);
+ try {
+ worker = new Worker('file_testserver.sjs?file='+escape("tests/dom/security/test/csp/file_child-src_worker.js"));
+
+ worker.onerror = function(e) {
+ window.parent.postMessage({id:page_id, message:"blocked"}, 'http://mochi.test:8888');
+ e.preventDefault();
+ }
+
+ worker.onmessage = function(ev) {
+ window.parent.postMessage({id:page_id, message:"allowed"}, 'http://mochi.test:8888');
+ }
+
+ worker.postMessage('foo');
+ }
+ catch (e) {
+ if (e.message.match(/Failed to load script/)) {
+ window.parent.postMessage({id:page_id, message:"blocked"}, 'http://mochi.test:8888');
+ } else {
+ window.parent.postMessage({id:page_id, message:"exception"}, 'http://mochi.test:8888');
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_child-src_worker.js b/dom/security/test/csp/file_child-src_worker.js
new file mode 100644
index 000000000..1d93cac6b
--- /dev/null
+++ b/dom/security/test/csp/file_child-src_worker.js
@@ -0,0 +1,4 @@
+onmessage = function(e) {
+ postMessage('worker');
+};
+
diff --git a/dom/security/test/csp/file_child-src_worker_data.html b/dom/security/test/csp/file_child-src_worker_data.html
new file mode 100644
index 000000000..e9e22f01d
--- /dev/null
+++ b/dom/security/test/csp/file_child-src_worker_data.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1045891</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ page_id = window.location.hash.substring(1);
+ try {
+ worker = new Worker('data:application/javascript;charset=UTF-8,'+escape('onmessage = function(e) { postMessage("worker"); };'));
+
+ worker.onerror = function(e) {
+ window.parent.postMessage({id:page_id, message:"blocked"}, 'http://mochi.test:8888');
+ e.preventDefault();
+ }
+
+ worker.onmessage = function(ev) {
+ window.parent.postMessage({id:page_id, message:"allowed"}, 'http://mochi.test:8888');
+ }
+
+ worker.postMessage('foo');
+ }
+ catch (e) {
+ if (e.message.match(/Failed to load script/)) {
+ window.parent.postMessage({id:page_id, message:"blocked"}, 'http://mochi.test:8888');
+ } else {
+ console.log(e);
+ window.parent.postMessage({id:page_id, message:"exception"}, 'http://mochi.test:8888');
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_child_worker.js b/dom/security/test/csp/file_child_worker.js
new file mode 100644
index 000000000..256234377
--- /dev/null
+++ b/dom/security/test/csp/file_child_worker.js
@@ -0,0 +1,39 @@
+function doXHR(uri) {
+ try {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", uri);
+ xhr.send();
+ } catch(ex) {}
+}
+
+var sameBase = "http://mochi.test:8888/tests/dom/security/test/csp/file_CSP.sjs?testid=";
+var crossBase = "http://example.com/tests/dom/security/test/csp/file_CSP.sjs?testid=";
+
+onmessage = (e) => {
+ for (base of [sameBase, crossBase]) {
+ var prefix;
+ var suffix;
+ if (e.data.inherited == "parent") {
+ //Worker inherits CSP from parent worker
+ prefix = base + "worker_child_inherited_parent_";
+ suffix = base == sameBase ? "_good" : "_bad";
+ } else if (e.data.inherited == "document") {
+ //Worker inherits CSP from owner document -> parent worker -> subworker
+ prefix = base + "worker_child_inherited_document_";
+ suffix = base == sameBase ? "_good" : "_bad";
+ } else {
+ // Worker delivers CSP from HTTP header
+ prefix = base + "worker_child_";
+ suffix = base == sameBase ? "_same_bad" : "_cross_bad";
+ }
+
+ doXHR(prefix + "xhr" + suffix);
+ // Fetch is likely failed in subworker
+ // See Bug 1273070 - Failed to fetch in subworker
+ // Enable fetch test after the bug is fixed
+ // fetch(prefix + "xhr" + suffix);
+ try {
+ importScripts(prefix + "script" + suffix);
+ } catch(ex) {}
+ }
+}
diff --git a/dom/security/test/csp/file_child_worker.js^headers^ b/dom/security/test/csp/file_child_worker.js^headers^
new file mode 100644
index 000000000..6581fd425
--- /dev/null
+++ b/dom/security/test/csp/file_child_worker.js^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'none'
diff --git a/dom/security/test/csp/file_connect-src-fetch.html b/dom/security/test/csp/file_connect-src-fetch.html
new file mode 100644
index 000000000..ff9b2f740
--- /dev/null
+++ b/dom/security/test/csp/file_connect-src-fetch.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1139667 - Test mapping of fetch() to connect-src</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+
+ // Please note that file_testserver.sjs?foo does not return a response.
+ // For testing purposes this is not necessary because we only want to check
+ // whether CSP allows or blocks the load.
+ fetch( "file_testserver.sjs?foo");
+
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_connect-src.html b/dom/security/test/csp/file_connect-src.html
new file mode 100644
index 000000000..17a940a0e
--- /dev/null
+++ b/dom/security/test/csp/file_connect-src.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1031530 - Test mapping of XMLHttpRequest to connect-src</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+
+ try {
+ // Please note that file_testserver.sjs?foo does not return a response.
+ // For testing purposes this is not necessary because we only want to check
+ // whether CSP allows or blocks the load.
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "file_testserver.sjs?foo", false);
+ xhr.send(null);
+ }
+ catch (e) { }
+
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_data-uri_blocked.html b/dom/security/test/csp/file_data-uri_blocked.html
new file mode 100644
index 000000000..293e857e3
--- /dev/null
+++ b/dom/security/test/csp/file_data-uri_blocked.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1242019
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 587377</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <img width='1' height='1' title='' alt='' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKTWlDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKkIAI0BAJkoRyQCQLsAYFWBUiwCwMIAoKxAIi4EwK4BgFm2MkcCgL0FAHaOWJAPQGAAgJlCLMwAIDgCAEMeE80DIEwDoDDSv+CpX3CFuEgBAMDLlc2XS9IzFLiV0Bp38vDg4iHiwmyxQmEXKRBmCeQinJebIxNI5wNMzgwAABr50cH+OD+Q5+bk4eZm52zv9MWi/mvwbyI+IfHf/ryMAgQAEE7P79pf5eXWA3DHAbB1v2upWwDaVgBo3/ldM9sJoFoK0Hr5i3k4/EAenqFQyDwdHAoLC+0lYqG9MOOLPv8z4W/gi372/EAe/tt68ABxmkCZrcCjg/1xYW52rlKO58sEQjFu9+cj/seFf/2OKdHiNLFcLBWK8ViJuFAiTcd5uVKRRCHJleIS6X8y8R+W/QmTdw0ArIZPwE62B7XLbMB+7gECiw5Y0nYAQH7zLYwaC5EAEGc0Mnn3AACTv/mPQCsBAM2XpOMAALzoGFyolBdMxggAAESggSqwQQcMwRSswA6cwR28wBcCYQZEQAwkwDwQQgbkgBwKoRiWQRlUwDrYBLWwAxqgEZrhELTBMTgN5+ASXIHrcBcGYBiewhi8hgkEQcgIE2EhOogRYo7YIs4IF5mOBCJhSDSSgKQg6YgUUSLFyHKkAqlCapFdSCPyLXIUOY1cQPqQ28ggMor8irxHMZSBslED1AJ1QLmoHxqKxqBz0XQ0D12AlqJr0Rq0Hj2AtqKn0UvodXQAfYqOY4DRMQ5mjNlhXIyHRWCJWBomxxZj5Vg1Vo81Yx1YN3YVG8CeYe8IJAKLgBPsCF6EEMJsgpCQR1hMWEOoJewjtBK6CFcJg4Qxwicik6hPtCV6EvnEeGI6sZBYRqwm7iEeIZ4lXicOE1+TSCQOyZLkTgohJZAySQtJa0jbSC2kU6Q+0hBpnEwm65Btyd7kCLKArCCXkbeQD5BPkvvJw+S3FDrFiOJMCaIkUqSUEko1ZT/lBKWfMkKZoKpRzame1AiqiDqfWkltoHZQL1OHqRM0dZolzZsWQ8ukLaPV0JppZ2n3aC/pdLoJ3YMeRZfQl9Jr6Afp5+mD9HcMDYYNg8dIYigZaxl7GacYtxkvmUymBdOXmchUMNcyG5lnmA+Yb1VYKvYqfBWRyhKVOpVWlX6V56pUVXNVP9V5qgtUq1UPq15WfaZGVbNQ46kJ1Bar1akdVbupNq7OUndSj1DPUV+jvl/9gvpjDbKGhUaghkijVGO3xhmNIRbGMmXxWELWclYD6yxrmE1iW7L57Ex2Bfsbdi97TFNDc6pmrGaRZp3mcc0BDsax4PA52ZxKziHODc57LQMtPy2x1mqtZq1+rTfaetq+2mLtcu0W7eva73VwnUCdLJ31Om0693UJuja6UbqFutt1z+o+02PreekJ9cr1Dund0Uf1bfSj9Rfq79bv0R83MDQINpAZbDE4Y/DMkGPoa5hpuNHwhOGoEctoupHEaKPRSaMnuCbuh2fjNXgXPmasbxxirDTeZdxrPGFiaTLbpMSkxeS+Kc2Ua5pmutG003TMzMgs3KzYrMnsjjnVnGueYb7ZvNv8jYWlRZzFSos2i8eW2pZ8ywWWTZb3rJhWPlZ5VvVW16xJ1lzrLOtt1ldsUBtXmwybOpvLtqitm63Edptt3xTiFI8p0in1U27aMez87ArsmuwG7Tn2YfYl9m32zx3MHBId1jt0O3xydHXMdmxwvOuk4TTDqcSpw+lXZxtnoXOd8zUXpkuQyxKXdpcXU22niqdun3rLleUa7rrStdP1o5u7m9yt2W3U3cw9xX2r+00umxvJXcM970H08PdY4nHM452nm6fC85DnL152Xlle+70eT7OcJp7WMG3I28Rb4L3Le2A6Pj1l+s7pAz7GPgKfep+Hvqa+It89viN+1n6Zfgf8nvs7+sv9j/i/4XnyFvFOBWABwQHlAb2BGoGzA2sDHwSZBKUHNQWNBbsGLww+FUIMCQ1ZH3KTb8AX8hv5YzPcZyya0RXKCJ0VWhv6MMwmTB7WEY6GzwjfEH5vpvlM6cy2CIjgR2yIuB9pGZkX+X0UKSoyqi7qUbRTdHF09yzWrORZ+2e9jvGPqYy5O9tqtnJ2Z6xqbFJsY+ybuIC4qriBeIf4RfGXEnQTJAntieTE2MQ9ieNzAudsmjOc5JpUlnRjruXcorkX5unOy553PFk1WZB8OIWYEpeyP+WDIEJQLxhP5aduTR0T8oSbhU9FvqKNolGxt7hKPJLmnVaV9jjdO31D+miGT0Z1xjMJT1IreZEZkrkj801WRNberM/ZcdktOZSclJyjUg1plrQr1zC3KLdPZisrkw3keeZtyhuTh8r35CP5c/PbFWyFTNGjtFKuUA4WTC+oK3hbGFt4uEi9SFrUM99m/ur5IwuCFny9kLBQuLCz2Lh4WfHgIr9FuxYji1MXdy4xXVK6ZHhp8NJ9y2jLspb9UOJYUlXyannc8o5Sg9KlpUMrglc0lamUycturvRauWMVYZVkVe9ql9VbVn8qF5VfrHCsqK74sEa45uJXTl/VfPV5bdra3kq3yu3rSOuk626s91m/r0q9akHV0IbwDa0b8Y3lG19tSt50oXpq9Y7NtM3KzQM1YTXtW8y2rNvyoTaj9nqdf13LVv2tq7e+2Sba1r/dd3vzDoMdFTve75TsvLUreFdrvUV99W7S7oLdjxpiG7q/5n7duEd3T8Wej3ulewf2Re/ranRvbNyvv7+yCW1SNo0eSDpw5ZuAb9qb7Zp3tXBaKg7CQeXBJ9+mfHvjUOihzsPcw83fmX+39QjrSHkr0jq/dawto22gPaG97+iMo50dXh1Hvrf/fu8x42N1xzWPV56gnSg98fnkgpPjp2Snnp1OPz3Umdx590z8mWtdUV29Z0PPnj8XdO5Mt1/3yfPe549d8Lxw9CL3Ytslt0utPa49R35w/eFIr1tv62X3y+1XPK509E3rO9Hv03/6asDVc9f41y5dn3m978bsG7duJt0cuCW69fh29u0XdwruTNxdeo94r/y+2v3qB/oP6n+0/rFlwG3g+GDAYM/DWQ/vDgmHnv6U/9OH4dJHzEfVI0YjjY+dHx8bDRq98mTOk+GnsqcTz8p+Vv9563Or59/94vtLz1j82PAL+YvPv655qfNy76uprzrHI8cfvM55PfGm/K3O233vuO+638e9H5ko/ED+UPPR+mPHp9BP9z7nfP78L/eE8/sl0p8zAAA6NGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxMTEgNzkuMTU4MzI1LCAyMDE1LzA5LzEwLTAxOjEwOjIwICAgICAgICAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgICAgICAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoTWFjaW50b3NoKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAxNi0wMi0wOFQxMzoyNjo1MSswOTowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TWV0YWRhdGFEYXRlPjIwMTYtMDItMDhUMTM6MjY6NTErMDk6MDA8L3htcDpNZXRhZGF0YURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDE2LTAyLTA4VDEzOjI2OjUxKzA5OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDplN2QyMjJjOS03Nzg2LTRhMjktOTlhMy0yYjRjNWI4YTA5MWY8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDo5OWE3ZTJmOC0wZWFhLTExNzktYTE4NS05M2Y1MjZhZGU2ZDg8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDoyYmY3M2M3Ny03NzczLTQzNjAtYThiZS1kY2UyZTgzMWQ2YmM8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6MmJmNzNjNzctNzc3My00MzYwLWE4YmUtZGNlMmU4MzFkNmJjPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE2LTAyLTA4VDEzOjI2OjUxKzA5OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoTWFjaW50b3NoKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgICAgIDxyZGY6bGkgcmRmOnBhcnNlVHlwZT0iUmVzb3VyY2UiPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6YWN0aW9uPnNhdmVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6ZTdkMjIyYzktNzc4Ni00YTI5LTk5YTMtMmI0YzViOGEwOTFmPC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE2LTAyLTA4VDEzOjI2OjUxKzA5OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgQ0MgMjAxNSAoTWFjaW50b3NoKTwvc3RFdnQ6c29mdHdhcmVBZ2VudD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmNoYW5nZWQ+Lzwvc3RFdnQ6Y2hhbmdlZD4KICAgICAgICAgICAgICAgPC9yZGY6bGk+CiAgICAgICAgICAgIDwvcmRmOlNlcT4KICAgICAgICAgPC94bXBNTTpIaXN0b3J5PgogICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3BuZzwvZGM6Zm9ybWF0PgogICAgICAgICA8cGhvdG9zaG9wOkNvbG9yTW9kZT4zPC9waG90b3Nob3A6Q29sb3JNb2RlPgogICAgICAgICA8cGhvdG9zaG9wOklDQ1Byb2ZpbGU+c1JHQiBJRUM2MTk2Ni0yLjE8L3Bob3Rvc2hvcDpJQ0NQcm9maWxlPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICAgICA8dGlmZjpYUmVzb2x1dGlvbj43MjAwMDAvMTAwMDA8L3RpZmY6WFJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOllSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICAgICAgICAgPGV4aWY6Q29sb3JTcGFjZT4xPC9leGlmOkNvbG9yU3BhY2U+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4xPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjE8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PoAp5ZwAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAABNJREFUeNpi+P//PwMAAAD//wMACPwC/tOldWEAAAAASUVORK5CYII='>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_data-uri_blocked.html^headers^ b/dom/security/test/csp/file_data-uri_blocked.html^headers^
new file mode 100644
index 000000000..f593253f9
--- /dev/null
+++ b/dom/security/test/csp/file_data-uri_blocked.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self'; img-src 'none'
diff --git a/dom/security/test/csp/file_doccomment_meta.html b/dom/security/test/csp/file_doccomment_meta.html
new file mode 100644
index 000000000..a0f36a4bf
--- /dev/null
+++ b/dom/security/test/csp/file_doccomment_meta.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 663570 - Test doc.write(meta csp)</title>
+ <meta charset="utf-8">
+
+ <!-- Use doc.write() to *un*apply meta csp -->
+ <script type="application/javascript">
+ document.write("<!--");
+ </script>
+
+ <meta http-equiv="Content-Security-Policy" content= "style-src 'none'; script-src 'none'; img-src 'none'">
+ -->
+
+ <!-- try to load a css on a page where meta CSP is commented out -->
+ <link rel="stylesheet" type="text/css" href="file_docwrite_meta.css">
+
+ <!-- try to load a script on a page where meta CSP is commented out -->
+ <script id="testscript" src="file_docwrite_meta.js"></script>
+
+</head>
+<body>
+
+ <!-- try to load an image on a page where meta CSP is commented out -->
+ <img id="testimage" src="http://mochi.test:8888/tests/image/test/mochitest/blue.png"></img>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_docwrite_meta.css b/dom/security/test/csp/file_docwrite_meta.css
new file mode 100644
index 000000000..de725038b
--- /dev/null
+++ b/dom/security/test/csp/file_docwrite_meta.css
@@ -0,0 +1,3 @@
+body {
+ background-color: rgb(255, 0, 0);
+}
diff --git a/dom/security/test/csp/file_docwrite_meta.html b/dom/security/test/csp/file_docwrite_meta.html
new file mode 100644
index 000000000..292de3bec
--- /dev/null
+++ b/dom/security/test/csp/file_docwrite_meta.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 663570 - Test doc.write(meta csp)</title>
+ <meta charset="utf-8">
+
+ <!-- Use doc.write() to apply meta csp -->
+ <script type="application/javascript">
+ var metaCSP = "style-src 'none'; script-src 'none'; img-src 'none'";
+ document.write("<meta http-equiv=\"Content-Security-Policy\" content=\" " + metaCSP + "\">");
+ </script>
+
+ <!-- try to load a css which is forbidden by meta CSP -->
+ <link rel="stylesheet" type="text/css" href="file_docwrite_meta.css">
+
+ <!-- try to load a script which is forbidden by meta CSP -->
+ <script id="testscript" src="file_docwrite_meta.js"></script>
+
+</head>
+<body>
+
+ <!-- try to load an image which is forbidden by meta CSP -->
+ <img id="testimage" src="http://mochi.test:8888/tests/image/test/mochitest/blue.png"></img>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_docwrite_meta.js b/dom/security/test/csp/file_docwrite_meta.js
new file mode 100644
index 000000000..722adc235
--- /dev/null
+++ b/dom/security/test/csp/file_docwrite_meta.js
@@ -0,0 +1,3 @@
+// set a variable on the document which we can check to verify
+// whether the external script was loaded or blocked
+document.myMetaCSPScript = "external-JS-loaded";
diff --git a/dom/security/test/csp/file_dual_header_testserver.sjs b/dom/security/test/csp/file_dual_header_testserver.sjs
new file mode 100644
index 000000000..d5631e783
--- /dev/null
+++ b/dom/security/test/csp/file_dual_header_testserver.sjs
@@ -0,0 +1,46 @@
+/*
+ * Custom sjs file serving a test page using *two* CSP policies.
+ * See Bug 1036399 - Multiple CSP policies should be combined towards an intersection
+ */
+
+const TIGHT_POLICY = "default-src 'self'";
+const LOOSE_POLICY = "default-src 'self' 'unsafe-inline'";
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ var csp = "";
+ // deliver *TWO* comma separated policies which is in fact the same as serving
+ // to separate CSP headers (AppendPolicy is called twice).
+ if (request.queryString == "tight") {
+ // script execution will be *blocked*
+ csp = TIGHT_POLICY + ", " + LOOSE_POLICY;
+ }
+ else {
+ // script execution will be *allowed*
+ csp = LOOSE_POLICY + ", " + LOOSE_POLICY;
+ }
+ response.setHeader("Content-Security-Policy", csp, false);
+
+ // Send HTML to test allowed/blocked behaviors
+ response.setHeader("Content-Type", "text/html", false);
+
+ // generate an html file that contains a div container which is updated
+ // in case the inline script is *not* blocked by CSP.
+ var html = "<!DOCTYPE HTML>" +
+ "<html>" +
+ "<head>" +
+ "<title>Testpage for Bug 1036399</title>" +
+ "</head>" +
+ "<body>" +
+ "<div id='testdiv'>blocked</div>" +
+ "<script type='text/javascript'>" +
+ "document.getElementById('testdiv').innerHTML = 'allowed';" +
+ "</script>" +
+ "</body>" +
+ "</html>";
+
+ response.write(html);
+}
diff --git a/dom/security/test/csp/file_evalscript_main.html b/dom/security/test/csp/file_evalscript_main.html
new file mode 100644
index 000000000..e83c1d9ed
--- /dev/null
+++ b/dom/security/test/csp/file_evalscript_main.html
@@ -0,0 +1,12 @@
+<html>
+ <head>
+ <title>CSP eval script tests</title>
+ <script type="application/javascript"
+ src="file_evalscript_main.js"></script>
+ </head>
+ <body>
+
+ Foo.
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_evalscript_main.html^headers^ b/dom/security/test/csp/file_evalscript_main.html^headers^
new file mode 100644
index 000000000..b91ba384d
--- /dev/null
+++ b/dom/security/test/csp/file_evalscript_main.html^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-cache
+Content-Security-Policy: default-src 'self'
diff --git a/dom/security/test/csp/file_evalscript_main.js b/dom/security/test/csp/file_evalscript_main.js
new file mode 100644
index 000000000..64ab05664
--- /dev/null
+++ b/dom/security/test/csp/file_evalscript_main.js
@@ -0,0 +1,154 @@
+// some javascript for the CSP eval() tests
+
+function logResult(str, passed) {
+ var elt = document.createElement('div');
+ var color = passed ? "#cfc;" : "#fcc";
+ elt.setAttribute('style', 'background-color:' + color + '; width:100%; border:1px solid black; padding:3px; margin:4px;');
+ elt.innerHTML = str;
+ document.body.appendChild(elt);
+}
+
+window._testResults = {};
+
+// check values for return values from blocked timeout or intervals
+var verifyZeroRetVal = (function(window) {
+ return function(val, details) {
+ logResult((val === 0 ? "PASS: " : "FAIL: ") + "Blocked interval/timeout should have zero return value; " + details, val === 0);
+ window.parent.verifyZeroRetVal(val, details);
+ };})(window);
+
+// callback for when stuff is allowed by CSP
+var onevalexecuted = (function(window) {
+ return function(shouldrun, what, data) {
+ window._testResults[what] = "ran";
+ window.parent.scriptRan(shouldrun, what, data);
+ logResult((shouldrun ? "PASS: " : "FAIL: ") + what + " : " + data, shouldrun);
+ };})(window);
+
+// callback for when stuff is blocked
+var onevalblocked = (function(window) {
+ return function(shouldrun, what, data) {
+ window._testResults[what] = "blocked";
+ window.parent.scriptBlocked(shouldrun, what, data);
+ logResult((shouldrun ? "FAIL: " : "PASS: ") + what + " : " + data, !shouldrun);
+ };})(window);
+
+
+// Defer until document is loaded so that we can write the pretty result boxes
+// out.
+addEventListener('load', function() {
+ // setTimeout(String) test -- mutate something in the window._testResults
+ // obj, then check it.
+ {
+ var str_setTimeoutWithStringRan = 'onevalexecuted(false, "setTimeout(String)", "setTimeout with a string was enabled.");';
+ function fcn_setTimeoutWithStringCheck() {
+ if (this._testResults["setTimeout(String)"] !== "ran") {
+ onevalblocked(false, "setTimeout(String)",
+ "setTimeout with a string was blocked");
+ }
+ }
+ setTimeout(fcn_setTimeoutWithStringCheck.bind(window), 10);
+ var res = setTimeout(str_setTimeoutWithStringRan, 10);
+ verifyZeroRetVal(res, "setTimeout(String)");
+ }
+
+ // setInterval(String) test -- mutate something in the window._testResults
+ // obj, then check it.
+ {
+ var str_setIntervalWithStringRan = 'onevalexecuted(false, "setInterval(String)", "setInterval with a string was enabled.");';
+ function fcn_setIntervalWithStringCheck() {
+ if (this._testResults["setInterval(String)"] !== "ran") {
+ onevalblocked(false, "setInterval(String)",
+ "setInterval with a string was blocked");
+ }
+ }
+ setTimeout(fcn_setIntervalWithStringCheck.bind(window), 10);
+ var res = setInterval(str_setIntervalWithStringRan, 10);
+ verifyZeroRetVal(res, "setInterval(String)");
+
+ // emergency cleanup, just in case.
+ if (res != 0) {
+ setTimeout(function () { clearInterval(res); }, 15);
+ }
+ }
+
+ // setTimeout(function) test -- mutate something in the window._testResults
+ // obj, then check it.
+ {
+ function fcn_setTimeoutWithFunctionRan() {
+ onevalexecuted(true, "setTimeout(function)",
+ "setTimeout with a function was enabled.")
+ }
+ function fcn_setTimeoutWithFunctionCheck() {
+ if (this._testResults["setTimeout(function)"] !== "ran") {
+ onevalblocked(true, "setTimeout(function)",
+ "setTimeout with a function was blocked");
+ }
+ }
+ setTimeout(fcn_setTimeoutWithFunctionRan.bind(window), 10);
+ setTimeout(fcn_setTimeoutWithFunctionCheck.bind(window), 10);
+ }
+
+ // eval() test -- should throw exception as per spec
+ try {
+ eval('onevalexecuted(false, "eval(String)", "eval() was enabled.");');
+ } catch (e) {
+ onevalblocked(false, "eval(String)",
+ "eval() was blocked");
+ }
+
+ // eval(foo,bar) test -- should throw exception as per spec
+ try {
+ eval('onevalexecuted(false, "eval(String,scope)", "eval() was enabled.");',1);
+ } catch (e) {
+ onevalblocked(false, "eval(String,object)",
+ "eval() with scope was blocked");
+ }
+
+ // [foo,bar].sort(eval) test -- should throw exception as per spec
+ try {
+ ['onevalexecuted(false, "[String, obj].sort(eval)", "eval() was enabled.");',1].sort(eval);
+ } catch (e) {
+ onevalblocked(false, "[String, obj].sort(eval)",
+ "eval() with scope via sort was blocked");
+ }
+
+ // [].sort.call([foo,bar], eval) test -- should throw exception as per spec
+ try {
+ [].sort.call(['onevalexecuted(false, "[String, obj].sort(eval)", "eval() was enabled.");',1], eval);
+ } catch (e) {
+ onevalblocked(false, "[].sort.call([String, obj], eval)",
+ "eval() with scope via sort/call was blocked");
+ }
+
+ // new Function() test -- should throw exception as per spec
+ try {
+ var fcn = new Function('onevalexecuted(false, "new Function(String)", "new Function(String) was enabled.");');
+ fcn();
+ } catch (e) {
+ onevalblocked(false, "new Function(String)",
+ "new Function(String) was blocked.");
+ }
+
+ // setTimeout(eval, 0, str)
+ {
+ // error is not catchable here, instead, we're going to side-effect
+ // 'worked'.
+ var worked = false;
+
+ setTimeout(eval, 0, 'worked = true');
+ setTimeout(function(worked) {
+ if (worked) {
+ onevalexecuted(false, "setTimeout(eval, 0, str)",
+ "setTimeout(eval, 0, string) was enabled.");
+ } else {
+ onevalblocked(false, "setTimeout(eval, 0, str)",
+ "setTimeout(eval, 0, str) was blocked.");
+ }
+ }, 0, worked);
+ }
+
+}, false);
+
+
+
diff --git a/dom/security/test/csp/file_evalscript_main_allowed.html b/dom/security/test/csp/file_evalscript_main_allowed.html
new file mode 100644
index 000000000..274972d9b
--- /dev/null
+++ b/dom/security/test/csp/file_evalscript_main_allowed.html
@@ -0,0 +1,12 @@
+<html>
+ <head>
+ <title>CSP eval script tests</title>
+ <script type="application/javascript"
+ src="file_evalscript_main_allowed.js"></script>
+ </head>
+ <body>
+
+ Foo.
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_evalscript_main_allowed.html^headers^ b/dom/security/test/csp/file_evalscript_main_allowed.html^headers^
new file mode 100644
index 000000000..0cb5288be
--- /dev/null
+++ b/dom/security/test/csp/file_evalscript_main_allowed.html^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-cache
+Content-Security-Policy: default-src 'self' ; script-src 'self' 'unsafe-eval'
diff --git a/dom/security/test/csp/file_evalscript_main_allowed.js b/dom/security/test/csp/file_evalscript_main_allowed.js
new file mode 100644
index 000000000..69e6f7a4f
--- /dev/null
+++ b/dom/security/test/csp/file_evalscript_main_allowed.js
@@ -0,0 +1,121 @@
+// some javascript for the CSP eval() tests
+// all of these evals should succeed, as the document loading this script
+// has script-src 'self' 'unsafe-eval'
+
+function logResult(str, passed) {
+ var elt = document.createElement('div');
+ var color = passed ? "#cfc;" : "#fcc";
+ elt.setAttribute('style', 'background-color:' + color + '; width:100%; border:1px solid black; padding:3px; margin:4px;');
+ elt.innerHTML = str;
+ document.body.appendChild(elt);
+}
+
+// callback for when stuff is allowed by CSP
+var onevalexecuted = (function(window) {
+ return function(shouldrun, what, data) {
+ window.parent.scriptRan(shouldrun, what, data);
+ logResult((shouldrun ? "PASS: " : "FAIL: ") + what + " : " + data, shouldrun);
+ };})(window);
+
+// callback for when stuff is blocked
+var onevalblocked = (function(window) {
+ return function(shouldrun, what, data) {
+ window.parent.scriptBlocked(shouldrun, what, data);
+ logResult((shouldrun ? "FAIL: " : "PASS: ") + what + " : " + data, !shouldrun);
+ };})(window);
+
+
+// Defer until document is loaded so that we can write the pretty result boxes
+// out.
+addEventListener('load', function() {
+ // setTimeout(String) test -- should pass
+ try {
+ setTimeout('onevalexecuted(true, "setTimeout(String)", "setTimeout with a string was enabled.");', 10);
+ } catch (e) {
+ onevalblocked(true, "setTimeout(String)",
+ "setTimeout with a string was blocked");
+ }
+
+ // setTimeout(function) test -- should pass
+ try {
+ setTimeout(function() {
+ onevalexecuted(true, "setTimeout(function)",
+ "setTimeout with a function was enabled.")
+ }, 10);
+ } catch (e) {
+ onevalblocked(true, "setTimeout(function)",
+ "setTimeout with a function was blocked");
+ }
+
+ // eval() test
+ try {
+ eval('onevalexecuted(true, "eval(String)", "eval() was enabled.");');
+ } catch (e) {
+ onevalblocked(true, "eval(String)",
+ "eval() was blocked");
+ }
+
+ // eval(foo,bar) test
+ try {
+ eval('onevalexecuted(true, "eval(String,scope)", "eval() was enabled.");',1);
+ } catch (e) {
+ onevalblocked(true, "eval(String,object)",
+ "eval() with scope was blocked");
+ }
+
+ // [foo,bar].sort(eval) test
+ try {
+ ['onevalexecuted(true, "[String, obj].sort(eval)", "eval() was enabled.");',1].sort(eval);
+ } catch (e) {
+ onevalblocked(true, "[String, obj].sort(eval)",
+ "eval() with scope via sort was blocked");
+ }
+
+ // [].sort.call([foo,bar], eval) test
+ try {
+ [].sort.call(['onevalexecuted(true, "[String, obj].sort(eval)", "eval() was enabled.");',1], eval);
+ } catch (e) {
+ onevalblocked(true, "[].sort.call([String, obj], eval)",
+ "eval() with scope via sort/call was blocked");
+ }
+
+ // new Function() test
+ try {
+ var fcn = new Function('onevalexecuted(true, "new Function(String)", "new Function(String) was enabled.");');
+ fcn();
+ } catch (e) {
+ onevalblocked(true, "new Function(String)",
+ "new Function(String) was blocked.");
+ }
+
+ function checkResult() {
+ //alert(bar);
+ if (bar) {
+ onevalexecuted(true, "setTimeout(eval, 0, str)",
+ "setTimeout(eval, 0, string) was enabled.");
+ } else {
+ onevalblocked(true, "setTimeout(eval, 0, str)",
+ "setTimeout(eval, 0, str) was blocked.");
+ }
+ }
+
+ var bar = false;
+
+ function foo() {
+ bar = true;
+ }
+
+ window.foo = foo;
+
+ // setTimeout(eval, 0, str)
+
+ // error is not catchable here
+
+ setTimeout(eval, 0, 'window.foo();');
+
+ setTimeout(checkResult.bind(this), 0);
+
+}, false);
+
+
+
diff --git a/dom/security/test/csp/file_fontloader.sjs b/dom/security/test/csp/file_fontloader.sjs
new file mode 100644
index 000000000..06f6e752d
--- /dev/null
+++ b/dom/security/test/csp/file_fontloader.sjs
@@ -0,0 +1,58 @@
+// custom *.sjs for Bug 1195172
+// CSP: 'block-all-mixed-content'
+
+const PRE_HEAD =
+ "<!DOCTYPE HTML>" +
+ "<html><head><meta charset=\"utf-8\">" +
+ "<title>Bug 1195172 - CSP should block font from cache</title>";
+
+const CSP_BLOCK =
+ "<meta http-equiv=\"Content-Security-Policy\" content=\"font-src 'none'\">";
+
+const CSP_ALLOW =
+ "<meta http-equiv=\"Content-Security-Policy\" content=\"font-src *\">";
+
+const CSS =
+ "<style>" +
+ " @font-face {" +
+ " font-family: myFontTest;" +
+ " src: url(file_fontloader.woff);" +
+ " }" +
+ " div {" +
+ " font-family: myFontTest;" +
+ " }" +
+ "</style>";
+
+const POST_HEAD_AND_BODY =
+ "</head>" +
+ "<body>" +
+ "<div> Just testing the font </div>" +
+ "</body>" +
+ "</html>";
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ var queryString = request.queryString;
+
+ if (queryString == "baseline") {
+ response.write(PRE_HEAD + POST_HEAD_AND_BODY);
+ return;
+ }
+ if (queryString == "no-csp") {
+ response.write(PRE_HEAD + CSS + POST_HEAD_AND_BODY);
+ return;
+ }
+ if (queryString == "csp-block") {
+ response.write(PRE_HEAD + CSP_BLOCK + CSS + POST_HEAD_AND_BODY);
+ return;
+ }
+ if (queryString == "csp-allow") {
+ response.write(PRE_HEAD + CSP_ALLOW + CSS + POST_HEAD_AND_BODY);
+ return;
+ }
+ // we should never get here, but just in case return something unexpected
+ response.write("do'h");
+}
diff --git a/dom/security/test/csp/file_fontloader.woff b/dom/security/test/csp/file_fontloader.woff
new file mode 100644
index 000000000..fbf7390d5
--- /dev/null
+++ b/dom/security/test/csp/file_fontloader.woff
Binary files differ
diff --git a/dom/security/test/csp/file_form-action.html b/dom/security/test/csp/file_form-action.html
new file mode 100644
index 000000000..cfff156ba
--- /dev/null
+++ b/dom/security/test/csp/file_form-action.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 529697 - Test mapping of form submission to form-action</title>
+</head>
+<body>
+ <form action="submit-form">
+ <input id="submitButton" type="submit" value="Submit form">
+ </form>
+ <script type="text/javascript">
+ var submitButton = document.getElementById('submitButton');
+ submitButton.click();
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_form_action_server.sjs b/dom/security/test/csp/file_form_action_server.sjs
new file mode 100644
index 000000000..f2771d898
--- /dev/null
+++ b/dom/security/test/csp/file_form_action_server.sjs
@@ -0,0 +1,33 @@
+// Custom *.sjs file specifically for the needs of Bug 1251043
+
+const FRAME = `
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <title>Bug 1251043 - Test form-action blocks URL</title>
+ <meta http-equiv="Content-Security-Policy" content="form-action 'none';">
+ </head>
+ <body>
+ CONTROL-TEXT
+ <form action="file_form_action_server.sjs?formsubmission" method="GET">
+ <input type="submit" id="submitButton" value="submit">
+ </form>
+ </body>
+ </html>`;
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // PART 1: Return a frame including the FORM and the CSP
+ if (request.queryString === "loadframe") {
+ response.write(FRAME);
+ return;
+ }
+
+ // PART 2: We should never get here because the form
+ // should not be submitted. Just in case; return
+ // something unexpected so the test fails!
+ response.write("do'h");
+}
diff --git a/dom/security/test/csp/file_frameancestors.sjs b/dom/security/test/csp/file_frameancestors.sjs
new file mode 100644
index 000000000..d0a77893f
--- /dev/null
+++ b/dom/security/test/csp/file_frameancestors.sjs
@@ -0,0 +1,54 @@
+// SJS file for CSP frame ancestor mochitests
+function handleRequest(request, response)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var [name, value] = val.split('=');
+ query[name] = unescape(value);
+ });
+
+ var isPreflight = request.method == "OPTIONS";
+
+
+ //avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // grab the desired policy from the query, and then serve a page
+ if (query['csp'])
+ response.setHeader("Content-Security-Policy",
+ unescape(query['csp']),
+ false);
+ if (query['scriptedreport']) {
+ // spit back a script that records that the page loaded
+ response.setHeader("Content-Type", "text/javascript", false);
+ if (query['double'])
+ response.write('window.parent.parent.parent.postMessage({call: "frameLoaded", testname: "' + query['scriptedreport'] + '", uri: "window.location.toString()"}, "*");');
+ else
+ response.write('window.parent.parent.postMessage({call: "frameLoaded", testname: "' + query['scriptedreport'] + '", uri: "window.location.toString()"}, "*");');
+ } else if (query['internalframe']) {
+ // spit back an internal iframe (one that might be blocked)
+ response.setHeader("Content-Type", "text/html", false);
+ response.write('<html><head>');
+ if (query['double'])
+ response.write('<script src="file_frameancestors.sjs?double=1&scriptedreport=' + query['testid'] + '"></script>');
+ else
+ response.write('<script src="file_frameancestors.sjs?scriptedreport=' + query['testid'] + '"></script>');
+ response.write('</head><body>');
+ response.write(unescape(query['internalframe']));
+ response.write('</body></html>');
+ } else if (query['externalframe']) {
+ // spit back an internal iframe (one that won't be blocked, and probably
+ // has no CSP)
+ response.setHeader("Content-Type", "text/html", false);
+ response.write('<html><head>');
+ response.write('</head><body>');
+ response.write(unescape(query['externalframe']));
+ response.write('</body></html>');
+ } else {
+ // default case: error.
+ response.setHeader("Content-Type", "text/html", false);
+ response.write('<html><body>');
+ response.write("ERROR: not sure what to serve.");
+ response.write('</body></html>');
+ }
+}
diff --git a/dom/security/test/csp/file_frameancestors_main.html b/dom/security/test/csp/file_frameancestors_main.html
new file mode 100644
index 000000000..97f9cb9ac
--- /dev/null
+++ b/dom/security/test/csp/file_frameancestors_main.html
@@ -0,0 +1,44 @@
+<html>
+ <head>
+ <title>CSP frame ancestors tests</title>
+
+ <!-- this page shouldn't have a CSP, just the sub-pages. -->
+ <script src='file_frameancestors_main.js'></script>
+
+ </head>
+ <body>
+
+<!-- These iframes will get populated by the attached javascript. -->
+<tt> aa_allow: /* innermost frame allows a */</tt><br/>
+<iframe id='aa_allow'></iframe><br/>
+
+<tt> aa_block: /* innermost frame denies a */</tt><br/>
+<iframe id='aa_block'></iframe><br/>
+
+<tt> ab_allow: /* innermost frame allows a */</tt><br/>
+<iframe id='ab_allow'></iframe><br/>
+
+<tt> ab_block: /* innermost frame denies a */</tt><br/>
+<iframe id='ab_block'></iframe><br/>
+
+<tt> aba_allow: /* innermost frame allows b,a */</tt><br/>
+<iframe id='aba_allow'></iframe><br/>
+
+<tt> aba_block: /* innermost frame denies b */</tt><br/>
+<iframe id='aba_block'></iframe><br/>
+
+<tt> aba2_block: /* innermost frame denies a */</tt><br/>
+<iframe id='aba2_block'></iframe><br/>
+
+<tt> abb_allow: /* innermost frame allows b,a */</tt><br/>
+<iframe id='abb_allow'></iframe><br/>
+
+<tt> abb_block: /* innermost frame denies b */</tt><br/>
+<iframe id='abb_block'></iframe><br/>
+
+<tt> abb2_block: /* innermost frame denies a */</tt><br/>
+<iframe id='abb2_block'></iframe><br/>
+
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_frameancestors_main.js b/dom/security/test/csp/file_frameancestors_main.js
new file mode 100644
index 000000000..caffc7257
--- /dev/null
+++ b/dom/security/test/csp/file_frameancestors_main.js
@@ -0,0 +1,65 @@
+// Script to populate the test frames in the frame ancestors mochitest.
+//
+function setupFrames() {
+
+ var $ = function(v) { return document.getElementById(v); }
+ var base = {
+ self: '/tests/dom/security/test/csp/file_frameancestors.sjs',
+ a: 'http://mochi.test:8888/tests/dom/security/test/csp/file_frameancestors.sjs',
+ b: 'http://example.com/tests/dom/security/test/csp/file_frameancestors.sjs'
+ };
+
+ var host = { a: 'http://mochi.test:8888', b: 'http://example.com:80' };
+
+ var innerframeuri = null;
+ var elt = null;
+
+ elt = $('aa_allow');
+ elt.src = base.a + "?testid=aa_allow&internalframe=aa_a&csp=" +
+ escape("default-src 'none'; frame-ancestors " + host.a + "; script-src 'self'");
+
+ elt = $('aa_block');
+ elt.src = base.a + "?testid=aa_block&internalframe=aa_b&csp=" +
+ escape("default-src 'none'; frame-ancestors 'none'; script-src 'self'");
+
+ elt = $('ab_allow');
+ elt.src = base.b + "?testid=ab_allow&internalframe=ab_a&csp=" +
+ escape("default-src 'none'; frame-ancestors " + host.a + "; script-src 'self'");
+
+ elt = $('ab_block');
+ elt.src = base.b + "?testid=ab_block&internalframe=ab_b&csp=" +
+ escape("default-src 'none'; frame-ancestors 'none'; script-src 'self'");
+
+ /* .... two-level framing */
+ elt = $('aba_allow');
+ innerframeuri = base.a + "?testid=aba_allow&double=1&internalframe=aba_a&csp=" +
+ escape("default-src 'none'; frame-ancestors " + host.a + " " + host.b + "; script-src 'self'");
+ elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
+
+ elt = $('aba_block');
+ innerframeuri = base.a + "?testid=aba_allow&double=1&internalframe=aba_b&csp=" +
+ escape("default-src 'none'; frame-ancestors " + host.a + "; script-src 'self'");
+ elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
+
+ elt = $('aba2_block');
+ innerframeuri = base.a + "?testid=aba_allow&double=1&internalframe=aba2_b&csp=" +
+ escape("default-src 'none'; frame-ancestors " + host.b + "; script-src 'self'");
+ elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
+
+ elt = $('abb_allow');
+ innerframeuri = base.b + "?testid=abb_allow&double=1&internalframe=abb_a&csp=" +
+ escape("default-src 'none'; frame-ancestors " + host.a + " " + host.b + "; script-src 'self'");
+ elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
+
+ elt = $('abb_block');
+ innerframeuri = base.b + "?testid=abb_allow&double=1&internalframe=abb_b&csp=" +
+ escape("default-src 'none'; frame-ancestors " + host.a + "; script-src 'self'");
+ elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
+
+ elt = $('abb2_block');
+ innerframeuri = base.b + "?testid=abb_allow&double=1&internalframe=abb2_b&csp=" +
+ escape("default-src 'none'; frame-ancestors " + host.b + "; script-src 'self'");
+ elt.src = base.b + "?externalframe=" + escape('<iframe src="' + innerframeuri + '"></iframe>');
+}
+
+window.addEventListener('load', setupFrames, false);
diff --git a/dom/security/test/csp/file_hash_source.html b/dom/security/test/csp/file_hash_source.html
new file mode 100644
index 000000000..47eba6cf3
--- /dev/null
+++ b/dom/security/test/csp/file_hash_source.html
@@ -0,0 +1,65 @@
+<!doctype html>
+<html>
+ <body>
+ <!-- inline scripts -->
+ <p id="inline-script-valid-hash">blocked</p>
+ <p id="inline-script-invalid-hash">blocked</p>
+ <p id="inline-script-invalid-hash-valid-nonce">blocked</p>
+ <p id="inline-script-valid-hash-invalid-nonce">blocked</p>
+ <p id="inline-script-invalid-hash-invalid-nonce">blocked</p>
+ <p id="inline-script-valid-sha512-hash">blocked</p>
+ <p id="inline-script-valid-sha384-hash">blocked</p>
+ <p id="inline-script-valid-sha1-hash">blocked</p>
+ <p id="inline-script-valid-md5-hash">blocked</p>
+
+ <!-- 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI=' (in policy) -->
+ <script>document.getElementById("inline-script-valid-hash").innerHTML = "allowed";</script>
+ <!-- 'sha256-cYPTF2pm0QeyDtbmJ3+xi00o2Rxrw7vphBoHgOg9EnQ=' (not in policy) -->
+ <script>document.getElementById("inline-script-invalid-hash").innerHTML = "allowed";</script>
+ <!-- 'sha256-SKtBKyfeMjBpOujES0etR9t/cklbouJu/3T4PXnjbIo=' (not in policy) -->
+ <script nonce="jPRxvuRHbiQnCWVuoCMAvQ==">document.getElementById("inline-script-invalid-hash-valid-nonce").innerHTML = "allowed";</script>
+ <!-- 'sha256-z7rzCkbOJqi08lga3CVQ3b+3948ZbJWaSxsBs8zPliE=' -->
+ <script nonce="foobar">document.getElementById("inline-script-valid-hash-invalid-nonce").innerHTML = "allowed";</script>
+ <!-- 'sha256-E5TX2PmYZ4YQOK/F3XR1wFcvFjbO7QHMmxHTT/18LbE=' (not in policy) -->
+ <script nonce="foobar">document.getElementById("inline-script-invalid-hash-invalid-nonce").innerHTML = "allowed";</script>
+ <!-- 'sha512-tMLuv22jJ5RHkvLNlv0otvA2fgw6PF16HKu6wy0ZDQ3M7UKzoygs1uxIMSfjMttgWrB5WRvIr35zrTZppMYBVw==' (in policy) -->
+ <script>document.getElementById("inline-script-valid-sha512-hash").innerHTML = "allowed";</script>
+ <!-- 'sha384-XjAD+FxZfipkxna4id1JrR2QP6OYUZfAxpn9+yHOmT1VSLVa9SQR/dz7CEb7jw7w' (in policy) -->
+ <script>document.getElementById("inline-script-valid-sha384-hash").innerHTML = "allowed";</script>
+ <!-- 'sha1-LHErkMxKGcSpa/znpzmKYkKnI30=' (in policy) -->
+ <script>document.getElementById("inline-script-valid-sha1-hash").innerHTML = "allowed";</script>
+ <!-- 'md5-/m4wX3YU+IHs158KwKOBWg==' (in policy) -->
+ <script>document.getElementById("inline-script-valid-md5-hash").innerHTML = "allowed";</script>
+
+ <!-- inline styles -->
+ <p id="inline-style-valid-hash"></p>
+ <p id="inline-style-invalid-hash"></p>
+ <p id="inline-style-invalid-hash-valid-nonce"></p>
+ <p id="inline-style-valid-hash-invalid-nonce"></p>
+ <p id="inline-style-invalid-hash-invalid-nonce"></p>
+ <p id="inline-style-valid-sha512-hash"></p>
+ <p id="inline-style-valid-sha384-hash"></p>
+ <p id="inline-style-valid-sha1-hash"></p>
+ <p id="inline-style-valid-md5-hash"></p>
+
+ <!-- 'sha256-UpNH6x+Ux99QTW1fJikQsVbBERJruIC98et0YDVKKHQ=' (in policy) -->
+ <style>p#inline-style-valid-hash { color: green; }</style>
+ <!-- 'sha256-+TYxTx+bsfTDdivWLZUwScEYyxuv6lknMbNjrgGBRZo=' (not in policy) -->
+ <style>p#inline-style-invalid-hash { color: red; }</style>
+ <!-- 'sha256-U+9UPC/CFzz3QuOrl5q3KCVNngOYWuIkE2jK6Ir0Mbs=' (not in policy) -->
+ <style nonce="ftL2UbGHlSEaZTLWMwtA5Q==">p#inline-style-invalid-hash-valid-nonce { color: green; }</style>
+ <!-- 'sha256-0IPbWW5IDJ/juvETq60oTnhC+XzOqdYp5/UBsBKCaOY=' (in policy) -->
+ <style nonce="foobar">p#inline-style-valid-hash-invalid-nonce { color: green; }</style>
+ <!-- 'sha256-KaHZgPd4nC4S8BVLT/9WjzdPDtunGWojR83C2whbd50=' (not in policy) -->
+ <style nonce="foobar">p#inline-style-invalid-hash-invalid-nonce { color: red; }</style>
+ <!-- 'sha512-EpcDbSuvFv0HIyKtU5tQMN7UtBMeEbljz1dWPfy7PNCa1RYdHKwdJWT1tie41evq/ZUL1rzadSVdEzq3jl6Twg==' (in policy) -->
+ <style>p#inline-style-valid-sha512-hash { color: green; }</style>
+ <!-- 'sha384-c5W8ON4WyeA2zEOGdrOGhRmRYI8+2UzUUmhGQFjUFP6yiPZx9FGEV3UOiQ+tIshF' (in policy) -->
+ <style>p#inline-style-valid-sha384-hash { color: green; }</style>
+ <!-- 'sha1-T/+b4sxCIiJxDr6XS9dAEyHKt2M=' (in policy) -->
+ <style>p#inline-style-valid-sha1-hash { color: red; }</style>
+ <!-- 'md5-oNrgrtzOZduwDYYi1yo12g==' (in policy) -->
+ <style>p#inline-style-valid-md5-hash { color: red; }</style>
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_hash_source.html^headers^ b/dom/security/test/csp/file_hash_source.html^headers^
new file mode 100644
index 000000000..785d63391
--- /dev/null
+++ b/dom/security/test/csp/file_hash_source.html^headers^
@@ -0,0 +1,2 @@
+Content-Security-Policy: script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI=' 'nonce-jPRxvuRHbiQnCWVuoCMAvQ==' 'sha256-z7rzCkbOJqi08lga3CVQ3b+3948ZbJWaSxsBs8zPliE=' 'sha512-tMLuv22jJ5RHkvLNlv0otvA2fgw6PF16HKu6wy0ZDQ3M7UKzoygs1uxIMSfjMttgWrB5WRvIr35zrTZppMYBVw==' 'sha384-XjAD+FxZfipkxna4id1JrR2QP6OYUZfAxpn9+yHOmT1VSLVa9SQR/dz7CEb7jw7w' 'sha1-LHErkMxKGcSpa/znpzmKYkKnI30=' 'md5-/m4wX3YU+IHs158KwKOBWg=='; style-src 'sha256-UpNH6x+Ux99QTW1fJikQsVbBERJruIC98et0YDVKKHQ=' 'nonce-ftL2UbGHlSEaZTLWMwtA5Q==' 'sha256-0IPbWW5IDJ/juvETq60oTnhC+XzOqdYp5/UBsBKCaOY=' 'sha512-EpcDbSuvFv0HIyKtU5tQMN7UtBMeEbljz1dWPfy7PNCa1RYdHKwdJWT1tie41evq/ZUL1rzadSVdEzq3jl6Twg==' 'sha384-c5W8ON4WyeA2zEOGdrOGhRmRYI8+2UzUUmhGQFjUFP6yiPZx9FGEV3UOiQ+tIshF' 'sha1-T/+b4sxCIiJxDr6XS9dAEyHKt2M=' 'md5-oNrgrtzOZduwDYYi1yo12g==';
+Cache-Control: no-cache
diff --git a/dom/security/test/csp/file_iframe_sandbox_document_write.html b/dom/security/test/csp/file_iframe_sandbox_document_write.html
new file mode 100644
index 000000000..cdfa87c8f
--- /dev/null
+++ b/dom/security/test/csp/file_iframe_sandbox_document_write.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head> <meta charset="utf-8"> </head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+ }
+ function doStuff() {
+ var beforePrincipal = SpecialPowers.wrap(document).nodePrincipal;
+ document.open();
+ document.write("rewritten sandboxed document");
+ document.close();
+ var afterPrincipal = SpecialPowers.wrap(document).nodePrincipal;
+ ok(beforePrincipal.equals(afterPrincipal),
+ "document.write() does not change underlying principal");
+ }
+</script>
+<body onLoad='doStuff();'>
+ sandboxed with allow-scripts
+</body>
+</html>
diff --git a/dom/security/test/csp/file_iframe_sandbox_srcdoc.html b/dom/security/test/csp/file_iframe_sandbox_srcdoc.html
new file mode 100644
index 000000000..bc700ed68
--- /dev/null
+++ b/dom/security/test/csp/file_iframe_sandbox_srcdoc.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1073952 - CSP should restrict scripts in srcdoc iframe even if sandboxed</title>
+</head>
+<body>
+<iframe srcdoc="<img src=x onerror='parent.postMessage({result: `unexpected-csp-violation`}, `*`);'>"
+ sandbox="allow-scripts"></iframe>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_iframe_sandbox_srcdoc.html^headers^ b/dom/security/test/csp/file_iframe_sandbox_srcdoc.html^headers^
new file mode 100644
index 000000000..cf869e07d
--- /dev/null
+++ b/dom/security/test/csp/file_iframe_sandbox_srcdoc.html^headers^
@@ -0,0 +1 @@
+content-security-policy: default-src *;
diff --git a/dom/security/test/csp/file_iframe_srcdoc.sjs b/dom/security/test/csp/file_iframe_srcdoc.sjs
new file mode 100644
index 000000000..6de8a029e
--- /dev/null
+++ b/dom/security/test/csp/file_iframe_srcdoc.sjs
@@ -0,0 +1,79 @@
+// Custom *.sjs file specifically for the needs of
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1073952
+
+"use strict";
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+const SCRIPT = `
+ <script>
+ parent.parent.postMessage({result: &quot;allowed&quot;}, &quot;*&quot;);
+ </script>`;
+
+const SIMPLE_IFRAME_SRCDOC = `
+ <!DOCTYPE html>
+ <html>
+ <head><meta charset="utf-8"></head>
+ <body>
+ <iframe sandbox="allow-scripts" srcdoc="` + SCRIPT + `"></iframe>
+ </body>
+ </html>`;
+
+const INNER_SRCDOC_IFRAME = `
+ <iframe sandbox='allow-scripts' srcdoc='<script>
+ parent.parent.parent.postMessage({result: &quot;allowed&quot;}, &quot;*&quot;);
+ </script>'>
+ </iframe>`;
+
+const NESTED_IFRAME_SRCDOC = `
+ <!DOCTYPE html>
+ <html>
+ <head><meta charset="utf-8"></head>
+ <body>
+ <iframe sandbox="allow-scripts" srcdoc="` + INNER_SRCDOC_IFRAME + `"></iframe>
+ </body>
+ </html>`;
+
+
+const INNER_DATAURI_IFRAME = `
+ <iframe sandbox='allow-scripts' src='data:text/html,<script>
+ parent.parent.parent.postMessage({result: &quot;allowed&quot;}, &quot;*&quot;);
+ </script>'>
+ </iframe>`;
+
+const NESTED_IFRAME_SRCDOC_DATAURI = `
+ <!DOCTYPE html>
+ <html>
+ <head><meta charset="utf-8"></head>
+ <body>
+ <iframe sandbox="allow-scripts" srcdoc="` + INNER_DATAURI_IFRAME + `"></iframe>
+ </body>
+ </html>`;
+
+function handleRequest(request, response) {
+ const query = new URLSearchParams(request.queryString);
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ if (typeof query.get("csp") === "string") {
+ response.setHeader("Content-Security-Policy", query.get("csp"), false);
+ }
+ response.setHeader("Content-Type", "text/html", false);
+
+ if (query.get("action") === "simple_iframe_srcdoc") {
+ response.write(SIMPLE_IFRAME_SRCDOC);
+ return;
+ }
+
+ if (query.get("action") === "nested_iframe_srcdoc") {
+ response.write(NESTED_IFRAME_SRCDOC);
+ return;
+ }
+
+ if (query.get("action") === "nested_iframe_srcdoc_datauri") {
+ response.write(NESTED_IFRAME_SRCDOC_DATAURI);
+ return;
+ }
+
+ // we should never get here, but just in case
+ // return something unexpected
+ response.write("do'h");
+}
diff --git a/dom/security/test/csp/file_ignore_unsafe_inline.html b/dom/security/test/csp/file_ignore_unsafe_inline.html
new file mode 100644
index 000000000..b0d44b570
--- /dev/null
+++ b/dom/security/test/csp/file_ignore_unsafe_inline.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Bug 1004703 - ignore 'unsafe-inline' if nonce- or hash-source specified</title>
+</head>
+<body>
+<div id="testdiv">a</div>
+
+<!-- first script whitelisted by 'unsafe-inline' -->
+<script type="application/javascript">
+document.getElementById('testdiv').innerHTML += 'b';
+</script>
+
+<!-- second script whitelisted by hash -->
+<!-- sha256-uJXAPKP5NZxnVMZMUkDofh6a9P3UMRc1CRTevVPS/rI= -->
+<script type="application/javascript">
+document.getElementById('testdiv').innerHTML += 'c';
+</script>
+
+<!-- thrid script whitelisted by nonce -->
+<script type="application/javascript" nonce="FooNonce">
+document.getElementById('testdiv').innerHTML += 'd';
+</script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_ignore_unsafe_inline_multiple_policies_server.sjs b/dom/security/test/csp/file_ignore_unsafe_inline_multiple_policies_server.sjs
new file mode 100644
index 000000000..0f0a8ad3e
--- /dev/null
+++ b/dom/security/test/csp/file_ignore_unsafe_inline_multiple_policies_server.sjs
@@ -0,0 +1,56 @@
+// custom *.sjs file specifically for the needs of:
+// * Bug 1004703 - ignore 'unsafe-inline' if nonce- or hash-source specified
+// * Bug 1198422: should not block inline script if default-src is not specified
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function loadHTMLFromFile(path) {
+ // Load the HTML to return in the response from file.
+ // Since it's relative to the cwd of the test runner, we start there and
+ // append to get to the actual path of the file.
+ var testHTMLFile =
+ Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("CurWorkD", Components.interfaces.nsILocalFile);
+ var dirs = path.split("/");
+ for (var i = 0; i < dirs.length; i++) {
+ testHTMLFile.append(dirs[i]);
+ }
+ var testHTMLFileStream =
+ Components.classes["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ testHTMLFileStream.init(testHTMLFile, -1, 0, 0);
+ var testHTML = NetUtil.readInputStreamToString(testHTMLFileStream, testHTMLFileStream.available());
+ return testHTML;
+}
+
+
+function handleRequest(request, response)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var [name, value] = val.split('=');
+ query[name] = unescape(value);
+ });
+
+ var csp1 = (query['csp1']) ? unescape(query['csp1']) : "";
+ var csp2 = (query['csp2']) ? unescape(query['csp2']) : "";
+ var file = unescape(query['file']);
+
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // deliver the CSP encoded in the URI
+ // please note that comma separation of two policies
+ // acts like sending *two* separate policies
+ var csp = csp1;
+ if (csp2 !== "") {
+ csp += ", " + csp2;
+ }
+ response.setHeader("Content-Security-Policy", csp, false);
+
+ // Send HTML to test allowed/blocked behaviors
+ response.setHeader("Content-Type", "text/html", false);
+
+ response.write(loadHTMLFromFile(file));
+}
diff --git a/dom/security/test/csp/file_inlinescript.html b/dom/security/test/csp/file_inlinescript.html
new file mode 100644
index 000000000..55a9b9b18
--- /dev/null
+++ b/dom/security/test/csp/file_inlinescript.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+ <title>CSP inline script tests</title>
+</head>
+<body onload="window.parent.postMessage('body-onload-fired', '*')">
+ <script type="text/javascript">
+ window.parent.postMessage("text-node-fired", "*");
+ </script>
+
+ <iframe src='javascript:window.parent.parent.postMessage("javascript-uri-fired", "*")'></iframe>
+
+ <a id='anchortoclick' href='javascript:window.parent.postMessage("javascript-uri-anchor-fired", "*")'>testlink</a>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_inlinestyle_main.html b/dom/security/test/csp/file_inlinestyle_main.html
new file mode 100644
index 000000000..a0d296988
--- /dev/null
+++ b/dom/security/test/csp/file_inlinestyle_main.html
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<html>
+ <head>
+ <title>CSP inline script tests</title>
+ <!-- content= "div#linkstylediv { color: #0f0; }" -->
+ <link rel="stylesheet" type="text/css"
+ href='file_CSP.sjs?type=text/css&content=div%23linkstylediv%20%7B%20color%3A%20%230f0%3B%20%7D' />
+ <!-- content= "div#modifycsstextdiv { color: #0f0; }" -->
+ <link rel="stylesheet" type="text/css"
+ href='file_CSP.sjs?type=text/css&content=div%23modifycsstextdiv%20%7B%20color%3A%20%23f00%3B%20%7D' />
+ <script>
+ function cssTest() {
+ var elem = document.getElementById('csstextstylediv');
+ elem.style.cssText = "color: #00FF00;";
+ getComputedStyle(elem, null).color;
+
+ document.styleSheets[1].cssRules[0].style.cssText = "color: #00FF00;";
+ elem = document.getElementById('modifycsstextdiv');
+ getComputedStyle(elem, null).color;
+ }
+ </script>
+ </head>
+ <body onload='cssTest()'>
+
+ <style type="text/css">
+ div#inlinestylediv {
+ color: #FF0000;
+ }
+ </style>
+
+ <div id='linkstylediv'>Link tag (external) stylesheet test (should be green)</div>
+ <div id='inlinestylediv'>Inline stylesheet test (should be black)</div>
+ <div id='attrstylediv' style="color: #FF0000;">Attribute stylesheet test (should be black)</div>
+ <div id='csstextstylediv'>cssText test (should be black)</div>
+ <div id='modifycsstextdiv'> modify rule from style sheet via cssText(should be green) </div>
+
+ <!-- tests for SMIL stuff - animations -->
+ <svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="100%"
+ height="100px">
+
+ <!-- Animates XML attribute, which is mapped into style. -->
+ <text id="xmlTest" x="0" y="15">
+ This shouldn't be red since the animation should be blocked by CSP.
+
+ <animate attributeName="fill" attributeType="XML"
+ values="red;orange;red" dur="2s"
+ repeatCount="indefinite" />
+ </text>
+
+ <!-- Animates override value for CSS property. -->
+ <text id="cssOverrideTest" x="0" y="35">
+ This shouldn't be red since the animation should be blocked by CSP.
+
+ <animate attributeName="fill" attributeType="CSS"
+ values="red;orange;red" dur="2s"
+ repeatCount="indefinite" />
+ </text>
+
+ <!-- Animates override value for CSS property targeted via ID. -->
+ <text id="cssOverrideTestById" x="0" y="55">
+ This shouldn't be red since the animation should be blocked by CSP.
+ </text>
+ <animate xlink:href="#cssOverrideTestById"
+ attributeName="fill"
+ values="red;orange;red"
+ dur="2s" repeatCount="indefinite" />
+
+ <!-- Sets value for CSS property targeted via ID. -->
+ <text id="cssSetTestById" x="0" y="75">
+ This shouldn't be red since the &lt;set&gt; should be blocked by CSP.
+ </text>
+ <set xlink:href="#cssSetTestById"
+ attributeName="fill"
+ to="red" />
+ </svg>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_inlinestyle_main.html^headers^ b/dom/security/test/csp/file_inlinestyle_main.html^headers^
new file mode 100644
index 000000000..7b6a25167
--- /dev/null
+++ b/dom/security/test/csp/file_inlinestyle_main.html^headers^
@@ -0,0 +1,2 @@
+Content-Security-Policy: default-src 'self' ; script-src 'self' 'unsafe-inline'
+Cache-Control: no-cache
diff --git a/dom/security/test/csp/file_inlinestyle_main_allowed.html b/dom/security/test/csp/file_inlinestyle_main_allowed.html
new file mode 100644
index 000000000..9b533ef07
--- /dev/null
+++ b/dom/security/test/csp/file_inlinestyle_main_allowed.html
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<html>
+ <head>
+ <title>CSP inline script tests</title>
+ <!-- content= "div#linkstylediv { color: #0f0; }" -->
+ <link rel="stylesheet" type="text/css"
+ href='file_CSP.sjs?type=text/css&content=div%23linkstylediv%20%7B%20color%3A%20%230f0%3B%20%7D' />
+ <!-- content= "div#modifycsstextdiv { color: #f00; }" -->
+ <link rel="stylesheet" type="text/css"
+ href='file_CSP.sjs?type=text/css&content=div%23modifycsstextdiv%20%7B%20color%3A%20%23f00%3B%20%7D' />
+ <script>
+ function cssTest() {
+ // CSSStyleDeclaration.cssText
+ var elem = document.getElementById('csstextstylediv');
+ elem.style.cssText = "color: #00FF00;";
+
+ // If I call getComputedStyle as below, this test passes as the parent page
+ // correctly detects that the text is colored green - if I remove this, getComputedStyle
+ // thinks the text is black when called by the parent page.
+ getComputedStyle(elem, null).color;
+
+ document.styleSheets[1].cssRules[0].style.cssText = "color: #00FF00;";
+ elem = document.getElementById('modifycsstextdiv');
+ getComputedStyle(elem, null).color;
+ }
+ </script>
+ </head>
+ <body onload='cssTest()'>
+
+ <style type="text/css">
+ div#inlinestylediv {
+ color: #00FF00;
+ }
+ </style>
+
+ <div id='linkstylediv'>Link tag (external) stylesheet test (should be green)</div>
+ <div id='inlinestylediv'>Inline stylesheet test (should be green)</div>
+ <div id='attrstylediv' style="color: #00FF00;">Attribute stylesheet test (should be green)</div>
+ <div id='csstextstylediv'>style.cssText test (should be green)</div>
+ <div id='modifycsstextdiv'> modify rule from style sheet via cssText(should be green) </div>
+
+ <!-- tests for SMIL stuff - animations -->
+ <svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="100%"
+ height="100px">
+
+ <!-- Animates XML attribute, which is mapped into style. -->
+ <text id="xmlTest" x="0" y="15">
+ This should be green since the animation should be allowed by CSP.
+
+ <animate attributeName="fill" attributeType="XML"
+ values="lime;green;lime" dur="2s"
+ repeatCount="indefinite" />
+ </text>
+
+ <!-- Animates override value for CSS property. -->
+ <text id="cssOverrideTest" x="0" y="35">
+ This should be green since the animation should be allowed by CSP.
+
+ <animate attributeName="fill" attributeType="CSS"
+ values="lime;green;lime" dur="2s"
+ repeatCount="indefinite" />
+ </text>
+
+ <!-- Animates override value for CSS property targeted via ID. -->
+ <text id="cssOverrideTestById" x="0" y="55">
+ This should be green since the animation should be allowed by CSP.
+ </text>
+ <animate xlink:href="#cssOverrideTestById"
+ attributeName="fill"
+ values="lime;green;lime"
+ dur="2s" repeatCount="indefinite" />
+
+ <!-- Sets value for CSS property targeted via ID. -->
+ <text id="cssSetTestById" x="0" y="75">
+ This should be green since the &lt;set&gt; should be allowed by CSP.
+ </text>
+ <set xlink:href="#cssSetTestById"
+ attributeName="fill"
+ to="lime" />
+ </svg>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_inlinestyle_main_allowed.html^headers^ b/dom/security/test/csp/file_inlinestyle_main_allowed.html^headers^
new file mode 100644
index 000000000..621d2536b
--- /dev/null
+++ b/dom/security/test/csp/file_inlinestyle_main_allowed.html^headers^
@@ -0,0 +1,2 @@
+Content-Security-Policy: default-src 'self' ; script-src 'self' 'unsafe-inline' ; style-src 'self' 'unsafe-inline'
+Cache-Control: no-cache
diff --git a/dom/security/test/csp/file_invalid_source_expression.html b/dom/security/test/csp/file_invalid_source_expression.html
new file mode 100644
index 000000000..83bb0ec0c
--- /dev/null
+++ b/dom/security/test/csp/file_invalid_source_expression.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1086612 - CSP: Let source expression be the empty set in case no valid source can be parsed</title>
+ </head>
+ <body>
+ <div id="testdiv">blocked</div>
+ <!-- Note, we reuse file_path_matching.js which only updates the testdiv to 'allowed' if loaded !-->
+ <script src="http://test1.example.com/tests/dom/security/test/csp/file_path_matching.js"></script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_leading_wildcard.html b/dom/security/test/csp/file_leading_wildcard.html
new file mode 100644
index 000000000..ea5e99344
--- /dev/null
+++ b/dom/security/test/csp/file_leading_wildcard.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1032303 - CSP - Keep FULL STOP when matching *.foo.com to disallow loads from foo.com</title>
+ </head>
+ <body>
+ <!-- Please note that both scripts do *not* exist in the file system -->
+ <script src="http://test1.example.com/tests/dom/security/test/csp/leading_wildcard_allowed.js" ></script>
+ <script src="http://example.com/tests/dom/security/test/csp/leading_wildcard_blocked.js" ></script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_main.html b/dom/security/test/csp/file_main.html
new file mode 100644
index 000000000..ddc838261
--- /dev/null
+++ b/dom/security/test/csp/file_main.html
@@ -0,0 +1,55 @@
+<html>
+ <head>
+ <link rel='stylesheet' type='text/css'
+ href='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=style_bad&type=text/css' />
+ <link rel='stylesheet' type='text/css'
+ href='file_CSP.sjs?testid=style_good&type=text/css' />
+
+
+ <style>
+ /* CSS font embedding tests */
+ @font-face {
+ font-family: "arbitrary_good";
+ src: url('file_CSP.sjs?testid=font_good&type=application/octet-stream');
+ }
+ @font-face {
+ font-family: "arbitrary_bad";
+ src: url('http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=font_bad&type=application/octet-stream');
+ }
+
+ .div_arbitrary_good { font-family: "arbitrary_good"; }
+ .div_arbitrary_bad { font-family: "arbitrary_bad"; }
+ </style>
+ </head>
+ <body>
+ <!-- these should be stopped by CSP. :) -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img_bad&type=img/png"> </img>
+ <audio src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=media_bad&type=audio/vorbis"></audio>
+ <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script_bad&type=text/javascript'></script>
+ <iframe src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=frame_bad&content=FAIL'></iframe>
+ <object width="10" height="10">
+ <param name="movie" value="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=object_bad&type=application/x-shockwave-flash">
+ <embed src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=object_bad&type=application/x-shockwave-flash"></embed>
+ </object>
+
+ <!-- these should load ok. :) -->
+ <img src="file_CSP.sjs?testid=img_good&type=img/png" />
+ <audio src="file_CSP.sjs?testid=media_good&type=audio/vorbis"></audio>
+ <script src='file_CSP.sjs?testid=script_good&type=text/javascript'></script>
+ <iframe src='file_CSP.sjs?testid=frame_good&content=PASS'></iframe>
+
+ <object width="10" height="10">
+ <param name="movie" value="file_CSP.sjs?testid=object_good&type=application/x-shockwave-flash">
+ <embed src="file_CSP.sjs?testid=object_good&type=application/x-shockwave-flash"></embed>
+ </object>
+
+ <!-- XHR tests... they're taken care of in this script,
+ and since the URI doesn't have any 'testid' values,
+ it will just be ignored by the test framework. -->
+ <script src='file_main.js'></script>
+
+ <!-- Support elements for the @font-face test -->
+ <div class="div_arbitrary_good">arbitrary good</div>
+ <div class="div_arbitrary_bad">arbitrary_bad</div>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_main.html^headers^ b/dom/security/test/csp/file_main.html^headers^
new file mode 100644
index 000000000..3338de389
--- /dev/null
+++ b/dom/security/test/csp/file_main.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self' blob: ; style-src 'unsafe-inline' 'self'
diff --git a/dom/security/test/csp/file_main.js b/dom/security/test/csp/file_main.js
new file mode 100644
index 000000000..d5afe580f
--- /dev/null
+++ b/dom/security/test/csp/file_main.js
@@ -0,0 +1,51 @@
+function doXHR(uri, callback) {
+ try {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", uri);
+ xhr.responseType = "blob";
+ xhr.send();
+ xhr.onload = function () {
+ if (callback) callback(xhr.response);
+ }
+ } catch(ex) {}
+}
+
+doXHR("http://mochi.test:8888/tests/dom/security/test/csp/file_CSP.sjs?testid=xhr_good");
+doXHR("http://example.com/tests/dom/security/test/csp/file_CSP.sjs?testid=xhr_bad");
+fetch("http://mochi.test:8888/tests/dom/security/test/csp/file_CSP.sjs?testid=fetch_good");
+fetch("http://example.com/tests/dom/security/test/csp/file_CSP.sjs?testid=fetch_bad");
+navigator.sendBeacon("http://mochi.test:8888/tests/dom/security/test/csp/file_CSP.sjs?testid=beacon_good");
+navigator.sendBeacon("http://example.com/tests/dom/security/test/csp/file_CSP.sjs?testid=beacon_bad");
+
+var topWorkerBlob;
+var nestedWorkerBlob;
+
+doXHR("file_main_worker.js", function (topResponse) {
+ topWorkerBlob = URL.createObjectURL(topResponse);
+ doXHR("file_child_worker.js", function (response) {
+ nestedWorkerBlob = URL.createObjectURL(response);
+ runWorker();
+ });
+});
+
+function runWorker() {
+ // Top level worker, no subworker
+ // Worker does not inherit CSP from owner document
+ new Worker("file_main_worker.js").postMessage({inherited : "none"});
+
+ // Top level worker, no subworker
+ // Worker inherits CSP from owner document
+ new Worker(topWorkerBlob).postMessage({inherited : "document"});
+
+ // Subworker
+ // Worker does not inherit CSP from parent worker
+ new Worker("file_main_worker.js").postMessage({inherited : "none", nested : nestedWorkerBlob});
+
+ // Subworker
+ // Worker inherits CSP from parent worker
+ new Worker("file_main_worker.js").postMessage({inherited : "parent", nested : nestedWorkerBlob});
+
+ // Subworker
+ // Worker inherits CSP from owner document -> parent worker -> subworker
+ new Worker(topWorkerBlob).postMessage({inherited : "document", nested : nestedWorkerBlob});
+}
diff --git a/dom/security/test/csp/file_main_worker.js b/dom/security/test/csp/file_main_worker.js
new file mode 100644
index 000000000..d1314c843
--- /dev/null
+++ b/dom/security/test/csp/file_main_worker.js
@@ -0,0 +1,48 @@
+function doXHR(uri) {
+ try {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", uri);
+ xhr.send();
+ } catch(ex) {}
+}
+
+var sameBase = "http://mochi.test:8888/tests/dom/security/test/csp/file_CSP.sjs?testid=";
+var crossBase = "http://example.com/tests/dom/security/test/csp/file_CSP.sjs?testid=";
+
+onmessage = (e) => {
+ // Tests of nested worker
+ if (e.data.nested) {
+ if (e.data.inherited != "none") {
+ // Worker inherits CSP
+ new Worker(e.data.nested).postMessage({inherited : e.data.inherited});
+ }
+ else {
+ // Worker does not inherit CSP
+ new Worker("file_child_worker.js").postMessage({inherited : e.data.inherited});
+ }
+ return;
+ }
+
+ //Tests of top level worker
+ for (base of [sameBase, crossBase]) {
+ var prefix;
+ var suffix;
+ if (e.data.inherited != "none") {
+ // Top worker inherits CSP from owner document
+ prefix = base + "worker_inherited_";
+ suffix = base == sameBase ? "_good" : "_bad";
+ }
+ else {
+ // Top worker delivers CSP from HTTP header
+ prefix = base + "worker_";
+ suffix = base == sameBase ? "_same_bad" : "_cross_good";
+ }
+
+ doXHR(prefix + "xhr" + suffix);
+ fetch(prefix + "fetch" + suffix);
+ try {
+ if (e.data.inherited == "none") suffix = base == sameBase ? "_same_good" : "_cross_bad";
+ importScripts(prefix + "script" + suffix);
+ } catch(ex) {}
+ }
+}
diff --git a/dom/security/test/csp/file_main_worker.js^headers^ b/dom/security/test/csp/file_main_worker.js^headers^
new file mode 100644
index 000000000..3f5008ca6
--- /dev/null
+++ b/dom/security/test/csp/file_main_worker.js^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self' blob: ; connect-src http://example.com
diff --git a/dom/security/test/csp/file_meta_element.html b/dom/security/test/csp/file_meta_element.html
new file mode 100644
index 000000000..1d8ec6aa9
--- /dev/null
+++ b/dom/security/test/csp/file_meta_element.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="Content-Security-Policy"
+ content= "img-src 'none'; script-src 'unsafe-inline'; report-uri http://www.example.com; frame-ancestors https:; sandbox allow-scripts">
+ <title>Bug 663570 - Implement Content Security Policy via meta tag</title>
+</head>
+<body>
+
+ <!-- try to load an image which is forbidden by meta CSP -->
+ <img id="testimage" src="http://mochi.test:8888/tests/image/test/mochitest/blue.png"></img>
+
+ <script type="application/javascript">
+ var myImg = document.getElementById("testimage");
+ myImg.onload = function(e) {
+ window.parent.postMessage({result: "img-loaded"}, "*");
+ };
+ myImg.onerror = function(e) {
+ window.parent.postMessage({result: "img-blocked"}, "*");
+ };
+ </script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_meta_header_dual.sjs b/dom/security/test/csp/file_meta_header_dual.sjs
new file mode 100644
index 000000000..ddc38ffe5
--- /dev/null
+++ b/dom/security/test/csp/file_meta_header_dual.sjs
@@ -0,0 +1,98 @@
+// Custom *.sjs file specifically for the needs of Bug:
+// Bug 663570 - Implement Content Security Policy via meta tag
+
+const HTML_HEAD =
+ "<!DOCTYPE HTML>" +
+ "<html>" +
+ "<head>" +
+ "<meta charset='utf-8'>" +
+ "<title>Bug 663570 - Implement Content Security Policy via <meta> tag</title>";
+
+const HTML_BODY =
+ "</head>" +
+ "<body>" +
+ "<img id='testimage' src='http://mochi.test:8888/tests/image/test/mochitest/blue.png'></img>" +
+ "<script type='application/javascript'>" +
+ " var myImg = document.getElementById('testimage');" +
+ " myImg.onload = function(e) {" +
+ " window.parent.postMessage({result: 'img-loaded'}, '*');" +
+ " };" +
+ " myImg.onerror = function(e) { " +
+ " window.parent.postMessage({result: 'img-blocked'}, '*');" +
+ " };" +
+ "</script>" +
+ "</body>" +
+ "</html>";
+
+const META_CSP_BLOCK_IMG =
+ "<meta http-equiv=\"Content-Security-Policy\" content=\"img-src 'none'\">";
+
+const META_CSP_ALLOW_IMG =
+ "<meta http-equiv=\"Content-Security-Policy\" content=\"img-src http://mochi.test:8888;\">";
+
+const HEADER_CSP_BLOCK_IMG = "img-src 'none';";
+
+const HEADER_CSP_ALLOW_IMG = "img-src http://mochi.test:8888";
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ var queryString = request.queryString;
+
+ if (queryString === "test1") {
+ /* load image without any CSP */
+ response.write(HTML_HEAD + HTML_BODY);
+ return;
+ }
+
+ if (queryString === "test2") {
+ /* load image where meta denies load */
+ response.write(HTML_HEAD + META_CSP_BLOCK_IMG + HTML_BODY);
+ return;
+ }
+
+ if (queryString === "test3") {
+ /* load image where meta allows load */
+ response.write(HTML_HEAD + META_CSP_ALLOW_IMG + HTML_BODY);
+ return;
+ }
+
+ if (queryString === "test4") {
+ /* load image where meta allows but header blocks */
+ response.setHeader("Content-Security-Policy", HEADER_CSP_BLOCK_IMG, false);
+ response.write(HTML_HEAD + META_CSP_ALLOW_IMG + HTML_BODY);
+ return;
+ }
+
+ if (queryString === "test5") {
+ /* load image where meta blocks but header allows */
+ response.setHeader("Content-Security-Policy", HEADER_CSP_ALLOW_IMG, false);
+ response.write(HTML_HEAD + META_CSP_BLOCK_IMG + HTML_BODY);
+ return;
+ }
+
+ if (queryString === "test6") {
+ /* load image where meta allows and header allows */
+ response.setHeader("Content-Security-Policy", HEADER_CSP_ALLOW_IMG, false);
+ response.write(HTML_HEAD + META_CSP_ALLOW_IMG + HTML_BODY);
+ return;
+ }
+
+ if (queryString === "test7") {
+ /* load image where meta1 allows but meta2 blocks */
+ response.write(HTML_HEAD + META_CSP_ALLOW_IMG + META_CSP_BLOCK_IMG + HTML_BODY);
+ return;
+ }
+
+ if (queryString === "test8") {
+ /* load image where meta1 allows and meta2 allows */
+ response.write(HTML_HEAD + META_CSP_ALLOW_IMG + META_CSP_ALLOW_IMG + HTML_BODY);
+ return;
+ }
+
+ // we should never get here, but just in case, return
+ // something unexpected
+ response.write("do'h");
+}
diff --git a/dom/security/test/csp/file_meta_whitespace_skipping.html b/dom/security/test/csp/file_meta_whitespace_skipping.html
new file mode 100644
index 000000000..c0cfc8cc2
--- /dev/null
+++ b/dom/security/test/csp/file_meta_whitespace_skipping.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <!-- Test all the different space characters within the meta csp:
+ * U+0020 space | &#x20;
+ * U+0009 tab | &#x9;
+ * U+000A line feed | &#xa;
+ * U+000C form feed | &#xc;
+ * U+000D carriage return | &#xd;
+ !-->
+ <meta http-equiv="Content-Security-Policy"
+ content= "
+ img-src&#x20; 'none'; &#x20;
+ script-src 'unsafe-inline' &#xd;
+ ;
+ style-src&#x9;&#x9; https://example.com&#xa;
+ https://foo.com;&#xc;;;;&#xc;;;&#xc; child-src foo.com
+ bar.com
+ &#xd;;&#xd;
+ font-src 'none'">
+ <title>Bug 1261634 - Update whitespace skipping for meta csp</title>
+</head>
+<body>
+ <script type="application/javascript">
+ // notify the including document that we are done parsing the meta csp
+ window.parent.postMessage({result: "meta-csp-parsed"}, "*");
+ </script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_multi_policy_injection_bypass.html b/dom/security/test/csp/file_multi_policy_injection_bypass.html
new file mode 100644
index 000000000..a3cb415a9
--- /dev/null
+++ b/dom/security/test/csp/file_multi_policy_injection_bypass.html
@@ -0,0 +1,15 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=717511
+-->
+ <body>
+ <!-- these should be stopped by CSP after fixing bug 717511. :) -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img_bad&type=img/png"> </img>
+ <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script_bad&type=text/javascript'></script>
+
+ <!-- these should load ok after fixing bug 717511. :) -->
+ <img src="file_CSP.sjs?testid=img_good&type=img/png" />
+ <script src='file_CSP.sjs?testid=script_good&type=text/javascript'></script>
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_multi_policy_injection_bypass.html^headers^ b/dom/security/test/csp/file_multi_policy_injection_bypass.html^headers^
new file mode 100644
index 000000000..e1b64a922
--- /dev/null
+++ b/dom/security/test/csp/file_multi_policy_injection_bypass.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self', default-src *
diff --git a/dom/security/test/csp/file_multi_policy_injection_bypass_2.html b/dom/security/test/csp/file_multi_policy_injection_bypass_2.html
new file mode 100644
index 000000000..3fa6c7ab9
--- /dev/null
+++ b/dom/security/test/csp/file_multi_policy_injection_bypass_2.html
@@ -0,0 +1,15 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=717511
+-->
+ <body>
+ <!-- these should be stopped by CSP after fixing bug 717511. :) -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img2_bad&type=img/png"> </img>
+ <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script2_bad&type=text/javascript'></script>
+
+ <!-- these should load ok after fixing bug 717511. :) -->
+ <img src="file_CSP.sjs?testid=img2_good&type=img/png" />
+ <script src='file_CSP.sjs?testid=script2_good&type=text/javascript'></script>
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_multi_policy_injection_bypass_2.html^headers^ b/dom/security/test/csp/file_multi_policy_injection_bypass_2.html^headers^
new file mode 100644
index 000000000..b523073cd
--- /dev/null
+++ b/dom/security/test/csp/file_multi_policy_injection_bypass_2.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self' , default-src *
diff --git a/dom/security/test/csp/file_multipart_testserver.sjs b/dom/security/test/csp/file_multipart_testserver.sjs
new file mode 100644
index 000000000..d2eb58c82
--- /dev/null
+++ b/dom/security/test/csp/file_multipart_testserver.sjs
@@ -0,0 +1,50 @@
+// SJS file specifically for the needs of bug
+// Bug 1223743 - CSP: Check baseChannel for CSP when loading multipart channel
+
+var CSP = "script-src 'unsafe-inline', img-src 'none'";
+var BOUNDARY = "fooboundary"
+
+// small red image
+const IMG_BYTES = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+ "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
+
+var RESPONSE = `
+ <script>
+ var myImg = new Image;
+ myImg.src = "file_multipart_testserver.sjs?img";
+ myImg.onerror = function(e) {
+ window.parent.postMessage("img-blocked", "*");
+ };
+ myImg.onload = function() {
+ window.parent.postMessage("img-loaded", "*");
+ };
+ document.body.appendChild(myImg);
+ </script>
+`;
+
+var myTimer;
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ if (request.queryString == "doc") {
+ response.setHeader("Content-Security-Policy", CSP, false);
+ response.setHeader("Content-Type", "multipart/x-mixed-replace; boundary=" + BOUNDARY, false);
+ response.write(BOUNDARY + "\r\n");
+ response.write(RESPONSE);
+ response.write(BOUNDARY + "\r\n");
+ return;
+ }
+
+ if (request.queryString == "img") {
+ response.setHeader("Content-Type", "image/png");
+ response.write(IMG_BYTES);
+ return;
+ }
+
+ // we should never get here - return something unexpected
+ response.write("d'oh");
+}
diff --git a/dom/security/test/csp/file_nonce_source.html b/dom/security/test/csp/file_nonce_source.html
new file mode 100644
index 000000000..bf14ffe64
--- /dev/null
+++ b/dom/security/test/csp/file_nonce_source.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<html>
+ <head>
+ <!-- external styles -->
+ <link rel='stylesheet' nonce="correctstylenonce" href="file_CSP.sjs?testid=external_style_correct_nonce_good&type=text/css" />
+ <link rel='stylesheet' nonce="incorrectstylenonce" href="file_CSP.sjs?testid=external_style_incorrect_nonce_bad&type=text/css" />
+ <link rel='stylesheet' nonce="correctscriptnonce" href="file_CSP.sjs?testid=external_style_correct_script_nonce_bad&type=text/css" />
+ <link rel='stylesheet' href="file_CSP.sjs?testid=external_style_no_nonce_bad&type=text/css" />
+ </head>
+ <body>
+ <!-- inline scripts -->
+ <ol>
+ <li id="inline-script-correct-nonce">(inline script with correct nonce) This text should be green.</li>
+ <li id="inline-script-incorrect-nonce">(inline script with incorrect nonce) This text should be black.</li>
+ <li id="inline-script-correct-style-nonce">(inline script with correct nonce for styles, but not for scripts) This text should be black.</li>
+ <li id="inline-script-no-nonce">(inline script with no nonce) This text should be black.</li>
+ </ol>
+ <script nonce="correctscriptnonce">
+ document.getElementById("inline-script-correct-nonce").style.color = "rgb(0, 128, 0)";
+ </script>
+ <script nonce="incorrectscriptnonce">
+ document.getElementById("inline-script-incorrect-nonce").style.color = "rgb(255, 0, 0)";
+ </script>
+ <script nonce="correctstylenonce">
+ document.getElementById("inline-script-correct-style-nonce").style.color = "rgb(255, 0, 0)";
+ </script>
+ <script>
+ document.getElementById("inline-script-no-nonce").style.color = "rgb(255, 0, 0)";
+ </script>
+
+ <!-- external scripts -->
+ <script nonce="correctscriptnonce" src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=external_script_correct_nonce_good&type=text/javascript"></script>
+ <script nonce="anothercorrectscriptnonce" src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=external_script_another_correct_nonce_good&type=text/javascript"></script>
+ <script nonce="incorrectscriptnonce" src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=external_script_incorrect_nonce_bad&type=text/javascript"></script>
+ <script nonce="correctstylenonce" src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=external_script_correct_style_nonce_bad&type=text/javascript"></script>
+ <script src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=external_script_no_nonce_bad&type=text/javascript"></script>
+
+ <!-- This external script has the correct nonce and comes from a whitelisted URI. It should be allowed. -->
+ <script nonce="correctscriptnonce" src="file_CSP.sjs?testid=external_script_correct_nonce_correct_uri_good&type=text/javascript"></script>
+ <!-- This external script has an incorrect nonce, but comes from a whitelisted URI. It should be allowed. -->
+ <script nonce="incorrectscriptnonce" src="file_CSP.sjs?testid=external_script_incorrect_nonce_correct_uri_good&type=text/javascript"></script>
+ <!-- This external script has no nonce and comes from a whitelisted URI. It should be allowed. -->
+ <script src="file_CSP.sjs?testid=external_script_no_nonce_correct_uri_good&type=text/javascript"></script>
+
+ <!-- inline styles -->
+ <ol>
+ <li id=inline-style-correct-nonce>
+ (inline style with correct nonce) This text should be green
+ </li>
+ <li id=inline-style-incorrect-nonce>
+ (inline style with incorrect nonce) This text should be black
+ </li>
+ <li id=inline-style-correct-script-nonce>
+ (inline style with correct script, not style, nonce) This text should be black
+ </li>
+ <li id=inline-style-no-nonce>
+ (inline style with no nonce) This text should be black
+ </li>
+ </ol>
+ <style nonce=correctstylenonce>
+ li#inline-style-correct-nonce { color: green; }
+ </style>
+ <style nonce=incorrectstylenonce>
+ li#inline-style-incorrect-nonce { color: red; }
+ </style>
+ <style nonce=correctscriptnonce>
+ li#inline-style-correct-script-nonce { color: red; }
+ </style>
+ <style>
+ li#inline-style-no-nonce { color: red; }
+ </style>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_nonce_source.html^headers^ b/dom/security/test/csp/file_nonce_source.html^headers^
new file mode 100644
index 000000000..865e5fe98
--- /dev/null
+++ b/dom/security/test/csp/file_nonce_source.html^headers^
@@ -0,0 +1,2 @@
+Content-Security-Policy: script-src 'self' 'nonce-correctscriptnonce' 'nonce-anothercorrectscriptnonce'; style-src 'nonce-correctstylenonce';
+Cache-Control: no-cache
diff --git a/dom/security/test/csp/file_null_baseuri.html b/dom/security/test/csp/file_null_baseuri.html
new file mode 100644
index 000000000..f995688b1
--- /dev/null
+++ b/dom/security/test/csp/file_null_baseuri.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1121857 - document.baseURI should not get blocked if baseURI is null</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ // check the initial base-uri
+ window.parent.postMessage({baseURI: document.baseURI, test: "initial_base_uri"}, "*");
+
+ // append a child and check the base-uri
+ var baseTag = document.head.appendChild(document.createElement('base'));
+ baseTag.href = 'http://www.base-tag.com';
+ window.parent.postMessage({baseURI: document.baseURI, test: "changed_base_uri"}, "*");
+
+ // remove the child and check that the base-uri is back to the initial one
+ document.head.remove(baseTag);
+ window.parent.postMessage({baseURI: document.baseURI, test: "initial_base_uri"}, "*");
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_path_matching.html b/dom/security/test/csp/file_path_matching.html
new file mode 100644
index 000000000..662fbfb8a
--- /dev/null
+++ b/dom/security/test/csp/file_path_matching.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 808292 - Implement path-level host-source matching to CSP</title>
+ </head>
+ <body>
+ <div id="testdiv">blocked</div>
+ <script src="http://test1.example.com/tests/dom/security/test/csp/file_path_matching.js#foo"></script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_path_matching.js b/dom/security/test/csp/file_path_matching.js
new file mode 100644
index 000000000..09286d42e
--- /dev/null
+++ b/dom/security/test/csp/file_path_matching.js
@@ -0,0 +1 @@
+document.getElementById("testdiv").innerHTML = "allowed";
diff --git a/dom/security/test/csp/file_path_matching_incl_query.html b/dom/security/test/csp/file_path_matching_incl_query.html
new file mode 100644
index 000000000..50af2b143
--- /dev/null
+++ b/dom/security/test/csp/file_path_matching_incl_query.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1147026 - CSP should ignore query string when checking a resource load</title>
+ </head>
+ <body>
+ <div id="testdiv">blocked</div>
+ <script src="http://test1.example.com/tests/dom/security/test/csp/file_path_matching.js?val=foo"></script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_path_matching_redirect.html b/dom/security/test/csp/file_path_matching_redirect.html
new file mode 100644
index 000000000..a16cc90ec
--- /dev/null
+++ b/dom/security/test/csp/file_path_matching_redirect.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 808292 - Implement path-level host-source matching to CSP</title>
+ </head>
+ <body>
+ <div id="testdiv">blocked</div>
+ <script src="http://example.com/tests/dom/security/test/csp/file_path_matching_redirect_server.sjs"></script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_path_matching_redirect_server.sjs b/dom/security/test/csp/file_path_matching_redirect_server.sjs
new file mode 100644
index 000000000..49a3160f4
--- /dev/null
+++ b/dom/security/test/csp/file_path_matching_redirect_server.sjs
@@ -0,0 +1,13 @@
+// Redirect server specifically to handle redirects
+// for path-level host-source matching
+// see https://bugzilla.mozilla.org/show_bug.cgi?id=808292
+
+function handleRequest(request, response)
+{
+
+ var newLocation = "http://test1.example.com/tests/dom/security/test/csp/file_path_matching.js";
+
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Location", newLocation, false);
+}
diff --git a/dom/security/test/csp/file_ping.html b/dom/security/test/csp/file_ping.html
new file mode 100644
index 000000000..8aaf34cc3
--- /dev/null
+++ b/dom/security/test/csp/file_ping.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1100181 - CSP: Enforce connect-src when submitting pings</title>
+</head>
+<body>
+ <!-- we are using an image for the test, but can be anything -->
+ <a id="testlink"
+ href="http://mochi.test:8888/tests/image/test/mochitest/blue.png"
+ ping="http://mochi.test:8888/tests/image/test/mochitest/blue.png?send-ping">
+ Send ping
+ </a>
+
+ <script type="text/javascript">
+ var link = document.getElementById("testlink");
+ link.click();
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_policyuri_regression_from_multipolicy.html b/dom/security/test/csp/file_policyuri_regression_from_multipolicy.html
new file mode 100644
index 000000000..2a75eef7e
--- /dev/null
+++ b/dom/security/test/csp/file_policyuri_regression_from_multipolicy.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<html>
+ <body>
+ <div id=testdiv>Inline script didn't run</div>
+ <script>
+ document.getElementById('testdiv').innerHTML = "Inline Script Executed";
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_policyuri_regression_from_multipolicy.html^headers^ b/dom/security/test/csp/file_policyuri_regression_from_multipolicy.html^headers^
new file mode 100644
index 000000000..c4ff8ea9f
--- /dev/null
+++ b/dom/security/test/csp/file_policyuri_regression_from_multipolicy.html^headers^
@@ -0,0 +1 @@
+content-security-policy-report-only: policy-uri /tests/dom/security/test/csp/file_policyuri_regression_from_multipolicy_policy
diff --git a/dom/security/test/csp/file_policyuri_regression_from_multipolicy_policy b/dom/security/test/csp/file_policyuri_regression_from_multipolicy_policy
new file mode 100644
index 000000000..a5c610cd7
--- /dev/null
+++ b/dom/security/test/csp/file_policyuri_regression_from_multipolicy_policy
@@ -0,0 +1 @@
+default-src 'self';
diff --git a/dom/security/test/csp/file_redirect_content.sjs b/dom/security/test/csp/file_redirect_content.sjs
new file mode 100644
index 000000000..080f179cd
--- /dev/null
+++ b/dom/security/test/csp/file_redirect_content.sjs
@@ -0,0 +1,38 @@
+// https://bugzilla.mozilla.org/show_bug.cgi?id=650386
+// This SJS file serves file_redirect_content.html
+// with a CSP that will trigger a violation and that will report it
+// to file_redirect_report.sjs
+//
+// This handles 301, 302, 303 and 307 redirects. The HTTP status code
+// returned/type of redirect to do comes from the query string
+// parameter passed in from the test_bug650386_* files and then also
+// uses that value in the report-uri parameter of the CSP
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // this gets used in the CSP as part of the report URI.
+ var redirect = request.queryString;
+
+ if (redirect < 301 || (redirect > 303 && redirect <= 306) || redirect > 307) {
+ // if we somehow got some bogus redirect code here,
+ // do a 302 redirect to the same URL as the report URI
+ // redirects to - this will fail the test.
+ var loc = "http://example.com/some/fake/path";
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", loc, false);
+ return;
+ }
+
+ var csp = "default-src \'self\';report-uri http://mochi.test:8888/tests/dom/security/test/csp/file_redirect_report.sjs?" + redirect;
+
+ response.setHeader("Content-Security-Policy", csp, false);
+
+ // the actual file content.
+ // this image load will (intentionally) fail due to the CSP policy of default-src: 'self'
+ // specified by the CSP string above.
+ var content = "<!DOCTYPE HTML><html><body><img src = \"http://some.other.domain.example.com\"></body></html>";
+
+ response.write(content);
+
+ return;
+}
diff --git a/dom/security/test/csp/file_redirect_report.sjs b/dom/security/test/csp/file_redirect_report.sjs
new file mode 100644
index 000000000..9cc7e6548
--- /dev/null
+++ b/dom/security/test/csp/file_redirect_report.sjs
@@ -0,0 +1,17 @@
+// https://bugzilla.mozilla.org/show_bug.cgi?id=650386
+// This SJS file serves as CSP violation report target
+// and issues a redirect, to make sure the browser does not post to the target
+// of the redirect, per CSP spec.
+// This handles 301, 302, 303 and 307 redirects. The HTTP status code
+// returned/type of redirect to do comes from the query string
+// parameter
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ var redirect = request.queryString;
+
+ var loc = "http://example.com/some/fake/path";
+ response.setStatusLine("1.1", redirect, "Found");
+ response.setHeader("Location", loc, false);
+ return;
+}
diff --git a/dom/security/test/csp/file_redirect_worker.sjs b/dom/security/test/csp/file_redirect_worker.sjs
new file mode 100644
index 000000000..27df6a5fd
--- /dev/null
+++ b/dom/security/test/csp/file_redirect_worker.sjs
@@ -0,0 +1,34 @@
+// SJS file to serve resources for CSP redirect tests
+// This file redirects to a specified resource.
+const THIS_SITE = "http://mochi.test:8888";
+const OTHER_SITE = "http://example.com";
+
+function handleRequest(request, response)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var [name, value] = val.split('=');
+ query[name] = unescape(value);
+ });
+
+ var resource = query['path'];
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ var loc = '';
+
+ // redirect to a resource on this site
+ if (query["redir"] == "same") {
+ loc = THIS_SITE+resource+"#"+query['page_id']
+ }
+
+ // redirect to a resource on a different site
+ else if (query["redir"] == "other") {
+ loc = OTHER_SITE+resource+"#"+query['page_id']
+ }
+
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", loc, false);
+
+ response.write('<html><head><meta http-equiv="refresh" content="0; url='+loc+'">');
+ return;
+}
diff --git a/dom/security/test/csp/file_redirects_main.html b/dom/security/test/csp/file_redirects_main.html
new file mode 100644
index 000000000..d05af88fe
--- /dev/null
+++ b/dom/security/test/csp/file_redirects_main.html
@@ -0,0 +1,37 @@
+<html>
+<head>
+<title>CSP redirect tests</title>
+</head>
+<body>
+<div id="container"></div>
+</body>
+
+<script>
+var thisSite = "http://mochi.test:8888";
+var otherSite = "http://example.com";
+var page = "/tests/dom/security/test/csp/file_redirects_page.sjs";
+
+var tests = { "font-src": thisSite+page+"?testid=font-src",
+ "frame-src": thisSite+page+"?testid=frame-src",
+ "img-src": thisSite+page+"?testid=img-src",
+ "media-src": thisSite+page+"?testid=media-src",
+ "object-src": thisSite+page+"?testid=object-src",
+ "script-src": thisSite+page+"?testid=script-src",
+ "style-src": thisSite+page+"?testid=style-src",
+ "xhr-src": thisSite+page+"?testid=xhr-src",
+ "from-worker": thisSite+page+"?testid=from-worker",
+ "from-blob-worker": thisSite+page+"?testid=from-blob-worker",
+ "img-src-from-css": thisSite+page+"?testid=img-src-from-css",
+ };
+
+var container = document.getElementById("container");
+
+// load each test in its own iframe
+for (tid in tests) {
+ var i = document.createElement("iframe");
+ i.id = tid;
+ i.src = tests[tid];
+ container.appendChild(i);
+}
+</script>
+</html>
diff --git a/dom/security/test/csp/file_redirects_page.sjs b/dom/security/test/csp/file_redirects_page.sjs
new file mode 100644
index 000000000..ced2d0787
--- /dev/null
+++ b/dom/security/test/csp/file_redirects_page.sjs
@@ -0,0 +1,103 @@
+// SJS file for CSP redirect mochitests
+// This file serves pages which can optionally specify a Content Security Policy
+function handleRequest(request, response)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var [name, value] = val.split('=');
+ query[name] = unescape(value);
+ });
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+
+ var resource = "/tests/dom/security/test/csp/file_redirects_resource.sjs";
+
+ // CSP header value
+ response.setHeader("Content-Security-Policy",
+ "default-src 'self' blob: ; style-src 'self' 'unsafe-inline'", false);
+
+ // downloadable font that redirects to another site
+ if (query["testid"] == "font-src") {
+ var resp = '<style type="text/css"> @font-face { font-family:' +
+ '"Redirecting Font"; src: url("' + resource +
+ '?res=font&redir=other&id=font-src-redir")} #test{font-family:' +
+ '"Redirecting Font"}</style></head><body>' +
+ '<div id="test">test</div></body>';
+ response.write(resp);
+ return;
+ }
+
+ // iframe that redirects to another site
+ if (query["testid"] == "frame-src") {
+ response.write('<iframe src="'+resource+'?res=iframe&redir=other&id=frame-src-redir"></iframe>');
+ return;
+ }
+
+ // image that redirects to another site
+ if (query["testid"] == "img-src") {
+ response.write('<img src="'+resource+'?res=image&redir=other&id=img-src-redir" />');
+ return;
+ }
+
+ // video content that redirects to another site
+ if (query["testid"] == "media-src") {
+ response.write('<video src="'+resource+'?res=media&redir=other&id=media-src-redir"></video>');
+ return;
+ }
+
+ // object content that redirects to another site
+ if (query["testid"] == "object-src") {
+ response.write('<object type="text/html" data="'+resource+'?res=object&redir=other&id=object-src-redir"></object>');
+ return;
+ }
+
+ // external script that redirects to another site
+ if (query["testid"] == "script-src") {
+ response.write('<script src="'+resource+'?res=script&redir=other&id=script-src-redir"></script>');
+ return;
+ }
+
+ // external stylesheet that redirects to another site
+ if (query["testid"] == "style-src") {
+ response.write('<link rel="stylesheet" type="text/css" href="'+resource+'?res=style&redir=other&id=style-src-redir"></link>');
+ return;
+ }
+
+ // script that XHR's to a resource that redirects to another site
+ if (query["testid"] == "xhr-src") {
+ response.write('<script src="'+resource+'?res=xhr"></script>');
+ return;
+ }
+
+ // for bug949706
+ if (query["testid"] == "img-src-from-css") {
+ // loads a stylesheet, which in turn loads an image that redirects.
+ response.write('<link rel="stylesheet" type="text/css" href="'+resource+'?res=cssLoader&id=img-src-redir-from-css">');
+ return;
+ }
+
+ if (query["testid"] == "from-worker") {
+ // loads a script; launches a worker; that worker uses importscript; which then gets redirected
+ // So it's:
+ // <script src="res=loadWorkerThatMakesRequests">
+ // .. loads Worker("res=makeRequestsWorker")
+ // .. calls importScript("res=script")
+ // .. calls xhr("res=xhr-resp")
+ // .. calls fetch("res=xhr-resp")
+ response.write('<script src="'+resource+'?res=loadWorkerThatMakesRequests&id=from-worker"></script>');
+ return;
+ }
+
+ if (query["testid"] == "from-blob-worker") {
+ // loads a script; launches a worker; that worker uses importscript; which then gets redirected
+ // So it's:
+ // <script src="res=loadBlobWorkerThatMakesRequests">
+ // .. loads Worker("res=makeRequestsWorker")
+ // .. calls importScript("res=script")
+ // .. calls xhr("res=xhr-resp")
+ // .. calls fetch("res=xhr-resp")
+ response.write('<script src="'+resource+'?res=loadBlobWorkerThatMakesRequests&id=from-blob-worker"></script>');
+ return;
+ }
+}
diff --git a/dom/security/test/csp/file_redirects_resource.sjs b/dom/security/test/csp/file_redirects_resource.sjs
new file mode 100644
index 000000000..da138630f
--- /dev/null
+++ b/dom/security/test/csp/file_redirects_resource.sjs
@@ -0,0 +1,149 @@
+// SJS file to serve resources for CSP redirect tests
+// This file mimics serving resources, e.g. fonts, images, etc., which a CSP
+// can include. The resource may redirect to a different resource, if specified.
+function handleRequest(request, response)
+{
+ var query = {};
+ request.queryString.split('&').forEach(function (val) {
+ var [name, value] = val.split('=');
+ query[name] = unescape(value);
+ });
+
+ var thisSite = "http://mochi.test:8888";
+ var otherSite = "http://example.com";
+ var resource = "/tests/dom/security/test/csp/file_redirects_resource.sjs";
+
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // redirect to a resource on this site
+ if (query["redir"] == "same") {
+ var loc = thisSite+resource+"?res="+query["res"]+"&testid="+query["id"];
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", loc, false);
+ return;
+ }
+
+ // redirect to a resource on a different site
+ else if (query["redir"] == "other") {
+ var loc = otherSite+resource+"?res="+query["res"]+"&testid="+query["id"];
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", loc, false);
+ return;
+ }
+
+ // not a redirect. serve some content.
+ // the content doesn't have to be valid, since we're only checking whether
+ // the request for the content was sent or not.
+
+ // downloadable font
+ if (query["res"] == "font") {
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write("font data...");
+ return;
+ }
+
+ // iframe with arbitrary content
+ if (query["res"] == "iframe") {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("iframe content...");
+ return;
+ }
+
+ // image
+ if (query["res"] == "image") {
+ response.setHeader("Content-Type", "image/gif", false);
+ response.write("image data...");
+ return;
+ }
+
+ // media content, e.g. Ogg video
+ if (query["res"] == "media") {
+ response.setHeader("Content-Type", "video/ogg", false);
+ response.write("video data...");
+ return;
+ }
+
+ // plugin content, e.g. <object>
+ if (query["res"] == "object") {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("object data...");
+ return;
+ }
+
+ // script
+ if (query["res"] == "script") {
+ response.setHeader("Content-Type", "application/javascript", false);
+ response.write("some script...");
+ return;
+ }
+
+ // external stylesheet
+ if (query["res"] == "style") {
+ response.setHeader("Content-Type", "text/css", false);
+ response.write("css data...");
+ return;
+ }
+
+ // internal stylesheet that loads an image from an external site
+ if (query["res"] == "cssLoader") {
+ let bgURL = thisSite + resource + '?redir=other&res=image&id=' + query["id"];
+ response.setHeader("Content-Type", "text/css", false);
+ response.write("body { background:url('" + bgURL + "'); }");
+ return;
+ }
+
+ // script that loads an internal worker that uses importScripts on a redirect
+ // to an external script.
+ if (query["res"] == "loadWorkerThatMakesRequests") {
+ // this creates a worker (same origin) that imports a redirecting script.
+ let workerURL = thisSite + resource + '?res=makeRequestsWorker&id=' + query["id"];
+ response.setHeader("Content-Type", "application/javascript", false);
+ response.write("new Worker('" + workerURL + "');");
+ return;
+ }
+
+ // script that loads an internal worker that uses importScripts on a redirect
+ // to an external script.
+ if (query["res"] == "loadBlobWorkerThatMakesRequests") {
+ // this creates a worker (same origin) that imports a redirecting script.
+ let workerURL = thisSite + resource + '?res=makeRequestsWorker&id=' + query["id"];
+ response.setHeader("Content-Type", "application/javascript", false);
+ response.write("var x = new XMLHttpRequest(); x.open('GET', '" + workerURL + "'); ");
+ response.write("x.responseType = 'blob'; x.send(); ");
+ response.write("x.onload = () => { new Worker(URL.createObjectURL(x.response)); };");
+ return;
+ }
+
+ // source for a worker that simply calls importScripts on a script that
+ // redirects.
+ if (query["res"] == "makeRequestsWorker") {
+ // this is code for a worker that imports a redirected script.
+ let scriptURL = thisSite + resource + "?redir=other&res=script&id=script-src-redir-" + query["id"];
+ let xhrURL = thisSite + resource + "?redir=other&res=xhr-resp&id=xhr-src-redir-" + query["id"];
+ let fetchURL = thisSite + resource + "?redir=other&res=xhr-resp&id=fetch-src-redir-" + query["id"];
+ response.setHeader("Content-Type", "application/javascript", false);
+ response.write("try { importScripts('" + scriptURL + "'); } catch(ex) {} ");
+ response.write("var x = new XMLHttpRequest(); x.open('GET', '" + xhrURL + "'); x.send();");
+ response.write("fetch('" + fetchURL + "');");
+ return;
+ }
+
+ // script that invokes XHR
+ if (query["res"] == "xhr") {
+ response.setHeader("Content-Type", "application/javascript", false);
+ var resp = 'var x = new XMLHttpRequest();x.open("GET", "' + thisSite +
+ resource+'?redir=other&res=xhr-resp&id=xhr-src-redir", false);\n' +
+ 'x.send(null);';
+ response.write(resp);
+ return;
+ }
+
+ // response to XHR
+ if (query["res"] == "xhr-resp") {
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.write('XHR response...');
+ return;
+ }
+}
diff --git a/dom/security/test/csp/file_referrerdirective.html b/dom/security/test/csp/file_referrerdirective.html
new file mode 100644
index 000000000..841ffe058
--- /dev/null
+++ b/dom/security/test/csp/file_referrerdirective.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Subframe test for bug 965727</title>
+
+<script type="text/javascript">
+// we can get the ID out of the querystring.
+var args = document.location.search.substring(1).split('&');
+var id = "unknown";
+for (var i=0; i < args.length; i++) {
+ var arg = unescape(args[i]);
+ if (arg.indexOf('=') > 0 && arg.indexOf('id') == 0) {
+ id = arg.split('=')[1].trim();
+ }
+}
+
+var results = {
+ 'id': id,
+ 'referrer': document.location.href,
+ 'results': {
+ 'sameorigin': false,
+ 'crossorigin': false,
+ 'downgrade': false
+ }
+};
+
+// this is called back by each script load.
+var postResult = function(loadType, referrerLevel, referrer) {
+ results.results[loadType] = referrerLevel;
+
+ // and then check if all three have loaded.
+ for (var id in results.results) {
+ if (!results.results[id]) {
+ return;
+ }
+ }
+ //finished if we don't return early
+ window.parent.postMessage(JSON.stringify(results), "*");
+ console.log(JSON.stringify(results));
+}
+
+</script>
+</head>
+<body>
+Testing ...
+
+<script src="https://example.com/tests/dom/security/test/csp/referrerdirective.sjs?type=sameorigin&"
+ onerror="postResult('sameorigin', 'error');"></script>
+<script src="https://test2.example.com/tests/dom/security/test/csp/referrerdirective.sjs?type=crossorigin&"
+ onerror="postResult('crossorigin', 'error');"></script>
+<script src="http://example.com/tests/dom/security/test/csp/referrerdirective.sjs?type=downgrade&"
+ onerror="postResult('downgrade', 'error');"></script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_report.html b/dom/security/test/csp/file_report.html
new file mode 100644
index 000000000..fb18af805
--- /dev/null
+++ b/dom/security/test/csp/file_report.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1033424 - Test csp-report properties </title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ var foo = "propEscFoo";
+ var bar = "propEscBar";
+ // just verifying that we properly escape newlines and quotes
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_report_chromescript.js b/dom/security/test/csp/file_report_chromescript.js
new file mode 100644
index 000000000..bf4f70edb
--- /dev/null
+++ b/dom/security/test/csp/file_report_chromescript.js
@@ -0,0 +1,54 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+const reportURI = "http://mochi.test:8888/foo.sjs";
+
+var openingObserver = {
+ observe: function(subject, topic, data) {
+ // subject should be an nsURI
+ if (subject.QueryInterface == undefined)
+ return;
+
+ var message = {report: "", error: false};
+
+ if (topic == 'http-on-opening-request') {
+ var asciiSpec = subject.QueryInterface(Ci.nsIHttpChannel).URI.asciiSpec;
+ if (asciiSpec !== reportURI) return;
+
+ var reportText = false;
+ try {
+ // Verify that the report was properly formatted.
+ // We'll parse the report text as JSON and verify that the properties
+ // have expected values.
+ var reportText = "{}";
+ var uploadStream = subject.QueryInterface(Ci.nsIUploadChannel).uploadStream;
+
+ if (uploadStream) {
+ // get the bytes from the request body
+ var binstream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
+ binstream.setInputStream(uploadStream);
+
+ var segments = [];
+ for (var count = uploadStream.available(); count; count = uploadStream.available()) {
+ var data = binstream.readBytes(count);
+ segments.push(data);
+ }
+
+ var reportText = segments.join("");
+ // rewind stream as we are supposed to - there will be an assertion later if we don't.
+ uploadStream.QueryInterface(Ci.nsISeekableStream).seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+ }
+ message.report = reportText;
+ } catch (e) {
+ message.error = e.toString();
+ }
+
+ sendAsyncMessage('opening-request-completed', message);
+ Services.obs.removeObserver(openingObserver, 'http-on-opening-request');
+ }
+ }
+};
+
+Services.obs.addObserver(openingObserver, 'http-on-opening-request', false);
diff --git a/dom/security/test/csp/file_report_for_import.css b/dom/security/test/csp/file_report_for_import.css
new file mode 100644
index 000000000..b578b77b3
--- /dev/null
+++ b/dom/security/test/csp/file_report_for_import.css
@@ -0,0 +1 @@
+@import url("http://example.com/tests/dom/security/test/csp/file_report_for_import_server.sjs?stylesheet");
diff --git a/dom/security/test/csp/file_report_for_import.html b/dom/security/test/csp/file_report_for_import.html
new file mode 100644
index 000000000..77a36faea
--- /dev/null
+++ b/dom/security/test/csp/file_report_for_import.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1048048 - Test sending csp-report when using import in css</title>
+ <link rel="stylesheet" type="text/css" href="file_report_for_import.css">
+ </head>
+ <body>
+ empty body, just testing @import in the included css for bug 1048048
+</body>
+</html>
diff --git a/dom/security/test/csp/file_report_for_import_server.sjs b/dom/security/test/csp/file_report_for_import_server.sjs
new file mode 100644
index 000000000..e69852b1b
--- /dev/null
+++ b/dom/security/test/csp/file_report_for_import_server.sjs
@@ -0,0 +1,49 @@
+// Custom *.sjs file specifically for the needs of Bug:
+// Bug 1048048 - CSP violation report not sent for @import
+
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ var queryString = request.queryString;
+
+ // (1) lets process the queryresult request async and
+ // wait till we have received the image request.
+ if (queryString === "queryresult") {
+ response.processAsync();
+ setObjectState("queryResult", response);
+ return;
+ }
+
+ // (2) handle the csp-report and return the JSON back to
+ // the testfile using the afore stored xml request in (1).
+ if (queryString === "report") {
+ getObjectState("queryResult", function(queryResponse) {
+ if (!queryResponse) {
+ return;
+ }
+
+ // send the report back to the XML request for verification
+ var report = new BinaryInputStream(request.bodyInputStream);
+ var avail;
+ var bytes = [];
+ while ((avail = report.available()) > 0) {
+ Array.prototype.push.apply(bytes, report.readByteArray(avail));
+ }
+ var data = String.fromCharCode.apply(null, bytes);
+ queryResponse.bodyOutputStream.write(data, data.length);
+ queryResponse.finish();
+ });
+ return;
+ }
+
+ // we should not get here ever, but just in case return
+ // something unexpected.
+ response.write("doh!");
+}
diff --git a/dom/security/test/csp/file_report_uri_missing_in_report_only_header.html b/dom/security/test/csp/file_report_uri_missing_in_report_only_header.html
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/dom/security/test/csp/file_report_uri_missing_in_report_only_header.html
diff --git a/dom/security/test/csp/file_report_uri_missing_in_report_only_header.html^headers^ b/dom/security/test/csp/file_report_uri_missing_in_report_only_header.html^headers^
new file mode 100644
index 000000000..3f2fdfe9e
--- /dev/null
+++ b/dom/security/test/csp/file_report_uri_missing_in_report_only_header.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy-Report-Only: default-src 'self';
diff --git a/dom/security/test/csp/file_require_sri_meta.js b/dom/security/test/csp/file_require_sri_meta.js
new file mode 100644
index 000000000..af0113297
--- /dev/null
+++ b/dom/security/test/csp/file_require_sri_meta.js
@@ -0,0 +1 @@
+var foo = 24;
diff --git a/dom/security/test/csp/file_require_sri_meta.sjs b/dom/security/test/csp/file_require_sri_meta.sjs
new file mode 100644
index 000000000..acaf742db
--- /dev/null
+++ b/dom/security/test/csp/file_require_sri_meta.sjs
@@ -0,0 +1,54 @@
+// custom *.sjs for Bug 1277557
+// META CSP: require-sri-for script;
+
+const PRE_INTEGRITY =
+ "<!DOCTYPE HTML>" +
+ "<html><head><meta charset=\"utf-8\">" +
+ "<title>Bug 1277557 - CSP require-sri-for does not block when CSP is in meta tag</title>" +
+ "<meta http-equiv=\"Content-Security-Policy\" content=\"require-sri-for script; script-src 'unsafe-inline' *\">" +
+ "</head>" +
+ "<body>" +
+ "<script id=\"testscript\"" +
+ // Using math.random() to avoid confusing cache behaviors within the test
+ " src=\"http://mochi.test:8888/tests/dom/security/test/csp/file_require_sri_meta.js?" + Math.random() + "\"";
+
+const WRONG_INTEGRITY =
+ " integrity=\"sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC\"";
+
+const CORRECT_INEGRITY =
+ " integrity=\"sha384-PkcuZQHmjBQKRyv1v3x0X8qFmXiSyFyYIP+f9SU86XWvRneifdNCPg2cYFWBuKsF\"";
+
+const POST_INTEGRITY =
+ " onload=\"window.parent.postMessage({result: 'script-loaded'}, '*');\"" +
+ " onerror=\"window.parent.postMessage({result: 'script-blocked'}, '*');\"" +
+ "></script>" +
+ "</body>" +
+ "</html>";
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+
+ var queryString = request.queryString;
+
+ if (queryString === "no-sri") {
+ response.write(PRE_INTEGRITY + POST_INTEGRITY);
+ return;
+ }
+
+ if (queryString === "wrong-sri") {
+ response.write(PRE_INTEGRITY + WRONG_INTEGRITY + POST_INTEGRITY);
+ return;
+ }
+
+ if (queryString === "correct-sri") {
+ response.write(PRE_INTEGRITY + CORRECT_INEGRITY + POST_INTEGRITY);
+ return;
+ }
+
+ // we should never get here, but just in case
+ // return something unexpected
+ response.write("do'h");
+}
diff --git a/dom/security/test/csp/file_sandbox_1.html b/dom/security/test/csp/file_sandbox_1.html
new file mode 100644
index 000000000..ce1e80c86
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_1.html
@@ -0,0 +1,16 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <!-- sandbox="allow-same-origin" -->
+ <!-- Content-Security-Policy: default-src 'self' -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img1_bad&type=img/png"> </img>
+
+ <!-- these should load ok -->
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img1a_good&type=img/png" />
+ <!-- should not execute script -->
+ <script src='/tests/dom/security/test/csp/file_sandbox_fail.js'></script>
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_sandbox_10.html b/dom/security/test/csp/file_sandbox_10.html
new file mode 100644
index 000000000..f934497ee
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_10.html
@@ -0,0 +1,12 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <!-- Content-Security-Policy: default-src 'none'; sandbox -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img10_bad&type=img/png"> </img>
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img10a_bad&type=img/png" />
+ <script src='/tests/dom/security/test/csp/file_sandbox_fail.js'></script>
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_sandbox_11.html b/dom/security/test/csp/file_sandbox_11.html
new file mode 100644
index 000000000..03697438a
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_11.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head> <meta charset="utf-8"> </head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+ }
+
+ function doStuff() {
+ ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
+ }
+</script>
+<script src='file_sandbox_fail.js'></script>
+<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
+ I am sandboxed but with only inline "allow-scripts"
+
+ <!-- Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline'; sandbox allow-scripts -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img11_bad&type=img/png" />
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img11a_bad&type=img/png"> </img>
+ <script src='/tests/dom/security/test/csp/file_CSP.sjs?testid=script11_bad&type=text/javascript'></script>
+ <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script11a_bad&type=text/javascript'></script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_sandbox_12.html b/dom/security/test/csp/file_sandbox_12.html
new file mode 100644
index 000000000..8bed9dc59
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_12.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+ }
+
+ function doStuff() {
+ ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
+
+ document.getElementById('a_form').submit();
+
+ // trigger the javascript: url test
+ sendMouseEvent({type:'click'}, 'a_link');
+ }
+</script>
+<script src='file_sandbox_pass.js'></script>
+<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
+ I am sandboxed but with "allow-same-origin" and allow-scripts"
+
+
+ <!-- Content-Security-Policy: sandbox allow-same-origin allow-scripts; default-src 'self' 'unsafe-inline'; -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img12_bad&type=img/png"> </img>
+ <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script12_bad&type=text/javascript'></script>
+
+ <form method="get" action="/tests/content/html/content/test/file_iframe_sandbox_form_fail.html" id="a_form">
+ First name: <input type="text" name="firstname">
+ Last name: <input type="text" name="lastname">
+ <input type="submit" onclick="doSubmit()" id="a_button">
+ </form>
+
+ <a href = 'javascript:ok(true, "documents sandboxed with allow-scripts should be able to run script from javascript: URLs");' id='a_link'>click me</a>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_sandbox_13.html b/dom/security/test/csp/file_sandbox_13.html
new file mode 100644
index 000000000..e4672ed05
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_13.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head> <meta charset="utf-8"> </head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+ }
+
+ function doStuff() {
+ ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
+ }
+</script>
+<script src='file_sandbox_fail.js'></script>
+<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
+ I am sandboxed but with only inline "allow-scripts"
+
+ <!-- Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline'; sandbox allow-scripts -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img13_bad&type=img/png" />
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img13a_bad&type=img/png"> </img>
+ <script src='/tests/dom/security/test/csp/file_CSP.sjs?testid=script13_bad&type=text/javascript'></script>
+ <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script13a_bad&type=text/javascript'></script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_sandbox_2.html b/dom/security/test/csp/file_sandbox_2.html
new file mode 100644
index 000000000..b37aa1bce
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_2.html
@@ -0,0 +1,16 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <!-- sandbox -->
+ <!-- Content-Security-Policy: default-src 'self' -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img2_bad&type=img/png"> </img>
+
+ <!-- these should load ok -->
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img2a_good&type=img/png" />
+ <!-- should not execute script -->
+ <script src='/tests/dom/security/test/csp/file_sandbox_fail.js'></script>
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_sandbox_3.html b/dom/security/test/csp/file_sandbox_3.html
new file mode 100644
index 000000000..ba808e47d
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_3.html
@@ -0,0 +1,13 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <!-- sandbox="allow-same-origin" -->
+ <!-- Content-Security-Policy: default-src 'none' -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img3_bad&type=img/png"> </img>
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img3a_bad&type=img/png" />
+ <script src='/tests/dom/security/test/csp/file_sandbox_fail.js'></script>
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_sandbox_4.html b/dom/security/test/csp/file_sandbox_4.html
new file mode 100644
index 000000000..b2d4ed094
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_4.html
@@ -0,0 +1,13 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <!-- sandbox -->
+ <!-- Content-Security-Policy: default-src 'none' -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="http://example.org/tests/dom/base/test/csp/file_CSP.sjs?testid=img4_bad&type=img/png"> </img>
+ <img src="/tests/dom/base/test/csp/file_CSP.sjs?testid=img4a_bad&type=img/png" />
+ <script src='/tests/dom/base/test/csp/file_sandbox_fail.js'></script>
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_sandbox_5.html b/dom/security/test/csp/file_sandbox_5.html
new file mode 100644
index 000000000..79894eabb
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_5.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head> <meta charset="utf-8"> </head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+ }
+
+ function doStuff() {
+ ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
+ }
+</script>
+<script src='file_sandbox_fail.js'></script>
+<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
+ I am sandboxed but with only inline "allow-scripts"
+
+ <!-- sandbox="allow-scripts" -->
+ <!-- Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline' -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img5_bad&type=img/png" />
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img5a_bad&type=img/png"> </img>
+ <script src='/tests/dom/security/test/csp/file_CSP.sjs?testid=script5_bad&type=text/javascript'></script>
+ <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script5a_bad&type=text/javascript'></script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_sandbox_6.html b/dom/security/test/csp/file_sandbox_6.html
new file mode 100644
index 000000000..c23d87860
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_6.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<script type="text/javascript">
+ function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+ }
+
+ function doStuff() {
+ ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts");
+
+ document.getElementById('a_form').submit();
+
+ // trigger the javascript: url test
+ sendMouseEvent({type:'click'}, 'a_link');
+ }
+</script>
+<script src='file_sandbox_pass.js'></script>
+<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'>
+ I am sandboxed but with "allow-same-origin" and allow-scripts"
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img6_bad&type=img/png"> </img>
+ <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script6_bad&type=text/javascript'></script>
+
+ <form method="get" action="/tests/content/html/content/test/file_iframe_sandbox_form_fail.html" id="a_form">
+ First name: <input type="text" name="firstname">
+ Last name: <input type="text" name="lastname">
+ <input type="submit" onclick="doSubmit()" id="a_button">
+ </form>
+
+ <a href = 'javascript:ok(true, "documents sandboxed with allow-scripts should be able to run script from javascript: URLs");' id='a_link'>click me</a>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_sandbox_7.html b/dom/security/test/csp/file_sandbox_7.html
new file mode 100644
index 000000000..3b249d410
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_7.html
@@ -0,0 +1,15 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <!-- Content-Security-Policy: default-src 'self'; sandbox allow-same-origin -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img7_bad&type=img/png"> </img>
+
+ <!-- these should load ok -->
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img7a_good&type=img/png" />
+ <!-- should not execute script -->
+ <script src='/tests/dom/security/test/csp/file_sandbox_fail.js'></script>
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_sandbox_8.html b/dom/security/test/csp/file_sandbox_8.html
new file mode 100644
index 000000000..4f9cd8916
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_8.html
@@ -0,0 +1,15 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <!-- Content-Security-Policy: sandbox; default-src 'self' -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img8_bad&type=img/png"> </img>
+
+ <!-- these should load ok -->
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img8a_good&type=img/png" />
+ <!-- should not execute script -->
+ <script src='/tests/dom/security/test/csp/file_sandbox_fail.js'></script>
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_sandbox_9.html b/dom/security/test/csp/file_sandbox_9.html
new file mode 100644
index 000000000..29ffc191c
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_9.html
@@ -0,0 +1,12 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <!-- Content-Security-Policy: default-src 'none'; sandbox allow-same-origin -->
+
+ <!-- these should be stopped by CSP -->
+ <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img9_bad&type=img/png"> </img>
+ <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img9a_bad&type=img/png" />
+
+ <script src='/tests/dom/security/test/csp/file_sandbox_fail.js'></script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_sandbox_allow_scripts.html b/dom/security/test/csp/file_sandbox_allow_scripts.html
new file mode 100644
index 000000000..faab9f0fc
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_allow_scripts.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <title>Bug 1396320: Fix CSP sandbox regression for allow-scripts</title>
+ </head>
+<body>
+<script type='application/javascript'>
+ window.parent.postMessage({result: document.domain }, '*');
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_sandbox_allow_scripts.html^headers^ b/dom/security/test/csp/file_sandbox_allow_scripts.html^headers^
new file mode 100644
index 000000000..4705ce9de
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_allow_scripts.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-scripts;
diff --git a/dom/security/test/csp/file_sandbox_fail.js b/dom/security/test/csp/file_sandbox_fail.js
new file mode 100644
index 000000000..5403ff218
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_fail.js
@@ -0,0 +1,4 @@
+function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+}
+ok(false, "documents sandboxed with allow-scripts should NOT be able to run <script src=...>");
diff --git a/dom/security/test/csp/file_sandbox_pass.js b/dom/security/test/csp/file_sandbox_pass.js
new file mode 100644
index 000000000..2fd0350dc
--- /dev/null
+++ b/dom/security/test/csp/file_sandbox_pass.js
@@ -0,0 +1,4 @@
+function ok(result, desc) {
+ window.parent.postMessage({ok: result, desc: desc}, "*");
+}
+ok(true, "documents sandboxed with allow-scripts should be able to run <script src=...>");
diff --git a/dom/security/test/csp/file_scheme_relative_sources.js b/dom/security/test/csp/file_scheme_relative_sources.js
new file mode 100644
index 000000000..09286d42e
--- /dev/null
+++ b/dom/security/test/csp/file_scheme_relative_sources.js
@@ -0,0 +1 @@
+document.getElementById("testdiv").innerHTML = "allowed";
diff --git a/dom/security/test/csp/file_scheme_relative_sources.sjs b/dom/security/test/csp/file_scheme_relative_sources.sjs
new file mode 100644
index 000000000..8c4d62ca5
--- /dev/null
+++ b/dom/security/test/csp/file_scheme_relative_sources.sjs
@@ -0,0 +1,42 @@
+/**
+ * Custom *.sjs specifically for the needs of
+ * Bug 921493 - CSP: test whitelisting of scheme-relative sources
+ */
+
+function handleRequest(request, response)
+{
+ Components.utils.importGlobalProperties(["URLSearchParams"]);
+ let query = new URLSearchParams(request.queryString);
+
+ let scheme = query.get("scheme");
+ let policy = query.get("policy");
+
+ let linkUrl = scheme +
+ "://example.com/tests/dom/security/test/csp/file_scheme_relative_sources.js";
+
+ let html = "<!DOCTYPE HTML>" +
+ "<html>" +
+ "<head>" +
+ "<title>test schemeless sources within CSP</title>" +
+ "</head>" +
+ "<body> " +
+ "<div id='testdiv'>blocked</div>" +
+ // try to load a scheme relative script
+ "<script src='" + linkUrl + "'></script>" +
+ // have an inline script that reports back to the parent whether
+ // the script got loaded or not from within the sandboxed iframe.
+ "<script type='application/javascript'>" +
+ "window.onload = function() {" +
+ "var inner = document.getElementById('testdiv').innerHTML;" +
+ "window.parent.postMessage({ result: inner }, '*');" +
+ "}" +
+ "</script>" +
+ "</body>" +
+ "</html>";
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Security-Policy", policy, false);
+
+ response.write(html);
+}
diff --git a/dom/security/test/csp/file_self_none_as_hostname_confusion.html b/dom/security/test/csp/file_self_none_as_hostname_confusion.html
new file mode 100644
index 000000000..16196bb19
--- /dev/null
+++ b/dom/security/test/csp/file_self_none_as_hostname_confusion.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Bug 587377 - CSP keywords "'self'" and "'none'" are easy to confuse with host names "self" and "none"</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_self_none_as_hostname_confusion.html^headers^ b/dom/security/test/csp/file_self_none_as_hostname_confusion.html^headers^
new file mode 100644
index 000000000..26af7ed9b
--- /dev/null
+++ b/dom/security/test/csp/file_self_none_as_hostname_confusion.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self' SELF;
diff --git a/dom/security/test/csp/file_sendbeacon.html b/dom/security/test/csp/file_sendbeacon.html
new file mode 100644
index 000000000..13202c65f
--- /dev/null
+++ b/dom/security/test/csp/file_sendbeacon.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="Content-Security-Policy" content= "connect-src 'none'">
+ <title>Bug 1234813 - sendBeacon should not throw if blocked by Content Policy</title>
+</head>
+<body>
+
+<script type="application/javascript">
+try {
+ navigator.sendBeacon("http://example.com/sendbeaconintonirvana");
+ window.parent.postMessage({result: "blocked-beacon-does-not-throw"}, "*");
+ }
+ catch (e) {
+ window.parent.postMessage({result: "blocked-beacon-throws"}, "*");
+ }
+</script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_service_worker.html b/dom/security/test/csp/file_service_worker.html
new file mode 100644
index 000000000..00a2b4020
--- /dev/null
+++ b/dom/security/test/csp/file_service_worker.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1208559 - ServiceWorker registration not governed by CSP</title>
+</head>
+<body>
+<script>
+ function finish(status) {
+ window.parent.postMessage({result: status}, "*");
+ }
+
+ navigator.serviceWorker.ready.then(finish.bind(null, 'allowed'),
+ finish.bind(null, 'blocked'));
+ navigator.serviceWorker
+ .register("file_service_worker.js", {scope: "."})
+ .then(null, finish.bind(null, 'blocked'));
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_service_worker.js b/dom/security/test/csp/file_service_worker.js
new file mode 100644
index 000000000..1bf583f4c
--- /dev/null
+++ b/dom/security/test/csp/file_service_worker.js
@@ -0,0 +1 @@
+dump("service workers: hello world");
diff --git a/dom/security/test/csp/file_shouldprocess.html b/dom/security/test/csp/file_shouldprocess.html
new file mode 100644
index 000000000..0684507e9
--- /dev/null
+++ b/dom/security/test/csp/file_shouldprocess.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Helper for Test Bug 908933</title>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <object type="application/x-java-test" codebase="test1"></object>
+
+ <object classid="java:test2" codebase="./test2"></object>
+
+ <object data="test3" classid="java:test3" codebase="./test3"></object>
+
+ <applet codebase="test4"></applet>
+
+ <embed src="test5.class" codebase="test5" type="application/x-java-test">
+
+ <embed type="application/x-java-test" codebase="test6">
+
+ <embed src="test7.class">
+
+ <embed src="test8.class" codebase="test8">
+
+ </body>
+</html>
diff --git a/dom/security/test/csp/file_strict_dynamic.js b/dom/security/test/csp/file_strict_dynamic.js
new file mode 100644
index 000000000..09286d42e
--- /dev/null
+++ b/dom/security/test/csp/file_strict_dynamic.js
@@ -0,0 +1 @@
+document.getElementById("testdiv").innerHTML = "allowed";
diff --git a/dom/security/test/csp/file_strict_dynamic_default_src.html b/dom/security/test/csp/file_strict_dynamic_default_src.html
new file mode 100644
index 000000000..35feacb4f
--- /dev/null
+++ b/dom/security/test/csp/file_strict_dynamic_default_src.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1299483 - CSP: Implement 'strict-dynamic'</title>
+</head>
+<body>
+
+<div id="testdiv">blocked</div>
+<script nonce="foo" src="http://mochi.test:8888/tests/dom/security/test/csp/file_strict_dynamic_default_src.js"></script>
+
+<img id="testimage" src="http://mochi.test:8888/tests/image/test/mochitest/blue.png"></img>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_strict_dynamic_default_src.js b/dom/security/test/csp/file_strict_dynamic_default_src.js
new file mode 100644
index 000000000..09286d42e
--- /dev/null
+++ b/dom/security/test/csp/file_strict_dynamic_default_src.js
@@ -0,0 +1 @@
+document.getElementById("testdiv").innerHTML = "allowed";
diff --git a/dom/security/test/csp/file_strict_dynamic_js_url.html b/dom/security/test/csp/file_strict_dynamic_js_url.html
new file mode 100644
index 000000000..bd53b0adb
--- /dev/null
+++ b/dom/security/test/csp/file_strict_dynamic_js_url.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1316826 - 'strict-dynamic' blocking DOM event handlers</title>
+</head>
+<body>
+<div id="testdiv">blocked</div>
+
+<a id="jslink" href='javascript:document.getElementById("testdiv").innerHTML = "allowed"'>click me</a>
+<script nonce="foo">
+ document.getElementById("jslink").click();
+</script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_strict_dynamic_non_parser_inserted.html b/dom/security/test/csp/file_strict_dynamic_non_parser_inserted.html
new file mode 100644
index 000000000..c51fefd72
--- /dev/null
+++ b/dom/security/test/csp/file_strict_dynamic_non_parser_inserted.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1299483 - CSP: Implement 'strict-dynamic'</title>
+</head>
+<body>
+<div id="testdiv">blocked</div>
+
+<script nonce="foo">
+ // generates a *non* parser inserted script and should be allowed
+ var myScript = document.createElement('script');
+ myScript.src = 'http://example.com/tests/dom/security/test/csp/file_strict_dynamic.js';
+ document.head.appendChild(myScript);
+</script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_strict_dynamic_non_parser_inserted_inline.html b/dom/security/test/csp/file_strict_dynamic_non_parser_inserted_inline.html
new file mode 100644
index 000000000..10a0f32e4
--- /dev/null
+++ b/dom/security/test/csp/file_strict_dynamic_non_parser_inserted_inline.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1299483 - CSP: Implement 'strict-dynamic'</title>
+</head>
+<body>
+<div id="testdiv">blocked</div>
+
+<script nonce="foo">
+ var dynamicScript = document.createElement('script');
+ dynamicScript.textContent = 'document.getElementById("testdiv").textContent="allowed"';
+ document.head.appendChild(dynamicScript);
+</script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_strict_dynamic_parser_inserted_doc_write.html b/dom/security/test/csp/file_strict_dynamic_parser_inserted_doc_write.html
new file mode 100644
index 000000000..2a3a7d499
--- /dev/null
+++ b/dom/security/test/csp/file_strict_dynamic_parser_inserted_doc_write.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1299483 - CSP: Implement 'strict-dynamic'</title>
+</head>
+<body>
+<div id="testdiv">blocked</div>
+
+<script nonce="foo">
+ // generates a parser inserted script and should be blocked
+ document.write("<script src='http://example.com/tests/dom/security/test/csp/file_strict_dynamic.js'><\/script>");
+</script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_strict_dynamic_parser_inserted_doc_write_correct_nonce.html b/dom/security/test/csp/file_strict_dynamic_parser_inserted_doc_write_correct_nonce.html
new file mode 100644
index 000000000..9938ef2dc
--- /dev/null
+++ b/dom/security/test/csp/file_strict_dynamic_parser_inserted_doc_write_correct_nonce.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1299483 - CSP: Implement 'strict-dynamic'</title>
+</head>
+<body>
+<div id="testdiv">blocked</div>
+
+<script nonce="foo">
+ // generates a parser inserted script with a valid nonce- and should be allowed
+ document.write("<script nonce='foo' src='http://example.com/tests/dom/security/test/csp/file_strict_dynamic.js'><\/script>");
+</script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_strict_dynamic_script_events.html b/dom/security/test/csp/file_strict_dynamic_script_events.html
new file mode 100644
index 000000000..088958382
--- /dev/null
+++ b/dom/security/test/csp/file_strict_dynamic_script_events.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1316826 - 'strict-dynamic' blocking DOM event handlers</title>
+</head>
+<body>
+<div id="testdiv">blocked</div>
+
+ <img src='/nonexisting.jpg'
+ onerror='document.getElementById("testdiv").innerHTML = "allowed";'
+ style='display:none'>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_strict_dynamic_script_events_xbl.html b/dom/security/test/csp/file_strict_dynamic_script_events_xbl.html
new file mode 100644
index 000000000..701ef3226
--- /dev/null
+++ b/dom/security/test/csp/file_strict_dynamic_script_events_xbl.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1316826 - 'strict-dynamic' blocking DOM event handlers</title>
+</head>
+<body>
+<div id="testdiv">blocked</div>
+
+<marquee onstart='document.getElementById("testdiv").innerHTML = "allowed";'>
+ Bug 1316826
+</marquee>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_strict_dynamic_script_extern.html b/dom/security/test/csp/file_strict_dynamic_script_extern.html
new file mode 100644
index 000000000..94b6aefb1
--- /dev/null
+++ b/dom/security/test/csp/file_strict_dynamic_script_extern.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1299483 - CSP: Implement 'strict-dynamic'</title>
+</head>
+<body>
+<div id="testdiv">blocked</div>
+<script nonce="foo" src="http://example.com/tests/dom/security/test/csp/file_strict_dynamic.js"></script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_strict_dynamic_script_inline.html b/dom/security/test/csp/file_strict_dynamic_script_inline.html
new file mode 100644
index 000000000..d17a58f27
--- /dev/null
+++ b/dom/security/test/csp/file_strict_dynamic_script_inline.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1299483 - CSP: Implement 'strict-dynamic'</title>
+</head>
+<body>
+<div id="testdiv">blocked</div>
+
+<script nonce="foo">
+ document.getElementById("testdiv").innerHTML = "allowed";
+</script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_strict_dynamic_unsafe_eval.html b/dom/security/test/csp/file_strict_dynamic_unsafe_eval.html
new file mode 100644
index 000000000..f0b26da91
--- /dev/null
+++ b/dom/security/test/csp/file_strict_dynamic_unsafe_eval.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1299483 - CSP: Implement 'strict-dynamic'</title>
+</head>
+<body>
+<div id="testdiv">blocked</div>
+
+<script nonce="foo">
+ eval('document.getElementById("testdiv").innerHTML = "allowed";');
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/security/test/csp/file_subframe_run_js_if_allowed.html b/dom/security/test/csp/file_subframe_run_js_if_allowed.html
new file mode 100644
index 000000000..3ba970ce8
--- /dev/null
+++ b/dom/security/test/csp/file_subframe_run_js_if_allowed.html
@@ -0,0 +1,13 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=702439
+
+This document is a child frame of a CSP document and the
+test verifies that it is permitted to run javascript: URLs
+if the parent has a policy that allows them.
+-->
+<body onload="document.getElementById('a').click()">
+<a id="a" href="javascript:parent.javascript_link_ran = true;
+ parent.checkResult();">click</a>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_subframe_run_js_if_allowed.html^headers^ b/dom/security/test/csp/file_subframe_run_js_if_allowed.html^headers^
new file mode 100644
index 000000000..233b35931
--- /dev/null
+++ b/dom/security/test/csp/file_subframe_run_js_if_allowed.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src *; script-src 'unsafe-inline'
diff --git a/dom/security/test/csp/file_testserver.sjs b/dom/security/test/csp/file_testserver.sjs
new file mode 100644
index 000000000..ae1826611
--- /dev/null
+++ b/dom/security/test/csp/file_testserver.sjs
@@ -0,0 +1,57 @@
+// SJS file for CSP mochitests
+"use strict";
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+function loadHTMLFromFile(path) {
+ // Load the HTML to return in the response from file.
+ // Since it's relative to the cwd of the test runner, we start there and
+ // append to get to the actual path of the file.
+ const testHTMLFile =
+ Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("CurWorkD", Components.interfaces.nsILocalFile);
+
+ const testHTMLFileStream =
+ Components.classes["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Components.interfaces.nsIFileInputStream);
+
+ path
+ .split("/")
+ .filter(path => path)
+ .reduce((file, path) => {
+ testHTMLFile.append(path)
+ return testHTMLFile;
+ }, testHTMLFile);
+ testHTMLFileStream.init(testHTMLFile, -1, 0, 0);
+ const isAvailable = testHTMLFileStream.available();
+ return NetUtil.readInputStreamToString(testHTMLFileStream, isAvailable);
+}
+
+function handleRequest(request, response) {
+ const query = new URLSearchParams(request.queryString);
+
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // Deliver the CSP policy encoded in the URL
+ if(query.has("csp")){
+ response.setHeader("Content-Security-Policy", query.get("csp"), false);
+ }
+
+ // Deliver the CSP report-only policy encoded in the URI
+ if(query.has("cspRO")){
+ response.setHeader("Content-Security-Policy-Report-Only", query.get("cspRO"), false);
+ }
+
+ // Deliver the CORS header in the URL
+ if(query.has("cors")){
+ response.setHeader("Access-Control-Allow-Origin", query.get("cors"), false);
+ }
+
+ // Send HTML to test allowed/blocked behaviors
+ response.setHeader("Content-Type", "text/html", false);
+ if(query.has("file")){
+ response.write(loadHTMLFromFile(query.get("file")));
+ }
+}
diff --git a/dom/security/test/csp/file_upgrade_insecure.html b/dom/security/test/csp/file_upgrade_insecure.html
new file mode 100644
index 000000000..d104a3a24
--- /dev/null
+++ b/dom/security/test/csp/file_upgrade_insecure.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1139297 - Implement CSP upgrade-insecure-requests directive</title>
+ <!-- style -->
+ <link rel='stylesheet' type='text/css' href='http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?style' media='screen' />
+
+ <!-- font -->
+ <style>
+ @font-face {
+ font-family: "foofont";
+ src: url('http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?font');
+ }
+ .div_foo { font-family: "foofont"; }
+ </style>
+</head>
+<body>
+
+ <!-- images: -->
+ <img src="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?img"></img>
+
+ <!-- redirects: upgrade http:// to https:// redirect to http:// and then upgrade to https:// again -->
+ <img src="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?redirect-image"></img>
+
+ <!-- script: -->
+ <script src="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?script"></script>
+
+ <!-- media: -->
+ <audio src="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?media"></audio>
+
+ <!-- objects: -->
+ <object width="10" height="10" data="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?object"></object>
+
+ <!-- font: (apply font loaded in header to div) -->
+ <div class="div_foo">foo</div>
+
+ <!-- iframe: (same origin) -->
+ <iframe src="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?iframe">
+ <!-- within that iframe we load an image over http and make sure the requested gets upgraded to https -->
+ </iframe>
+
+ <!-- xhr: -->
+ <script type="application/javascript">
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?xhr");
+ myXHR.send(null);
+ </script>
+
+ <!-- websockets: upgrade ws:// to wss://-->
+ <script type="application/javascript">
+ var mySocket = new WebSocket("ws://example.com/tests/dom/security/test/csp/file_upgrade_insecure");
+ mySocket.onopen = function(e) {
+ if (mySocket.url.includes("wss://")) {
+ window.parent.postMessage({result: "websocket-ok"}, "*");
+ }
+ else {
+ window.parent.postMessage({result: "websocket-error"}, "*");
+ }
+ };
+ mySocket.onerror = function(e) {
+ window.parent.postMessage({result: "websocket-unexpected-error"}, "*");
+ };
+ </script>
+
+ <!-- form action: (upgrade POST from http:// to https://) -->
+ <iframe name='formFrame' id='formFrame'></iframe>
+ <form target="formFrame" action="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?form" method="POST">
+ <input name="foo" value="foo">
+ <input type="submit" id="submitButton" formenctype='multipart/form-data' value="Submit form">
+ </form>
+ <script type="text/javascript">
+ var submitButton = document.getElementById('submitButton');
+ submitButton.click();
+ </script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_upgrade_insecure_cors.html b/dom/security/test/csp/file_upgrade_insecure_cors.html
new file mode 100644
index 000000000..e675c62e9
--- /dev/null
+++ b/dom/security/test/csp/file_upgrade_insecure_cors.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1139297 - Implement CSP upgrade-insecure-requests directive</title>
+</head>
+<body>
+
+<script type="text/javascript">
+ // === TEST 1
+ var url1 = "http://test1.example.com/tests/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs?test1";
+ var xhr1 = new XMLHttpRequest();
+ xhr1.open("GET", url1, true);
+ xhr1.onload = function() {
+ window.parent.postMessage(xhr1.response, "*");
+ };
+ xhr1.onerror = function() {
+ window.parent.postMessage("test1-failed", "*");
+ };
+ xhr1.send();
+
+ // === TEST 2
+ var url2 = "http://test1.example.com/tests/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs?test2";
+ var xhr2 = new XMLHttpRequest();
+ xhr2.open("GET", url2, true);
+ xhr2.onload = function() {
+ window.parent.postMessage(xhr2.response, "*");
+ };
+ xhr2.onerror = function() {
+ window.parent.postMessage("test2-failed", "*");
+ };
+ xhr2.send();
+
+ // === TEST 3
+ var url3 = "http://test2.example.com/tests/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs?test3";
+ var xhr3 = new XMLHttpRequest();
+ xhr3.open("GET", url3, true);
+ xhr3.onload = function() {
+ window.parent.postMessage(xhr3.response, "*");
+ };
+ xhr3.onerror = function() {
+ window.parent.postMessage("test3-failed", "*");
+ };
+ xhr3.send();
+
+</script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs b/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs
new file mode 100644
index 000000000..33f6c3b23
--- /dev/null
+++ b/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs
@@ -0,0 +1,62 @@
+// Custom *.sjs file specifically for the needs of Bug:
+// Bug 1139297 - Implement CSP upgrade-insecure-requests directive
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // perform sanity check and make sure that all requests get upgraded to use https
+ if (request.scheme !== "https") {
+ response.write("request not https");
+ return;
+ }
+
+ var queryString = request.queryString;
+
+ // TEST 1
+ if (queryString === "test1") {
+ var newLocation =
+ "http://test1.example.com/tests/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs?redir-test1";
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", newLocation, false);
+ return;
+ }
+ if (queryString === "redir-test1") {
+ response.write("test1-no-cors-ok");
+ return;
+ }
+
+ // TEST 2
+ if (queryString === "test2") {
+ var newLocation =
+ "http://test1.example.com:443/tests/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs?redir-test2";
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", newLocation, false);
+ return;
+ }
+ if (queryString === "redir-test2") {
+ response.write("test2-no-cors-diffport-ok");
+ return;
+ }
+
+ // TEST 3
+ response.setHeader("Access-Control-Allow-Headers", "content-type", false);
+ response.setHeader("Access-Control-Allow-Methods", "POST, GET", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+
+ if (queryString === "test3") {
+ var newLocation =
+ "http://test1.example.com/tests/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs?redir-test3";
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", newLocation, false);
+ return;
+ }
+ if (queryString === "redir-test3") {
+ response.write("test3-cors-ok");
+ return;
+ }
+
+ // we should not get here, but just in case return something unexpected
+ response.write("d'oh");
+}
diff --git a/dom/security/test/csp/file_upgrade_insecure_docwrite_iframe.sjs b/dom/security/test/csp/file_upgrade_insecure_docwrite_iframe.sjs
new file mode 100644
index 000000000..6870a57bb
--- /dev/null
+++ b/dom/security/test/csp/file_upgrade_insecure_docwrite_iframe.sjs
@@ -0,0 +1,54 @@
+// custom *.sjs for Bug 1273430
+// META CSP: upgrade-insecure-requests
+
+// important: the IFRAME_URL is *http* and needs to be upgraded to *https* by upgrade-insecure-requests
+const IFRAME_URL =
+ "http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_docwrite_iframe.sjs?docwriteframe";
+
+const TEST_FRAME = `
+ <!DOCTYPE HTML>
+ <html><head><meta charset="utf-8">
+ <title>TEST_FRAME</title>
+ <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
+ </head>
+ <body>
+ <script type="text/javascript">
+ document.write('<iframe src="` + IFRAME_URL + `"/>');
+ </script>
+ </body>
+ </html>`;
+
+
+// doc.write(iframe) sends a post message to the parent indicating the current
+// location so the parent can make sure the request was upgraded to *https*.
+const DOC_WRITE_FRAME = `
+ <!DOCTYPE HTML>
+ <html><head><meta charset="utf-8">
+ <title>DOC_WRITE_FRAME</title>
+ </head>
+ <body onload="window.parent.parent.postMessage({result: document.location.href}, '*');">
+ </body>
+ </html>`;
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+
+ var queryString = request.queryString;
+
+ if (queryString === "testframe") {
+ response.write(TEST_FRAME);
+ return;
+ }
+
+ if (queryString === "docwriteframe") {
+ response.write(DOC_WRITE_FRAME);
+ return;
+ }
+
+ // we should never get here, but just in case
+ // return something unexpected
+ response.write("do'h");
+}
diff --git a/dom/security/test/csp/file_upgrade_insecure_meta.html b/dom/security/test/csp/file_upgrade_insecure_meta.html
new file mode 100644
index 000000000..5f65e78ec
--- /dev/null
+++ b/dom/security/test/csp/file_upgrade_insecure_meta.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests; default-src https: wss: 'unsafe-inline'; form-action https:;">
+ <meta charset="utf-8">
+ <title>Bug 1139297 - Implement CSP upgrade-insecure-requests directive</title>
+ <!-- style -->
+ <link rel='stylesheet' type='text/css' href='http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?style' media='screen' />
+
+ <!-- font -->
+ <style>
+ @font-face {
+ font-family: "foofont";
+ src: url('http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?font');
+ }
+ .div_foo { font-family: "foofont"; }
+ </style>
+</head>
+<body>
+
+ <!-- images: -->
+ <img src="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?img"></img>
+
+ <!-- redirects: upgrade http:// to https:// redirect to http:// and then upgrade to https:// again -->
+ <img src="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?redirect-image"></img>
+
+ <!-- script: -->
+ <script src="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?script"></script>
+
+ <!-- media: -->
+ <audio src="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?media"></audio>
+
+ <!-- objects: -->
+ <object width="10" height="10" data="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?object"></object>
+
+ <!-- font: (apply font loaded in header to div) -->
+ <div class="div_foo">foo</div>
+
+ <!-- iframe: (same origin) -->
+ <iframe src="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?iframe">
+ <!-- within that iframe we load an image over http and make sure the requested gets upgraded to https -->
+ </iframe>
+
+ <!-- xhr: -->
+ <script type="application/javascript">
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?xhr");
+ myXHR.send(null);
+ </script>
+
+ <!-- websockets: upgrade ws:// to wss://-->
+ <script type="application/javascript">
+ var mySocket = new WebSocket("ws://example.com/tests/dom/security/test/csp/file_upgrade_insecure");
+ mySocket.onopen = function(e) {
+ if (mySocket.url.includes("wss://")) {
+ window.parent.postMessage({result: "websocket-ok"}, "*");
+ }
+ else {
+ window.parent.postMessage({result: "websocket-error"}, "*");
+ }
+ };
+ mySocket.onerror = function(e) {
+ window.parent.postMessage({result: "websocket-unexpected-error"}, "*");
+ };
+ </script>
+
+ <!-- form action: (upgrade POST from http:// to https://) -->
+ <iframe name='formFrame' id='formFrame'></iframe>
+ <form target="formFrame" action="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?form" method="POST">
+ <input name="foo" value="foo">
+ <input type="submit" id="submitButton" formenctype='multipart/form-data' value="Submit form">
+ </form>
+ <script type="text/javascript">
+ var submitButton = document.getElementById('submitButton');
+ submitButton.click();
+ </script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_upgrade_insecure_referrer.sjs b/dom/security/test/csp/file_upgrade_insecure_referrer.sjs
new file mode 100644
index 000000000..e149afa4b
--- /dev/null
+++ b/dom/security/test/csp/file_upgrade_insecure_referrer.sjs
@@ -0,0 +1,55 @@
+// special *.sjs specifically customized for the needs of
+// Bug 1139297 and Bug 663570
+
+const PRE_HEAD =
+ "<!DOCTYPE HTML>" +
+ "<html>" +
+ "<head>";
+
+ const POST_HEAD =
+ "<meta charset='utf-8'>" +
+ "<title>Bug 1139297 - Implement CSP upgrade-insecure-requests directive</title>" +
+ "</head>" +
+ "<body>" +
+ "<img id='testimage' src='http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_referrer_server.sjs?img'></img>" +
+ "</body>" +
+ "</html>";
+
+const PRE_CSP = "upgrade-insecure-requests; default-src https:; ";
+const CSP_REFERRER_ORIGIN = "referrer origin";
+const CSP_REFEFFER_NO_REFERRER = "referrer no-referrer";
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ var queryString = request.queryString;
+
+ if (queryString === "test1") {
+ response.setHeader("Content-Security-Policy", PRE_CSP + CSP_REFERRER_ORIGIN, false);
+ response.write(PRE_HEAD + POST_HEAD);
+ return;
+ }
+
+ if (queryString === "test2") {
+ response.setHeader("Content-Security-Policy", PRE_CSP + CSP_REFEFFER_NO_REFERRER, false);
+ response.write(PRE_HEAD + POST_HEAD);
+ return;
+ }
+
+ if (queryString === "test3") {
+ var metacsp = "<meta http-equiv=\"Content-Security-Policy\" content = \"" + PRE_CSP + CSP_REFERRER_ORIGIN + "\" >";
+ response.write(PRE_HEAD + metacsp + POST_HEAD);
+ return;
+ }
+
+ if (queryString === "test4") {
+ var metacsp = "<meta http-equiv=\"Content-Security-Policy\" content = \"" + PRE_CSP + CSP_REFEFFER_NO_REFERRER + "\" >";
+ response.write(PRE_HEAD + metacsp + POST_HEAD);
+ return;
+ }
+
+ // we should never get here, but just in case return
+ // something unexpected
+ response.write("do'h");
+}
diff --git a/dom/security/test/csp/file_upgrade_insecure_referrer_server.sjs b/dom/security/test/csp/file_upgrade_insecure_referrer_server.sjs
new file mode 100644
index 000000000..be1e6da0c
--- /dev/null
+++ b/dom/security/test/csp/file_upgrade_insecure_referrer_server.sjs
@@ -0,0 +1,56 @@
+// Custom *.sjs file specifically for the needs of Bug:
+// Bug 1139297 - Implement CSP upgrade-insecure-requests directive
+
+// small red image
+const IMG_BYTES = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+ "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ var queryString = request.queryString;
+
+ // (1) lets process the queryresult request async and
+ // wait till we have received the image request.
+ if (queryString == "queryresult") {
+ response.processAsync();
+ setObjectState("queryResult", response);
+ return;
+ }
+
+ // (2) Handle the image request and return the referrer
+ // result back to the stored queryresult request.
+ if (request.queryString == "img") {
+ response.setHeader("Content-Type", "image/png");
+ response.write(IMG_BYTES);
+
+ let referrer = "";
+ try {
+ referrer = request.getHeader("referer");
+ } catch (e) {
+ referrer = "";
+ }
+ // make sure the received image request was upgraded to https,
+ // otherwise we return not only the referrer but also indicate
+ // that the request was not upgraded to https. Note, that
+ // all upgrades happen in the browser before any non-secure
+ // request hits the wire.
+ referrer += (request.scheme == "https") ?
+ "" : " but request is not https";
+
+ getObjectState("queryResult", function(queryResponse) {
+ if (!queryResponse) {
+ return;
+ }
+ queryResponse.write(referrer);
+ queryResponse.finish();
+ });
+ return;
+ }
+
+ // we should not get here ever, but just in case return
+ // something unexpected.
+ response.write("doh!");
+}
diff --git a/dom/security/test/csp/file_upgrade_insecure_reporting.html b/dom/security/test/csp/file_upgrade_insecure_reporting.html
new file mode 100644
index 000000000..c78e9a784
--- /dev/null
+++ b/dom/security/test/csp/file_upgrade_insecure_reporting.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1139297 - Implement CSP upgrade-insecure-requests directive</title>
+</head>
+<body>
+
+ <!-- upgrade img from http:// to https:// -->
+ <img id="testimage" src="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_reporting_server.sjs?img"></img>
+
+ <script type="application/javascript">
+ var myImg = document.getElementById("testimage");
+ myImg.onload = function(e) {
+ window.parent.postMessage({result: "img-ok"}, "*");
+ };
+ myImg.onerror = function(e) {
+ window.parent.postMessage({result: "img-error"}, "*");
+ };
+ </script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/file_upgrade_insecure_reporting_server.sjs b/dom/security/test/csp/file_upgrade_insecure_reporting_server.sjs
new file mode 100644
index 000000000..b9940a7fd
--- /dev/null
+++ b/dom/security/test/csp/file_upgrade_insecure_reporting_server.sjs
@@ -0,0 +1,80 @@
+// Custom *.sjs specifically for the needs of Bug
+// Bug 1139297 - Implement CSP upgrade-insecure-requests directive
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+// small red image
+const IMG_BYTES = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+ "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
+
+const REPORT_URI = "https://example.com/tests/dom/security/test/csp/file_upgrade_insecure_reporting_server.sjs?report";
+const POLICY = "upgrade-insecure-requests; default-src https: 'unsafe-inline'";
+const POLICY_RO = "default-src https: 'unsafe-inline'; report-uri " + REPORT_URI;
+
+function loadHTMLFromFile(path) {
+ // Load the HTML to return in the response from file.
+ // Since it's relative to the cwd of the test runner, we start there and
+ // append to get to the actual path of the file.
+ var testHTMLFile =
+ Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("CurWorkD", Components.interfaces.nsILocalFile);
+ var dirs = path.split("/");
+ for (var i = 0; i < dirs.length; i++) {
+ testHTMLFile.append(dirs[i]);
+ }
+ var testHTMLFileStream =
+ Components.classes["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ testHTMLFileStream.init(testHTMLFile, -1, 0, 0);
+ var testHTML = NetUtil.readInputStreamToString(testHTMLFileStream, testHTMLFileStream.available());
+ return testHTML;
+}
+
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // (1) Store the query that will report back whether the violation report was received
+ if (request.queryString == "queryresult") {
+ response.processAsync();
+ setObjectState("queryResult", response);
+ return;
+ }
+
+ // (2) We load a page using a CSP and a report only CSP
+ if (request.queryString == "toplevel") {
+ response.setHeader("Content-Security-Policy", POLICY, false);
+ response.setHeader("Content-Security-Policy-Report-Only", POLICY_RO, false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(loadHTMLFromFile("tests/dom/security/test/csp/file_upgrade_insecure_reporting.html"));
+ return;
+ }
+
+ // (3) Return the image back to the client
+ if (request.queryString == "img") {
+ response.setHeader("Content-Type", "image/png");
+ response.write(IMG_BYTES);
+ return;
+ }
+
+ // (4) Finally we receive the report, let's return the request from (1)
+ // signaling that we received the report correctly
+ if (request.queryString == "report") {
+ getObjectState("queryResult", function(queryResponse) {
+ if (!queryResponse) {
+ return;
+ }
+ queryResponse.write("report-ok");
+ queryResponse.finish();
+ });
+ return;
+ }
+
+ // we should never get here, but just in case ...
+ response.setHeader("Content-Type", "text/plain");
+ response.write("doh!");
+}
diff --git a/dom/security/test/csp/file_upgrade_insecure_server.sjs b/dom/security/test/csp/file_upgrade_insecure_server.sjs
new file mode 100644
index 000000000..e27b97830
--- /dev/null
+++ b/dom/security/test/csp/file_upgrade_insecure_server.sjs
@@ -0,0 +1,102 @@
+// Custom *.sjs file specifically for the needs of Bug:
+// Bug 1139297 - Implement CSP upgrade-insecure-requests directive
+
+const TOTAL_EXPECTED_REQUESTS = 11;
+
+const IFRAME_CONTENT =
+ "<!DOCTYPE HTML>" +
+ "<html>" +
+ "<head><meta charset='utf-8'>" +
+ "<title>Bug 1139297 - Implement CSP upgrade-insecure-requests directive</title>" +
+ "</head>" +
+ "<body>" +
+ "<img src='http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?nested-img'></img>" +
+ "</body>" +
+ "</html>";
+
+const expectedQueries = [ "script", "style", "img", "iframe", "form", "xhr",
+ "media", "object", "font", "img-redir", "nested-img"];
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ var queryString = request.queryString;
+
+ // initialize server variables and save the object state
+ // of the initial request, which returns async once the
+ // server has processed all requests.
+ if (queryString == "queryresult") {
+ setState("totaltests", TOTAL_EXPECTED_REQUESTS.toString());
+ setState("receivedQueries", "");
+ response.processAsync();
+ setObjectState("queryResult", response);
+ return;
+ }
+
+ // handle img redirect (https->http)
+ if (queryString == "redirect-image") {
+ var newLocation =
+ "http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?img-redir";
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", newLocation, false);
+ return;
+ }
+
+ // just in case error handling for unexpected queries
+ if (expectedQueries.indexOf(queryString) == -1) {
+ response.write("doh!");
+ return;
+ }
+
+ // make sure all the requested queries are indeed https
+ queryString += (request.scheme == "https") ? "-ok" : "-error";
+
+ var receivedQueries = getState("receivedQueries");
+
+ // images, scripts, etc. get queried twice, do not
+ // confuse the server by storing the preload as
+ // well as the actual load. If either the preload
+ // or the actual load is not https, then we would
+ // append "-error" in the array and the test would
+ // fail at the end.
+ if (receivedQueries.includes(queryString)) {
+ return;
+ }
+
+ // append the result to the total query string array
+ if (receivedQueries != "") {
+ receivedQueries += ",";
+ }
+ receivedQueries += queryString;
+ setState("receivedQueries", receivedQueries);
+
+ // keep track of how many more requests the server
+ // is expecting
+ var totaltests = parseInt(getState("totaltests"));
+ totaltests -= 1;
+ setState("totaltests", totaltests.toString());
+
+ // return content (img) for the nested iframe to test
+ // that subresource requests within nested contexts
+ // get upgraded as well. We also have to return
+ // the iframe context in case of an error so we
+ // can test both, using upgrade-insecure as well
+ // as the base case of not using upgrade-insecure.
+ if ((queryString == "iframe-ok") || (queryString == "iframe-error")) {
+ response.write(IFRAME_CONTENT);
+ }
+
+ // if we have received all the requests, we return
+ // the result back.
+ if (totaltests == 0) {
+ getObjectState("queryResult", function(queryResponse) {
+ if (!queryResponse) {
+ return;
+ }
+ var receivedQueries = getState("receivedQueries");
+ queryResponse.write(receivedQueries);
+ queryResponse.finish();
+ });
+ }
+}
diff --git a/dom/security/test/csp/file_upgrade_insecure_wsh.py b/dom/security/test/csp/file_upgrade_insecure_wsh.py
new file mode 100644
index 000000000..0ae161bff
--- /dev/null
+++ b/dom/security/test/csp/file_upgrade_insecure_wsh.py
@@ -0,0 +1,7 @@
+from mod_pywebsocket import msgutil
+
+def web_socket_do_extra_handshake(request):
+ pass
+
+def web_socket_transfer_data(request):
+ pass
diff --git a/dom/security/test/csp/file_web_manifest.html b/dom/security/test/csp/file_web_manifest.html
new file mode 100644
index 000000000..0f6a67460
--- /dev/null
+++ b/dom/security/test/csp/file_web_manifest.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<meta charset=utf-8>
+<head>
+<link rel="manifest" href="file_web_manifest.json">
+</head>
+<h1>Support Page for Web Manifest Tests</h1> \ No newline at end of file
diff --git a/dom/security/test/csp/file_web_manifest.json b/dom/security/test/csp/file_web_manifest.json
new file mode 100644
index 000000000..917ab73ef
--- /dev/null
+++ b/dom/security/test/csp/file_web_manifest.json
@@ -0,0 +1 @@
+{"name": "loaded"} \ No newline at end of file
diff --git a/dom/security/test/csp/file_web_manifest.json^headers^ b/dom/security/test/csp/file_web_manifest.json^headers^
new file mode 100644
index 000000000..e0e00c4be
--- /dev/null
+++ b/dom/security/test/csp/file_web_manifest.json^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: http://example.org \ No newline at end of file
diff --git a/dom/security/test/csp/file_web_manifest_https.html b/dom/security/test/csp/file_web_manifest_https.html
new file mode 100644
index 000000000..b0ff9ef85
--- /dev/null
+++ b/dom/security/test/csp/file_web_manifest_https.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<meta charset=utf-8>
+<link rel="manifest" href="https://example.com:443/tests/dom/security/test/csp/file_web_manifest_https.json">
+<h1>Support Page for Web Manifest Tests</h1> \ No newline at end of file
diff --git a/dom/security/test/csp/file_web_manifest_https.json b/dom/security/test/csp/file_web_manifest_https.json
new file mode 100644
index 000000000..917ab73ef
--- /dev/null
+++ b/dom/security/test/csp/file_web_manifest_https.json
@@ -0,0 +1 @@
+{"name": "loaded"} \ No newline at end of file
diff --git a/dom/security/test/csp/file_web_manifest_mixed_content.html b/dom/security/test/csp/file_web_manifest_mixed_content.html
new file mode 100644
index 000000000..55f17c0f9
--- /dev/null
+++ b/dom/security/test/csp/file_web_manifest_mixed_content.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<meta charset=utf-8>
+<head>
+<link
+ rel="manifest"
+ href="http://example.org/tests/dom/security/test/csp/file_testserver.sjs?file=/test/dom/security/test/csp/file_web_manifest.json&amp;cors=*">
+</head>
+<h1>Support Page for Web Manifest Tests</h1>
+<p>Used to try to load a resource over an insecure connection to trigger mixed content blocking.</p> \ No newline at end of file
diff --git a/dom/security/test/csp/file_web_manifest_remote.html b/dom/security/test/csp/file_web_manifest_remote.html
new file mode 100644
index 000000000..7ecf8eec4
--- /dev/null
+++ b/dom/security/test/csp/file_web_manifest_remote.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset=utf-8>
+<link rel="manifest"
+ crossorigin
+ href="//mochi.test:8888/tests/dom/security/test/csp/file_testserver.sjs?file=/tests/dom/security/test/csp/file_web_manifest.json&amp;cors=*">
+
+<h1>Support Page for Web Manifest Tests</h1>
+<p>Loads a manifest from mochi.test:8888 with CORS set to "*".</p> \ No newline at end of file
diff --git a/dom/security/test/csp/mochitest.ini b/dom/security/test/csp/mochitest.ini
new file mode 100644
index 000000000..8add999c3
--- /dev/null
+++ b/dom/security/test/csp/mochitest.ini
@@ -0,0 +1,300 @@
+[DEFAULT]
+support-files =
+ file_base_uri_server.sjs
+ file_blob_data_schemes.html
+ file_connect-src.html
+ file_connect-src-fetch.html
+ file_CSP.css
+ file_CSP.sjs
+ file_allow_https_schemes.html
+ file_bug663567.xsl
+ file_bug663567_allows.xml
+ file_bug663567_allows.xml^headers^
+ file_bug663567_blocks.xml
+ file_bug663567_blocks.xml^headers^
+ file_bug802872.html
+ file_bug802872.html^headers^
+ file_bug802872.js
+ file_bug802872.sjs
+ file_bug885433_allows.html
+ file_bug885433_allows.html^headers^
+ file_bug885433_blocks.html
+ file_bug885433_blocks.html^headers^
+ file_bug888172.html
+ file_bug888172.sjs
+ file_evalscript_main.js
+ file_evalscript_main_allowed.js
+ file_evalscript_main.html
+ file_evalscript_main.html^headers^
+ file_evalscript_main_allowed.html
+ file_evalscript_main_allowed.html^headers^
+ file_frameancestors_main.html
+ file_frameancestors_main.js
+ file_frameancestors.sjs
+ file_inlinescript.html
+ file_inlinestyle_main.html
+ file_inlinestyle_main.html^headers^
+ file_inlinestyle_main_allowed.html
+ file_inlinestyle_main_allowed.html^headers^
+ file_invalid_source_expression.html
+ file_main.html
+ file_main.html^headers^
+ file_main.js
+ file_main_worker.js
+ file_main_worker.js^headers^
+ file_child_worker.js
+ file_child_worker.js^headers^
+ file_web_manifest.html
+ file_web_manifest_remote.html
+ file_web_manifest_https.html
+ file_web_manifest.json
+ file_web_manifest.json^headers^
+ file_web_manifest_https.json
+ file_web_manifest_mixed_content.html
+ file_bug836922_npolicies.html
+ file_bug836922_npolicies.html^headers^
+ file_bug836922_npolicies_ro_violation.sjs
+ file_bug836922_npolicies_violation.sjs
+ file_bug886164.html
+ file_bug886164.html^headers^
+ file_bug886164_2.html
+ file_bug886164_2.html^headers^
+ file_bug886164_3.html
+ file_bug886164_3.html^headers^
+ file_bug886164_4.html
+ file_bug886164_4.html^headers^
+ file_bug886164_5.html
+ file_bug886164_5.html^headers^
+ file_bug886164_6.html
+ file_bug886164_6.html^headers^
+ file_redirects_main.html
+ file_redirects_page.sjs
+ file_redirects_resource.sjs
+ file_bug910139.sjs
+ file_bug910139.xml
+ file_bug910139.xsl
+ file_bug909029_star.html
+ file_bug909029_star.html^headers^
+ file_bug909029_none.html
+ file_bug909029_none.html^headers^
+ file_bug1229639.html
+ file_bug1229639.html^headers^
+ file_bug1312272.html
+ file_bug1312272.js
+ file_bug1312272.html^headers^
+ file_policyuri_regression_from_multipolicy.html
+ file_policyuri_regression_from_multipolicy.html^headers^
+ file_policyuri_regression_from_multipolicy_policy
+ file_shouldprocess.html
+ file_nonce_source.html
+ file_nonce_source.html^headers^
+ file_bug941404.html
+ file_bug941404_xhr.html
+ file_bug941404_xhr.html^headers^
+ file_hash_source.html
+ file_dual_header_testserver.sjs
+ file_hash_source.html^headers^
+ file_scheme_relative_sources.js
+ file_scheme_relative_sources.sjs
+ file_ignore_unsafe_inline.html
+ file_ignore_unsafe_inline_multiple_policies_server.sjs
+ file_self_none_as_hostname_confusion.html
+ file_self_none_as_hostname_confusion.html^headers^
+ file_path_matching.html
+ file_path_matching_incl_query.html
+ file_path_matching.js
+ file_path_matching_redirect.html
+ file_path_matching_redirect_server.sjs
+ file_testserver.sjs
+ file_report_uri_missing_in_report_only_header.html
+ file_report_uri_missing_in_report_only_header.html^headers^
+ file_report.html
+ file_report_chromescript.js
+ file_redirect_content.sjs
+ file_redirect_report.sjs
+ file_subframe_run_js_if_allowed.html
+ file_subframe_run_js_if_allowed.html^headers^
+ file_leading_wildcard.html
+ file_multi_policy_injection_bypass.html
+ file_multi_policy_injection_bypass.html^headers^
+ file_multi_policy_injection_bypass_2.html
+ file_multi_policy_injection_bypass_2.html^headers^
+ file_null_baseuri.html
+ file_form-action.html
+ file_referrerdirective.html
+ referrerdirective.sjs
+ file_upgrade_insecure.html
+ file_upgrade_insecure_meta.html
+ file_upgrade_insecure_server.sjs
+ file_upgrade_insecure_wsh.py
+ file_upgrade_insecure_reporting.html
+ file_upgrade_insecure_reporting_server.sjs
+ file_upgrade_insecure_referrer.sjs
+ file_upgrade_insecure_referrer_server.sjs
+ file_upgrade_insecure_cors.html
+ file_upgrade_insecure_cors_server.sjs
+ file_report_for_import.css
+ file_report_for_import.html
+ file_report_for_import_server.sjs
+ file_service_worker.html
+ file_service_worker.js
+ file_child-src_iframe.html
+ file_child-src_inner_frame.html
+ file_child-src_worker.html
+ file_child-src_worker_data.html
+ file_child-src_worker-redirect.html
+ file_child-src_worker.js
+ file_child-src_service_worker.html
+ file_child-src_service_worker.js
+ file_child-src_shared_worker.html
+ file_child-src_shared_worker_data.html
+ file_child-src_shared_worker-redirect.html
+ file_child-src_shared_worker.js
+ file_redirect_worker.sjs
+ file_meta_element.html
+ file_meta_header_dual.sjs
+ file_docwrite_meta.html
+ file_doccomment_meta.html
+ file_docwrite_meta.css
+ file_docwrite_meta.js
+ file_multipart_testserver.sjs
+ file_fontloader.sjs
+ file_fontloader.woff
+ file_block_all_mcb.sjs
+ file_block_all_mixed_content_frame_navigation1.html
+ file_block_all_mixed_content_frame_navigation2.html
+ file_form_action_server.sjs
+ !/image/test/mochitest/blue.png
+ file_meta_whitespace_skipping.html
+ file_ping.html
+ test_iframe_sandbox_top_1.html^headers^
+ file_iframe_sandbox_document_write.html
+ file_sandbox_pass.js
+ file_sandbox_fail.js
+ file_sandbox_1.html
+ file_sandbox_2.html
+ file_sandbox_3.html
+ file_sandbox_4.html
+ file_sandbox_5.html
+ file_sandbox_6.html
+ file_sandbox_7.html
+ file_sandbox_8.html
+ file_sandbox_9.html
+ file_sandbox_10.html
+ file_sandbox_11.html
+ file_sandbox_12.html
+ file_sandbox_13.html
+ file_require_sri_meta.sjs
+ file_require_sri_meta.js
+ file_sendbeacon.html
+ file_upgrade_insecure_docwrite_iframe.sjs
+ file_data-uri_blocked.html
+ file_data-uri_blocked.html^headers^
+ file_strict_dynamic_js_url.html
+ file_strict_dynamic_script_events.html
+ file_strict_dynamic_script_events_xbl.html
+ file_strict_dynamic_script_inline.html
+ file_strict_dynamic_script_extern.html
+ file_strict_dynamic.js
+ file_strict_dynamic_parser_inserted_doc_write.html
+ file_strict_dynamic_parser_inserted_doc_write_correct_nonce.html
+ file_strict_dynamic_non_parser_inserted.html
+ file_strict_dynamic_non_parser_inserted_inline.html
+ file_strict_dynamic_unsafe_eval.html
+ file_strict_dynamic_default_src.html
+ file_strict_dynamic_default_src.js
+ file_iframe_srcdoc.sjs
+ file_iframe_sandbox_srcdoc.html
+ file_iframe_sandbox_srcdoc.html^headers^
+
+[test_base-uri.html]
+[test_blob_data_schemes.html]
+[test_connect-src.html]
+[test_CSP.html]
+[test_allow_https_schemes.html]
+[test_bug663567.html]
+[test_bug802872.html]
+[test_bug885433.html]
+[test_bug888172.html]
+[test_evalscript.html]
+[test_frameancestors.html]
+skip-if = toolkit == 'android' # Times out, not sure why (bug 1008445)
+[test_inlinescript.html]
+[test_inlinestyle.html]
+[test_invalid_source_expression.html]
+[test_bug836922_npolicies.html]
+[test_bug886164.html]
+[test_redirects.html]
+[test_bug910139.html]
+[test_bug909029.html]
+[test_bug1229639.html]
+[test_policyuri_regression_from_multipolicy.html]
+[test_nonce_source.html]
+[test_bug941404.html]
+[test_form-action.html]
+[test_hash_source.html]
+[test_scheme_relative_sources.html]
+[test_ignore_unsafe_inline.html]
+[test_self_none_as_hostname_confusion.html]
+[test_path_matching.html]
+[test_path_matching_redirect.html]
+[test_report_uri_missing_in_report_only_header.html]
+[test_report.html]
+[test_301_redirect.html]
+[test_302_redirect.html]
+[test_303_redirect.html]
+[test_307_redirect.html]
+[test_subframe_run_js_if_allowed.html]
+[test_leading_wildcard.html]
+[test_multi_policy_injection_bypass.html]
+[test_null_baseuri.html]
+[test_referrerdirective.html]
+[test_dual_header.html]
+[test_upgrade_insecure.html]
+# no ssl support as well as websocket tests do not work (see test_websocket.html)
+skip-if = toolkit == 'android' || (os != 'linux' && !debug) # Bug 1316305, Bug 1183300
+[test_upgrade_insecure_reporting.html]
+skip-if = toolkit == 'android'
+[test_upgrade_insecure_referrer.html]
+skip-if = toolkit == 'android'
+[test_upgrade_insecure_cors.html]
+skip-if = toolkit == 'android'
+[test_report_for_import.html]
+[test_blocked_uri_in_reports.html]
+[test_service_worker.html]
+[test_child-src_worker.html]
+[test_shouldprocess.html]
+# Fennec platform does not support Java applet plugin
+skip-if = toolkit == 'android' #investigate in bug 1250814
+[test_child-src_worker_data.html]
+[test_child-src_worker-redirect.html]
+[test_child-src_iframe.html]
+[test_meta_element.html]
+[test_meta_header_dual.html]
+[test_docwrite_meta.html]
+[test_multipartchannel.html]
+[test_fontloader.html]
+[test_block_all_mixed_content.html]
+tags = mcb
+[test_block_all_mixed_content_frame_navigation.html]
+tags = mcb
+[test_form_action_blocks_url.html]
+[test_meta_whitespace_skipping.html]
+[test_iframe_sandbox.html]
+[test_iframe_sandbox_top_1.html]
+[test_sandbox.html]
+[test_ping.html]
+[test_require_sri_meta.html]
+[test_sendbeacon.html]
+[test_upgrade_insecure_docwrite_iframe.html]
+[test_bug1242019.html]
+[test_bug1312272.html]
+[test_strict_dynamic.html]
+[test_strict_dynamic_parser_inserted.html]
+[test_strict_dynamic_default_src.html]
+[test_iframe_sandbox_srcdoc.html]
+[test_iframe_srcdoc.html]
+[test_sandbox_allow_scripts.html]
+support-files =
+ file_sandbox_allow_scripts.html
+ file_sandbox_allow_scripts.html^headers^
diff --git a/dom/security/test/csp/referrerdirective.sjs b/dom/security/test/csp/referrerdirective.sjs
new file mode 100644
index 000000000..f238ab452
--- /dev/null
+++ b/dom/security/test/csp/referrerdirective.sjs
@@ -0,0 +1,36 @@
+// Used for bug 965727 to serve up really simple scripts reflecting the
+// referrer sent to load this back to the loader.
+
+
+function handleRequest(request, response) {
+ // skip speculative loads.
+
+ var splits = request.queryString.split('&');
+ var params = {};
+ splits.forEach(function(v) {
+ let parts = v.split('=');
+ params[parts[0]] = unescape(parts[1]);
+ });
+
+ var loadType = params['type'];
+ var referrerLevel = 'error';
+
+ if (request.hasHeader('Referer')) {
+ var referrer = request.getHeader('Referer');
+ if (referrer.indexOf("file_testserver.sjs") > -1) {
+ referrerLevel = "full";
+ } else {
+ referrerLevel = "origin";
+ }
+ } else {
+ referrerLevel = 'none';
+ }
+
+ var theScript = 'window.postResult("' + loadType + '", "' + referrerLevel + '");';
+ response.setHeader('Content-Type', 'application/javascript; charset=utf-8', false);
+ response.setHeader('Cache-Control', 'no-cache', false);
+
+ if (request.method != "OPTIONS") {
+ response.write(theScript);
+ }
+}
diff --git a/dom/security/test/csp/test_301_redirect.html b/dom/security/test/csp/test_301_redirect.html
new file mode 100644
index 000000000..8b625a401
--- /dev/null
+++ b/dom/security/test/csp/test_301_redirect.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=650386
+Test that CSP violation reports are not sent when a 301 redirect is encountered
+-->
+<head>
+ <title>Test for Bug 650386</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=650386">Mozilla Bug 650386</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id = "content_iframe"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 650386 **/
+
+// This is used to watch the redirect of the report POST get blocked
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ if (topic === "specialpowers-http-notify-request") {
+ // this is used to fail the test - if we see the POST to the target of the redirect
+ // we know this is a fail
+ var uri = data;
+ if (uri == "http://example.com/some/fake/path")
+ window.done(false);
+ }
+
+ if(topic === "csp-on-violate-policy") {
+ // something was blocked, but we are looking specifically for the redirect being blocked
+ if (data == "denied redirect while sending violation report")
+ window.done(true);
+ }
+ },
+
+ // must eventually call this to remove the listener,
+ // or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+
+window.examiner = new examiner();
+
+// result == true if we saw the redirect blocked notify, false if we saw the post
+// to the redirect target go out
+window.done = function(result) {
+ ok(result, "a 301 redirect when posting violation report should be blocked");
+
+ // clean up observers and finish the test
+ window.examiner.remove();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('content_iframe').src = 'file_redirect_content.sjs?301';
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_302_redirect.html b/dom/security/test/csp/test_302_redirect.html
new file mode 100644
index 000000000..616ecd9eb
--- /dev/null
+++ b/dom/security/test/csp/test_302_redirect.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=650386
+Test that CSP violation reports are not sent when a 302 redirect is encountered
+-->
+<head>
+ <title>Test for Bug 650386</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=650386">Mozilla Bug 650386</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id = "content_iframe"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 650386 **/
+
+// This is used to watch the redirect of the report POST get blocked
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ if (topic === "specialpowers-http-notify-request") {
+ // this is used to fail the test - if we see the POST to the target of the redirect
+ // we know this is a fail
+ var uri = data;
+ if (uri == "http://example.com/some/fake/path")
+ window.done(false);
+ }
+
+ if(topic === "csp-on-violate-policy") {
+ // something was blocked, but we are looking specifically for the redirect being blocked
+ if (data == "denied redirect while sending violation report")
+ window.done(true);
+ }
+ },
+
+ // must eventually call this to remove the listener,
+ // or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+
+window.examiner = new examiner();
+
+// result == true if we saw the redirect blocked notify, false if we saw the post
+// to the redirect target go out
+window.done = function(result) {
+ ok(result, "a 302 redirect when posting violation report should be blocked");
+
+ // clean up observers and finish the test
+ window.examiner.remove();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('content_iframe').src = 'file_redirect_content.sjs?302';
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_303_redirect.html b/dom/security/test/csp/test_303_redirect.html
new file mode 100644
index 000000000..9e59a18f6
--- /dev/null
+++ b/dom/security/test/csp/test_303_redirect.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=650386
+Test that CSP violation reports are not sent when a 303 redirect is encountered
+-->
+<head>
+ <title>Test for Bug 650386</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=650386">Mozilla Bug 650386</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id = "content_iframe"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 650386 **/
+
+// This is used to watch the redirect of the report POST get blocked
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ if (topic === "specialpowers-http-notify-request") {
+ // this is used to fail the test - if we see the POST to the target of the redirect
+ // we know this is a fail
+ var uri = data;
+ if (uri == "http://example.com/some/fake/path")
+ window.done(false);
+ }
+
+ if(topic === "csp-on-violate-policy") {
+ // something was blocked, but we are looking specifically for the redirect being blocked
+ if (data == "denied redirect while sending violation report")
+ window.done(true);
+ }
+ },
+
+ // must eventually call this to remove the listener,
+ // or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+
+window.examiner = new examiner();
+
+// result == true if we saw the redirect blocked notify, false if we saw the post
+// to the redirect target go out
+window.done = function(result) {
+ ok(result, "a 303 redirect when posting violation report should be blocked");
+
+ // clean up observers and finish the test
+ window.examiner.remove();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('content_iframe').src = 'file_redirect_content.sjs?303';
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_307_redirect.html b/dom/security/test/csp/test_307_redirect.html
new file mode 100644
index 000000000..5e30b1b86
--- /dev/null
+++ b/dom/security/test/csp/test_307_redirect.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=650386
+Test that CSP violation reports are not sent when a 307 redirect is encountered
+-->
+<head>
+ <title>Test for Bug 650386</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=650386">Mozilla Bug 650386</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id = "content_iframe"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 650386 **/
+
+// This is used to watch the redirect of the report POST get blocked
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ if (topic === "specialpowers-http-notify-request") {
+ // this is used to fail the test - if we see the POST to the target of the redirect
+ // we know this is a fail
+ var uri = data;
+ if (uri == "http://example.com/some/fake/path")
+ window.done(false);
+ }
+
+ if(topic === "csp-on-violate-policy") {
+ // something was blocked, but we are looking specifically for the redirect being blocked
+ if (data == "denied redirect while sending violation report")
+ window.done(true);
+ }
+ },
+
+ // must eventually call this to remove the listener,
+ // or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+
+window.examiner = new examiner();
+
+// result == true if we saw the redirect blocked notify, false if we saw the post
+// to the redirect target go out
+window.done = function(result) {
+ ok(result, "a 307 redirect when posting violation report should be blocked");
+
+ // clean up observers and finish the test
+ window.examiner.remove();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('content_iframe').src = 'file_redirect_content.sjs?307';
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_CSP.html b/dom/security/test/csp/test_CSP.html
new file mode 100644
index 000000000..1cde9902d
--- /dev/null
+++ b/dom/security/test/csp/test_CSP.html
@@ -0,0 +1,147 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Content Security Policy Connections</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
+<script class="testbody" type="text/javascript">
+
+// These are test results: -1 means it hasn't run,
+// true/false is the pass/fail result.
+window.tests = {
+ img_good: -1,
+ img_bad: -1,
+ style_good: -1,
+ style_bad: -1,
+ frame_good: -1,
+ frame_bad: -1,
+ script_good: -1,
+ script_bad: -1,
+ xhr_good: -1,
+ xhr_bad: -1,
+ fetch_good: -1,
+ fetch_bad: -1,
+ beacon_good: -1,
+ beacon_bad: -1,
+ worker_xhr_same_bad: -1,
+ worker_xhr_cross_good: -1,
+ worker_fetch_same_bad: -1,
+ worker_fetch_cross_good: -1,
+ worker_script_same_good: -1,
+ worker_script_cross_bad: -1,
+ worker_inherited_xhr_good: -1,
+ worker_inherited_xhr_bad: -1,
+ worker_inherited_fetch_good: -1,
+ worker_inherited_fetch_bad: -1,
+ worker_inherited_script_good: -1,
+ worker_inherited_script_bad: -1,
+ worker_child_xhr_same_bad: -1,
+ worker_child_xhr_cross_bad: -1,
+ worker_child_script_same_bad: -1,
+ worker_child_script_cross_bad: -1,
+ worker_child_inherited_parent_xhr_bad: -1,
+ worker_child_inherited_parent_xhr_good: -1,
+ worker_child_inherited_parent_script_good: -1,
+ worker_child_inherited_parent_script_bad: -1,
+ worker_child_inherited_document_xhr_good: -1,
+ worker_child_inherited_document_xhr_bad: -1,
+ worker_child_inherited_document_script_good: -1,
+ worker_child_inherited_document_script_bad: -1,
+ media_good: -1,
+ media_bad: -1,
+ font_good: -1,
+ font_bad: -1,
+ object_good: -1,
+ object_bad: -1,
+};
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire.
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ var testpat = new RegExp("testid=([a-z0-9_]+)");
+
+ //_good things better be allowed!
+ //_bad things better be stopped!
+
+ // This is a special observer topic that is proxied from
+ // http-on-modify-request in the parent process to inform us when a URI is
+ // loaded
+ if (topic === "specialpowers-http-notify-request") {
+ var uri = data;
+ if (!testpat.test(uri)) return;
+ var testid = testpat.exec(uri)[1];
+
+ window.testResult(testid,
+ /_good/.test(testid),
+ uri + " allowed by csp");
+ }
+
+ if (topic === "csp-on-violate-policy") {
+ // these were blocked... record that they were blocked
+ var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
+ if (!testpat.test(asciiSpec)) return;
+ var testid = testpat.exec(asciiSpec)[1];
+ window.testResult(testid,
+ /_bad/.test(testid),
+ asciiSpec + " blocked by \"" + data + "\"");
+ }
+ },
+
+ // must eventually call this to remove the listener,
+ // or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+
+window.examiner = new examiner();
+
+window.testResult = function(testname, result, msg) {
+ // test already complete.... forget it... remember the first result.
+ if (window.tests[testname] != -1)
+ return;
+
+ ok(testname in window.tests, "It's a real test");
+ window.tests[testname] = result;
+ is(result, true, testname + ' test: ' + msg);
+
+ // if any test is incomplete, keep waiting
+ for (var v in window.tests)
+ if(tests[v] == -1)
+ return;
+
+ // ... otherwise, finish
+ window.examiner.remove();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ {'set':[// This defaults to 0 ("preload none") on mobile (B2G/Android), which
+ // blocks loading the resource until the user interacts with a
+ // corresponding widget, which breaks the media_* tests. We set it
+ // back to the default used by desktop Firefox to get consistent
+ // behavior.
+ ["media.preload.default", 2]]},
+ function() {
+ // save this for last so that our listeners are registered.
+ // ... this loads the testbed of good and bad requests.
+ document.getElementById('cspframe').src = 'file_main.html';
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_allow_https_schemes.html b/dom/security/test/csp/test_allow_https_schemes.html
new file mode 100644
index 000000000..713464200
--- /dev/null
+++ b/dom/security/test/csp/test_allow_https_schemes.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 826805 - Allow http and https for scheme-less sources</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ <iframe style="width:100%;" id="testframe"></iframe>
+ </div>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/* Description of the test:
+ * We are loading the following url (including a fragment portion):
+ * https://example.com/tests/dom/security/test/csp/file_path_matching.js#foo
+ * using different policies that lack specification of a scheme.
+ *
+ * Since the file is served over http:, the upgrade to https should be
+ * permitted by CSP in case no port is specified.
+ */
+
+var policies = [
+ ["allowed", "example.com"],
+ ["allowed", "example.com:443"],
+ ["allowed", "example.com:80"],
+ ["allowed", "http://*:80"],
+ ["allowed", "https://*:443"],
+ // our testing framework only supports :80 and :443, but
+ // using :8000 in a policy does the trick for the test.
+ ["blocked", "example.com:8000"],
+]
+
+var counter = 0;
+var policy;
+
+function loadNextTest() {
+ if (counter == policies.length) {
+ SimpleTest.finish();
+ }
+ else {
+ policy = policies[counter++];
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape("tests/dom/security/test/csp/file_allow_https_schemes.html");
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape("default-src 'none'; script-src " + policy[1]);
+
+ document.getElementById("testframe").addEventListener("load", test, false);
+ document.getElementById("testframe").src = src;
+ }
+}
+
+function test() {
+ try {
+ document.getElementById("testframe").removeEventListener('load', test, false);
+ var testframe = document.getElementById("testframe");
+ var divcontent = testframe.contentWindow.document.getElementById('testdiv').innerHTML;
+ is(divcontent, policy[0], "should be " + policy[0] + " in test " + (counter - 1) + "!");
+ }
+ catch (e) {
+ ok(false, "ERROR: could not access content in test " + (counter - 1) + "!");
+ }
+ loadNextTest();
+}
+
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_base-uri.html b/dom/security/test/csp/test_base-uri.html
new file mode 100644
index 000000000..7b8dcf7e2
--- /dev/null
+++ b/dom/security/test/csp/test_base-uri.html
@@ -0,0 +1,124 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1045897 - Test CSP base-uri directive</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ <iframe style="width:100%;" id="testframe"></iframe>
+ </div>
+
+<script class="testbody" type="text/javascript">
+
+/*
+ * Description of the test:
+ * We load a page in an iframe (served over http://example.com) that tries to
+ * modify the 'base' either through setting or also removing the base-uri. We
+ * load that page using different policies and verify that setting the base-uri
+ * is correctly blocked by CSP.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+var tests = [
+ { csp: "base-uri http://mochi.test;",
+ base1: "http://mochi.test",
+ base2: "",
+ action: "enforce-csp",
+ result: "http://mochi.test",
+ desc: "CSP allows base uri"
+ },
+ { csp: "base-uri http://example.com;",
+ base1: "http://mochi.test",
+ base2: "",
+ action: "enforce-csp",
+ result: "http://example.com",
+ desc: "CSP blocks base uri"
+ },
+ { csp: "base-uri https:",
+ base1: "http://mochi.test",
+ base2: "",
+ action: "enforce-csp",
+ result: "http://example.com",
+ desc: "CSP blocks http base"
+ },
+ { csp: "base-uri 'none'",
+ base1: "http://mochi.test",
+ base2: "",
+ action: "enforce-csp",
+ result: "http://example.com",
+ desc: "CSP allows no base modification"
+ },
+ { csp: "",
+ base1: "http://foo:foo/",
+ base2: "",
+ action: "enforce-csp",
+ result: "http://example.com",
+ desc: "Invalid base should be ignored"
+ },
+ { csp: "base-uri http://mochi.test",
+ base1: "http://mochi.test",
+ base2: "http://test1.example.com",
+ action: "remove-base1",
+ result: "http://example.com",
+ desc: "Removing first base should result in fallback base"
+ },
+ { csp: "",
+ base1: "http://mochi.test",
+ base2: "http://test1.example.com",
+ action: "remove-base1",
+ result: "http://test1.example.com",
+ desc: "Removing first base should result in the second base"
+ },
+];
+
+// initializing to -1 so we start at index 0 when we start the test
+var counter = -1;
+
+function finishTest() {
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+}
+
+// a postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin' to bubble up results back to this main page.
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ var result = event.data.result;
+ // we only care about the base uri, so instead of comparing the complete uri
+ // we just make sure that the base is correct which is sufficient here.
+ ok(result.startsWith(tests[counter].result),
+ `${tests[counter].desc}: Expected a base URI that starts
+ with ${tests[counter].result} but got ${result}`);
+ loadNextTest();
+}
+
+function loadNextTest() {
+ counter++;
+ if (counter == tests.length) {
+ finishTest();
+ return;
+ }
+ var src = "http://example.com/tests/dom/security/test/csp/file_base_uri_server.sjs";
+ // append the CSP that should be used to serve the file
+ // please note that we have to include 'unsafe-inline' to permit sending the postMessage
+ src += "?csp=" + escape("script-src 'unsafe-inline'; " + tests[counter].csp);
+ // append potential base tags
+ src += "&base1=" + escape(tests[counter].base1);
+ src += "&base2=" + escape(tests[counter].base2);
+ // append potential action
+ src += "&action=" + escape(tests[counter].action);
+
+ document.getElementById("testframe").src = src;
+}
+
+// start running the tests
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_blob_data_schemes.html b/dom/security/test/csp/test_blob_data_schemes.html
new file mode 100644
index 000000000..37b327b10
--- /dev/null
+++ b/dom/security/test/csp/test_blob_data_schemes.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1086999 - Wildcard should not match blob:, data:</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * We load an image using a data: and a blob: scheme and make
+ * sure a CSP containing a single ASTERISK (*) does not whitelist
+ * those loads. The single ASTERISK character should not match a
+ * URI's scheme of a type designating globally unique identifier
+ * (such as blob:, data:, or filesystem:)
+ */
+
+var tests = [
+ {
+ policy : "default-src 'unsafe-inline' blob: data:",
+ expected : "allowed",
+ },
+ {
+ policy : "default-src 'unsafe-inline' *",
+ expected : "blocked"
+ }
+];
+
+var testIndex = 0;
+var messageCounter = 0;
+var curTest;
+
+// onError handler is over-reporting, hence we make sure that
+// we get an error for both testcases: data and blob before we
+// move on to the next test.
+var dataRan = false;
+var blobRan = false;
+
+// a postMessage handler to communicate the results back to the parent.
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event)
+{
+ is(event.data.result, curTest.expected, event.data.scheme + " should be " + curTest.expected);
+
+ if (event.data.scheme === "data") {
+ dataRan = true;
+ }
+ if (event.data.scheme === "blob") {
+ blobRan = true;
+ }
+ if (dataRan && blobRan) {
+ loadNextTest();
+ }
+}
+
+function loadNextTest() {
+ if (testIndex === tests.length) {
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+ return;
+ }
+
+ dataRan = false;
+ blobRan = false;
+
+ curTest = tests[testIndex++];
+ // reset the messageCounter to make sure we receive all the postMessages from the iframe
+ messageCounter = 0;
+
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape("tests/dom/security/test/csp/file_blob_data_schemes.html");
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(curTest.policy);
+
+ document.getElementById("testframe").src = src;
+}
+
+SimpleTest.waitForExplicitFinish();
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_block_all_mixed_content.html b/dom/security/test/csp/test_block_all_mixed_content.html
new file mode 100644
index 000000000..d5c4cda8b
--- /dev/null
+++ b/dom/security/test/csp/test_block_all_mixed_content.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1122236 - CSP: Implement block-all-mixed-content</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the tests:
+ * Test 1:
+ * We load mixed display content in a frame using the CSP
+ * directive 'block-all-mixed-content' and observe that the image is blocked.
+ *
+ * Test 2:
+ * We load mixed display content in a frame using a CSP that allows the load
+ * and observe that the image is loaded.
+ *
+ * Test 3:
+ * We load mixed display content in a frame not using a CSP at all
+ * and observe that the image is loaded.
+ *
+ * Test 4:
+ * We load mixed display content in a frame using the CSP
+ * directive 'block-all-mixed-content' and observe that the image is blocked.
+ * Please note that Test 3 loads the image we are about to load in Test 4 into
+ * the img cache. Let's make sure the cached (mixed display content) image is
+ * not allowed to be loaded.
+ */
+
+const BASE_URI = "https://example.com/tests/dom/security/test/csp/";
+
+const tests = [
+ { // Test 1
+ query: "csp-block",
+ expected: "img-blocked",
+ description: "(csp-block) block-all-mixed content should block mixed display content"
+ },
+ { // Test 2
+ query: "csp-allow",
+ expected: "img-loaded",
+ description: "(csp-allow) mixed display content should be loaded"
+ },
+ { // Test 3
+ query: "no-csp",
+ expected: "img-loaded",
+ description: "(no-csp) mixed display content should be loaded"
+ },
+ { // Test 4
+ query: "csp-block",
+ expected: "img-blocked",
+ description: "(csp-block) block-all-mixed content should block insecure cache loads"
+ },
+ { // Test 5
+ query: "cspro-block",
+ expected: "img-loaded",
+ description: "(cspro-block) block-all-mixed in report only mode should not block"
+ },
+];
+
+var curTest;
+var counter = -1;
+
+function checkResults(result) {
+ is(result, curTest.expected, curTest.description);
+ loadNextTest();
+}
+
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ checkResults(event.data.result);
+}
+
+function loadNextTest() {
+ counter++;
+ if (counter == tests.length) {
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+ return;
+ }
+ curTest = tests[counter];
+ testframe.src = BASE_URI + "file_block_all_mcb.sjs?" + curTest.query;
+}
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ { 'set': [["security.mixed_content.block_display_content", false]] },
+ function() { loadNextTest(); }
+);
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_block_all_mixed_content_frame_navigation.html b/dom/security/test/csp/test_block_all_mixed_content_frame_navigation.html
new file mode 100644
index 000000000..c9d671fd7
--- /dev/null
+++ b/dom/security/test/csp/test_block_all_mixed_content_frame_navigation.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1122236 - CSP: Implement block-all-mixed-content</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ *
+ * http://a.com embeds https://b.com.
+ * https://b.com has a CSP using 'block-all-mixed-content'.
+ * | site | http://a.com
+ * | embeds | https://b.com (uses block-all-mixed-content)
+ *
+ * The user navigates the embedded frame from
+ * https://b.com -> http://c.com.
+ * The test makes sure that such a navigation is not blocked
+ * by block-all-mixed-content.
+ */
+
+function checkResults(result) {
+ is(result, "frame-navigated", "frame should be allowed to be navigated");
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+}
+
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ checkResults(event.data.result);
+}
+
+SimpleTest.waitForExplicitFinish();
+// http://a.com loads https://b.com
+document.getElementById("testframe").src =
+ "https://example.com/tests/dom/security/test/csp/file_block_all_mixed_content_frame_navigation1.html";
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_blocked_uri_in_reports.html b/dom/security/test/csp/test_blocked_uri_in_reports.html
new file mode 100644
index 000000000..f68d8c03f
--- /dev/null
+++ b/dom/security/test/csp/test_blocked_uri_in_reports.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1069762 - Check blocked-uri in csp-reports after redirect</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/* Description of the test:
+ * We try to load a script from:
+ * http://example.com/tests/dom/security/test/csp/file_path_matching_redirect_server.sjs
+ * which gets redirected to:
+ * http://test1.example.com/tests/dom/security//test/csp/file_path_matching.js
+ *
+ * The blocked-uri in the csp-report should be:
+ * test1.example.com
+ * instead of:
+ * http://test1.example.com/tests/com/security/test/csp/file_path_matching.js
+ *
+ * see also: http://www.w3.org/TR/CSP/#violation-reports
+ *
+ * Note, that we reuse the test-setup from
+ * test_path_matching_redirect.html
+ */
+
+const reportURI = "http://mochi.test:8888/foo.sjs";
+const policy = "script-src http://example.com; report-uri " + reportURI;
+const testfile = "tests/dom/security/test/csp/file_path_matching_redirect.html";
+
+var chromeScriptUrl = SimpleTest.getTestFileURL("file_report_chromescript.js");
+var script = SpecialPowers.loadChromeScript(chromeScriptUrl);
+
+script.addMessageListener('opening-request-completed', function ml(msg) {
+ if (msg.error) {
+ ok(false, "Could not query report (exception: " + msg.error + ")");
+ } else {
+ try {
+ var reportObj = JSON.parse(msg.report);
+ } catch (e) {
+ ok(false, "Could not parse JSON (exception: " + e + ")");
+ }
+ try {
+ var cspReport = reportObj["csp-report"];
+ // blocked-uri should only be the asciiHost instead of:
+ // http://test1.example.com/tests/dom/security/test/csp/file_path_matching.js
+ is(cspReport["blocked-uri"], "http://test1.example.com", "Incorrect blocked-uri");
+ } catch (e) {
+ ok(false, "Could not query report (exception: " + e + ")");
+ }
+ }
+
+ script.removeMessageListener('opening-request-completed', ml);
+ SimpleTest.finish();
+});
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape(testfile);
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(policy);
+
+ document.getElementById("cspframe").src = src;
+}
+
+runTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_bug1229639.html b/dom/security/test/csp/test_bug1229639.html
new file mode 100644
index 000000000..cd322d36d
--- /dev/null
+++ b/dom/security/test/csp/test_bug1229639.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1229639 - Percent encoded CSP path matching.</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+
+<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
+<script class="testbody" type="text/javascript">
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire.
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ if (data === 'http://mochi.test:8888/tests/dom/security/test/csp/%24.js') {
+ is(topic, "specialpowers-http-notify-request");
+ this.remove();
+ SimpleTest.finish();
+ }
+ },
+
+ // must eventually call this to remove the listener,
+ // or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+
+window.examiner = new examiner();
+
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('cspframe').src = 'file_bug1229639.html';
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_bug1242019.html b/dom/security/test/csp/test_bug1242019.html
new file mode 100644
index 000000000..d57fa02bf
--- /dev/null
+++ b/dom/security/test/csp/test_bug1242019.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1242019
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1242019</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1242019">Mozilla Bug 1242019</a>
+<p id="display"></p>
+
+<iframe id="cspframe"></iframe>
+
+<pre id="test">
+
+<script class="testbody" type="text/javascript">
+function cleanup() {
+ SpecialPowers.postConsoleSentinel();
+ SimpleTest.finish();
+};
+
+var expectedURI = "data:image/png;base64,iVBORw0KGgoAAAANSU"
+
+SpecialPowers.registerConsoleListener(function ConsoleMsgListener(aMsg) {
+ // look for the message with data uri and see the data uri is truncated to 40 chars
+ data_start = aMsg.message.indexOf(expectedURI)
+ if (data_start > -1) {
+ data_uri = "";
+ data_uri = aMsg.message.substr(data_start);
+ // this will either match the elipsis after the URI or the . at the end of the message
+ data_uri = data_uri.substr(0, data_uri.indexOf("."));
+ if (data_uri == "") {
+ return;
+ }
+
+ ok(data_uri.length == 40, "Data URI only shows 40 characters in the console");
+ SimpleTest.executeSoon(cleanup);
+ }
+});
+
+// set up and start testing
+SimpleTest.waitForExplicitFinish();
+document.getElementById('cspframe').src = 'file_data-uri_blocked.html';
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_bug1312272.html b/dom/security/test/csp/test_bug1312272.html
new file mode 100644
index 000000000..2cbebb844
--- /dev/null
+++ b/dom/security/test/csp/test_bug1312272.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+
+ <title>Test for bug 1312272</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe id="cspframe" style="width:100%"></iframe>
+
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+function handler(evt) {
+ console.log(evt);
+ if (evt.data === "finish") {
+ ok(true, 'Other events continue to work fine.')
+ SimpleTest.finish();
+ //removeEventListener('message', handler);
+ } else {
+ ok(false, "Should not get any other message")
+ }
+}
+var cspframe = document.getElementById("cspframe");
+cspframe.src = "file_bug1312272.html";
+addEventListener("message", handler);
+console.log("assignign frame");
+</script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/test_bug663567.html b/dom/security/test/csp/test_bug663567.html
new file mode 100644
index 000000000..293aa2914
--- /dev/null
+++ b/dom/security/test/csp/test_bug663567.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test if XSLT stylesheet is subject to document's CSP</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <iframe style="width:100%;" id='xsltframe'></iframe>
+ <iframe style="width:100%;" id='xsltframe2'></iframe>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// define the expected output of this test
+var header = "this xml file should be formatted using an xsl file(lower iframe should contain xml dump)!";
+
+var finishedTests = 0;
+var numberOfTests = 2;
+
+var checkExplicitFinish = function() {
+ finishedTests++;
+ if (finishedTests == numberOfTests) {
+ SimpleTest.finish();
+ }
+}
+
+function checkAllowed () {
+ /* The policy for this test is:
+ * Content-Security-Policy: default-src 'self'
+ *
+ * we load the xsl file using:
+ * <?xml-stylesheet type="text/xsl" href="file_bug663467.xsl"?>
+ */
+ try {
+ var cspframe = document.getElementById('xsltframe');
+ var xsltAllowedHeader = cspframe.contentWindow.document.getElementById('xsltheader').innerHTML;
+ is(xsltAllowedHeader, header, "XSLT loaded from 'self' should be allowed!");
+ }
+ catch (e) {
+ ok(false, "Error: could not access content in xsltframe!")
+ }
+ checkExplicitFinish();
+}
+
+function checkBlocked () {
+ /* The policy for this test is:
+ * Content-Security-Policy: default-src *.example.com
+ *
+ * we load the xsl file using:
+ * <?xml-stylesheet type="text/xsl" href="file_bug663467.xsl"?>
+ */
+ try {
+ var cspframe = document.getElementById('xsltframe2');
+ var xsltBlockedHeader = cspframe.contentWindow.document.getElementById('xsltheader');
+ is(xsltBlockedHeader, null, "XSLT loaded from different host should be blocked!");
+ }
+ catch (e) {
+ ok(false, "Error: could not access content in xsltframe2!")
+ }
+ checkExplicitFinish();
+}
+
+document.getElementById('xsltframe').addEventListener('load', checkAllowed, false);
+document.getElementById('xsltframe').src = 'file_bug663567_allows.xml';
+
+document.getElementById('xsltframe2').addEventListener('load', checkBlocked, false);
+document.getElementById('xsltframe2').src = 'file_bug663567_blocks.xml';
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_bug802872.html b/dom/security/test/csp/test_bug802872.html
new file mode 100644
index 000000000..70584c14f
--- /dev/null
+++ b/dom/security/test/csp/test_bug802872.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 802872</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <iframe style="width:100%;" id='eventframe'></iframe>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var finishedTests = 0;
+var numberOfTests = 2;
+
+var checkExplicitFinish = function () {
+ finishedTests++;
+ if (finishedTests == numberOfTests) {
+ SimpleTest.finish();
+ }
+}
+
+// add event listeners for CSP-permitted EventSrc callbacks
+addEventListener('allowedEventSrcCallbackOK', function (e) {
+ ok(true, "OK: CSP allows EventSource for whitelisted domain!");
+ checkExplicitFinish();
+}, false);
+addEventListener('allowedEventSrcCallbackFailed', function (e) {
+ ok(false, "Error: CSP blocks EventSource for whitelisted domain!");
+ checkExplicitFinish();
+}, false);
+
+// add event listeners for CSP-blocked EventSrc callbacks
+addEventListener('blockedEventSrcCallbackOK', function (e) {
+ ok(false, "Error: CSP allows EventSource to not whitelisted domain!");
+ checkExplicitFinish();
+}, false);
+addEventListener('blockedEventSrcCallbackFailed', function (e) {
+ ok(true, "OK: CSP blocks EventSource for not whitelisted domain!");
+ checkExplicitFinish();
+}, false);
+
+// load it
+document.getElementById('eventframe').src = 'file_bug802872.html';
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_bug836922_npolicies.html b/dom/security/test/csp/test_bug836922_npolicies.html
new file mode 100644
index 000000000..8d0390eed
--- /dev/null
+++ b/dom/security/test/csp/test_bug836922_npolicies.html
@@ -0,0 +1,240 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Content Security Policy multiple policy support (regular and Report-Only mode)</title>
+ <script type="text/javascript" src="/MochiKit/packed.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
+<script class="testbody" type="text/javascript">
+
+var path = "/tests/dom/security/test/csp/";
+
+// These are test results: verified indicates whether or not the test has run.
+// true/false is the pass/fail result.
+window.loads = {
+ css_self: {expected: true, verified: false},
+ img_self: {expected: false, verified: false},
+ script_self: {expected: true, verified: false},
+};
+
+window.violation_reports = {
+ css_self:
+ {expected: 0, expected_ro: 0}, /* totally fine */
+ img_self:
+ {expected: 1, expected_ro: 0}, /* violates enforced CSP */
+ script_self:
+ {expected: 0, expected_ro: 1}, /* violates report-only */
+};
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire. This also watches for violation reports to go out.
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ var testpat = new RegExp("testid=([a-z0-9_]+)");
+
+ if (topic === "specialpowers-http-notify-request") {
+ var uri = data;
+ if (!testpat.test(uri)) return;
+ var testid = testpat.exec(uri)[1];
+
+ // violation reports don't come through here, but the requested resources do
+ // if the test has already finished, move on. Some things throw multiple
+ // requests (preloads and such)
+ try {
+ if (window.loads[testid].verified) return;
+ } catch(e) { return; }
+
+ // these are requests that were allowed by CSP
+ var testid = testpat.exec(uri)[1];
+ window.testResult(testid, 'allowed', uri + " allowed by csp");
+ }
+
+ if(topic === "csp-on-violate-policy") {
+ // if the violated policy was report-only, the resource will still be
+ // loaded even if this topic is notified.
+ var asciiSpec = SpecialPowers.getPrivilegedProps(
+ SpecialPowers.do_QueryInterface(subject, "nsIURI"),
+ "asciiSpec");
+ if (!testpat.test(asciiSpec)) return;
+ var testid = testpat.exec(asciiSpec)[1];
+
+ // if the test has already finished, move on.
+ try {
+ if (window.loads[testid].verified) return;
+ } catch(e) { return; }
+
+ // record the ones that were supposed to be blocked, but don't use this
+ // as an indicator for tests that are not blocked but do generate reports.
+ // We skip recording the result if the load is expected since a
+ // report-only policy will generate a request *and* a violation note.
+ if (!window.loads[testid].expected) {
+ window.testResult(testid,
+ 'blocked',
+ asciiSpec + " blocked by \"" + data + "\"");
+ }
+ }
+
+ // if any test is unverified, keep waiting
+ for (var v in window.loads) {
+ if(!window.loads[v].verified) {
+ return;
+ }
+ }
+
+ window.bug836922examiner.remove();
+ window.resultPoller.pollForFinish();
+ },
+
+ // must eventually call this to remove the listener,
+ // or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+window.bug836922examiner = new examiner();
+
+
+// Poll for results and see if enough reports came in. Keep trying
+// for a few seconds before failing with lack of reports.
+// Have to do this because there's a race between the async reporting
+// and this test finishing, and we don't want to win the race.
+window.resultPoller = {
+
+ POLL_ATTEMPTS_LEFT: 14,
+
+ pollForFinish:
+ function() {
+ var vr = resultPoller.tallyReceivedReports();
+ if (resultPoller.verifyReports(vr, resultPoller.POLL_ATTEMPTS_LEFT < 1)) {
+ // report success condition.
+ resultPoller.resetReportServer();
+ SimpleTest.finish();
+ } else {
+ resultPoller.POLL_ATTEMPTS_LEFT--;
+ // try again unless we reached the threshold.
+ setTimeout(resultPoller.pollForFinish, 100);
+ }
+ },
+
+ resetReportServer:
+ function() {
+ var xhr = new XMLHttpRequest();
+ var xhr_ro = new XMLHttpRequest();
+ xhr.open("GET", "file_bug836922_npolicies_violation.sjs?reset", false);
+ xhr_ro.open("GET", "file_bug836922_npolicies_ro_violation.sjs?reset", false);
+ xhr.send(null);
+ xhr_ro.send(null);
+ },
+
+ tallyReceivedReports:
+ function() {
+ var xhr = new XMLHttpRequest();
+ var xhr_ro = new XMLHttpRequest();
+ xhr.open("GET", "file_bug836922_npolicies_violation.sjs?results", false);
+ xhr_ro.open("GET", "file_bug836922_npolicies_ro_violation.sjs?results", false);
+ xhr.send(null);
+ xhr_ro.send(null);
+
+ var received = JSON.parse(xhr.responseText);
+ var received_ro = JSON.parse(xhr_ro.responseText);
+
+ var results = {enforced: {}, reportonly: {}};
+ for (var r in window.violation_reports) {
+ results.enforced[r] = 0;
+ results.reportonly[r] = 0;
+ }
+
+ for (var r in received) {
+ results.enforced[r] += received[r];
+ }
+ for (var r in received_ro) {
+ results.reportonly[r] += received_ro[r];
+ }
+
+ return results;
+ },
+
+ verifyReports:
+ function(receivedCounts, lastAttempt) {
+ for (var r in window.violation_reports) {
+ var exp = window.violation_reports[r].expected;
+ var exp_ro = window.violation_reports[r].expected_ro;
+ var rec = receivedCounts.enforced[r];
+ var rec_ro = receivedCounts.reportonly[r];
+
+ // if this test breaks, these are helpful dumps:
+ //dump(">>> Verifying " + r + "\n");
+ //dump(" > Expected: " + exp + " / " + exp_ro + " (ro)\n");
+ //dump(" > Received: " + rec + " / " + rec_ro + " (ro) \n");
+
+ // in all cases, we're looking for *at least* the expected number of
+ // reports of each type (there could be more in some edge cases).
+ // If there are not enough, we keep waiting and poll the server again
+ // later. If there are enough, we can successfully finish.
+
+ if (exp == 0)
+ is(rec, 0,
+ "Expected zero enforced-policy violation " +
+ "reports for " + r + ", got " + rec);
+ else if (lastAttempt)
+ ok(rec >= exp,
+ "Received (" + rec + "/" + exp + ") " +
+ "enforced-policy reports for " + r);
+ else if (rec < exp)
+ return false; // continue waiting for more
+
+ if(exp_ro == 0)
+ is(rec_ro, 0,
+ "Expected zero report-only-policy violation " +
+ "reports for " + r + ", got " + rec_ro);
+ else if (lastAttempt)
+ ok(rec_ro >= exp_ro,
+ "Received (" + rec_ro + "/" + exp_ro + ") " +
+ "report-only-policy reports for " + r);
+ else if (rec_ro < exp_ro)
+ return false; // continue waiting for more
+ }
+
+ // if we complete the loop, we've found all of the violation
+ // reports we expect.
+ if (lastAttempt) return true;
+
+ // Repeat successful tests once more to record successes via ok()
+ return resultPoller.verifyReports(receivedCounts, true);
+ }
+};
+
+window.testResult = function(testname, result, msg) {
+ // otherwise, make sure the allowed ones are expected and blocked ones are not.
+ if (window.loads[testname].expected) {
+ is(result, 'allowed', ">> " + msg);
+ } else {
+ is(result, 'blocked', ">> " + msg);
+ }
+ window.loads[testname].verified = true;
+}
+
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('cspframe').src = 'http://mochi.test:8888' + path + 'file_bug836922_npolicies.html';
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_bug885433.html b/dom/security/test/csp/test_bug885433.html
new file mode 100644
index 000000000..22db0ed24
--- /dev/null
+++ b/dom/security/test/csp/test_bug885433.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Content Security Policy inline stylesheets stuff</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<iframe style="width:100%;" id='cspframe'></iframe>
+<iframe style="width:100%;" id='cspframe2'></iframe>
+<script class="testbody" type="text/javascript">
+
+//////////////////////////////////////////////////////////////////////
+// set up and go
+SimpleTest.waitForExplicitFinish();
+
+// utilities for check functions
+// black means the style wasn't applied, applied styles are green
+var green = 'rgb(0, 128, 0)';
+var black = 'rgb(0, 0, 0)';
+
+// We test both script and style execution by observing changes in computed styles
+function checkAllowed () {
+ var cspframe = document.getElementById('cspframe');
+ var color;
+
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('unsafe-inline-script-allowed')).color;
+ ok(color === green, "Inline script should be allowed");
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('unsafe-eval-script-allowed')).color;
+ ok(color === green, "Eval should be allowed");
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('unsafe-inline-style-allowed')).color;
+ ok(color === green, "Inline style should be allowed");
+
+ document.getElementById('cspframe2').src = 'file_bug885433_blocks.html';
+ document.getElementById('cspframe2').addEventListener('load', checkBlocked, false);
+}
+
+function checkBlocked () {
+ var cspframe = document.getElementById('cspframe2');
+ var color;
+
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('unsafe-inline-script-blocked')).color;
+ ok(color === black, "Inline script should be blocked");
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('unsafe-eval-script-blocked')).color;
+ ok(color === black, "Eval should be blocked");
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('unsafe-inline-style-blocked')).color;
+ ok(color === black, "Inline style should be blocked");
+
+ SimpleTest.finish();
+}
+
+document.getElementById('cspframe').src = 'file_bug885433_allows.html';
+document.getElementById('cspframe').addEventListener('load', checkAllowed, false);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_bug886164.html b/dom/security/test/csp/test_bug886164.html
new file mode 100644
index 000000000..74fd98458
--- /dev/null
+++ b/dom/security/test/csp/test_bug886164.html
@@ -0,0 +1,172 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 886164 - Enforce CSP in sandboxed iframe</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<iframe style="width:200px;height:200px;" id='cspframe' sandbox="allow-same-origin"></iframe>
+<iframe style="width:200px;height:200px;" id='cspframe2' sandbox></iframe>
+<iframe style="width:200px;height:200px;" id='cspframe3' sandbox="allow-same-origin"></iframe>
+<iframe style="width:200px;height:200px;" id='cspframe4' sandbox></iframe>
+<iframe style="width:200px;height:200px;" id='cspframe5' sandbox="allow-scripts"></iframe>
+<iframe style="width:200px;height:200px;" id='cspframe6' sandbox="allow-same-origin allow-scripts"></iframe>
+<script class="testbody" type="text/javascript">
+
+
+var path = "/tests/dom/security/test/csp/";
+
+// These are test results: -1 means it hasn't run,
+// true/false is the pass/fail result.
+window.tests = {
+ // sandbox allow-same-origin; 'self'
+ img_good: -1, // same origin
+ img_bad: -1, //example.com
+
+ // sandbox; 'self'
+ img2_bad: -1, //example.com
+ img2a_good: -1, // same origin & is image
+
+ // sandbox allow-same-origin; 'none'
+ img3_bad: -1,
+ img3a_bad: -1,
+
+ // sandbox; 'none'
+ img4_bad: -1,
+ img4a_bad: -1,
+
+ // sandbox allow-scripts; 'none' 'unsafe-inline'
+ img5_bad: -1,
+ img5a_bad: -1,
+ script5_bad: -1,
+ script5a_bad: -1,
+
+ // sandbox allow-same-origin allow-scripts; 'self' 'unsafe-inline'
+ img6_bad: -1,
+ script6_bad: -1,
+};
+
+// a postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin' to communicate pass/fail back to this main page.
+// it expects to be called with an object like {ok: true/false, desc:
+// <description of the test> which it then forwards to ok()
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event)
+{
+ ok_wrapper(event.data.ok, event.data.desc);
+}
+
+var cspTestsDone = false;
+var iframeSandboxTestsDone = false;
+
+// iframe related
+var completedTests = 0;
+var passedTests = 0;
+
+function ok_wrapper(result, desc) {
+ ok(result, desc);
+
+ completedTests++;
+
+ if (result) {
+ passedTests++;
+ }
+
+ if (completedTests === 5) {
+ iframeSandboxTestsDone = true;
+ if (cspTestsDone) {
+ SimpleTest.finish();
+ }
+ }
+}
+
+
+//csp related
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire.
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ var testpat = new RegExp("testid=([a-z0-9_]+)");
+
+ //_good things better be allowed!
+ //_bad things better be stopped!
+
+ if (topic === "specialpowers-http-notify-request") {
+ //these things were allowed by CSP
+ var uri = data;
+ if (!testpat.test(uri)) return;
+ var testid = testpat.exec(uri)[1];
+
+ window.testResult(testid,
+ /_good/.test(testid),
+ uri + " allowed by csp");
+ }
+
+ if(topic === "csp-on-violate-policy") {
+ //these were blocked... record that they were blocked
+ var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
+ if (!testpat.test(asciiSpec)) return;
+ var testid = testpat.exec(asciiSpec)[1];
+ window.testResult(testid,
+ /_bad/.test(testid),
+ asciiSpec + " blocked by \"" + data + "\"");
+ }
+ },
+
+ // must eventually call this to remove the listener,
+ // or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+
+window.examiner = new examiner();
+
+window.testResult = function(testname, result, msg) {
+ //test already complete.... forget it... remember the first result.
+ if (window.tests[testname] != -1)
+ return;
+
+ window.tests[testname] = result;
+ ok(result, testname + ' test: ' + msg);
+
+ // if any test is incomplete, keep waiting
+ for (var v in window.tests)
+ if(tests[v] == -1)
+ return;
+
+ // ... otherwise, finish
+ window.examiner.remove();
+ cspTestsDone = true;
+ if (iframeSandboxTestsDone) {
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('cspframe').src = 'file_bug886164.html';
+document.getElementById('cspframe2').src = 'file_bug886164_2.html';
+document.getElementById('cspframe3').src = 'file_bug886164_3.html';
+document.getElementById('cspframe4').src = 'file_bug886164_4.html';
+document.getElementById('cspframe5').src = 'file_bug886164_5.html';
+document.getElementById('cspframe6').src = 'file_bug886164_6.html';
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_bug888172.html b/dom/security/test/csp/test_bug888172.html
new file mode 100644
index 000000000..200e8c942
--- /dev/null
+++ b/dom/security/test/csp/test_bug888172.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 888172 - CSP 1.0 does not process 'unsafe-inline' or 'unsafe-eval' for default-src</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<iframe style="width:100%;" id='testframe1'></iframe>
+<iframe style="width:100%;" id='testframe2'></iframe>
+<iframe style="width:100%;" id='testframe3'></iframe>
+<script class="testbody" type="text/javascript">
+
+//////////////////////////////////////////////////////////////////////
+// set up and go
+SimpleTest.waitForExplicitFinish();
+
+// utilities for check functions
+// black means the style wasn't applied, applied styles are green
+var green = 'rgb(0, 128, 0)';
+var black = 'rgb(0, 0, 0)';
+
+function getElementColorById(doc, id) {
+ return window.getComputedStyle(doc.contentDocument.getElementById(id)).color;
+}
+
+// We test both script and style execution by observing changes in computed styles
+function checkDefaultSrcOnly() {
+ var testframe = document.getElementById('testframe1');
+
+ ok(getElementColorById(testframe, 'unsafe-inline-script') === green, "Inline script should be allowed");
+ ok(getElementColorById(testframe, 'unsafe-eval-script') === green, "Eval should be allowed");
+ ok(getElementColorById(testframe, 'unsafe-inline-style') === green, "Inline style should be allowed");
+
+ document.getElementById('testframe2').src = 'file_bug888172.sjs?csp=' +
+ escape("default-src 'self' 'unsafe-inline' 'unsafe-eval'; script-src 'self'");
+ document.getElementById('testframe2').addEventListener('load', checkDefaultSrcWithScriptSrc, false);
+}
+
+function checkDefaultSrcWithScriptSrc() {
+ var testframe = document.getElementById('testframe2');
+
+ ok(getElementColorById(testframe, 'unsafe-inline-script') === black, "Inline script should be blocked");
+ ok(getElementColorById(testframe, 'unsafe-eval-script') === black, "Eval should be blocked");
+ ok(getElementColorById(testframe, 'unsafe-inline-style') === green, "Inline style should be allowed");
+
+ document.getElementById('testframe3').src = 'file_bug888172.sjs?csp=' +
+ escape("default-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self'");
+ document.getElementById('testframe3').addEventListener('load', checkDefaultSrcWithStyleSrc, false);
+}
+
+function checkDefaultSrcWithStyleSrc() {
+ var testframe = document.getElementById('testframe3');
+
+ ok(getElementColorById(testframe, 'unsafe-inline-script') === green, "Inline script should be allowed");
+ ok(getElementColorById(testframe, 'unsafe-eval-script') === green, "Eval should be allowed");
+ ok(getElementColorById(testframe, 'unsafe-inline-style') === black, "Inline style should be blocked");
+
+ // last test calls finish
+ SimpleTest.finish();
+}
+
+document.getElementById('testframe1').src = 'file_bug888172.sjs?csp=' +
+ escape("default-src 'self' 'unsafe-inline' 'unsafe-eval'");
+document.getElementById('testframe1').addEventListener('load', checkDefaultSrcOnly, false);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_bug909029.html b/dom/security/test/csp/test_bug909029.html
new file mode 100644
index 000000000..aebfabf48
--- /dev/null
+++ b/dom/security/test/csp/test_bug909029.html
@@ -0,0 +1,129 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Bug 909029 - CSP source-lists ignore some source expressions like 'unsafe-inline' when * or 'none' are used (e.g., style-src, script-src)</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <div id=content style="visibility:hidden">
+ <iframe id=testframe1></iframe>
+ <iframe id=testframe2></iframe>
+ </div>
+ <script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+window.tests = {
+ starExternalStylesLoaded: -1,
+ starExternalImgLoaded: -1,
+ noneExternalStylesBlocked: -1,
+ noneExternalImgLoaded: -1,
+ starInlineStyleAllowed: -1,
+ starInlineScriptBlocked: -1,
+ noneInlineStyleAllowed: -1,
+ noneInlineScriptBlocked: -1
+}
+
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ var testpat = new RegExp("testid=([a-zA-Z]+)");
+
+ if (topic === "specialpowers-http-notify-request") {
+ var uri = data;
+ if (!testpat.test(uri)) return;
+ var testid = testpat.exec(uri)[1];
+ window.testResult(testid,
+ /Loaded/.test(testid),
+ "resource loaded");
+ }
+
+ if(topic === "csp-on-violate-policy") {
+ // these were blocked... record that they were blocked
+ // try because the subject could be an nsIURI or an nsISupportsCString
+ try {
+ var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
+ if (!testpat.test(asciiSpec)) return;
+ var testid = testpat.exec(asciiSpec)[1];
+ window.testResult(testid,
+ /Blocked/.test(testid),
+ "resource blocked by CSP");
+ } catch(e) {
+ // if that fails, the subject is probably a string. Strings are only
+ // reported for inline and eval violations. Since we are testing those
+ // via the observed effects of script on CSSOM, we can simply ignore
+ // these subjects.
+ }
+ }
+ },
+
+ // must eventually call this to remove the listener,
+ // or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+
+window.examiner = new examiner();
+
+window.testResult = function(testname, result, msg) {
+ //dump("in testResult: testname = " + testname + "\n");
+
+ //test already complete.... forget it... remember the first result.
+ if (window.tests[testname] != -1)
+ return;
+
+ window.tests[testname] = result;
+ is(result, true, testname + ' test: ' + msg);
+
+ // if any test is incomplete, keep waiting
+ for (var v in window.tests)
+ if(tests[v] == -1)
+ return;
+
+ // ... otherwise, finish
+ window.examiner.remove();
+ SimpleTest.finish();
+}
+
+// Helpers for inline script/style checks
+var black = 'rgb(0, 0, 0)';
+var green = 'rgb(0, 128, 0)';
+function getElementColorById(doc, id) {
+ return window.getComputedStyle(doc.contentDocument.getElementById(id)).color;
+}
+
+function checkInlineWithStar() {
+ var testframe = document.getElementById('testframe1');
+ window.testResult("starInlineStyleAllowed",
+ getElementColorById(testframe, 'inline-style') === green,
+ "Inline styles should be allowed (style-src 'unsafe-inline' with star)");
+ window.testResult("starInlineScriptBlocked",
+ getElementColorById(testframe, 'inline-script') === black,
+ "Inline scripts should be blocked (style-src 'unsafe-inline' with star)");
+}
+
+function checkInlineWithNone() {
+ // If a directive has 'none' in addition to other sources, 'none' is ignored
+ // and the other sources are used. 'none' is only a valid source if it is
+ // used by itself.
+ var testframe = document.getElementById('testframe2');
+ window.testResult("noneInlineStyleAllowed",
+ getElementColorById(testframe, 'inline-style') === green,
+ "Inline styles should be allowed (style-src 'unsafe-inline' with none)");
+ window.testResult("noneInlineScriptBlocked",
+ getElementColorById(testframe, 'inline-script') === black,
+ "Inline scripts should be blocked (style-src 'unsafe-inline' with none)");
+}
+
+document.getElementById('testframe1').src = 'file_bug909029_star.html';
+document.getElementById('testframe1').addEventListener('load', checkInlineWithStar, false);
+document.getElementById('testframe2').src = 'file_bug909029_none.html';
+document.getElementById('testframe2').addEventListener('load', checkInlineWithNone, false);
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/test_bug910139.html b/dom/security/test/csp/test_bug910139.html
new file mode 100644
index 000000000..63a7f77d1
--- /dev/null
+++ b/dom/security/test/csp/test_bug910139.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>CSP should block XSLT as script, not as style</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <iframe style="width:100%;" id='xsltframe'></iframe>
+ <iframe style="width:100%;" id='xsltframe2'></iframe>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// define the expected output of this test
+var header = "this xml file should be formatted using an xsl file(lower iframe should contain xml dump)!";
+
+function checkAllowed () {
+ /* The policy for this test is:
+ * Content-Security-Policy: default-src 'self'; script-src 'self'
+ *
+ * we load the xsl file using:
+ * <?xml-stylesheet type="text/xsl" href="file_bug910139.xsl"?>
+ */
+ try {
+ var cspframe = document.getElementById('xsltframe');
+ var xsltAllowedHeader = cspframe.contentWindow.document.getElementById('xsltheader').innerHTML;
+ is(xsltAllowedHeader, header, "XSLT loaded from 'self' should be allowed!");
+ }
+ catch (e) {
+ ok(false, "Error: could not access content in xsltframe!")
+ }
+
+ // continue with the next test
+ document.getElementById('xsltframe2').addEventListener('load', checkBlocked, false);
+ document.getElementById('xsltframe2').src = 'file_bug910139.sjs';
+}
+
+function checkBlocked () {
+ /* The policy for this test is:
+ * Content-Security-Policy: default-src 'self'; script-src *.example.com
+ *
+ * we load the xsl file using:
+ * <?xml-stylesheet type="text/xsl" href="file_bug910139.xsl"?>
+ */
+ try {
+ var cspframe = document.getElementById('xsltframe2');
+ var xsltBlockedHeader = cspframe.contentWindow.document.getElementById('xsltheader');
+ is(xsltBlockedHeader, null, "XSLT loaded from different host should be blocked!");
+ }
+ catch (e) {
+ ok(false, "Error: could not access content in xsltframe2!")
+ }
+ SimpleTest.finish();
+}
+
+document.getElementById('xsltframe').addEventListener('load', checkAllowed, false);
+document.getElementById('xsltframe').src = 'file_bug910139.sjs';
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_bug941404.html b/dom/security/test/csp/test_bug941404.html
new file mode 100644
index 000000000..07f45d176
--- /dev/null
+++ b/dom/security/test/csp/test_bug941404.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 941404 - Data documents should not set CSP</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+
+</div>
+
+<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
+<script class="testbody" type="text/javascript">
+
+
+var path = "/tests/dom/security/test/csp/";
+
+// These are test results: -1 means it hasn't run,
+// true/false is the pass/fail result.
+window.tests = {
+ img_good: -1,
+ img2_good: -1,
+};
+
+
+//csp related
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire.
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ var testpat = new RegExp("testid=([a-z0-9_]+)");
+
+ //_good things better be allowed!
+ //_bad things better be stopped!
+
+ if (topic === "specialpowers-http-notify-request") {
+ //these things were allowed by CSP
+ var uri = data;
+ if (!testpat.test(uri)) return;
+ var testid = testpat.exec(uri)[1];
+
+ window.testResult(testid,
+ /_good/.test(testid),
+ uri + " allowed by csp");
+ }
+
+ if(topic === "csp-on-violate-policy") {
+ //these were blocked... record that they were blocked
+ var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
+ if (!testpat.test(asciiSpec)) return;
+ var testid = testpat.exec(asciiSpec)[1];
+ window.testResult(testid,
+ /_bad/.test(testid),
+ asciiSpec + " blocked by \"" + data + "\"");
+ }
+ },
+
+ // must eventually call this to remove the listener,
+ // or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+
+window.examiner = new examiner();
+
+window.testResult = function(testname, result, msg) {
+ //test already complete.... forget it... remember the first result.
+ if (window.tests[testname] != -1)
+ return;
+
+ window.tests[testname] = result;
+ is(result, true, testname + ' test: ' + msg);
+
+ // if any test is incomplete, keep waiting
+ for (var v in window.tests)
+ if(tests[v] == -1) {
+ console.log(v + " is not complete");
+ return;
+ }
+
+ // ... otherwise, finish
+ window.examiner.remove();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('cspframe').src = 'file_bug941404.html';
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_child-src_iframe.html b/dom/security/test/csp/test_child-src_iframe.html
new file mode 100644
index 000000000..b4ba36f89
--- /dev/null
+++ b/dom/security/test/csp/test_child-src_iframe.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1045891</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ </div>
+
+<script class="testbody" type="text/javascript">
+
+/*
+ * Description of the test:
+ * We load a page with a given CSP and verify that child frames and workers are correctly
+ * evaluated through the "child-src" directive.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+var IFRAME_SRC="file_child-src_iframe.html"
+
+var tests = {
+ 'same-src': {
+ id: "same-src",
+ file: IFRAME_SRC,
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src http://mochi.test:8888"
+ },
+ 'star-src': {
+ id: "star-src",
+ file: IFRAME_SRC,
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src *"
+ },
+ 'other-src': {
+ id: "other-src",
+ file: IFRAME_SRC,
+ result : "blocked",
+ policy : "default-src http://mochi.test:8888; script-src 'unsafe-inline'; child-src http://www.example.com"
+ },
+ 'same-src-by-frame-src': {
+ id: "same-src-by-frame-src",
+ file: IFRAME_SRC,
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src 'none'; frame-src http://mochi.test:8888"
+ },
+ 'star-src-by-frame-src': {
+ id: "star-src-by-frame-src",
+ file: IFRAME_SRC,
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src 'none'; frame-src *"
+ },
+ 'other-src-by-frame-src': {
+ id: "other-src-by-frame-src",
+ file: IFRAME_SRC,
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src http://mochi.test:8888; frame-src http://www.example.com"
+ },
+ 'none-src-by-frame-src': {
+ id: "none-src-by-frame-src",
+ file: "file_child-src_iframe.html",
+ file: IFRAME_SRC,
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src http://mochi.test:8888; frame-src 'none'"
+ }
+};
+
+finished = {};
+
+function checkFinished() {
+ if (Object.keys(finished).length == Object.keys(tests).length) {
+ window.removeEventListener('message', recvMessage);
+ SimpleTest.finish();
+ }
+}
+
+function recvMessage(ev) {
+ is(ev.data.message, tests[ev.data.id].result, "CSP child-src test " + ev.data.id);
+ finished[ev.data.id] = ev.data.message;
+
+ checkFinished();
+}
+
+window.addEventListener('message', recvMessage, false);
+
+function loadNextTest() {
+ for (item in tests) {
+ test = tests[item];
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape("tests/dom/security/test/csp/" + test.file);
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(test.policy);
+ // add our identifier
+ src += "#" + escape(test.id);
+
+ content = document.getElementById('content');
+ testframe = document.createElement("iframe");
+ testframe.setAttribute('id', test.id);
+ content.appendChild(testframe);
+ testframe.src = src;
+ }
+}
+
+// start running the tests
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_child-src_worker-redirect.html b/dom/security/test/csp/test_child-src_worker-redirect.html
new file mode 100644
index 000000000..dfb99149c
--- /dev/null
+++ b/dom/security/test/csp/test_child-src_worker-redirect.html
@@ -0,0 +1,125 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1045891</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ </div>
+
+ <script class="testbody" type="text/javascript">
+ /*
+ * Description of the test:
+ * We load a page with a given CSP and verify that child frames and workers are correctly
+ * evaluated through the "child-src" directive.
+ */
+
+ SimpleTest.waitForExplicitFinish();
+
+ var WORKER_REDIRECT_TEST_FILE = "file_child-src_worker-redirect.html";
+ var SHARED_WORKER_REDIRECT_TEST_FILE = "file_child-src_shared_worker-redirect.html";
+
+ var tests = {
+ 'same-src-worker_redir-same': {
+ id: "same-src-worker_redir-same",
+ file: WORKER_REDIRECT_TEST_FILE,
+ result : "allowed",
+ redir: "same",
+ policy : "default-src 'none'; script-src 'self' 'unsafe-inline'; child-src http://mochi.test:8888"
+ },
+ 'same-src-worker_redir-other': {
+ id: "same-src-worker_redir-other",
+ file: WORKER_REDIRECT_TEST_FILE,
+ result : "blocked",
+ redir: "other",
+ policy : "default-src 'none'; script-src 'self' 'unsafe-inline'; child-src http://mochi.test:8888"
+ },
+ 'star-src-worker_redir-same': {
+ id: "star-src-worker_redir-same",
+ file: WORKER_REDIRECT_TEST_FILE,
+ redir: "same",
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'self' 'unsafe-inline'; child-src *"
+ },
+ 'other-src-worker_redir-same': {
+ id: "other-src-worker_redir-same",
+ file: WORKER_REDIRECT_TEST_FILE,
+ redir: "same",
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'self' 'unsafe-inline'; child-src https://www.example.org"
+ },
+ /* shared workers */
+ 'same-src-shared_worker_redir-same': {
+ id: "same-src-shared_worker_redir-same",
+ file: SHARED_WORKER_REDIRECT_TEST_FILE,
+ result : "allowed",
+ redir: "same",
+ policy : "default-src 'none'; script-src 'self' 'unsafe-inline'; child-src http://mochi.test:8888"
+ },
+ 'same-src-shared_worker_redir-other': {
+ id: "same-src-shared_worker_redir-other",
+ file: SHARED_WORKER_REDIRECT_TEST_FILE,
+ result : "blocked",
+ redir: "other",
+ policy : "default-src 'none'; script-src 'self' 'unsafe-inline'; child-src http://mochi.test:8888"
+ },
+ 'star-src-shared_worker_redir-same': {
+ id: "star-src-shared_worker_redir-same",
+ file: SHARED_WORKER_REDIRECT_TEST_FILE,
+ redir: "same",
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'self' 'unsafe-inline'; child-src *"
+ },
+ 'other-src-shared_worker_redir-same': {
+ id: "other-src-shared_worker_redir-same",
+ file: SHARED_WORKER_REDIRECT_TEST_FILE,
+ redir: "same",
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'self' 'unsafe-inline'; child-src https://www.example.org"
+ },
+ };
+
+ finished = {};
+
+ function recvMessage(ev) {
+ is(ev.data.message, tests[ev.data.id].result, "CSP child-src worker test " + ev.data.id);
+ finished[ev.data.id] = ev.data.message;
+
+ if (Object.keys(finished).length == Object.keys(tests).length) {
+ window.removeEventListener('message', recvMessage);
+ SimpleTest.finish();
+ }
+ }
+
+ window.addEventListener('message', recvMessage, false);
+
+ function loadNextTest() {
+ for (item in tests) {
+ test = tests[item];
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape("tests/dom/security/test/csp/" + test.file);
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(test.policy);
+ // add whether redirect is to same or different
+ src += "&redir=" + escape(test.policy);
+ // add our identifier
+ src += "#" + escape(test.id);
+
+ content = document.getElementById('content');
+ testframe = document.createElement("iframe");
+ testframe.setAttribute('id', test.id);
+ content.appendChild(testframe);
+ testframe.src = src;
+ }
+ }
+
+ // start running the tests
+ loadNextTest();
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/test_child-src_worker.html b/dom/security/test/csp/test_child-src_worker.html
new file mode 100644
index 000000000..7dcbd03f6
--- /dev/null
+++ b/dom/security/test/csp/test_child-src_worker.html
@@ -0,0 +1,148 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1045891</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ </div>
+
+ <script class="testbody" type="text/javascript">
+ /*
+ * Description of the test:
+ * We load a page with a given CSP and verify that child frames and workers are correctly
+ * evaluated through the "child-src" directive.
+ */
+
+ SimpleTest.waitForExplicitFinish();
+
+ var WORKER_TEST_FILE = "file_child-src_worker.html";
+ var SERVICE_WORKER_TEST_FILE = "file_child-src_service_worker.html";
+ var SHARED_WORKER_TEST_FILE = "file_child-src_shared_worker.html";
+
+ var tests = {
+ 'same-src-worker': {
+ id: "same-src-worker",
+ file: WORKER_TEST_FILE,
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src http://mochi.test:8888"
+ },
+ 'same-src-service_worker': {
+ id: "same-src-service_worker",
+ file: SERVICE_WORKER_TEST_FILE,
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src http://mochi.test:8888"
+ },
+ 'same-src-shared_worker': {
+ id: "same-src-shared_worker",
+ file: SHARED_WORKER_TEST_FILE,
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src http://mochi.test:8888"
+ },
+ 'star-src-worker': {
+ id: "star-src-worker",
+ file: WORKER_TEST_FILE,
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src *"
+ },
+ 'star-src-service_worker': {
+ id: "star-src-service_worker",
+ file: SERVICE_WORKER_TEST_FILE,
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src *"
+ },
+ 'star-src-shared_worker': {
+ id: "star-src-shared_worker",
+ file: SHARED_WORKER_TEST_FILE,
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src *"
+ },
+ 'other-src-worker': {
+ id: "other-src-worker",
+ file: WORKER_TEST_FILE,
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src https://www.example.org"
+ },
+ 'other-src-service_worker': {
+ id: "other-src-service_worker",
+ file: SERVICE_WORKER_TEST_FILE,
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src https://www.example.org"
+ },
+ 'other-src-shared_worker': {
+ id: "other-src-shared_worker",
+ file: SHARED_WORKER_TEST_FILE,
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src https://www.example.org"
+ },
+ 'script-src-worker': {
+ id: "script-src-worker",
+ file: WORKER_TEST_FILE,
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'self' 'unsafe-inline'"
+ },
+ 'script-src-service_worker': {
+ id: "script-src-service_worker",
+ file: SERVICE_WORKER_TEST_FILE,
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'self' 'unsafe-inline'"
+ },
+ 'script-src-self-shared_worker': {
+ id: "script-src-self-shared_worker",
+ file: SHARED_WORKER_TEST_FILE,
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'self' 'unsafe-inline'"
+ },
+ };
+
+ finished = {};
+
+ function recvMessage(ev) {
+ is(ev.data.message, tests[ev.data.id].result, "CSP child-src worker test " + ev.data.id);
+ finished[ev.data.id] = ev.data.message;
+
+ if (Object.keys(finished).length == Object.keys(tests).length) {
+ window.removeEventListener('message', recvMessage);
+ SimpleTest.finish();
+ }
+ }
+
+ window.addEventListener('message', recvMessage, false);
+
+ function loadNextTest() {
+ for (item in tests) {
+ test = tests[item];
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape("tests/dom/security/test/csp/" + test.file);
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(test.policy);
+ // add our identifier
+ src += "#" + escape(test.id);
+
+ content = document.getElementById('content');
+ testframe = document.createElement("iframe");
+ testframe.setAttribute('id', test.id);
+ content.appendChild(testframe);
+ testframe.src = src;
+ }
+ }
+
+ onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true]
+ ]}, loadNextTest);
+ };
+
+ // start running the tests
+ //loadNextTest();
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/test_child-src_worker_data.html b/dom/security/test/csp/test_child-src_worker_data.html
new file mode 100644
index 000000000..089d32dbe
--- /dev/null
+++ b/dom/security/test/csp/test_child-src_worker_data.html
@@ -0,0 +1,126 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Bug 1045891</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ </div>
+
+ <script class="testbody" type="text/javascript">
+ /*
+ * Description of the test:
+ * We load a page with a given CSP and verify that child frames and workers are correctly
+ * evaluated through the "child-src" directive.
+ */
+
+ SimpleTest.waitForExplicitFinish();
+
+ var WORKER_TEST_FILE = "file_child-src_worker_data.html";
+ var SHARED_WORKER_TEST_FILE = "file_child-src_shared_worker_data.html";
+
+ var tests = {
+ 'same-src-worker-no-data': {
+ id: "same-src-worker-no-data",
+ file: WORKER_TEST_FILE,
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src 'self'"
+ },
+ 'same-src-worker': {
+ id: "same-src-worker",
+ file: WORKER_TEST_FILE,
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src 'self' data:"
+ },
+ 'same-src-shared_worker-no-data': {
+ id: "same-src-shared_worker-no-data",
+ file: SHARED_WORKER_TEST_FILE,
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src 'self'"
+ },
+ 'same-src-shared_worker': {
+ id: "same-src-shared_worker",
+ file: SHARED_WORKER_TEST_FILE,
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src 'self' data:"
+ },
+ 'star-src-worker': {
+ id: "star-src-worker",
+ file: WORKER_TEST_FILE,
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src * data:"
+ },
+ 'star-src-worker-no-data': {
+ id: "star-src-worker-no-data",
+ file: WORKER_TEST_FILE,
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src *"
+ },
+ 'star-src-shared_worker-no-data': {
+ id: "star-src-shared_worker-no-data",
+ file: SHARED_WORKER_TEST_FILE,
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src *"
+ },
+ 'star-src-shared_worker': {
+ id: "star-src-shared_worker",
+ file: SHARED_WORKER_TEST_FILE,
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src * data:"
+ },
+ 'other-src-worker-no-data': {
+ id: "other-src-worker-no-data",
+ file: WORKER_TEST_FILE,
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src https://www.example.org"
+ },
+ 'other-src-shared_worker-no-data': {
+ id: "other-src-shared_worker-no-data",
+ file: SHARED_WORKER_TEST_FILE,
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; child-src https://www.example.org"
+ },
+ };
+
+ finished = {};
+
+ function recvMessage(ev) {
+ is(ev.data.message, tests[ev.data.id].result, "CSP child-src worker test " + ev.data.id);
+ finished[ev.data.id] = ev.data.message;
+
+ if (Object.keys(finished).length == Object.keys(tests).length) {
+ window.removeEventListener('message', recvMessage);
+ SimpleTest.finish();
+ }
+ }
+
+ window.addEventListener('message', recvMessage, false);
+
+ function loadNextTest() {
+ for (item in tests) {
+ test = tests[item];
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape("tests/dom/security/test/csp/" + test.file);
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(test.policy);
+ // add our identifier
+ src += "#" + escape(test.id);
+
+ content = document.getElementById('content');
+ testframe = document.createElement("iframe");
+ testframe.setAttribute('id', test.id);
+ content.appendChild(testframe);
+ testframe.src = src;
+ }
+ }
+
+ // start running the tests
+ loadNextTest();
+ </script>
+ </body>
+</html>
diff --git a/dom/security/test/csp/test_connect-src.html b/dom/security/test/csp/test_connect-src.html
new file mode 100644
index 000000000..5e42d9838
--- /dev/null
+++ b/dom/security/test/csp/test_connect-src.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1031530 and Bug 1139667 - Test mapping of XMLHttpRequest and fetch() to connect-src</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ <iframe style="width:100%;" id="testframe"></iframe>
+ </div>
+
+<script class="testbody" type="text/javascript">
+
+/*
+ * Description of the test:
+ * We load a page with a given CSP and verify that XMLHttpRequests and fetches are correctly
+ * evaluated through the "connect-src" directive. All XMLHttpRequests are served
+ * using http://mochi.test:8888, which allows the requests to succeed for the first
+ * two policies and to fail for the last policy. Please note that we have to add
+ * 'unsafe-inline' so we can run the JS test code in file_connect-src.html.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+var tests = [
+ {
+ file: "file_connect-src.html",
+ result : "allowed",
+ policy : "default-src 'none' script-src 'unsafe-inline'; connect-src http://mochi.test:8888"
+ },
+ {
+ file: "file_connect-src.html",
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; connect-src *"
+ },
+ {
+ file: "file_connect-src.html",
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; connect-src http://www.example.com"
+ },
+ {
+ file: "file_connect-src-fetch.html",
+ result : "allowed",
+ policy : "default-src 'none' script-src 'unsafe-inline'; connect-src http://mochi.test:8888"
+ },
+ {
+ file: "file_connect-src-fetch.html",
+ result : "allowed",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; connect-src *"
+ },
+ {
+ file: "file_connect-src-fetch.html",
+ result : "blocked",
+ policy : "default-src 'none'; script-src 'unsafe-inline'; connect-src http://www.example.com"
+ }
+];
+
+// initializing to -1 so we start at index 0 when we start the test
+var counter = -1;
+
+function checkResult(aResult) {
+ is(aResult, tests[counter].result, "should be " + tests[counter].result + " in test " + counter + "!");
+ loadNextTest();
+}
+
+// We use the examiner to identify requests that hit the wire and requests
+// that are blocked by CSP and bubble up the result to the including iframe
+// document (parent).
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ if (topic === "specialpowers-http-notify-request") {
+ // making sure we do not bubble a result for something other
+ // then the request in question.
+ if (!data.includes("file_testserver.sjs?foo")) {
+ return;
+ }
+ checkResult("allowed");
+ }
+
+ if (topic === "csp-on-violate-policy") {
+ // making sure we do not bubble a result for something other
+ // then the request in question.
+ var asciiSpec = SpecialPowers.getPrivilegedProps(
+ SpecialPowers.do_QueryInterface(subject, "nsIURI"),
+ "asciiSpec");
+
+ if (!asciiSpec.includes("file_testserver.sjs?foo")) {
+ return;
+ }
+ checkResult("blocked");
+ }
+ },
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+window.ConnectSrcExaminer = new examiner();
+
+function loadNextTest() {
+ counter++;
+ if (counter == tests.length) {
+ window.ConnectSrcExaminer.remove();
+ SimpleTest.finish();
+ return;
+ }
+
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape("tests/dom/security/test/csp/" + tests[counter].file);
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(tests[counter].policy);
+
+ document.getElementById("testframe").src = src;
+}
+
+// start running the tests
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_docwrite_meta.html b/dom/security/test/csp/test_docwrite_meta.html
new file mode 100644
index 000000000..26794199a
--- /dev/null
+++ b/dom/security/test/csp/test_docwrite_meta.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 663570 - Implement Content Security Policy via meta tag</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<iframe style="width:100%;" id="writemetacspframe"></iframe>
+<iframe style="width:100%;" id="commentmetacspframe"></iframe>
+
+
+<script class="testbody" type="text/javascript">
+/* Description of the test:
+ * We load two frames, where the first frame does doc.write(meta csp) and
+ * the second does doc.write(comment out meta csp).
+ * We make sure to reuse/invalidate preloads depending on the policy.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+var writemetacspframe = document.getElementById("writemetacspframe");
+var commentmetacspframe = document.getElementById("commentmetacspframe");
+var seenResults = 0;
+
+function checkTestsDone() {
+ seenResults++;
+ if (seenResults < 2) {
+ return;
+ }
+ SimpleTest.finish();
+}
+
+// document.write(<meta csp ...>) should block resources from being included in the doc
+function checkResultsBlocked() {
+ writemetacspframe.removeEventListener('load', checkResultsBlocked, false);
+
+ // stylesheet: default background color within FF is transparent
+ var bgcolor = window.getComputedStyle(writemetacspframe.contentDocument.body)
+ .getPropertyValue("background-color");
+ is(bgcolor, "transparent", "inital background value in FF should be 'transparent'");
+
+ // image: make sure image is blocked
+ var img = writemetacspframe.contentDocument.getElementById("testimage");
+ is(img.width, 0, "image widht should be 0");
+ is(img.height, 0, "image widht should be 0");
+
+ // script: make sure defined variable in external script is undefined
+ is(writemetacspframe.contentDocument.myMetaCSPScript, undefined, "myMetaCSPScript should be 'undefined'");
+
+ checkTestsDone();
+}
+
+// document.write(<--) to comment out meta csp should allow resources to be loaded
+// after the preload failed
+function checkResultsAllowed() {
+ commentmetacspframe.removeEventListener('load', checkResultsAllowed, false);
+
+ // stylesheet: should be applied; bgcolor should be red
+ var bgcolor = window.getComputedStyle(commentmetacspframe.contentDocument.body).getPropertyValue("background-color");
+ is(bgcolor, "rgb(255, 0, 0)", "background should be red/rgb(255, 0, 0)");
+
+ // image: should be completed
+ var img = commentmetacspframe.contentDocument.getElementById("testimage");
+ ok(img.complete, "image should not be loaded");
+
+ // script: defined variable in external script should be accessible
+ is(commentmetacspframe.contentDocument.myMetaCSPScript, "external-JS-loaded", "myMetaCSPScript should be 'external-JS-loaded'");
+
+ checkTestsDone();
+}
+
+// doc.write(meta csp) should should allow preloads but should block actual loads
+writemetacspframe.src = 'file_docwrite_meta.html';
+writemetacspframe.addEventListener('load', checkResultsBlocked, false);
+
+// commenting out a meta CSP should result in loaded image, script, style
+commentmetacspframe.src = 'file_doccomment_meta.html';
+commentmetacspframe.addEventListener('load', checkResultsAllowed, false);
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_dual_header.html b/dom/security/test/csp/test_dual_header.html
new file mode 100644
index 000000000..6d3a35fd0
--- /dev/null
+++ b/dom/security/test/csp/test_dual_header.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1036399 - Multiple CSP policies should be combined towards an intersection</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ <iframe style="width:100%;" id="testframe"></iframe>
+ </div>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * We have two tests where each tests serves a page using two CSP policies:
+ * a) * default-src 'self'
+ * * default-src 'self' 'unsafe-inline'
+ *
+ * b) * default-src 'self' 'unsafe-inline'
+ * * default-src 'self' 'unsafe-inline'
+ *
+ * We make sure the inline script is *blocked* for test (a) but *allowed* for test (b).
+ * Multiple CSPs should be combined towards an intersection and it shouldn't be possible
+ * to open up (loosen) a CSP policy.
+ */
+
+const TESTS = [
+ { query: "tight", result: "blocked" },
+ { query: "loose", result: "allowed" }
+];
+var testCounter = -1;
+
+function ckeckResult() {
+ try {
+ document.getElementById("testframe").removeEventListener('load', ckeckResult, false);
+ var testframe = document.getElementById("testframe");
+ var divcontent = testframe.contentWindow.document.getElementById('testdiv').innerHTML;
+ is(divcontent, curTest.result, "should be 'blocked'!");
+ }
+ catch (e) {
+ ok(false, "error: could not access content in div container!");
+ }
+ loadNextTest();
+}
+
+function loadNextTest() {
+ testCounter++;
+ if (testCounter >= TESTS.length) {
+ SimpleTest.finish();
+ return;
+ }
+ curTest = TESTS[testCounter];
+ var src = "file_dual_header_testserver.sjs?" + curTest.query;
+ document.getElementById("testframe").addEventListener("load", ckeckResult, false);
+ document.getElementById("testframe").src = src;
+}
+
+SimpleTest.waitForExplicitFinish();
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_evalscript.html b/dom/security/test/csp/test_evalscript.html
new file mode 100644
index 000000000..f0ec3407c
--- /dev/null
+++ b/dom/security/test/csp/test_evalscript.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Content Security Policy "no eval" base restriction</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<iframe style="width:100%;height:300px;" id='cspframe'></iframe>
+<iframe style="width:100%;height:300px;" id='cspframe2'></iframe>
+<script class="testbody" type="text/javascript">
+
+var evalScriptsThatRan = 0;
+var evalScriptsBlocked = 0;
+var evalScriptsTotal = 17;
+
+// called by scripts that run
+var scriptRan = function(shouldrun, testname, data) {
+ evalScriptsThatRan++;
+ ok(shouldrun, 'EVAL SCRIPT RAN: ' + testname + '(' + data + ')');
+ checkTestResults();
+}
+
+// called when a script is blocked
+var scriptBlocked = function(shouldrun, testname, data) {
+ evalScriptsBlocked++;
+ ok(!shouldrun, 'EVAL SCRIPT BLOCKED: ' + testname + '(' + data + ')');
+ checkTestResults();
+}
+
+var verifyZeroRetVal = function(val, testname) {
+ ok(val === 0, 'RETURN VALUE SHOULD BE ZERO, was ' + val + ': ' + testname);
+}
+
+// Check to see if all the tests have run
+var checkTestResults = function() {
+ // if any test is incomplete, keep waiting
+ if (evalScriptsTotal - evalScriptsBlocked - evalScriptsThatRan > 0)
+ return;
+
+ // ... otherwise, finish
+ SimpleTest.finish();
+}
+
+//////////////////////////////////////////////////////////////////////
+// set up and go
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('cspframe').src = 'file_evalscript_main.html';
+document.getElementById('cspframe2').src = 'file_evalscript_main_allowed.html';
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_fontloader.html b/dom/security/test/csp/test_fontloader.html
new file mode 100644
index 000000000..cdb177f2a
--- /dev/null
+++ b/dom/security/test/csp/test_fontloader.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1122236 - CSP: Implement block-all-mixed-content</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <!-- Including WindowSnapshot.js so we can take screenshots of containers !-->
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTests()">
+<iframe style="width:100%;" id="baselineframe"></iframe>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the tests:
+ * We load a baselineFrame and compare the testFrame using
+ * compareSnapshots whether the font got loaded or blocked.
+ * Test 1: Use font-src 'none' so font gets blocked
+ * Test 2: Use font-src * so font gets loaded
+ * Test 3: Use no csp so font gets loaded
+ * Test 4: Use font-src 'none' so font gets blocked
+ * Makes sure the cache gets invalidated.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+const BASE_URI = "https://example.com/tests/dom/security/test/csp/";
+
+const tests = [
+ { // test 1
+ query: "csp-block",
+ expected: true, // frames should be equal since font is *not* allowed to load
+ description: "font should be blocked by csp (csp-block)"
+ },
+ { // test 2
+ query: "csp-allow",
+ expected: false, // frames should *not* be equal since font is loaded
+ description: "font should load and apply (csp-allow)"
+ },
+ { // test 3
+ query: "no-csp",
+ expected: false, // frames should *not* be equals since font is loaded
+ description: "font should load and apply (no-csp)"
+ },
+ { // test 4
+ query: "csp-block",
+ expected: true, // frames should be equal since font is *not* allowed to load
+ description: "font should be blocked by csp (csp-block) [apply csp to cache]"
+ }
+];
+
+var curTest;
+var counter = -1;
+var baselineframe = document.getElementById("baselineframe");
+var testframe = document.getElementById("testframe");
+
+function checkResult() {
+ testframe.removeEventListener('load', checkResult, false);
+ try {
+ ok(compareSnapshots(snapshotWindow(baselineframe.contentWindow),
+ snapshotWindow(testframe.contentWindow),
+ curTest.expected)[0],
+ curTest.description);
+ } catch(err) {
+ ok(false, "error: " + err.message);
+ }
+ loadNextTest();
+}
+
+function loadNextTest() {
+ counter++;
+ if (counter == tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+ curTest = tests[counter];
+ testframe.addEventListener("load", checkResult, false);
+ testframe.src = BASE_URI + "file_fontloader.sjs?" + curTest.query;
+}
+
+// once the baselineframe is loaded we can start running tests
+function startTests() {
+ baselineframe.removeEventListener('load', startTests, false);
+ loadNextTest();
+}
+
+// make sure the main page is loaded before we start the test
+function setupTests() {
+ baselineframe.addEventListener("load", startTests, false);
+ baselineframe.src = BASE_URI + "file_fontloader.sjs?baseline";
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_form-action.html b/dom/security/test/csp/test_form-action.html
new file mode 100644
index 000000000..b909ca701
--- /dev/null
+++ b/dom/security/test/csp/test_form-action.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 529697 - Test mapping of form submission to form-action</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ <iframe style="width:100%;" id="testframe"></iframe>
+ </div>
+
+<script class="testbody" type="text/javascript">
+
+/*
+ * Description of the test:
+ * We load a page with a given CSP and verify that form submissions are correctly
+ * evaluated through the "form-action" directive.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+var tests = [
+ {
+ page : "file_form-action.html",
+ result : "allowed",
+ policy : "form-action 'self'"
+ },
+ {
+ page : "file_form-action.html",
+ result : "blocked",
+ policy : "form-action 'none'"
+ }
+];
+
+// initializing to -1 so we start at index 0 when we start the test
+var counter = -1;
+
+function checkResult(aResult) {
+ is(aResult, tests[counter].result, "should be " + tests[counter].result + " in test " + counter + "!");
+ loadNextTest();
+}
+
+// We use the examiner to identify requests that hit the wire and requests
+// that are blocked by CSP and bubble up the result to the including iframe
+// document (parent).
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ if (topic === "specialpowers-http-notify-request") {
+ // making sure we do not bubble a result for something other
+ // then the request in question.
+ if (!data.includes("submit-form")) {
+ return;
+ }
+ checkResult("allowed");
+ }
+
+ if (topic === "csp-on-violate-policy") {
+ // making sure we do not bubble a result for something other
+ // then the request in question.
+ var asciiSpec = SpecialPowers.getPrivilegedProps(
+ SpecialPowers.do_QueryInterface(subject, "nsIURI"),
+ "asciiSpec");
+ if (!asciiSpec.includes("submit-form")) {
+ return;
+ }
+ checkResult("blocked");
+ }
+ },
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+window.FormActionExaminer = new examiner();
+
+function loadNextTest() {
+ counter++;
+ if (counter == tests.length) {
+ window.FormActionExaminer.remove();
+ SimpleTest.finish();
+ return;
+ }
+
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape("tests/dom/security/test/csp/" + tests[counter].page);
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(tests[counter].policy);
+
+ document.getElementById("testframe").src = src;
+}
+
+// start running the tests
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_form_action_blocks_url.html b/dom/security/test/csp/test_form_action_blocks_url.html
new file mode 100644
index 000000000..ef5c8d9b4
--- /dev/null
+++ b/dom/security/test/csp/test_form_action_blocks_url.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Bug 1251043 - Test form-action blocks URL</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <iframe id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+/*
+ * Description of the test:
+ * 1) Let's load a form into an iframe which uses a CSP of: form-action 'none';
+ * 2) Let's hit the submit button and make sure the form is not submitted.
+ *
+ * Since a blocked form submission does not fire any event handler, we have to
+ * use timeout triggered function that verifies that the form didn't get submitted.
+ */
+
+SimpleTest.requestFlakyTimeout(
+ "Form submission blocked by CSP does not fire any events " +
+ "hence we have to check back after 300ms to make sure the form " +
+ "is not submitted");
+SimpleTest.waitForExplicitFinish();
+
+const FORM_SUBMITTED = "form submission succeeded";
+var timeOutId;
+var testframe = document.getElementById("testframe");
+
+// In case the form gets submitted, the test would receive an 'load'
+// event and would trigger the test to fail early.
+function logFormSubmittedError() {
+ clearTimeout(timeOutId);
+ testframe.removeEventListener('load', logFormSubmittedError, false);
+ ok(false, "form submission should be blocked");
+ SimpleTest.finish();
+}
+
+// After 300ms we verify the form did not get submitted.
+function verifyFormNotSubmitted() {
+ clearTimeout(timeOutId);
+ var frameContent = testframe.contentWindow.document.body.innerHTML;
+ isnot(frameContent.indexOf("CONTROL-TEXT"), -1,
+ "form should not be submitted and still contain the control text");
+ SimpleTest.finish();
+}
+
+function submitForm() {
+ // Part 1: The form has loaded in the testframe
+ // unregister the current event handler
+ testframe.removeEventListener('load', submitForm, false);
+
+ // Part 2: Register a new load event handler. In case the
+ // form gets submitted, this load event fires and we can
+ // fail the test right away.
+ testframe.addEventListener("load", logFormSubmittedError, false);
+
+ // Part 3: Since blocking the form does not throw any kind of error;
+ // Firefox just logs the CSP error to the console we have to register
+ // this timeOut function which then verifies that the form didn't
+ // get submitted.
+ timeOutId = setTimeout(verifyFormNotSubmitted, 300);
+
+ // Part 4: We are ready, let's hit the submit button of the form.
+ var submitButton = testframe.contentWindow.document.getElementById('submitButton');
+ submitButton.click();
+}
+
+testframe.addEventListener("load", submitForm, false);
+testframe.src = "file_form_action_server.sjs?loadframe";
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_frameancestors.html b/dom/security/test/csp/test_frameancestors.html
new file mode 100644
index 000000000..8ccbc8156
--- /dev/null
+++ b/dom/security/test/csp/test_frameancestors.html
@@ -0,0 +1,157 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Content Security Policy Frame Ancestors directive</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<iframe style="width:100%;height:300px;" id='cspframe'></iframe>
+<script class="testbody" type="text/javascript">
+
+// These are test results: -1 means it hasn't run,
+// true/false is the pass/fail result.
+var framesThatShouldLoad = {
+ aa_allow: -1, /* innermost frame allows a *
+ //aa_block: -1, /* innermost frame denies a */
+ ab_allow: -1, /* innermost frame allows a */
+ //ab_block: -1, /* innermost frame denies a */
+ aba_allow: -1, /* innermost frame allows b,a */
+ //aba_block: -1, /* innermost frame denies b */
+ //aba2_block: -1, /* innermost frame denies a */
+ abb_allow: -1, /* innermost frame allows b,a */
+ //abb_block: -1, /* innermost frame denies b */
+ //abb2_block: -1, /* innermost frame denies a */
+};
+
+// we normally expect _6_ violations (6 test cases that cause blocks), but many
+// of the cases cause violations due to the // origin of the test harness (same
+// as 'a'). When the violation is cross-origin, the URI passed to the observers
+// is null (see bug 846978). This means we can't tell if it's part of the test
+// case or if it is the test harness frame (also origin 'a').
+// As a result, we'll get an extra violation for the following cases:
+// ab_block "frame-ancestors 'none'" (outer frame and test harness)
+// aba2_block "frame-ancestors b" (outer frame and test harness)
+// abb2_block "frame-ancestors b" (outer frame and test harness)
+//
+// and while we can detect the test harness check for this one case since
+// the violations are not cross-origin and we get the URI:
+// aba2_block "frame-ancestors b" (outer frame and test harness)
+//
+// we can't for these other ones:
+// ab_block "frame-ancestors 'none'" (outer frame and test harness)
+// abb2_block "frame-ancestors b" (outer frame and test harness)
+//
+// so that results in 2 extra violation notifications due to the test harness.
+// expected = 6, total = 8
+//
+// Number of tests that pass for this file should be 12 (8 violations 4 loads)
+var expectedViolationsLeft = 8;
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire.
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+}
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ // subject should be an nsURI... though could be null since CSP
+ // prohibits cross-origin URI reporting during frame ancestors checks.
+ if (subject && !SpecialPowers.can_QI(subject))
+ return;
+
+ var asciiSpec = subject;
+
+ try {
+ asciiSpec = SpecialPowers.getPrivilegedProps(
+ SpecialPowers.do_QueryInterface(subject, "nsIURI"),
+ "asciiSpec");
+
+ // skip checks on the test harness -- can't do this skipping for
+ // cross-origin blocking since the observer doesn't get the URI. This
+ // can cause this test to over-succeed (but only in specific cases).
+ if (asciiSpec.includes("test_frameancestors.html")) {
+ return;
+ }
+ } catch (ex) {
+ // was not an nsIURI, so it was probably a cross-origin report.
+ }
+
+
+ if (topic === "csp-on-violate-policy") {
+ //these were blocked... record that they were blocked
+ window.frameBlocked(asciiSpec, data);
+ }
+ },
+
+ // must eventually call this to remove the listener,
+ // or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ }
+}
+
+// called when a frame is loaded
+// -- if it's not enumerated above, it should not load!
+var frameLoaded = function(testname, uri) {
+ //test already complete.... forget it... remember the first result.
+ if (window.framesThatShouldLoad[testname] != -1)
+ return;
+
+ if (typeof window.framesThatShouldLoad[testname] === 'undefined') {
+ // uh-oh, we're not expecting this frame to load!
+ ok(false, testname + ' framed site should not have loaded: ' + uri);
+ } else {
+ framesThatShouldLoad[testname] = true;
+ ok(true, testname + ' framed site when allowed by csp (' + uri + ')');
+ }
+ checkTestResults();
+}
+
+// called when a frame is blocked
+// -- we can't determine *which* frame was blocked, but at least we can count them
+var frameBlocked = function(uri, policy) {
+ ok(true, 'a CSP policy blocked frame from being loaded: ' + policy);
+ expectedViolationsLeft--;
+ checkTestResults();
+}
+
+
+// Check to see if all the tests have run
+var checkTestResults = function() {
+ // if any test is incomplete, keep waiting
+ for (var v in framesThatShouldLoad)
+ if(window.framesThatShouldLoad[v] == -1)
+ return;
+
+ if (window.expectedViolationsLeft > 0)
+ return;
+
+ // ... otherwise, finish
+ window.examiner.remove();
+ SimpleTest.finish();
+}
+
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event) {
+ if (event.data.call && event.data.call == 'frameLoaded')
+ frameLoaded(event.data.testname, event.data.uri);
+}
+
+//////////////////////////////////////////////////////////////////////
+// set up and go
+window.examiner = new examiner();
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('cspframe').src = 'file_frameancestors_main.html';
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_hash_source.html b/dom/security/test/csp/test_hash_source.html
new file mode 100644
index 000000000..6dde11dac
--- /dev/null
+++ b/dom/security/test/csp/test_hash_source.html
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test CSP 1.1 hash-source for inline scripts and styles</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="visibility:hidden">
+ <iframe style="width:100%;" id='cspframe'></iframe>
+</div>
+<script class="testbody" type="text/javascript">
+
+function cleanup() {
+ // finish the tests
+ SimpleTest.finish();
+}
+
+function checkInline () {
+ var cspframe = document.getElementById('cspframe').contentDocument;
+
+ var inlineScriptTests = {
+ 'inline-script-valid-hash': {
+ shouldBe: 'allowed',
+ message: 'Inline script with valid hash should be allowed'
+ },
+ 'inline-script-invalid-hash': {
+ shouldBe: 'blocked',
+ message: 'Inline script with invalid hash should be blocked'
+ },
+ 'inline-script-invalid-hash-valid-nonce': {
+ shouldBe: 'allowed',
+ message: 'Inline script with invalid hash and valid nonce should be allowed'
+ },
+ 'inline-script-valid-hash-invalid-nonce': {
+ shouldBe: 'allowed',
+ message: 'Inline script with valid hash and invalid nonce should be allowed'
+ },
+ 'inline-script-invalid-hash-invalid-nonce': {
+ shouldBe: 'blocked',
+ message: 'Inline script with invalid hash and invalid nonce should be blocked'
+ },
+ 'inline-script-valid-sha512-hash': {
+ shouldBe: 'allowed',
+ message: 'Inline script with a valid sha512 hash should be allowed'
+ },
+ 'inline-script-valid-sha384-hash': {
+ shouldBe: 'allowed',
+ message: 'Inline script with a valid sha384 hash should be allowed'
+ },
+ 'inline-script-valid-sha1-hash': {
+ shouldBe: 'blocked',
+ message: 'Inline script with a valid sha1 hash should be blocked, because sha1 is not a valid hash function'
+ },
+ 'inline-script-valid-md5-hash': {
+ shouldBe: 'blocked',
+ message: 'Inline script with a valid md5 hash should be blocked, because md5 is not a valid hash function'
+ }
+ }
+
+ for (testId in inlineScriptTests) {
+ var test = inlineScriptTests[testId];
+ is(cspframe.getElementById(testId).innerHTML, test.shouldBe, test.message);
+ }
+
+ // Inline style tries to change an element's color to green. If blocked, the
+ // element's color will be the default black.
+ var green = "rgb(0, 128, 0)";
+ var black = "rgb(0, 0, 0)";
+
+ var getElementColorById = function (id) {
+ return window.getComputedStyle(cspframe.getElementById(id), null).color;
+ };
+
+ var inlineStyleTests = {
+ 'inline-style-valid-hash': {
+ shouldBe: green,
+ message: 'Inline style with valid hash should be allowed'
+ },
+ 'inline-style-invalid-hash': {
+ shouldBe: black,
+ message: 'Inline style with invalid hash should be blocked'
+ },
+ 'inline-style-invalid-hash-valid-nonce': {
+ shouldBe: green,
+ message: 'Inline style with invalid hash and valid nonce should be allowed'
+ },
+ 'inline-style-valid-hash-invalid-nonce': {
+ shouldBe: green,
+ message: 'Inline style with valid hash and invalid nonce should be allowed'
+ },
+ 'inline-style-invalid-hash-invalid-nonce' : {
+ shouldBe: black,
+ message: 'Inline style with invalid hash and invalid nonce should be blocked'
+ },
+ 'inline-style-valid-sha512-hash': {
+ shouldBe: green,
+ message: 'Inline style with a valid sha512 hash should be allowed'
+ },
+ 'inline-style-valid-sha384-hash': {
+ shouldBe: green,
+ message: 'Inline style with a valid sha384 hash should be allowed'
+ },
+ 'inline-style-valid-sha1-hash': {
+ shouldBe: black,
+ message: 'Inline style with a valid sha1 hash should be blocked, because sha1 is not a valid hash function'
+ },
+ 'inline-style-valid-md5-hash': {
+ shouldBe: black,
+ message: 'Inline style with a valid md5 hash should be blocked, because md5 is not a valid hash function'
+ }
+ }
+
+ for (testId in inlineStyleTests) {
+ var test = inlineStyleTests[testId];
+ is(getElementColorById(testId), test.shouldBe, test.message);
+ }
+
+ cleanup();
+}
+
+//////////////////////////////////////////////////////////////////////
+// set up and go
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('cspframe').src = 'file_hash_source.html';
+document.getElementById('cspframe').addEventListener('load', checkInline, false);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_iframe_sandbox.html b/dom/security/test/csp/test_iframe_sandbox.html
new file mode 100644
index 000000000..f79f94135
--- /dev/null
+++ b/dom/security/test/csp/test_iframe_sandbox.html
@@ -0,0 +1,239 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=671389
+Bug 671389 - Implement CSP sandbox directive
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 671389</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ // Check if two sandbox flags are the same, ignoring case-sensitivity.
+ // getSandboxFlags returns a list of sandbox flags (if any) or
+ // null if the flag is not set.
+ // This function checks if two flags are the same, i.e., they're
+ // either not set or have the same flags.
+ function eqFlags(a, b) {
+ if (a === null && b === null) { return true; }
+ if (a === null || b === null) { return false; }
+ if (a.length !== b.length) { return false; }
+ var a_sorted = a.map(function(e) { return e.toLowerCase(); }).sort();
+ var b_sorted = b.map(function(e) { return e.toLowerCase(); }).sort();
+ for (var i in a_sorted) {
+ if (a_sorted[i] !== b_sorted[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Get the sandbox flags of document doc.
+ // If the flag is not set sandboxFlagsAsString returns null,
+ // this function also returns null.
+ // If the flag is set it may have some flags; in this case
+ // this function returns the (potentially empty) list of flags.
+ function getSandboxFlags(doc) {
+ var flags = doc.sandboxFlagsAsString;
+ if (flags === null) { return null; }
+ return flags? flags.split(" "):[];
+ }
+
+ // Constructor for a CSP sandbox flags test. The constructor
+ // expectes a description 'desc' and set of options 'opts':
+ // - sandboxAttribute: [null] or string corresponding to the iframe sandbox attributes
+ // - csp: [null] or string corresponding to the CSP sandbox flags
+ // - cspReportOnly: [null] or string corresponding to the CSP report-only sandbox flags
+ // - file: [null] or string corresponding to file the server should serve
+ // Above, we use [brackets] to denote default values.
+ function CSPFlagsTest(desc, opts) {
+ function ifundef(x, v) {
+ return (x !== undefined) ? x : v;
+ }
+
+ function intersect(as, bs) { // Intersect two csp attributes:
+ as = as === null ? null
+ : as.split(' ').filter(function(x) { return !!x; });
+ bs = bs === null ? null
+ : bs.split(' ').filter(function(x) { return !!x; });
+
+ if (as === null) { return bs; }
+ if (bs === null) { return as; }
+
+ var cs = [];
+ as.forEach(function(a) {
+ if (a && bs.indexOf(a) != -1)
+ cs.push(a);
+ });
+ return cs;
+ }
+
+ this.desc = desc || "Untitled test";
+ this.attr = ifundef(opts.sandboxAttribute, null);
+ this.csp = ifundef(opts.csp, null);
+ this.cspRO = ifundef(opts.cspReportOnly, null);
+ this.file = ifundef(opts.file, null);
+ this.expected = intersect(this.attr, this.csp);
+ }
+
+ // Return function that checks that the actual flags are the same as the
+ // expected flags
+ CSPFlagsTest.prototype.checkFlags = function(iframe) {
+ var this_ = this;
+ return function() {
+ try {
+ var actual = getSandboxFlags(SpecialPowers.wrap(iframe).contentDocument);
+ ok(eqFlags(actual, this_.expected),
+ this_.desc, 'expected: "' + this_.expected + '", got: "' + actual + '"');
+ } catch (e) {
+ ok(false, this_.desc, 'expected: "' + this_.expected + '", failed with: "' + e + '"');
+ }
+ runNextTest();
+ };
+ };
+
+ // Set the iframe src and sandbox attribute
+ CSPFlagsTest.prototype.runTest = function () {
+ var iframe = document.createElement('iframe');
+ document.getElementById("content").appendChild(iframe);
+ iframe.onload = this.checkFlags(iframe);
+
+ // set sandbox attribute
+ if (this.attr === null) {
+ iframe.removeAttribute('sandbox');
+ } else {
+ iframe.sandbox = this.attr;
+ }
+
+ // set query string
+ var src = 'http://mochi.test:8888/tests/dom/security/test/csp/file_testserver.sjs';
+
+ var delim = '?';
+
+ if (this.csp !== null) {
+ src += delim + 'csp=' + escape('sandbox ' + this.csp);
+ delim = '&';
+ }
+
+ if (this.cspRO !== null) {
+ src += delim + 'cspRO=' + escape('sandbox ' + this.cspRO);
+ delim = '&';
+ }
+
+ if (this.file !== null) {
+ src += delim + 'file=' + escape(this.file);
+ delim = '&';
+ }
+
+ iframe.src = src;
+ iframe.width = iframe.height = 10;
+
+ }
+
+ testCases = [
+ {
+ desc: "Test 1: Header should not override attribute",
+ sandboxAttribute: "",
+ csp: "allow-forms aLLOw-POinter-lock alLOW-popups aLLOW-SAME-ORIGin ALLOW-SCRIPTS allow-top-navigation"
+ },
+ {
+ desc: "Test 2: Attribute should not override header",
+ sandboxAttribute: "sandbox allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-top-navigation",
+ csp: ""
+ },
+ {
+ desc: "Test 3: Header and attribute intersect",
+ sandboxAttribute: "allow-same-origin allow-scripts",
+ csp: "allow-forms allow-same-origin allow-scripts"
+ },
+ {
+ desc: "Test 4: CSP sandbox sets the right flags (pt 1)",
+ csp: "alLOW-FORms ALLOW-pointer-lock allow-popups allow-same-origin allow-scripts ALLOW-TOP-NAVIGation"
+ },
+ {
+ desc: "Test 5: CSP sandbox sets the right flags (pt 2)",
+ csp: "allow-same-origin allow-TOP-navigation"
+ },
+ {
+ desc: "Test 6: CSP sandbox sets the right flags (pt 3)",
+ csp: "allow-FORMS ALLOW-scripts"
+ },
+ {
+ desc: "Test 7: CSP sandbox sets the right flags (pt 4)",
+ csp: ""
+ },
+ {
+ desc: "Test 8: CSP sandbox sets the right flags (pt 5)",
+ csp: null
+ },
+ {
+ desc: "Test 9: Read-only header should not override attribute",
+ sandboxAttribute: "",
+ cspReportOnly: "allow-forms ALLOW-pointer-lock allow-POPUPS allow-same-origin ALLOW-scripts allow-top-NAVIGATION"
+ },
+ {
+ desc: "Test 10: Read-only header should not override CSP header",
+ csp: "allow-forms allow-scripts",
+ cspReportOnly: "allow-forms aLlOw-PoInTeR-lOcK aLLow-pOPupS aLLoW-SaME-oRIgIN alLow-scripts allow-tOp-navigation"
+ },
+ {
+ desc: "Test 11: Read-only header should not override attribute or CSP header",
+ sandboxAttribute: "allow-same-origin allow-scripts",
+ csp: "allow-forms allow-same-origin allow-scripts",
+ cspReportOnly: "allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-top-navigation"
+ },
+ {
+ desc: "Test 12: CSP sandbox not affected by document.write()",
+ csp: "allow-scripts",
+ file: 'tests/dom/security/test/csp/file_iframe_sandbox_document_write.html'
+ },
+ ].map(function(t) { return (new CSPFlagsTest(t.desc,t)); });
+
+
+ var testCaseIndex = 0;
+
+ // Track ok messages from iframes
+ var childMessages = 0;
+ var totalChildMessages = 1;
+
+
+ // Check to see if we ran all the tests and received all messges
+ // from child iframes. If so, finish.
+ function tryFinish() {
+ if (testCaseIndex === testCases.length && childMessages === totalChildMessages){
+ SimpleTest.finish();
+ }
+ }
+
+ function runNextTest() {
+
+ tryFinish();
+
+ if (testCaseIndex < testCases.length) {
+ testCases[testCaseIndex].runTest();
+ testCaseIndex++;
+ }
+ }
+
+ function receiveMessage(event) {
+ ok(event.data.ok, event.data.desc);
+ childMessages++;
+ tryFinish();
+ }
+
+ window.addEventListener("message", receiveMessage, false);
+
+ addLoadEvent(runNextTest);
+</script>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=671389">Mozilla Bug 671389</a> - Implement CSP sandbox directive
+ <p id="display"></p>
+ <div id="content">
+ </div>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_iframe_sandbox_srcdoc.html b/dom/security/test/csp/test_iframe_sandbox_srcdoc.html
new file mode 100644
index 000000000..53beafcac
--- /dev/null
+++ b/dom/security/test/csp/test_iframe_sandbox_srcdoc.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1073952 - CSP should restrict scripts in srcdoc iframe even if sandboxed</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display">Bug 1073952</p>
+<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
+<script class="testbody" type="text/javascript">
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire.
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+}
+
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+
+ if(topic === "csp-on-violate-policy") {
+ var violationString = SpecialPowers.getPrivilegedProps(SpecialPowers.
+ do_QueryInterface(subject, "nsISupportsCString"), "data");
+ // the violation subject for inline script violations is unfortunately vague,
+ // all we can do is match the string.
+ if (!violationString.includes("Inline Script")) {
+ return
+ }
+ ok(true, "CSP inherited into sandboxed srcdoc iframe, script blocked.");
+ window.testFinished();
+ }
+ },
+
+ // must eventually call this to remove the listener,
+ // or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ }
+}
+
+window.examiner = new examiner();
+
+function testFinished() {
+ window.examiner.remove();
+ SimpleTest.finish();
+}
+
+addEventListener("message", function(e) {
+ ok(false, "We should not execute JS in srcdoc iframe.");
+ window.testFinished();
+})
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('cspframe').src = 'file_iframe_sandbox_srcdoc.html';
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_iframe_sandbox_top_1.html b/dom/security/test/csp/test_iframe_sandbox_top_1.html
new file mode 100644
index 000000000..d9ba71824
--- /dev/null
+++ b/dom/security/test/csp/test_iframe_sandbox_top_1.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=671389
+Bug 671389 - Implement CSP sandbox directive
+
+Tests CSP sandbox attribute on top-level page.
+
+Minimal flags: allow-same-origin allow-scripts:
+Since we need to load the SimpleTest files, we have to set the
+allow-same-origin flag. Additionally, we set the allow-scripts flag
+since we need JS to check the flags.
+
+Though not necessary, for this test we also set the allow-forms flag.
+We may later wish to extend the testing suite with sandbox_csp_top_*
+tests that set different permutations of the flags.
+
+CSP header: Content-Security-Policy: sandbox allow-forms allow-scripts allow-same-origin
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 671389</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// Check if two sandbox flags are the same.
+// getSandboxFlags returns a list of sandbox flags (if any) or
+// null if the flag is not set.
+// This function checks if two flags are the same, i.e., they're
+// either not set or have the same flags.
+function eqFlags(a, b) {
+ if (a === null && b === null) { return true; }
+ if (a === null || b === null) { return false; }
+ if (a.length !== b.length) { return false; }
+ var a_sorted = a.sort();
+ var b_sorted = b.sort();
+ for (var i in a_sorted) {
+ if (a_sorted[i] !== b_sorted[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Get the sandbox flags of document doc.
+// If the flag is not set sandboxFlagsAsString returns null,
+// this function also returns null.
+// If the flag is set it may have some flags; in this case
+// this function returns the (potentially empty) list of flags.
+function getSandboxFlags(doc) {
+ var flags = doc.sandboxFlagsAsString;
+ if (flags === null) { return null; }
+ return flags? flags.split(" "):[];
+}
+
+function checkFlags(expected) {
+ try {
+ var flags = getSandboxFlags(SpecialPowers.wrap(document));
+ ok(eqFlags(flags, expected), name + ' expected: "' + expected + '", got: "' + flags + '"');
+ } catch (e) {
+ ok(false, name + ' expected "' + expected + ', but failed with ' + e);
+ }
+ SimpleTest.finish();
+}
+
+</script>
+
+<body onLoad='checkFlags(["allow-forms", "allow-scripts", "allow-same-origin"]);'>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=671389">Mozilla Bug 671389</a> - Implement CSP sandbox directive
+<p id="display"></p>
+<div id="content">
+ I am a top-level page sandboxed with "allow-scripts allow-forms
+ allow-same-origin".
+</div>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_iframe_sandbox_top_1.html^headers^ b/dom/security/test/csp/test_iframe_sandbox_top_1.html^headers^
new file mode 100644
index 000000000..d9cd0606e
--- /dev/null
+++ b/dom/security/test/csp/test_iframe_sandbox_top_1.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: sAnDbOx aLLow-FOrms aLlOw-ScRiPtS ALLOW-same-origin
diff --git a/dom/security/test/csp/test_iframe_srcdoc.html b/dom/security/test/csp/test_iframe_srcdoc.html
new file mode 100644
index 000000000..95b924a5e
--- /dev/null
+++ b/dom/security/test/csp/test_iframe_srcdoc.html
@@ -0,0 +1,140 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1073952 - Test CSP enforcement within iframe srcdoc</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/*
+ * Description of the test:
+ * (1) We serve a site which makes use of script-allowed sandboxed iframe srcdoc
+ * and make sure that CSP applies to the nested browsing context
+ * within the iframe.
+ * [PAGE WITH CSP [IFRAME SANDBOX SRCDOC [SCRIPT]]]
+ *
+ * (2) We serve a site which nests script within an script-allowed sandboxed
+ * iframe srcdoc within another script-allowed sandboxed iframe srcdoc and
+ * make sure that CSP applies to the nested browsing context
+ * within the iframe*s*.
+ * [PAGE WITH CSP [IFRAME SANDBOX SRCDOC [IFRAME SANDBOX SRCDOC [SCRIPT]]]]
+ *
+ * Please note that the test relies on the "csp-on-violate-policy" observer.
+ * Whenever the script within the iframe is blocked observers are notified.
+ * In turn, this renders the 'result' within tests[] unused. In case the script
+ * would execute however, the postMessageHandler would bubble up 'allowed' and
+ * the test would fail.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+var tests = [
+ // [PAGE *WITHOUT* CSP [IFRAME SRCDOC [SCRIPT]]]
+ { csp: "",
+ result: "allowed",
+ query: "simple_iframe_srcdoc",
+ desc: "No CSP should run script within script-allowed sandboxed iframe srcdoc"
+ },
+ { csp: "script-src https://test1.com",
+ result: "blocked",
+ query: "simple_iframe_srcdoc",
+ desc: "CSP should block script within script-allowed sandboxediframe srcdoc"
+ },
+ // [PAGE *WITHOUT* CSP [IFRAME SRCDOC [IFRAME SRCDOC [SCRIPT]]]]
+ { csp: "",
+ result: "allowed",
+ query: "nested_iframe_srcdoc",
+ desc: "No CSP should run script within script-allowed sandboxed iframe srcdoc nested within another script-allowed sandboxed iframe srcdoc"
+ },
+ // [PAGE WITH CSP [IFRAME SRCDOC ]]
+ { csp: "script-src https://test2.com",
+ result: "blocked",
+ query: "nested_iframe_srcdoc",
+ desc: "CSP should block script within script-allowed sandboxed iframe srcdoc nested within another script-allowed sandboxed iframe srcdoc"
+ },
+ { csp: "",
+ result: "allowed",
+ query: "nested_iframe_srcdoc_datauri",
+ desc: "No CSP, should run script within script-allowed sandboxed iframe src with data URL nested within another script-allowed sandboxed iframe srcdoc"
+ },
+ { csp: "script-src https://test3.com",
+ result: "blocked",
+ query: "nested_iframe_srcdoc_datauri",
+ desc: "CSP should block script within script-allowed sandboxed iframe src with data URL nested within another script-allowed sandboxed iframe srcdoc"
+ },
+
+];
+
+// initializing to -1 so we start at index 0 when we start the test
+var counter = -1;
+
+function finishTest() {
+ window.removeEventListener("message", receiveMessage, false);
+ window.examiner.remove();
+ SimpleTest.finish();
+}
+
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ var result = event.data.result;
+ testComplete(result, tests[counter].result, tests[counter].desc);
+}
+
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+}
+
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ if (topic === "csp-on-violate-policy") {
+ var violationString = SpecialPowers.getPrivilegedProps(SpecialPowers.
+ do_QueryInterface(subject, "nsISupportsCString"), "data");
+ // the violation subject for inline script violations is unfortunately vague,
+ // all we can do is match the string.
+ if (!violationString.includes("Inline Script")) {
+ return
+ }
+ testComplete("blocked", tests[counter].result, tests[counter].desc);
+ }
+ },
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ }
+}
+
+function testComplete(result, expected, desc) {
+ is(result, expected, desc);
+ // ignore cases when we get csp violations and postMessage from the same frame.
+ var frameURL = new URL(document.getElementById("testframe").src);
+ var params = new URLSearchParams(frameURL.search);
+ var counterInFrame = params.get("counter");
+ if (counterInFrame == counter) {
+ loadNextTest();
+ }
+}
+
+function loadNextTest() {
+ counter++;
+ if (counter == tests.length) {
+ finishTest();
+ return;
+ }
+ var src = "file_iframe_srcdoc.sjs";
+ src += "?csp=" + escape(tests[counter].csp);
+ src += "&action=" + escape(tests[counter].query);
+ src += "&counter=" + counter;
+ document.getElementById("testframe").src = src;
+}
+
+// start running the tests
+window.examiner = new examiner();
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_ignore_unsafe_inline.html b/dom/security/test/csp/test_ignore_unsafe_inline.html
new file mode 100644
index 000000000..50f9cbb8d
--- /dev/null
+++ b/dom/security/test/csp/test_ignore_unsafe_inline.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1004703 - ignore 'unsafe-inline' if nonce- or hash-source specified</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/* Description of the test:
+ * We load a page that contains three scripts using different policies
+ * and make sure 'unsafe-inline' is ignored within script-src if hash-source
+ * or nonce-source is specified.
+ *
+ * The expected output of each test is a sequence of chars.
+ * E.g. the default char we expect is 'a', depending on what inline scripts
+ * are allowed to run we also expect 'b', 'c', 'd'.
+ *
+ * The test also covers the handling of multiple policies where the second
+ * policy makes use of a directive that should *not* fallback to
+ * default-src, see Bug 1198422.
+ */
+
+const POLICY_PREFIX = "default-src 'none'; script-src ";
+
+var tests = [
+ {
+ policy1: POLICY_PREFIX + "'unsafe-inline'",
+ policy2: "frame-ancestors 'self'",
+ description: "'unsafe-inline' allows all scripts to execute",
+ file: "file_ignore_unsafe_inline.html",
+ result: "abcd",
+ },
+ {
+ policy1: POLICY_PREFIX + "'unsafe-inline' 'sha256-uJXAPKP5NZxnVMZMUkDofh6a9P3UMRc1CRTevVPS/rI='",
+ policy2: "base-uri http://mochi.test",
+ description: "defining a hash should only allow one script to execute",
+ file: "file_ignore_unsafe_inline.html",
+ result: "ac",
+ },
+ {
+ policy1: POLICY_PREFIX + "'unsafe-inline' 'nonce-FooNonce'",
+ policy2: "form-action 'none'",
+ description: "defining a nonce should only allow one script to execute",
+ file: "file_ignore_unsafe_inline.html",
+ result: "ad",
+ },
+ {
+ policy1: POLICY_PREFIX + "'unsafe-inline' 'sha256-uJXAPKP5NZxnVMZMUkDofh6a9P3UMRc1CRTevVPS/rI=' 'nonce-FooNonce'",
+ policy2: "upgrade-insecure-requests",
+ description: "defining hash and nonce should allow two scripts to execute",
+ file: "file_ignore_unsafe_inline.html",
+ result: "acd",
+ },
+ {
+ policy1: POLICY_PREFIX + "'unsafe-inline' 'sha256-uJXAPKP5NZxnVMZMUkDofh6a9P3UMRc1CRTevVPS/rI=' 'nonce-FooNonce' 'unsafe-inline'",
+ policy2: "referrer origin",
+ description: "defining hash, nonce and 'unsafe-inline' twice should still only allow two scripts to execute",
+ file: "file_ignore_unsafe_inline.html",
+ result: "acd",
+ },
+ {
+ policy1: "default-src 'unsafe-inline' 'sha256-uJXAPKP5NZxnVMZMUkDofh6a9P3UMRc1CRTevVPS/rI=' 'nonce-FooNonce' ",
+ policy2: "sandbox allow-scripts allow-same-origin",
+ description: "unsafe-inline should *not* be ignored within default-src even if hash or nonce is specified",
+ file: "file_ignore_unsafe_inline.html",
+ result: "abcd",
+ },
+];
+
+var counter = 0;
+var curTest;
+
+function loadNextTest() {
+ if (counter == tests.length) {
+ document.getElementById("testframe").removeEventListener("load", test, false);
+ SimpleTest.finish();
+ return;
+ }
+
+ curTest = tests[counter++];
+ var src = "file_ignore_unsafe_inline_multiple_policies_server.sjs?file=";
+ // append the file that should be served
+ src += escape("tests/dom/security/test/csp/" + curTest.file);
+
+ // append the first CSP that should be used to serve the file
+ src += "&csp1=" + escape(curTest.policy1);
+ // append the second CSP that should be used to serve the file
+ src += "&csp2=" + escape(curTest.policy2);
+
+ document.getElementById("testframe").addEventListener("load", test, false);
+ document.getElementById("testframe").src = src;
+}
+
+function test() {
+ try {
+ document.getElementById("testframe").removeEventListener('load', test, false);
+ var testframe = document.getElementById("testframe");
+ var divcontent = testframe.contentWindow.document.getElementById('testdiv').innerHTML;
+ // sort the characters to make sure the result is in ascending order
+ // in case handlers run out of order
+ divcontent = divcontent.split('').sort().join('');
+
+ is(divcontent, curTest.result, curTest.description);
+ }
+ catch (e) {
+ ok(false, "error: could not access content for test " + curTest.description + "!");
+ }
+ loadNextTest();
+}
+
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_inlinescript.html b/dom/security/test/csp/test_inlinescript.html
new file mode 100644
index 000000000..7fd5695d5
--- /dev/null
+++ b/dom/security/test/csp/test_inlinescript.html
@@ -0,0 +1,123 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Content Security Policy Frame Ancestors directive</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<iframe style="width:100%;height:300px;" id='testframe'></iframe>
+
+<script class="testbody" type="text/javascript">
+
+var tests = [
+ {
+ /* test allowed */
+ csp: "default-src 'self'; script-src 'self' 'unsafe-inline'",
+ results: ["body-onload-fired", "text-node-fired",
+ "javascript-uri-fired", "javascript-uri-anchor-fired"],
+ desc: "allow inline scripts",
+ received: 0, // counter to make sure we received all 4 reports
+ },
+ {
+ /* test blocked */
+ csp: "default-src 'self'",
+ results: ["inline-script-blocked"],
+ desc: "block inline scripts",
+ received: 0, // counter to make sure we received all 4 reports
+ }
+];
+
+var counter = 0;
+var curTest;
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire.
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+}
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ if (topic !== "csp-on-violate-policy") {
+ return;
+ }
+
+ var what = SpecialPowers.getPrivilegedProps(SpecialPowers.
+ do_QueryInterface(subject, "nsISupportsCString"), "data");
+
+ if (!what.includes("Inline Script had invalid hash") &&
+ !what.includes("Inline Scripts will not execute")) {
+ return;
+ }
+ window.checkResults("inline-script-blocked");
+ },
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ }
+}
+
+function finishTest() {
+ window.examiner.remove();
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+}
+
+// Check to see if all the tests have run
+var checkResults = function(result) {
+ var index = curTest.results.indexOf(result);
+ isnot(index, -1, "should find result (" + result +") within test: " + curTest.desc);
+ if (index > -1) {
+ curTest.received += 1;
+ }
+
+ // make sure we receive all the 4 reports for the 4 inline scripts
+ if (curTest.received < 4) {
+ return;
+ }
+
+ if (counter < tests.length) {
+ loadNextTest();
+ return;
+ }
+ finishTest();
+}
+
+// a postMessage handler that is used to bubble up results from the testframe
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ checkResults(event.data);
+}
+
+function clickit() {
+ document.getElementById("testframe").removeEventListener('load', clickit, false);
+ var testframe = document.getElementById('testframe');
+ var a = testframe.contentDocument.getElementById('anchortoclick');
+ sendMouseEvent({type:'click'}, a, testframe.contentWindow);
+}
+
+function loadNextTest() {
+ curTest = tests[counter++];
+ var src = "file_testserver.sjs?file=";
+ // append the file that should be served
+ src += escape("tests/dom/security/test/csp/file_inlinescript.html");
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(curTest.csp);
+
+ document.getElementById("testframe").src = src;
+ document.getElementById("testframe").addEventListener("load", clickit, false);
+}
+
+// set up the test and go
+window.examiner = new examiner();
+SimpleTest.waitForExplicitFinish();
+loadNextTest();
+
+</script>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/test_inlinestyle.html b/dom/security/test/csp/test_inlinestyle.html
new file mode 100644
index 000000000..eadcf1e38
--- /dev/null
+++ b/dom/security/test/csp/test_inlinestyle.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Content Security Policy inline stylesheets stuff</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<iframe style="width:100%;height:300px;" id='cspframe1'></iframe>
+<iframe style="width:100%;height:300px;" id='cspframe2'></iframe>
+<script class="testbody" type="text/javascript">
+
+//////////////////////////////////////////////////////////////////////
+// set up and go
+SimpleTest.waitForExplicitFinish();
+
+var done = 0;
+
+// When a CSP 1.0 compliant policy is specified we should block inline
+// styles applied by <style> element, style attribute, and SMIL <animate> and <set> tags
+// (when it's not explicitly allowed.)
+function checkStyles(evt) {
+ var cspframe = document.getElementById('cspframe1');
+ var color;
+
+ // black means the style wasn't applied. green colors are used for styles
+ //expected to be applied. A color is red if a style is erroneously applied
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('linkstylediv'),null)['color'];
+ ok('rgb(0, 255, 0)' === color, 'External Stylesheet (' + color + ')');
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('inlinestylediv'),null)['color'];
+ ok('rgb(0, 0, 0)' === color, 'Inline Style TAG (' + color + ')');
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('attrstylediv'),null)['color'];
+ ok('rgb(0, 0, 0)' === color, 'Style Attribute (' + color + ')');
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('csstextstylediv'),null)['color'];
+ ok('rgb(0, 255, 0)' === color, 'cssText (' + color + ')');
+ // SMIL tests
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('xmlTest',null))['fill'];
+ ok('rgb(0, 0, 0)' === color, 'XML Attribute styling (SMIL) (' + color + ')');
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('cssOverrideTest',null))['fill'];
+ ok('rgb(0, 0, 0)' === color, 'CSS Override styling (SMIL) (' + color + ')');
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('cssOverrideTestById',null))['fill'];
+ ok('rgb(0, 0, 0)' === color, 'CSS Override styling via ID lookup (SMIL) (' + color + ')');
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('cssSetTestById',null))['fill'];
+ ok('rgb(0, 0, 0)' === color, 'CSS Set Element styling via ID lookup (SMIL) (' + color + ')');
+
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('modifycsstextdiv'),null)['color'];
+ ok('rgb(0, 255, 0)' === color, 'Modify loaded style sheet via cssText (' + color + ')');
+
+ checkIfDone();
+}
+
+// When a CSP 1.0 compliant policy is specified we should allow inline
+// styles when it is explicitly allowed.
+function checkStylesAllowed(evt) {
+ var cspframe = document.getElementById('cspframe2');
+ var color;
+
+ // black means the style wasn't applied. green colors are used for styles
+ // expected to be applied. A color is red if a style is erroneously applied
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('linkstylediv'),null)['color'];
+ ok('rgb(0, 255, 0)' === color, 'External Stylesheet (' + color + ')');
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('inlinestylediv'),null)['color'];
+ ok('rgb(0, 255, 0)' === color, 'Inline Style TAG (' + color + ')');
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('attrstylediv'),null)['color'];
+ ok('rgb(0, 255, 0)' === color, 'Style Attribute (' + color + ')');
+
+ // Note that the below test will fail if "script-src: 'unsafe-inline'" breaks,
+ // since it relies on executing script to set .cssText
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('csstextstylediv'),null)['color'];
+ ok('rgb(0, 255, 0)' === color, 'style.cssText (' + color + ')');
+ // SMIL tests
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('xmlTest',null))['fill'];
+ ok('rgb(0, 255, 0)' === color, 'XML Attribute styling (SMIL) (' + color + ')');
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('cssOverrideTest',null))['fill'];
+ ok('rgb(0, 255, 0)' === color, 'CSS Override styling (SMIL) (' + color + ')');
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('cssOverrideTestById',null))['fill'];
+ ok('rgb(0, 255, 0)' === color, 'CSS Override styling via ID lookup (SMIL) (' + color + ')');
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('cssSetTestById',null))['fill'];
+ ok('rgb(0, 255, 0)' === color, 'CSS Set Element styling via ID lookup (SMIL) (' + color + ')');
+
+ color = window.getComputedStyle(cspframe.contentDocument.getElementById('modifycsstextdiv'),null)['color'];
+ ok('rgb(0, 255, 0)' === color, 'Modify loaded style sheet via cssText (' + color + ')');
+
+ checkIfDone();
+}
+
+function checkIfDone() {
+ done++;
+ if (done == 2)
+ SimpleTest.finish();
+}
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('cspframe1').src = 'file_inlinestyle_main.html';
+document.getElementById('cspframe1').addEventListener('load', checkStyles, false);
+document.getElementById('cspframe2').src = 'file_inlinestyle_main_allowed.html';
+document.getElementById('cspframe2').addEventListener('load', checkStylesAllowed, false);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_invalid_source_expression.html b/dom/security/test/csp/test_invalid_source_expression.html
new file mode 100644
index 000000000..ad0b34999
--- /dev/null
+++ b/dom/security/test/csp/test_invalid_source_expression.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1086612 - CSP: Let source expression be the empty set in case no valid source can be parsed</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ <iframe style="width:100%;" id="testframe"></iframe>
+ </div>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/* Description of the test:
+ * We try to parse a policy:
+ * script-src bankid:/*
+ * where the source expression (bankid:/*) is invalid. In that case the source-expression
+ * should be the empty set ('none'), see: http://www.w3.org/TR/CSP11/#source-list-parsing
+ * We confirm that the script is blocked by CSP.
+ */
+
+const policy = "script-src bankid:/*";
+
+function runTest() {
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape("tests/dom/security/test/csp/file_invalid_source_expression.html");
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(policy);
+
+ document.getElementById("testframe").addEventListener("load", test, false);
+ document.getElementById("testframe").src = src;
+}
+
+function test() {
+ try {
+ document.getElementById("testframe").removeEventListener('load', test, false);
+ var testframe = document.getElementById("testframe");
+ var divcontent = testframe.contentWindow.document.getElementById('testdiv').innerHTML;
+ is(divcontent, "blocked", "should be 'blocked'!");
+ }
+ catch (e) {
+ ok(false, "ERROR: could not access content!");
+ }
+ SimpleTest.finish();
+}
+
+runTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_leading_wildcard.html b/dom/security/test/csp/test_leading_wildcard.html
new file mode 100644
index 000000000..81c9f58ec
--- /dev/null
+++ b/dom/security/test/csp/test_leading_wildcard.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1032303 - CSP - Keep FULL STOP when matching *.foo.com to disallow loads from foo.com</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ <iframe style="width:100%;" id="testframe"></iframe>
+ </div>
+
+<script class="testbody" type="text/javascript">
+
+/*
+ * Description of the test:
+ * We load a page with a CSP that allows scripts to be loaded from *.example.com.
+ * On that page we try to load two scripts:
+ * a) [allowed] leading_wildcard_allowed.js which is served from test1.example.com
+ * b) [blocked] leading_wildcard_blocked.js which is served from example.com
+ *
+ * We verify that only the allowed script executes by registering observers which listen
+ * to CSP violations and http-notifications. Please note that both scripts do *not* exist
+ * in the file system. The test just verifies that CSP blocks correctly.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+var policy = "default-src 'none' script-src *.example.com";
+var testsExecuted = 0;
+
+function finishTest() {
+ if (++testsExecuted < 2) {
+ return;
+ }
+ window.wildCardExaminer.remove();
+ SimpleTest.finish();
+}
+
+// We use the examiner to identify requests that hit the wire and requests
+// that are blocked by CSP.
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+
+ // allowed requests
+ if (topic === "specialpowers-http-notify-request") {
+ if (data.includes("leading_wildcard_allowed.js")) {
+ ok (true, "CSP should allow file_leading_wildcard_allowed.js!");
+ finishTest();
+ }
+ if (data.includes("leading_wildcard_blocked.js")) {
+ ok(false, "CSP should not allow file_leading_wildcard_blocked.js!");
+ finishTest();
+ }
+ }
+
+ // blocked requests
+ if (topic === "csp-on-violate-policy") {
+ var asciiSpec = SpecialPowers.getPrivilegedProps(
+ SpecialPowers.do_QueryInterface(subject, "nsIURI"),
+ "asciiSpec");
+
+ if (asciiSpec.includes("leading_wildcard_allowed.js")) {
+ ok (false, "CSP should not block file_leading_wildcard_allowed.js!");
+ finishTest();
+ }
+ if (asciiSpec.includes("leading_wildcard_blocked.js")) {
+ ok (true, "CSP should block file_leading_wildcard_blocked.js!");
+ finishTest();
+ }
+ }
+ },
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+window.wildCardExaminer = new examiner();
+
+function runTest() {
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape("tests/dom/security/test/csp/file_leading_wildcard.html");
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(policy);
+
+ document.getElementById("testframe").src = src;
+}
+
+// start running the tests
+runTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_meta_element.html b/dom/security/test/csp/test_meta_element.html
new file mode 100644
index 000000000..98c12fce8
--- /dev/null
+++ b/dom/security/test/csp/test_meta_element.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 663570 - Implement Content Security Policy via <meta> tag</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<iframe style="width:100%;" id="testframe" src="file_meta_element.html"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * The test is twofold:
+ * First, by loading a page using meta csp (into an iframe) we make sure that
+ * images get correctly blocked as the csp policy includes "img-src 'none'";
+ *
+ * Second, we make sure meta csp ignores the following directives:
+ * * report-uri
+ * * frame-ancestors
+ * * sandbox
+ *
+ * Please note that the CSP sanbdox directive (bug 671389) has not landed yet.
+ * Once bug 671389 lands this test will fail and needs to be updated.
+ */
+
+SimpleTest.waitForExplicitFinish();
+const EXPECTED_DIRS = ["img-src", "script-src"];
+
+function finishTest() {
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+}
+
+function checkResults(result) {
+ is(result, "img-blocked", "loading images should be blocked by meta csp");
+
+ try {
+ // get the csp in JSON notation from the principal
+ var frame = document.getElementById("testframe");
+ var principal = SpecialPowers.wrap(frame.contentDocument).nodePrincipal;
+ var cspJSON = principal.cspJSON;
+ ok(cspJSON, "CSP applied through meta element");
+
+ // parse the cspJSON in a csp-object
+ var cspOBJ = JSON.parse(cspJSON);
+ ok(cspOBJ, "was able to parse the JSON");
+
+ // make sure we only got one policy
+ var policies = cspOBJ["csp-policies"];
+ is(policies.length, 1, "there should be one policy applied");
+
+ // iterate the policy and make sure to only encounter
+ // expected directives.
+ var policy = policies[0];
+ for (var dir in policy) {
+ // special case handling for report-only which is not a directive
+ // but present in the JSON notation of the CSP.
+ if (dir === "report-only") {
+ continue;
+ }
+ var index = EXPECTED_DIRS.indexOf(dir);
+ isnot(index, -1, "meta csp contains directive: " + dir + "!");
+
+ // take the element out of the array so we can make sure
+ // that we have seen all the expected values in the end.
+ EXPECTED_DIRS.splice(index, 1);
+ }
+ is(EXPECTED_DIRS.length, 0, "have seen all the expected values");
+ }
+ catch (e) {
+ ok(false, "uuh, something went wrong within meta csp test");
+ }
+
+ finishTest();
+}
+
+// a postMessage handler used to bubble up the onsuccess/onerror state
+// from within the iframe.
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ checkResults(event.data.result);
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_meta_header_dual.html b/dom/security/test/csp/test_meta_header_dual.html
new file mode 100644
index 000000000..4d6258d6e
--- /dev/null
+++ b/dom/security/test/csp/test_meta_header_dual.html
@@ -0,0 +1,137 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 663570 - Implement Content Security Policy via meta tag</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * We test all sorts of CSPs on documents, including documents with no
+ * CSP, with meta CSP and with meta CSP in combination with a CSP header.
+ */
+
+const TESTS = [
+ {
+ /* load image without any CSP */
+ query: "test1",
+ result: "img-loaded",
+ policyLen: 0,
+ desc: "no CSP should allow load",
+ },
+ {
+ /* load image where meta denies load */
+ query: "test2",
+ result: "img-blocked",
+ policyLen: 1,
+ desc: "meta (img-src 'none') should block load"
+ },
+ {
+ /* load image where meta allows load */
+ query: "test3",
+ result: "img-loaded",
+ policyLen: 1,
+ desc: "meta (img-src http://mochi.test) should allow load"
+ },
+ {
+ /* load image where meta allows but header blocks */
+ query: "test4", // triggers speculative load
+ result: "img-blocked",
+ policyLen: 2,
+ desc: "meta (img-src http://mochi.test), header (img-src 'none') should block load"
+ },
+ {
+ /* load image where meta blocks but header allows */
+ query: "test5", // triggers speculative load
+ result: "img-blocked",
+ policyLen: 2,
+ desc: "meta (img-src 'none'), header (img-src http://mochi.test) should block load"
+ },
+ {
+ /* load image where meta allows and header allows */
+ query: "test6", // triggers speculative load
+ result: "img-loaded",
+ policyLen: 2,
+ desc: "meta (img-src http://mochi.test), header (img-src http://mochi.test) should allow load"
+ },
+ {
+ /* load image where meta1 allows but meta2 blocks */
+ query: "test7",
+ result: "img-blocked",
+ policyLen: 2,
+ desc: "meta1 (img-src http://mochi.test), meta2 (img-src 'none') should allow blocked"
+ },
+ {
+ /* load image where meta1 allows and meta2 allows */
+ query: "test8",
+ result: "img-loaded",
+ policyLen: 2,
+ desc: "meta1 (img-src http://mochi.test), meta2 (img-src http://mochi.test) should allow allowed"
+ },
+];
+
+var curTest;
+var counter = -1;
+
+function finishTest() {
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+}
+
+function checkResults(result) {
+ // make sure the image got loaded or blocked
+ is(result, curTest.result, curTest.query + ": " + curTest.desc);
+
+ if (curTest.policyLen != 0) {
+ // make sure that meta policy got not parsed and appended twice
+ try {
+ // get the csp in JSON notation from the principal
+ var frame = document.getElementById("testframe");
+ var principal = SpecialPowers.wrap(frame.contentDocument).nodePrincipal;
+ var cspJSON = principal.cspJSON;
+ var cspOBJ = JSON.parse(cspJSON);
+
+ // make sure that the speculative policy and the actual policy
+ // are not appended twice.
+ var policies = cspOBJ["csp-policies"];
+ is(policies.length, curTest.policyLen, curTest.query + " should have: " + curTest.policyLen + " policies");
+ }
+ catch (e) {
+ ok(false, "uuh, something went wrong within cspToJSON in " + curTest.query);
+ }
+ }
+ // move on to the next test
+ runNextTest();
+}
+
+// a postMessage handler used to bubble up the
+// onsuccess/onerror state from within the iframe.
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ checkResults(event.data.result);
+}
+
+function runNextTest() {
+ if (++counter == TESTS.length) {
+ finishTest();
+ return;
+ }
+ curTest = TESTS[counter];
+ // load next test
+ document.getElementById("testframe").src = "file_meta_header_dual.sjs?" + curTest.query;
+}
+
+// start the test
+SimpleTest.waitForExplicitFinish();
+runNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_meta_whitespace_skipping.html b/dom/security/test/csp/test_meta_whitespace_skipping.html
new file mode 100644
index 000000000..67c636c3b
--- /dev/null
+++ b/dom/security/test/csp/test_meta_whitespace_skipping.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1261634 - Update whitespace skipping for meta csp</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe" src="file_meta_whitespace_skipping.html"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * We load a site using meta CSP into an iframe. We make sure that all directives
+ * are parsed correclty by the CSP parser even though the directives are separated
+ * not only by whitespace but also by line breaks
+ */
+
+SimpleTest.waitForExplicitFinish();
+const EXPECTED_DIRS = [
+ "img-src", "script-src", "style-src", "child-src", "font-src"];
+
+function finishTest() {
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+}
+
+function checkResults(result) {
+ // sanity check that the site was loaded and the meta csp was parsed.
+ is(result, "meta-csp-parsed", "loading images should be blocked by meta csp");
+
+ try {
+ // get the csp in JSON notation from the principal
+ var frame = document.getElementById("testframe");
+ var principal = SpecialPowers.wrap(frame.contentDocument).nodePrincipal;
+ var cspJSON = principal.cspJSON;
+ ok(cspJSON, "CSP applied through meta element");
+
+ // parse the cspJSON in a csp-object
+ var cspOBJ = JSON.parse(cspJSON);
+ ok(cspOBJ, "was able to parse the JSON");
+
+ // make sure we only got one policy
+ var policies = cspOBJ["csp-policies"];
+ is(policies.length, 1, "there should be one policy applied");
+
+ // iterate the policy and make sure to only encounter
+ // expected directives.
+ var policy = policies[0];
+ for (var dir in policy) {
+ // special case handling for report-only which is not a directive
+ // but present in the JSON notation of the CSP.
+ if (dir === "report-only") {
+ continue;
+ }
+ var index = EXPECTED_DIRS.indexOf(dir);
+ isnot(index, -1, "meta csp contains directive: " + dir + "!");
+
+ // take the element out of the array so we can make sure
+ // that we have seen all the expected values in the end.
+ EXPECTED_DIRS.splice(index, 1);
+ }
+ is(EXPECTED_DIRS.length, 0, "have seen all the expected values");
+ }
+ catch (e) {
+ ok(false, "uuh, something went wrong within meta csp test");
+ }
+
+ finishTest();
+}
+
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ checkResults(event.data.result);
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_multi_policy_injection_bypass.html b/dom/security/test/csp/test_multi_policy_injection_bypass.html
new file mode 100644
index 000000000..83a9fa265
--- /dev/null
+++ b/dom/security/test/csp/test_multi_policy_injection_bypass.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=717511
+-->
+<head>
+ <title>Test for Bug 717511</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+
+</div>
+
+<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
+<iframe style="width:200px;height:200px;" id='cspframe2'></iframe>
+<script class="testbody" type="text/javascript">
+
+var path = "/tests/dom/security/test/csp/";
+
+// These are test results: -1 means it hasn't run,
+// true/false is the pass/fail result.
+// This is not exhaustive, just double-checking the 'self' vs * policy conflict in the two HTTP headers.
+window.tests = {
+ img_good: -1,
+ img_bad: -1,
+ script_good: -1,
+ script_bad: -1,
+ img2_good: -1,
+ img2_bad: -1,
+ script2_good: -1,
+ script2_bad: -1,
+};
+
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire.
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ var testpat = new RegExp("testid=([a-z0-9_]+)");
+
+ //_good things better be allowed!
+ //_bad things better be stopped!
+
+ if (topic === "specialpowers-http-notify-request") {
+ //these things were allowed by CSP
+ var asciiSpec = data;
+ if (!testpat.test(asciiSpec)) return;
+ var testid = testpat.exec(asciiSpec)[1];
+ window.testResult(testid,
+ /_good/.test(testid),
+ asciiSpec + " allowed by csp");
+
+ }
+
+ if(topic === "csp-on-violate-policy") {
+ // subject should be an nsIURI for csp-on-violate-policy
+ if (!SpecialPowers.can_QI(subject)) {
+ return;
+ }
+
+ //these were blocked... record that they were blocked
+ var asciiSpec = SpecialPowers.getPrivilegedProps(
+ SpecialPowers.do_QueryInterface(subject, "nsIURI"),
+ "asciiSpec");
+ if (!testpat.test(asciiSpec)) return;
+ var testid = testpat.exec(asciiSpec)[1];
+ window.testResult(testid,
+ /_bad/.test(testid),
+ asciiSpec + " blocked by \"" + data + "\"");
+ }
+ },
+
+ // must eventually call this to remove the listener,
+ // or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+
+window.examiner = new examiner();
+
+window.testResult = function(testname, result, msg) {
+
+ //test already complete.... forget it... remember the first result.
+ if (window.tests[testname] != -1)
+ return;
+
+ window.tests[testname] = result;
+ is(result, true, testname + ' test: ' + msg);
+
+ // if any test is incomplete, keep waiting
+ for (var v in window.tests)
+ if(tests[v] == -1)
+ return;
+
+ // ... otherwise, finish
+ window.examiner.remove();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('cspframe').src = 'file_multi_policy_injection_bypass.html';
+document.getElementById('cspframe2').src = 'file_multi_policy_injection_bypass_2.html';
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_multipartchannel.html b/dom/security/test/csp/test_multipartchannel.html
new file mode 100644
index 000000000..3dd42783b
--- /dev/null
+++ b/dom/security/test/csp/test_multipartchannel.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1223743 - CSP: Check baseChannel for CSP when loading multipart channel</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/* Description of the test:
+ * We apply a CSP to a multipart channel and then try to load an image
+ * within a segment making sure the image is blocked correctly by CSP.
+ */
+
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ is(event.data, "img-blocked", "image should be blocked");
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+}
+
+// start the test
+document.getElementById("testframe").src = "file_multipart_testserver.sjs?doc";
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_nonce_source.html b/dom/security/test/csp/test_nonce_source.html
new file mode 100644
index 000000000..73a613576
--- /dev/null
+++ b/dom/security/test/csp/test_nonce_source.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test CSP 1.1 nonce-source for scripts and styles</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="visibility:hidden">
+ <iframe style="width:100%;" id='cspframe'></iframe>
+</div>
+<script class="testbody" type="text/javascript">
+
+var testsRun = 0;
+var totalTests = 20;
+
+// This is used to watch the blocked data bounce off CSP
+function examiner() {
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+}
+
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ var testid_re = new RegExp("testid=([a-z0-9_]+)");
+
+ //_good things better be allowed!
+ //_bad things better be blocked!
+
+ if (topic === "specialpowers-http-notify-request") {
+ var uri = data;
+ if (!testid_re.test(uri)) return;
+ var testid = testid_re.exec(uri)[1];
+ ok(/_good/.test(testid), "should allow URI with good testid " + testid);
+ ranTests(1);
+ }
+
+ if (topic === "csp-on-violate-policy") {
+ try {
+ // if it is an blocked external load, subject will be the URI of the resource
+ var blocked_uri = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
+ if (!testid_re.test(blocked_uri)) return;
+ var testid = testid_re.exec(blocked_uri)[1];
+ ok(/_bad/.test(testid), "should block URI with bad testid " + testid);
+ ranTests(1);
+ } catch (e) {
+ // if the subject is blocked inline, data will be a violation message
+ // we can't distinguish which resources triggered these, so we ignore them
+ }
+ }
+ },
+ // must eventually call this to remove the listener, or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ }
+}
+
+function cleanup() {
+ // remove the observer so we don't bork other tests
+ window.examiner.remove();
+ // finish the tests
+ SimpleTest.finish();
+}
+
+function ranTests(num) {
+ testsRun += num;
+ if (testsRun < totalTests) {
+ return;
+ }
+ cleanup();
+}
+
+function checkInlineScriptsAndStyles () {
+ var cspframe = document.getElementById('cspframe');
+ var getElementColorById = function (id) {
+ return window.getComputedStyle(cspframe.contentDocument.getElementById(id), null).color;
+ };
+ // Inline style tries to change an element's color to green. If blocked, the
+ // element's color will be the (unchanged) default black.
+ var green = "rgb(0, 128, 0)";
+ var red = "rgb(255,0,0)";
+ var black = "rgb(0, 0, 0)";
+
+ // inline script tests
+ is(getElementColorById('inline-script-correct-nonce'), green,
+ "Inline script with correct nonce should execute");
+ is(getElementColorById('inline-script-incorrect-nonce'), black,
+ "Inline script with incorrect nonce should not execute");
+ is(getElementColorById('inline-script-correct-style-nonce'), black,
+ "Inline script with correct nonce for styles (but not for scripts) should not execute");
+ is(getElementColorById('inline-script-no-nonce'), black,
+ "Inline script with no nonce should not execute");
+
+ // inline style tests
+ is(getElementColorById('inline-style-correct-nonce'), green,
+ "Inline style with correct nonce should be allowed");
+ is(getElementColorById('inline-style-incorrect-nonce'), black,
+ "Inline style with incorrect nonce should be blocked");
+ is(getElementColorById('inline-style-correct-script-nonce'), black,
+ "Inline style with correct nonce for scripts (but incorrect nonce for styles) should be blocked");
+ is(getElementColorById('inline-style-no-nonce'), black,
+ "Inline style with no nonce should be blocked");
+
+ ranTests(8);
+}
+
+//////////////////////////////////////////////////////////////////////
+// set up and go
+window.examiner = new examiner();
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('cspframe').src = 'file_nonce_source.html';
+document.getElementById('cspframe').addEventListener('load', checkInlineScriptsAndStyles, false);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_null_baseuri.html b/dom/security/test/csp/test_null_baseuri.html
new file mode 100644
index 000000000..4592d582f
--- /dev/null
+++ b/dom/security/test/csp/test_null_baseuri.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1121857 - document.baseURI should not get blocked if baseURI is null</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ <iframe style="width:100%;" id="testframe"></iframe>
+ </div>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * Creating a 'base' element and appending that element
+ * to document.head. After setting baseTag.href and finally
+ * removing the created element from the head, the baseURI
+ * should be the inital baseURI of the page.
+ */
+
+const TOTAL_TESTS = 3;
+var test_counter = 0;
+
+// a postMessage handler to communicate the results back to the parent.
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event)
+{
+ // make sure the base-uri before and after the test is the initial base uri of the page
+ if (event.data.test === "initial_base_uri") {
+ ok(event.data.baseURI.startsWith("http://mochi.test"), "baseURI should be 'http://mochi.test'!");
+ }
+ // check that appending the child and setting the base tag actually affects the base-uri
+ else if (event.data.test === "changed_base_uri") {
+ ok(event.data.baseURI === "http://www.base-tag.com/", "baseURI should be 'http://www.base-tag.com'!");
+ }
+ // we shouldn't get here, but just in case, throw an error.
+ else {
+ ok(false, "unrecognized test!");
+ }
+
+ if (++test_counter === TOTAL_TESTS) {
+ SimpleTest.finish();
+ }
+}
+
+function startTest() {
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape("tests/dom/security/test/csp/file_null_baseuri.html");
+ // using 'unsafe-inline' since we load the testcase using an inline script
+ // within file_null_baseuri.html
+ src += "&csp=" + escape("default-src * 'unsafe-inline';");
+
+ document.getElementById("testframe").src = src;
+}
+
+
+SimpleTest.waitForExplicitFinish();
+startTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_path_matching.html b/dom/security/test/csp/test_path_matching.html
new file mode 100644
index 000000000..e02751803
--- /dev/null
+++ b/dom/security/test/csp/test_path_matching.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 808292 - Implement path-level host-source matching to CSP</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ <iframe style="width:100%;" id="testframe"></iframe>
+ </div>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/* Description of the test:
+ * We are loading the following url (including a fragment portion):
+ * http://test1.example.com/tests/dom/security/test/csp/file_path_matching.js#foo
+ * using different policies and verify that the applied policy is accurately enforced.
+ */
+
+var policies = [
+ ["allowed", "*"],
+ ["allowed", "http://*"], // test for bug 1075230, enforcing scheme and wildcard
+ ["allowed", "test1.example.com"],
+ ["allowed", "test1.example.com/"],
+ ["allowed", "test1.example.com/tests/dom/security/test/csp/"],
+ ["allowed", "test1.example.com/tests/dom/security/test/csp/file_path_matching.js"],
+
+ ["allowed", "test1.example.com?foo=val"],
+ ["allowed", "test1.example.com/?foo=val"],
+ ["allowed", "test1.example.com/tests/dom/security/test/csp/?foo=val"],
+ ["allowed", "test1.example.com/tests/dom/security/test/csp/file_path_matching.js?foo=val"],
+
+ ["allowed", "test1.example.com#foo"],
+ ["allowed", "test1.example.com/#foo"],
+ ["allowed", "test1.example.com/tests/dom/security/test/csp/#foo"],
+ ["allowed", "test1.example.com/tests/dom/security/test/csp/file_path_matching.js#foo"],
+
+ ["allowed", "*.example.com"],
+ ["allowed", "*.example.com/"],
+ ["allowed", "*.example.com/tests/dom/security/test/csp/"],
+ ["allowed", "*.example.com/tests/dom/security/test/csp/file_path_matching.js"],
+
+ ["allowed", "test1.example.com:80"],
+ ["allowed", "test1.example.com:80/"],
+ ["allowed", "test1.example.com:80/tests/dom/security/test/csp/"],
+ ["allowed", "test1.example.com:80/tests/dom/security/test/csp/file_path_matching.js"],
+
+ ["allowed", "test1.example.com:*"],
+ ["allowed", "test1.example.com:*/"],
+ ["allowed", "test1.example.com:*/tests/dom/security/test/csp/"],
+ ["allowed", "test1.example.com:*/tests/dom/security/test/csp/file_path_matching.js"],
+
+ ["blocked", "test1.example.com/tests"],
+ ["blocked", "test1.example.com/tests/dom/security/test/csp"],
+ ["blocked", "test1.example.com/tests/dom/security/test/csp/file_path_matching.py"],
+
+ ["blocked", "test1.example.com:8888/tests"],
+ ["blocked", "test1.example.com:8888/tests/dom/security/test/csp"],
+ ["blocked", "test1.example.com:8888/tests/dom/security/test/csp/file_path_matching.py"],
+
+ // case insensitive matching for scheme and host, but case sensitive matching for paths
+ ["allowed", "HTTP://test1.EXAMPLE.com/tests/"],
+ ["allowed", "test1.EXAMPLE.com/tests/"],
+ ["blocked", "test1.example.com/tests/dom/security/test/CSP/?foo=val"],
+ ["blocked", "test1.example.com/tests/dom/security/test/csp/FILE_path_matching.js?foo=val"],
+]
+
+var counter = 0;
+var policy;
+
+function loadNextTest() {
+ if (counter == policies.length) {
+ SimpleTest.finish();
+ }
+ else {
+ policy = policies[counter++];
+ var src = "file_testserver.sjs?file=";
+ // append the file that should be served
+ src += (counter % 2 == 0)
+ // load url including ref: example.com#foo
+ ? escape("tests/dom/security/test/csp/file_path_matching.html")
+ // load url including query: example.com?val=foo (bug 1147026)
+ : escape("tests/dom/security/test/csp/file_path_matching_incl_query.html");
+
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape("default-src 'none'; script-src " + policy[1]);
+
+ document.getElementById("testframe").addEventListener("load", test, false);
+ document.getElementById("testframe").src = src;
+ }
+}
+
+function test() {
+ try {
+ document.getElementById("testframe").removeEventListener('load', test, false);
+ var testframe = document.getElementById("testframe");
+ var divcontent = testframe.contentWindow.document.getElementById('testdiv').innerHTML;
+ is(divcontent, policy[0], "should be " + policy[0] + " in test " + (counter - 1) + "!");
+ }
+ catch (e) {
+ ok(false, "ERROR: could not access content in test " + (counter - 1) + "!");
+ }
+ loadNextTest();
+}
+
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_path_matching_redirect.html b/dom/security/test/csp/test_path_matching_redirect.html
new file mode 100644
index 000000000..7d1044052
--- /dev/null
+++ b/dom/security/test/csp/test_path_matching_redirect.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 808292 - Implement path-level host-source matching to CSP (redirects)</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ <iframe style="width:100%;" id="testframe"></iframe>
+ </div>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/* Description of the test:
+ * First, we try to load a script where the *path* does not match.
+ * Second, we try to load a script which is allowed by the CSPs
+ * script-src directive. The script then gets redirected to
+ * an URL where the host matches, but the path wouldn't.
+ * Since 'paths' should not be taken into account after redirects,
+ * that load should succeed. We are using a similar test setup
+ * as described in the spec, see:
+ * http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects
+ */
+
+var policy = "script-src http://example.com http://test1.example.com/CSPAllowsScriptsInThatFolder";
+
+var tests = [
+ {
+ // the script in file_path_matching.html
+ // <script src="http://test1.example.com/tests/dom/security/..">
+ // is not within the whitelisted path by the csp-policy
+ // hence the script is 'blocked' by CSP.
+ expected: "blocked",
+ uri: "tests/dom/security/test/csp/file_path_matching.html"
+ },
+ {
+ // the script in file_path_matching_redirect.html
+ // <script src="http://example.com/tests/dom/..">
+ // gets redirected to: http://test1.example.com/tests/dom
+ // where after the redirect the path of the policy is not enforced
+ // anymore and hence execution of the script is 'allowed'.
+ expected: "allowed",
+ uri: "tests/dom/security/test/csp/file_path_matching_redirect.html"
+ },
+];
+
+var counter = 0;
+var curTest;
+
+function checkResult() {
+ try {
+ document.getElementById("testframe").removeEventListener('load', checkResult, false);
+ var testframe = document.getElementById("testframe");
+ var divcontent = testframe.contentWindow.document.getElementById('testdiv').innerHTML;
+ is(divcontent, curTest.expected, "should be blocked in test " + (counter - 1) + "!");
+ }
+ catch (e) {
+ ok(false, "ERROR: could not access content in test " + (counter - 1) + "!");
+ }
+ loadNextTest();
+}
+
+function loadNextTest() {
+ if (counter == tests.length) {
+ SimpleTest.finish();
+ }
+ else {
+ curTest = tests[counter++];
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape(curTest.uri);
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(policy);
+
+ document.getElementById("testframe").addEventListener("load", checkResult, false);
+ document.getElementById("testframe").src = src;
+ }
+}
+
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_ping.html b/dom/security/test/csp/test_ping.html
new file mode 100644
index 000000000..a896794e1
--- /dev/null
+++ b/dom/security/test/csp/test_ping.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1100181 - CSP: Enforce connect-src when submitting pings</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/*
+ * Description of the test:
+ * We load a page with a given CSP and verify that hyperlink auditing
+ * is correctly evaluated through the "connect-src" directive.
+ */
+
+// Need to pref hyperlink auditing on since it's disabled by default.
+SpecialPowers.setBoolPref("browser.send_pings", true);
+
+SimpleTest.waitForExplicitFinish();
+
+var tests = [
+ {
+ result : "allowed",
+ policy : "connect-src 'self'"
+ },
+ {
+ result : "blocked",
+ policy : "connect-src 'none'"
+ }
+];
+
+// initializing to -1 so we start at index 0 when we start the test
+var counter = -1;
+
+function checkResult(aResult) {
+ is(aResult, tests[counter].result, "should be " + tests[counter].result + " in test " + counter + "!");
+ loadNextTest();
+}
+
+// We use the examiner to identify requests that hit the wire and requests
+// that are blocked by CSP and bubble up the result to the including iframe
+// document (parent).
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ if (topic === "specialpowers-http-notify-request") {
+ // making sure we do not bubble a result for something
+ // other then the request in question.
+ if (!data.includes("send-ping")) {
+ return;
+ }
+ checkResult("allowed");
+ return;
+ }
+
+ if (topic === "csp-on-violate-policy") {
+ // making sure we do not bubble a result for something
+ // other then the request in question.
+ var asciiSpec = SpecialPowers.getPrivilegedProps(
+ SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
+ if (!asciiSpec.includes("send-ping")) {
+ return;
+ }
+ checkResult("blocked");
+ }
+ },
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+window.ConnectSrcExaminer = new examiner();
+
+function loadNextTest() {
+ counter++;
+ if (counter == tests.length) {
+ window.ConnectSrcExaminer.remove();
+ SimpleTest.finish();
+ return;
+ }
+
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape("tests/dom/security/test/csp/file_ping.html");
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(tests[counter].policy);
+
+ document.getElementById("testframe").src = src;
+}
+
+// start running the tests
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_policyuri_regression_from_multipolicy.html b/dom/security/test/csp/test_policyuri_regression_from_multipolicy.html
new file mode 100644
index 000000000..f69e8f558
--- /dev/null
+++ b/dom/security/test/csp/test_policyuri_regression_from_multipolicy.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 924708</title>
+ <!--
+ test that a report-only policy that uses policy-uri is not incorrectly
+ enforced due to regressions introduced by Bug 836922.
+ -->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:200px;height:200px;" id='testframe'></iframe>
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var testframe = document.getElementById('testframe');
+testframe.src = 'file_policyuri_regression_from_multipolicy.html';
+testframe.addEventListener('load', function checkInlineScriptExecuted () {
+ is(this.contentDocument.getElementById('testdiv').innerHTML,
+ 'Inline Script Executed',
+ 'Inline script should execute (it would be blocked by the policy, but the policy is report-only)');
+ SimpleTest.finish();
+});
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_redirects.html b/dom/security/test/csp/test_redirects.html
new file mode 100644
index 000000000..df01e3b41
--- /dev/null
+++ b/dom/security/test/csp/test_redirects.html
@@ -0,0 +1,137 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Tests for Content Security Policy during redirects</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+
+<iframe style="width:100%;height:300px;" id="harness"></iframe>
+<pre id="log"></pre>
+<script class="testbody" type="text/javascript">
+
+var path = "/tests/dom/security/test/csp/";
+
+// debugging
+function log(s) {
+ return;
+ dump("**" + s + "\n");
+ var log = document.getElementById("log");
+ log.textContent = log.textContent+s+"\n";
+}
+
+// used to watch if requests are blocked by CSP or allowed through
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ var testpat = new RegExp("testid=([a-z0-9-]+)");
+ var asciiSpec;
+ var testid;
+
+ if (topic === "specialpowers-http-notify-request") {
+ // request was sent
+ var allowedUri = data;
+ if (!testpat.test(allowedUri)) return;
+ testid = testpat.exec(allowedUri)[1];
+ if (testExpectedResults[testid] == "completed") return;
+ log("allowed: "+allowedUri);
+ window.testResult(testid, allowedUri, true);
+ }
+
+ else if (topic === "csp-on-violate-policy") {
+ // request was blocked
+ asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
+ if (!testpat.test(asciiSpec)) return;
+ testid = testpat.exec(asciiSpec)[1];
+ // had to add this check because http-on-modify-request can fire after
+ // csp-on-violate-policy, apparently, even though the request does
+ // not hit the wire.
+ if (testExpectedResults[testid] == "completed") return;
+ log("BLOCKED: "+asciiSpec);
+ window.testResult(testid, asciiSpec, false);
+ }
+ },
+
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+window.examiner = new examiner();
+
+// contains { test_frame_id : expected_result }
+var testExpectedResults = { "font-src": true,
+ "font-src-redir": false,
+ "frame-src": true,
+ "frame-src-redir": false,
+ "img-src": true,
+ "img-src-redir": false,
+ "media-src": true,
+ "media-src-redir": false,
+ "object-src": true,
+ "object-src-redir": false,
+ "script-src": true,
+ "script-src-redir": false,
+ "style-src": true,
+ "style-src-redir": false,
+ "xhr-src": true,
+ "xhr-src-redir": false,
+ "from-worker": true,
+ "script-src-redir-from-worker": true, // redir is allowed since policy isn't inherited
+ "xhr-src-redir-from-worker": true, // redir is allowed since policy isn't inherited
+ "fetch-src-redir-from-worker": true, // redir is allowed since policy isn't inherited
+ "from-blob-worker": true,
+ "script-src-redir-from-blob-worker": false,
+ "xhr-src-redir-from-blob-worker": false,
+ "fetch-src-redir-from-blob-worker": false,
+ "img-src-from-css": true,
+ "img-src-redir-from-css": false,
+ };
+
+// takes the name of the test, the URL that was tested, and whether the
+// load occurred
+var testResult = function(testName, url, result) {
+ log(" testName: "+testName+", result: "+result+", expected: "+testExpectedResults[testName]+"\n");
+ is(result, testExpectedResults[testName], testName+" test: "+url);
+
+ // mark test as completed
+ testExpectedResults[testName] = "completed";
+
+ // don't finish until we've run all the tests
+ for (var t in testExpectedResults) {
+ if (testExpectedResults[t] != "completed") {
+ return;
+ }
+ }
+
+ window.examiner.remove();
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ {'set':[// This defaults to 0 ("preload none") on mobile (B2G/Android), which
+ // blocks loading the resource until the user interacts with a
+ // corresponding widget, which breaks the media_* tests. We set it
+ // back to the default used by desktop Firefox to get consistent
+ // behavior.
+ ["media.preload.default", 2]]},
+ function() {
+ // save this for last so that our listeners are registered.
+ // ... this loads the testbed of good and bad requests.
+ document.getElementById("harness").src = "file_redirects_main.html";
+ });
+</script>
+</pre>
+
+</body>
+</html>
diff --git a/dom/security/test/csp/test_referrerdirective.html b/dom/security/test/csp/test_referrerdirective.html
new file mode 100644
index 000000000..770fcc40b
--- /dev/null
+++ b/dom/security/test/csp/test_referrerdirective.html
@@ -0,0 +1,145 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=965727
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Content Security Policy referrer Directive (Bug 965727)</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+/*
+ * This tests various referrer policies and the referrer-sending behavior when
+ * requesting scripts in different ways:
+ * - cross-origin (https://example.com -> https://test2.example.com)
+ * - same-origin (https://example.com -> https://example.com)
+ * - downgrade (https://example.com -> http://example.com)
+ *
+ * Each test creates an iframe that loads scripts for each of the checks. If
+ * the scripts are blocked, the test fails (they should run). When loaded,
+ * each script updates a results object in the test page, and then when the
+ * test page has finished loading all the scripts, it postMessages back to this
+ * page. Once all tests are done, the results are checked.
+ */
+
+var testData = {
+ 'default': { 'csp': "script-src * 'unsafe-inline'; referrer default",
+ 'expected': { 'sameorigin': 'full',
+ 'crossorigin': 'full',
+ 'downgrade': 'none' }},
+
+ 'origin': { 'csp': "script-src * 'unsafe-inline'; referrer origin",
+ 'expected': { 'sameorigin': 'origin',
+ 'crossorigin': 'origin',
+ 'downgrade': 'origin' }},
+
+ 'origin-when-cross-origin': { 'csp': "script-src * 'unsafe-inline'; referrer origin-when-cross-origin",
+ 'expected': { 'sameorigin': 'full',
+ 'crossorigin': 'origin',
+ 'downgrade': 'origin' }},
+
+ 'unsafe-url': { 'csp': "script-src * 'unsafe-inline'; referrer unsafe-url",
+ 'expected': { 'sameorigin': 'full',
+ 'crossorigin': 'full',
+ 'downgrade': 'full' }},
+
+ 'none': { 'csp': "script-src * 'unsafe-inline'; referrer no-referrer",
+ 'expected': { 'sameorigin': 'none',
+ 'crossorigin': 'none',
+ 'downgrade': 'none' }},
+
+ // referrer delivered through CSPRO should be ignored
+ 'ignore-cspro': { 'cspro': "script-src * 'unsafe-inline'; referrer origin",
+ 'expected': { 'sameorigin': 'full',
+ 'crossorigin': 'full',
+ 'downgrade': 'none' }},
+
+ // referrer delivered through CSPRO should be ignored
+ 'ignore-cspro2': { 'csp' : "script-src * 'unsafe-inline'; referrer no-referrer",
+ 'cspro': "script-src * 'unsafe-inline'; referrer origin",
+ 'expected': { 'sameorigin': 'none',
+ 'crossorigin': 'none',
+ 'downgrade': 'none' }},
+ };
+
+var referrerDirectiveTests = {
+ // called via postMessage when one of the iframes is done running.
+ onIframeComplete: function(event) {
+ try {
+ var results = JSON.parse(event.data);
+ ok(results.hasOwnProperty('id'), "'id' property required in posted message " + event.data);
+
+ ok(testData.hasOwnProperty(results['id']), "Test " + results['id'] + " must be expected.");
+
+ // check all the various load types' referrers.
+ var expected = testData[results['id']].expected;
+ for (var t in expected) {
+ is(results.results[t], expected[t],
+ " referrer must match expected for " + t + " in " + results['id']);
+ }
+ testData[results['id']]['complete'] = true;
+
+ } catch(e) {
+ // fail -- should always be JSON
+ ok(false, "failed to parse posted message + " + event.data);
+ // have to end as well since not all messages were valid.
+ SimpleTest.finish();
+ }
+
+ referrerDirectiveTests.checkForCompletion();
+ },
+
+ // checks to see if all the parallel tests are done and validates results.
+ checkForCompletion: function() {
+ for (var id in testData) {
+ if (!testData[id].hasOwnProperty('complete')) {
+ return;
+ }
+ }
+ SimpleTest.finish();
+ }
+};
+
+SimpleTest.waitForExplicitFinish();
+// have to disable mixed content blocking to test https->http referrers.
+SpecialPowers.pushPrefEnv({
+ 'set': [['security.mixed_content.block_active_content', false],
+ ['security.mixed_content.block_display_content', false],
+ ['security.mixed_content.send_hsts_priming', false],
+ ['security.mixed_content.use_hsts', false],
+ ]
+ },
+ function() {
+ // each of the iframes we create will call us back when its contents are loaded.
+ window.addEventListener("message", referrerDirectiveTests.onIframeComplete.bind(window), false);
+
+ // one iframe created for each test case
+ for (var id in testData) {
+ var elt = document.createElement("iframe");
+ var src = "https://example.com/tests/dom/security/test/csp/file_testserver.sjs?id=" + id;
+ if (testData[id]['csp']) {
+ src += "&csp=" + escape(testData[id]['csp']);
+ }
+ if (testData[id]['cspro']) {
+ src += "&cspro=" + escape(testData[id]['cspro']);
+ }
+ src += "&file=tests/dom/security/test/csp/file_referrerdirective.html";
+ elt.src = src;
+ document.getElementById("content").appendChild(elt);
+ }
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_report.html b/dom/security/test/csp/test_report.html
new file mode 100644
index 000000000..e8cfb8778
--- /dev/null
+++ b/dom/security/test/csp/test_report.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=548193
+-->
+<head>
+ <title>Test for Bug 548193</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
+<script class="testbody" type="text/javascript">
+
+/*
+ * Description of the test:
+ * We try to load an inline-src using a policy that constrains
+ * all scripts from running (default-src 'none'). We verify that
+ * the generated csp-report contains the expceted values. If any
+ * of the JSON is not formatted properly (e.g. not properly escaped)
+ * then JSON.parse will fail, which allows to pinpoint such errors
+ * in the catch block, and the test will fail. Since we use an
+ * observer, we can set the actual report-uri to a foo value.
+ */
+
+const testfile = "tests/dom/security/test/csp/file_report.html";
+const reportURI = "http://mochi.test:8888/foo.sjs";
+const policy = "default-src 'none'; report-uri " + reportURI;
+const docUri = "http://mochi.test:8888/tests/dom/security/test/csp/file_testserver.sjs" +
+ "?file=tests/dom/security/test/csp/file_report.html" +
+ "&csp=default-src%20%27none%27%3B%20report-uri%20http%3A//mochi.test%3A8888/foo.sjs";
+
+window.checkResults = function(reportObj) {
+ var cspReport = reportObj["csp-report"];
+
+ // The following uris' fragments should be stripped before reporting:
+ // * document-uri
+ // * blocked-uri
+ // * source-file
+ // see http://www.w3.org/TR/CSP11/#violation-reports
+ is(cspReport["document-uri"], docUri, "Incorrect document-uri");
+
+ // we can not test for the whole referrer since it includes platform specific information
+ ok(cspReport["referrer"].startsWith("http://mochi.test:8888/tests/dom/security/test/csp/test_report.html"),
+ "Incorrect referrer");
+
+ is(cspReport["blocked-uri"], "self", "Incorrect blocked-uri");
+
+ is(cspReport["violated-directive"], "default-src 'none'", "Incorrect violated-directive");
+
+ is(cspReport["original-policy"], "default-src 'none'; report-uri http://mochi.test:8888/foo.sjs",
+ "Incorrect original-policy");
+
+ is(cspReport["source-file"], docUri, "Incorrect source-file");
+
+ is(cspReport["script-sample"], "\n var foo = \"propEscFoo\";\n var bar...",
+ "Incorrect script-sample");
+
+ is(cspReport["line-number"], 7, "Incorrect line-number");
+}
+
+var chromeScriptUrl = SimpleTest.getTestFileURL("file_report_chromescript.js");
+var script = SpecialPowers.loadChromeScript(chromeScriptUrl);
+
+script.addMessageListener('opening-request-completed', function ml(msg) {
+ if (msg.error) {
+ ok(false, "Could not query report (exception: " + msg.error + ")");
+ } else {
+ try {
+ var reportObj = JSON.parse(msg.report);
+ } catch (e) {
+ ok(false, "Could not parse JSON (exception: " + e + ")");
+ }
+ try {
+ // test for the proper values in the report object
+ window.checkResults(reportObj);
+ } catch (e) {
+ ok(false, "Could not query report (exception: " + e + ")");
+ }
+ }
+
+ script.removeMessageListener('opening-request-completed', ml);
+ SimpleTest.finish();
+});
+
+SimpleTest.waitForExplicitFinish();
+
+// load the resource which will generate a CSP violation report
+// save this for last so that our listeners are registered.
+var src = "file_testserver.sjs";
+// append the file that should be served
+src += "?file=" + escape(testfile);
+// append the CSP that should be used to serve the file
+src += "&csp=" + escape(policy);
+// appending a fragment so we can test that it's correctly stripped
+// for document-uri and source-file.
+src += "#foo";
+document.getElementById("cspframe").src = src;
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_report_for_import.html b/dom/security/test/csp/test_report_for_import.html
new file mode 100644
index 000000000..be112d51c
--- /dev/null
+++ b/dom/security/test/csp/test_report_for_import.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=548193
+-->
+<head>
+ <title>Test for Bug 548193</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
+<script class="testbody" type="text/javascript">
+
+/*
+ * Description of the test:
+ * We are loading a stylesheet using a csp policy that only allows styles from 'self'
+ * to be loaded. In other words, the *.css file itself should be allowed to load, but
+ * the @import file within the CSS should get blocked. We verify that the generated
+ * csp-report is sent and contains all the expected values.
+ * In detail, the test starts by sending an XHR request to the report-server
+ * which waits on the server side till the report was received and hands the
+ * report in JSON format back to the testfile which then verifies accuracy
+ * of all the different report fields in the CSP report.
+ */
+
+const TEST_FILE = "tests/dom/security/test/csp/file_report_for_import.html";
+const REPORT_URI =
+ "http://mochi.test:8888/tests/dom/security/test/csp/file_report_for_import_server.sjs?report";
+const POLICY = "style-src 'self'; report-uri " + REPORT_URI;
+
+const DOC_URI =
+ "http://mochi.test:8888/tests/dom/security/test/csp/file_testserver.sjs?" +
+ "file=tests/dom/security/test/csp/file_report_for_import.html&" +
+ "csp=style-src%20%27self%27%3B%20" +
+ "report-uri%20http%3A//mochi.test%3A8888/tests/dom/security/test/csp/" +
+ "file_report_for_import_server.sjs%3Freport";
+
+function checkResults(reportStr) {
+ try {
+ var reportObj = JSON.parse(reportStr);
+ var cspReport = reportObj["csp-report"];
+
+ is(cspReport["document-uri"], DOC_URI, "Incorrect document-uri");
+ is(cspReport["referrer"],
+ "http://mochi.test:8888/tests/dom/security/test/csp/test_report_for_import.html",
+ "Incorrect referrer");
+ is(cspReport["violated-directive"],
+ "style-src http://mochi.test:8888",
+ "Incorrect violated-directive");
+ is(cspReport["original-policy"],
+ "style-src http://mochi.test:8888; report-uri " +
+ "http://mochi.test:8888/tests/dom/security/test/csp/file_report_for_import_server.sjs?report",
+ "Incorrect original-policy");
+ is(cspReport["blocked-uri"],
+ "http://example.com",
+ "Incorrect blocked-uri");
+
+ // we do not always set the following fields
+ is(cspReport["source-file"], undefined, "Incorrect source-file");
+ is(cspReport["script-sample"], undefined, "Incorrect script-sample");
+ is(cspReport["line-number"], undefined, "Incorrect line-number");
+ }
+ catch (e) {
+ ok(false, "Could not parse JSON (exception: " + e + ")");
+ }
+}
+
+function loadTestPageIntoFrame() {
+ // load the resource which will generate a CSP violation report
+ // save this for last so that our listeners are registered.
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape(TEST_FILE);
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(POLICY);
+ // appending a fragment so we can test that it's correctly stripped
+ // for document-uri and source-file.
+ src += "#foo";
+ document.getElementById("cspframe").src = src;
+}
+
+function runTest() {
+ // send an xhr request to the server which is processed async, which only
+ // returns after the server has received the csp report.
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "file_report_for_import_server.sjs?queryresult");
+ myXHR.onload = function(e) {
+ checkResults(myXHR.responseText);
+ SimpleTest.finish();
+ }
+ myXHR.onerror = function(e) {
+ ok(false, "could not query results from server (" + e.message + ")");
+ SimpleTest.finish();
+ }
+ myXHR.send();
+
+ // give it some time and run the testpage
+ SimpleTest.executeSoon(loadTestPageIntoFrame);
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_report_uri_missing_in_report_only_header.html b/dom/security/test/csp/test_report_uri_missing_in_report_only_header.html
new file mode 100644
index 000000000..d02911141
--- /dev/null
+++ b/dom/security/test/csp/test_report_uri_missing_in_report_only_header.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=847081
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 847081</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=847081">Mozilla Bug 847081</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<iframe id="cspframe"></iframe>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var stringBundleService = SpecialPowers.Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(SpecialPowers.Ci.nsIStringBundleService);
+var localizer = stringBundleService.createBundle("chrome://global/locale/security/csp.properties");
+var warningMsg = localizer.formatStringFromName("reportURInotInReportOnlyHeader", [window.location.origin], 1);
+function cleanup() {
+ SpecialPowers.postConsoleSentinel();
+ SimpleTest.finish();
+}
+
+SpecialPowers.registerConsoleListener(function ConsoleMsgListener(aMsg) {
+ if (aMsg.message.indexOf(warningMsg) > -1) {
+ ok(true, "report-uri not specified in Report-Only should throw a CSP warning.");
+ SimpleTest.executeSoon(cleanup);
+ return;
+ } else {
+ // if some other console message is present, we wait
+ return;
+ }
+});
+
+
+// set up and start testing
+SimpleTest.waitForExplicitFinish();
+document.getElementById('cspframe').src = 'file_report_uri_missing_in_report_only_header.html';
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_require_sri_meta.html b/dom/security/test/csp/test_require_sri_meta.html
new file mode 100644
index 000000000..a06fe122a
--- /dev/null
+++ b/dom/security/test/csp/test_require_sri_meta.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1277557 - CSP require-sri-for does not block when CSP is in meta tag</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * We load scripts within an iframe and make sure that meta-csp of
+ * require-sri-for applies correctly to preloaded scripts.
+ * Please note that we have to use <script src=""> to kick
+ * off the html preloader.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.setBoolPref("security.csp.experimentalEnabled", true);
+
+var curTest;
+var counter = -1;
+
+const tests = [
+ { // test 1
+ description: "script with *no* SRI should be blocked",
+ query: "no-sri",
+ expected: "script-blocked"
+ },
+ { // test 2
+ description: "script-with *incorrect* SRI should be blocked",
+ query: "wrong-sri",
+ expected: "script-blocked"
+ },
+ { // test 3
+ description: "script-with *correct* SRI should be loaded",
+ query: "correct-sri",
+ expected: "script-loaded"
+ },
+];
+
+function finishTest() {
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+}
+
+function checkResults(result) {
+ is(result, curTest.expected, curTest.description);
+ loadNextTest();
+}
+
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ checkResults(event.data.result);
+}
+
+function loadNextTest() {
+ counter++;
+ if (counter == tests.length) {
+ finishTest();
+ return;
+ }
+ curTest = tests[counter];
+ var testframe = document.getElementById("testframe");
+ testframe.src = "file_require_sri_meta.sjs?" + curTest.query;
+}
+
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_sandbox.html b/dom/security/test/csp/test_sandbox.html
new file mode 100644
index 000000000..f0df66d6a
--- /dev/null
+++ b/dom/security/test/csp/test_sandbox.html
@@ -0,0 +1,249 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Tests for bugs 886164 and 671389</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content">
+</div>
+
+<script class="testbody" type="text/javascript">
+
+var testCases = [
+ {
+ // Test 1: don't load image from non-same-origin; allow loading
+ // images from same-same origin
+ sandboxAttribute: "allow-same-origin",
+ csp: "default-src 'self'",
+ file: "file_sandbox_1.html",
+ results: { img1a_good: -1, img1_bad: -1 }
+ // fails if scripts execute
+ },
+ {
+ // Test 2: don't load image from non-same-origin; allow loading
+ // images from same-same origin, even without allow-same-origin
+ // flag
+ sandboxAttribute: "",
+ csp: "default-src 'self'",
+ file: "file_sandbox_2.html",
+ results: { img2_bad: -1, img2a_good: -1 }
+ // fails if scripts execute
+ },
+ {
+ // Test 3: disallow loading images from any host, even with
+ // allow-same-origin flag set
+ sandboxAttribute: "allow-same-origin",
+ csp: "default-src 'none'",
+ file: "file_sandbox_3.html",
+ results: { img3_bad: -1, img3a_bad: -1 },
+ // fails if scripts execute
+ },
+ {
+ // Test 4: disallow loading images from any host
+ sandboxAttribute: "",
+ csp: "default-src 'none'",
+ file: "file_sandbox_4.html",
+ results: { img4_bad: -1, img4a_bad: -1 }
+ // fails if scripts execute
+ },
+ {
+ // Test 5: disallow loading images or scripts, allow inline scripts
+ sandboxAttribute: "allow-scripts",
+ csp: "default-src 'none'; script-src 'unsafe-inline';",
+ file: "file_sandbox_5.html",
+ results: { img5_bad: -1, img5a_bad: -1, script5_bad: -1, script5a_bad: -1 },
+ nrOKmessages: 2 // sends 2 ok message
+ // fails if scripts execute
+ },
+ {
+ // Test 6: disallow non-same-origin images, allow inline and same origin scripts
+ sandboxAttribute: "allow-same-origin allow-scripts",
+ csp: "default-src 'self' 'unsafe-inline';",
+ file: "file_sandbox_6.html",
+ results: { img6_bad: -1, script6_bad: -1 },
+ nrOKmessages: 4 // sends 4 ok message
+ // fails if forms are not disallowed
+ },
+ {
+ // Test 7: same as Test 1
+ csp: "default-src 'self'; sandbox allow-same-origin",
+ file: "file_sandbox_7.html",
+ results: { img7a_good: -1, img7_bad: -1 }
+ },
+ {
+ // Test 8: same as Test 2
+ csp: "sandbox allow-same-origin; default-src 'self'",
+ file: "file_sandbox_8.html",
+ results: { img8_bad: -1, img8a_good: -1 }
+ },
+ {
+ // Test 9: same as Test 3
+ csp: "default-src 'none'; sandbox allow-same-origin",
+ file: "file_sandbox_9.html",
+ results: { img9_bad: -1, img9a_bad: -1 }
+ },
+ {
+ // Test 10: same as Test 4
+ csp: "default-src 'none'; sandbox allow-same-origin",
+ file: "file_sandbox_10.html",
+ results: { img10_bad: -1, img10a_bad: -1 }
+ },
+ {
+ // Test 11: same as Test 5
+ csp: "default-src 'none'; script-src 'unsafe-inline'; sandbox allow-scripts allow-same-origin",
+ file: "file_sandbox_11.html",
+ results: { img11_bad: -1, img11a_bad: -1, script11_bad: -1, script11a_bad: -1 },
+ nrOKmessages: 2 // sends 2 ok message
+ },
+ {
+ // Test 12: same as Test 6
+ csp: "sandbox allow-same-origin allow-scripts; default-src 'self' 'unsafe-inline';",
+ file: "file_sandbox_12.html",
+ results: { img12_bad: -1, script12_bad: -1 },
+ nrOKmessages: 4 // sends 4 ok message
+ },
+ {
+ // Test 13: same as Test 5 and Test 11, but:
+ // * using sandbox flag 'allow-scripts' in CSP and not as iframe attribute
+ // * not using allow-same-origin in CSP (so a new NullPrincipal is created).
+ csp: "default-src 'none'; script-src 'unsafe-inline'; sandbox allow-scripts",
+ file: "file_sandbox_13.html",
+ results: { img13_bad: -1, img13a_bad: -1, script13_bad: -1, script13a_bad: -1 },
+ nrOKmessages: 2 // sends 2 ok message
+ },
+];
+
+// a postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin' to communicate pass/fail back to this main page.
+// it expects to be called with an object like:
+// { ok: true/false,
+// desc: <description of the test> which it then forwards to ok() }
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event) {
+ ok_wrapper(event.data.ok, event.data.desc);
+}
+
+var completedTests = 0;
+var passedTests = 0;
+
+var totalTests = (function() {
+ var nrCSPloadTests = 0;
+ for(var i = 0; i < testCases.length; i++) {
+ nrCSPloadTests += Object.keys(testCases[i].results).length;
+ if (testCases[i].nrOKmessages) {
+ // + number of expected postMessages from iframe
+ nrCSPloadTests += testCases[i].nrOKmessages;
+ }
+ }
+ return nrCSPloadTests;
+})();
+
+function ok_wrapper(result, desc) {
+ ok(result, desc);
+
+ completedTests++;
+
+ if (result) {
+ passedTests++;
+ }
+
+ if (completedTests === totalTests) {
+ window.examiner.remove();
+ SimpleTest.finish();
+ }
+}
+
+// Set the iframe src and sandbox attribute
+function runTest(test) {
+ var iframe = document.createElement('iframe');
+
+ document.getElementById('content').appendChild(iframe);
+
+ // set sandbox attribute
+ if (test.sandboxAttribute !== undefined) {
+ iframe.sandbox = test.sandboxAttribute;
+ }
+
+ // set query string
+ var src = 'file_testserver.sjs';
+ // path where the files are
+ var path = '/tests/dom/security/test/csp/';
+
+ src += '?file=' + escape(path+test.file);
+
+ if (test.csp !== undefined) {
+ src += '&csp=' + escape(test.csp);
+ }
+
+ iframe.src = src;
+ iframe.width = iframe.height = 10;
+}
+
+// Examiner related
+
+// This is used to watch the blocked data bounce off CSP and allowed data
+// get sent out to the wire.
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+ SpecialPowers.addObserver(this, "specialpowers-http-notify-request", false);
+}
+
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ var testpat = new RegExp("testid=([a-z0-9_]+)");
+
+ //_good things better be allowed!
+ //_bad things better be stopped!
+
+ if (topic === "specialpowers-http-notify-request") {
+ //these things were allowed by CSP
+ var uri = data;
+ if (!testpat.test(uri)) return;
+ var testid = testpat.exec(uri)[1];
+
+ if(/_good/.test(testid)) {
+ ok_wrapper(true, uri + " is allowed by csp");
+ } else {
+ ok_wrapper(false, uri + " should not be allowed by csp");
+ }
+ }
+
+ if(topic === "csp-on-violate-policy") {
+ //these were blocked... record that they were blocked
+ var asciiSpec = SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
+ if (!testpat.test(asciiSpec)) return;
+ var testid = testpat.exec(asciiSpec)[1];
+ if(/_bad/.test(testid)) {
+ ok_wrapper(true, asciiSpec + " was blocked by \"" + data + "\"");
+ } else {
+ ok_wrapper(false, asciiSpec + " should have been blocked by \"" + data + "\"");
+ }
+ }
+ },
+
+ // must eventually call this to remove the listener,
+ // or mochitests might get borked.
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ SpecialPowers.removeObserver(this, "specialpowers-http-notify-request");
+ }
+}
+
+window.examiner = new examiner();
+
+SimpleTest.waitForExplicitFinish();
+
+(function() { // Run tests:
+ for(var i = 0; i < testCases.length; i++) {
+ runTest(testCases[i]);
+ }
+})();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_sandbox_allow_scripts.html b/dom/security/test/csp/test_sandbox_allow_scripts.html
new file mode 100644
index 000000000..10acaae43
--- /dev/null
+++ b/dom/security/test/csp/test_sandbox_allow_scripts.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1396320: Fix CSP sandbox regression for allow-scripts</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * Load an iframe using a CSP of 'sandbox allow-scripts' and make sure
+ * the security context of the iframe is sandboxed (cross origin)
+ */
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("message", receiveMessage);
+function receiveMessage(event) {
+ is(event.data.result, "",
+ "document.domain of sandboxed iframe should be opaque");
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+}
+
+let testframe = document.getElementById("testframe");
+testframe.src = "file_sandbox_allow_scripts.html";
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_scheme_relative_sources.html b/dom/security/test/csp/test_scheme_relative_sources.html
new file mode 100644
index 000000000..21271cdd1
--- /dev/null
+++ b/dom/security/test/csp/test_scheme_relative_sources.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 921493 - CSP: test whitelisting of scheme-relative sources</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/* Description of the test:
+ * We load http and https pages and verify that scheme relative sources
+ * are allowed unless its a downgrade from https -> http.
+ *
+ * Please note that the policy contains 'unsafe-inline' so we can use
+ * an inline script to query the result from within the sandboxed iframe
+ * and report it back to the parent document.
+ */
+
+var POLICY = "default-src 'none'; script-src 'unsafe-inline' example.com;";
+
+var tests = [
+ {
+ description: "http -> http",
+ from: "http",
+ to: "http",
+ result: "allowed",
+ },
+ {
+ description: "http -> https",
+ from: "http",
+ to: "https",
+ result: "allowed",
+ },
+ {
+ description: "https -> https",
+ from: "https",
+ to: "https",
+ result: "allowed",
+ },
+ {
+ description: "https -> http",
+ from: "https",
+ to: "http",
+ result: "blocked",
+ }
+];
+
+var counter = 0;
+var curTest;
+
+function loadNextTest() {
+ if (counter == tests.length) {
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+ return;
+ }
+
+ curTest = tests[counter++];
+
+ var src = curTest.from +
+ "://example.com/tests/dom/security/test/csp/file_scheme_relative_sources.sjs" +
+ "?scheme=" + curTest.to +
+ "&policy=" + escape(POLICY);
+
+ document.getElementById("testframe").src = src;
+}
+
+// using a postMessage handler to report the result back from
+// within the sandboxed iframe without 'allow-same-origin'.
+window.addEventListener("message", receiveMessage, false);
+
+function receiveMessage(event) {
+
+ is(event.data.result, curTest.result,
+ "should be " + curTest.result + " in test (" + curTest.description + ")!");
+
+ loadNextTest();
+}
+
+// get the test started
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_self_none_as_hostname_confusion.html b/dom/security/test/csp/test_self_none_as_hostname_confusion.html
new file mode 100644
index 000000000..e5b93c538
--- /dev/null
+++ b/dom/security/test/csp/test_self_none_as_hostname_confusion.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=587377
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 587377</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=587377">Mozilla Bug 587377</a>
+<p id="display"></p>
+
+<iframe id="cspframe"></iframe>
+
+<pre id="test">
+
+<script class="testbody" type="text/javascript">
+// Load locale string during mochitest
+var stringBundleService = SpecialPowers.Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(SpecialPowers.Ci.nsIStringBundleService);
+var localizer = stringBundleService.createBundle("chrome://global/locale/security/csp.properties");
+var confusionMsg = localizer.formatStringFromName("hostNameMightBeKeyword", ["SELF", "self"], 2);
+
+function cleanup() {
+ SpecialPowers.postConsoleSentinel();
+ SimpleTest.finish();
+};
+
+// To prevent the test from asserting twice and calling SimpleTest.finish() twice,
+// startTest will be marked false as soon as the confusionMsg is detected.
+startTest = false;
+SpecialPowers.registerConsoleListener(function ConsoleMsgListener(aMsg) {
+ if (startTest) {
+ if (aMsg.message.indexOf(confusionMsg) > -1) {
+ startTest = false;
+ ok(true, "CSP header with a hostname similar to keyword should be warned");
+ SimpleTest.executeSoon(cleanup);
+ } else {
+ // don't see the warning yet? wait.
+ return;
+ }
+ }
+});
+
+// set up and start testing
+SimpleTest.waitForExplicitFinish();
+document.getElementById('cspframe').src = 'file_self_none_as_hostname_confusion.html';
+startTest = true;
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_sendbeacon.html b/dom/security/test/csp/test_sendbeacon.html
new file mode 100644
index 000000000..1b4cfbc86
--- /dev/null
+++ b/dom/security/test/csp/test_sendbeacon.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1234813 - sendBeacon should not throw if blocked by Content Policy</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<iframe style="width:100%;" id="testframe" src="file_sendbeacon.html"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * Let's try to fire a sendBeacon which gets blocked by CSP. Let's make sure
+ * sendBeacon does not throw an exception.
+ */
+SimpleTest.waitForExplicitFinish();
+
+// a postMessage handler used to bubble up the
+// result from within the iframe.
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ var result = event.data.result;
+ is(result, "blocked-beacon-does-not-throw", "sendBeacon should not throw");
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_service_worker.html b/dom/security/test/csp/test_service_worker.html
new file mode 100644
index 000000000..0cff84751
--- /dev/null
+++ b/dom/security/test/csp/test_service_worker.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1208559 - ServiceWorker registration not governed by CSP</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * Spawning a worker from https://example.com but script-src is 'test1.example.com'
+ * CSP is not consulted
+ */
+SimpleTest.waitForExplicitFinish();
+
+var tests = [
+ {
+ policy: "default-src 'self'; script-src 'unsafe-inline'; child-src test1.example.com;",
+ expected: "blocked"
+ },
+];
+
+var counter = 0;
+var curTest;
+
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ is(event.data.result, curTest.expected, "Should be (" + curTest.expected + ") in Test " + counter + "!");
+ loadNextTest();
+}
+
+onload = function() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ["dom.caches.enabled", true]
+ ]}, loadNextTest);
+}
+
+function loadNextTest() {
+ if (counter == tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+ curTest = tests[counter++];
+ var src = "https://example.com/tests/dom/security/test/csp/file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape("tests/dom/security/test/csp/file_service_worker.html");
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(curTest.policy);
+ document.getElementById("testframe").src = src;
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_shouldprocess.html b/dom/security/test/csp/test_shouldprocess.html
new file mode 100644
index 000000000..5d0925167
--- /dev/null
+++ b/dom/security/test/csp/test_shouldprocess.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=908933
+-->
+<head>
+ <title>Test Bug 908933</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+/*
+ * Description of the test:
+ * We load variations of 'objects' and make sure all the
+ * resource loads are correctly blocked by CSP.
+ * For all the testing we use a CSP with "object-src 'none'"
+ * so that all the loads are either blocked by
+ * shouldProcess or shouldLoad.
+ */
+
+const POLICY = "default-src http://mochi.test:8888; object-src 'none'";
+const TESTFILE = "tests/dom/security/test/csp/file_shouldprocess.html";
+
+SimpleTest.waitForExplicitFinish();
+
+var tests = [
+ // Note that the files listed below don't actually exist.
+ // Since loading of them should be blocked by shouldProcess, we don't
+ // really need these files.
+
+ // blocked by shouldProcess
+ "http://mochi.test:8888/tests/dom/security/test/csp/test1",
+ "http://mochi.test:8888/tests/dom/security/test/csp/test2",
+ "http://mochi.test:8888/tests/dom/security/test/csp/test3",
+ "http://mochi.test:8888/tests/dom/security/test/csp/test4",
+ "http://mochi.test:8888/tests/dom/security/test/csp/test5",
+ "http://mochi.test:8888/tests/dom/security/test/csp/test6",
+ // blocked by shouldLoad
+ "http://mochi.test:8888/tests/dom/security/test/csp/test7.class",
+ "http://mochi.test:8888/tests/dom/security/test/csp/test8.class",
+];
+
+function checkResults(aURI) {
+ var index = tests.indexOf(aURI);
+ if (index > -1) {
+ tests.splice(index, 1);
+ ok(true, "ShouldLoad or ShouldProcess blocks TYPE_OBJECT with uri: " + aURI + "!");
+ }
+ else {
+ ok(false, "ShouldLoad or ShouldProcess incorreclty blocks TYPE_OBJECT with uri: " + aURI + "!");
+ }
+ if (tests.length == 0) {
+ window.examiner.remove();
+ SimpleTest.finish();
+ }
+}
+
+// used to watch that shouldProcess blocks TYPE_OBJECT
+function examiner() {
+ SpecialPowers.addObserver(this, "csp-on-violate-policy", false);
+}
+examiner.prototype = {
+ observe: function(subject, topic, data) {
+ if (topic === "csp-on-violate-policy") {
+ var asciiSpec =
+ SpecialPowers.getPrivilegedProps(SpecialPowers.do_QueryInterface(subject, "nsIURI"), "asciiSpec");
+ checkResults(asciiSpec);
+ }
+ },
+ remove: function() {
+ SpecialPowers.removeObserver(this, "csp-on-violate-policy");
+ }
+}
+window.examiner = new examiner();
+
+function loadFrame() {
+ var src = "file_testserver.sjs";
+ // append the file that should be served
+ src += "?file=" + escape(TESTFILE);
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(POLICY);
+
+ var iframe = document.createElement("iframe");
+ iframe.src = src;
+ document.body.appendChild(iframe);
+}
+
+SpecialPowers.pushPrefEnv(
+ { "set": [['plugin.java.mime', 'application/x-java-test']] },
+ loadFrame);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_strict_dynamic.html b/dom/security/test/csp/test_strict_dynamic.html
new file mode 100644
index 000000000..00e75143f
--- /dev/null
+++ b/dom/security/test/csp/test_strict_dynamic.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1299483 - CSP: Implement 'strict-dynamic'</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.setBoolPref("security.csp.enableStrictDynamic", true);
+
+/* Description of the test:
+ * We load scripts with a CSP of 'strict-dynamic' with valid
+ * and invalid nonces and make sure scripts are allowed/blocked
+ * accordingly. Different tests load inline and external scripts
+ * also using a CSP including http: and https: making sure
+ * other srcs are invalided by 'strict-dynamic'.
+ */
+
+var tests = [
+ {
+ desc: "strict-dynamic with valid nonce should be allowed",
+ result: "allowed",
+ file: "file_strict_dynamic_script_extern.html",
+ policy: "script-src 'strict-dynamic' 'nonce-foo' https: 'none' 'self'"
+ },
+ {
+ desc: "strict-dynamic with invalid nonce should be blocked",
+ result: "blocked",
+ file: "file_strict_dynamic_script_extern.html",
+ policy: "script-src 'strict-dynamic' 'nonce-bar' http: http://example.com"
+ },
+ {
+ desc: "strict-dynamic, whitelist and invalid nonce should be blocked",
+ result: "blocked",
+ file: "file_strict_dynamic_script_extern.html",
+ policy: "script-src 'strict-dynamic' 'nonce-bar' 'unsafe-inline' http: http://example.com"
+ },
+ {
+ desc: "strict-dynamic with no 'nonce-' should be blocked",
+ result: "blocked",
+ file: "file_strict_dynamic_script_extern.html",
+ policy: "script-src 'strict-dynamic'"
+ },
+ // inline scripts
+ {
+ desc: "strict-dynamic with valid nonce should be allowed",
+ result: "allowed",
+ file: "file_strict_dynamic_script_inline.html",
+ policy: "script-src 'strict-dynamic' 'nonce-foo' https: 'none' 'self'"
+ },
+ {
+ desc: "strict-dynamic with invalid nonce should be blocked",
+ result: "blocked",
+ file: "file_strict_dynamic_script_inline.html",
+ policy: "script-src 'strict-dynamic' 'nonce-bar' http: http://example.com"
+ },
+ {
+ desc: "strict-dynamic, unsafe-inline and invalid nonce should be blocked",
+ result: "blocked",
+ file: "file_strict_dynamic_script_inline.html",
+ policy: "script-src 'strict-dynamic' 'nonce-bar' 'unsafe-inline' http: http://example.com"
+ },
+ {
+ desc: "strict-dynamic with no 'nonce-' should be blocked",
+ result: "blocked",
+ file: "file_strict_dynamic_script_inline.html",
+ policy: "script-src 'strict-dynamic'"
+ },
+ {
+ desc: "strict-dynamic with DOM events should be blocked",
+ result: "blocked",
+ file: "file_strict_dynamic_script_events.html",
+ policy: "script-src 'strict-dynamic' 'nonce-foo'"
+ },
+ {
+ // marquee is a special snowflake. Extra test for xbl things.
+ desc: "strict-dynamic with DOM events should be blocked (XBL)",
+ result: "blocked",
+ file: "file_strict_dynamic_script_events_xbl.html",
+ policy: "script-src 'strict-dynamic' 'nonce-foo'"
+ },
+ {
+ desc: "strict-dynamic with JS URLs should be blocked",
+ result: "blocked",
+ file: "file_strict_dynamic_js_url.html",
+ policy: "script-src 'strict-dynamic' 'nonce-foo'"
+ },
+];
+
+var counter = 0;
+var curTest;
+
+function loadNextTest() {
+ if (counter == tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ curTest = tests[counter++];
+ var src = "file_testserver.sjs?file=";
+ // append the file that should be served
+ src += escape("tests/dom/security/test/csp/" + curTest.file)
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(curTest.policy);
+
+ document.getElementById("testframe").addEventListener("load", test, false);
+ document.getElementById("testframe").src = src;
+}
+
+function test() {
+ try {
+ document.getElementById("testframe").removeEventListener('load', test, false);
+ var testframe = document.getElementById("testframe");
+ var divcontent = testframe.contentWindow.document.getElementById('testdiv').innerHTML;
+ is(divcontent, curTest.result, curTest.desc);
+ }
+ catch (e) {
+ ok(false, "ERROR: could not access content for test: '" + curTest.desc + "'");
+ }
+ loadNextTest();
+}
+
+// start running the tests
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_strict_dynamic_default_src.html b/dom/security/test/csp/test_strict_dynamic_default_src.html
new file mode 100644
index 000000000..17518444e
--- /dev/null
+++ b/dom/security/test/csp/test_strict_dynamic_default_src.html
@@ -0,0 +1,136 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1299483 - CSP: Implement 'strict-dynamic'</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.setBoolPref("security.csp.enableStrictDynamic", true);
+
+/* Description of the test:
+ * We load scripts and images with a CSP of 'strict-dynamic' making sure
+ * whitelists get ignored for scripts but not for images when strict-dynamic
+ * appears in default-src.
+ *
+ * Please note that we do not support strict-dynamic within default-src yet,
+ * see Bug 1313937. When updating this test please do not change the
+ * csp policies, but only replace todo_is() with is().
+ */
+
+var tests = [
+ {
+ script_desc: "(test1) script should be allowed because of valid nonce",
+ img_desc: "(test1) img should be allowed because of 'self'",
+ script_result: "allowed",
+ img_result: "allowed",
+ policy: "default-src 'strict-dynamic' 'self'; script-src 'nonce-foo'"
+ },
+ {
+ script_desc: "(test 2) script should be blocked because of invalid nonce",
+ img_desc: "(test 2) img should be allowed because of valid scheme-src",
+ script_result: "blocked",
+ img_result: "allowed",
+ policy: "default-src 'strict-dynamic' http:; script-src 'nonce-bar' http:"
+ },
+ {
+ script_desc: "(test 3) script should be blocked because of invalid nonce",
+ img_desc: "(test 3) img should be allowed because of valid host-src",
+ script_result: "blocked",
+ script_enforced: "",
+ img_result: "allowed",
+ policy: "default-src 'strict-dynamic' mochi.test; script-src 'nonce-bar' http:"
+ },
+ {
+ script_desc: "(test 4) script should be allowed because of valid nonce",
+ img_desc: "(test 4) img should be blocked because of default-src 'strict-dynamic'",
+ script_result: "allowed",
+ img_result: "blocked",
+ policy: "default-src 'strict-dynamic'; script-src 'nonce-foo'"
+ },
+ // some reverse order tests (have script-src appear before default-src)
+ {
+ script_desc: "(test 5) script should be allowed because of valid nonce",
+ img_desc: "(test 5) img should be blocked because of default-src 'strict-dynamic'",
+ script_result: "allowed",
+ img_result: "blocked",
+ policy: "script-src 'nonce-foo'; default-src 'strict-dynamic';"
+ },
+ {
+ script_desc: "(test 6) script should be allowed because of valid nonce",
+ img_desc: "(test 6) img should be blocked because of default-src http:",
+ script_result: "blocked",
+ img_result: "allowed",
+ policy: "script-src 'nonce-bar' http:; default-src 'strict-dynamic' http:;"
+ },
+ {
+ script_desc: "(test 7) script should be allowed because of invalid nonce",
+ img_desc: "(test 7) img should be blocked because of image-src http:",
+ script_result: "blocked",
+ img_result: "allowed",
+ policy: "script-src 'nonce-bar' http:; default-src 'strict-dynamic' http:; img-src http:"
+ },
+];
+
+var counter = 0;
+var curTest;
+
+function loadNextTest() {
+ if (counter == tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ curTest = tests[counter++];
+ var src = "file_testserver.sjs?file=";
+ // append the file that should be served
+ src += escape("tests/dom/security/test/csp/file_strict_dynamic_default_src.html");
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(curTest.policy);
+
+ document.getElementById("testframe").addEventListener("load", checkResults, false);
+ document.getElementById("testframe").src = src;
+}
+
+function checkResults() {
+ try {
+ var testframe = document.getElementById("testframe");
+ testframe.removeEventListener('load', checkResults, false);
+
+ // check if script loaded
+ var divcontent = testframe.contentWindow.document.getElementById('testdiv').innerHTML;
+ if (curTest.script_result === "blocked") {
+ todo_is(divcontent, curTest.script_result, curTest.script_desc);
+ }
+ else {
+ is(divcontent, curTest.script_result, curTest.script_desc);
+ }
+
+ // check if image loaded
+ var testimg = testframe.contentWindow.document.getElementById("testimage");
+ if (curTest.img_result === "allowed") {
+ ok(testimg.complete, curTest.img_desc);
+ }
+ else {
+ ok((testimg.width == 0) && (testimg.height == 0), curTest.img_desc);
+ }
+ }
+ catch (e) {
+ ok(false, "ERROR: could not access content for test: '" + curTest.script_desc + "'");
+ }
+
+ loadNextTest();
+}
+
+// start running the tests
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_strict_dynamic_parser_inserted.html b/dom/security/test/csp/test_strict_dynamic_parser_inserted.html
new file mode 100644
index 000000000..9e588660d
--- /dev/null
+++ b/dom/security/test/csp/test_strict_dynamic_parser_inserted.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1299483 - CSP: Implement 'strict-dynamic'</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.setBoolPref("security.csp.enableStrictDynamic", true);
+
+/* Description of the test:
+ * We loader parser and non parser inserted scripts making sure that
+ * parser inserted scripts are blocked if strict-dynamic is present
+ * and no valid nonce and also making sure that non-parser inserted
+ * scripts are allowed to execute.
+ */
+
+var tests = [
+ {
+ desc: "(parser inserted script) using doc.write(<script>) should be blocked",
+ result: "blocked",
+ file: "file_strict_dynamic_parser_inserted_doc_write.html",
+ policy: "script-src 'strict-dynamic' 'nonce-foo' http:"
+ },
+ {
+ desc: "(parser inserted script with valid nonce) using doc.write(<script>) should be allowed",
+ result: "allowed",
+ file: "file_strict_dynamic_parser_inserted_doc_write_correct_nonce.html",
+ policy: "script-src 'strict-dynamic' 'nonce-foo' https:"
+ },
+ {
+ desc: "(non parser inserted script) using appendChild() should allow external script",
+ result: "allowed",
+ file: "file_strict_dynamic_non_parser_inserted.html",
+ policy: "script-src 'strict-dynamic' 'nonce-foo' https:"
+ },
+ {
+ desc: "(non parser inserted script) using appendChild() should allow inline script",
+ result: "allowed",
+ file: "file_strict_dynamic_non_parser_inserted_inline.html",
+ policy: "script-src 'strict-dynamic' 'nonce-foo' https:"
+ },
+ {
+ desc: "strict-dynamic should not invalidate 'unsafe-eval'",
+ result: "allowed",
+ file: "file_strict_dynamic_unsafe_eval.html",
+ policy: "script-src 'strict-dynamic' 'nonce-foo' 'unsafe-eval'"
+ },
+];
+
+var counter = 0;
+var curTest;
+
+function loadNextTest() {
+ if (counter == tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ curTest = tests[counter++];
+ var src = "file_testserver.sjs?file=";
+ // append the file that should be served
+ src += escape("tests/dom/security/test/csp/" + curTest.file)
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(curTest.policy);
+
+ document.getElementById("testframe").addEventListener("load", test, false);
+ document.getElementById("testframe").src = src;
+}
+
+function test() {
+ try {
+ document.getElementById("testframe").removeEventListener('load', test, false);
+ var testframe = document.getElementById("testframe");
+ var divcontent = testframe.contentWindow.document.getElementById('testdiv').innerHTML;
+ is(divcontent, curTest.result, curTest.desc);
+ }
+ catch (e) {
+ ok(false, "ERROR: could not access content for test: '" + curTest.desc + "'");
+ }
+ loadNextTest();
+}
+
+// start running the tests
+loadNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_subframe_run_js_if_allowed.html b/dom/security/test/csp/test_subframe_run_js_if_allowed.html
new file mode 100644
index 000000000..ccc81a265
--- /dev/null
+++ b/dom/security/test/csp/test_subframe_run_js_if_allowed.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=702439
+
+This test verifies that child iframes of CSP documents are
+permitted to execute javascript: URLs assuming the policy
+allows this.
+-->
+<head>
+ <title>Test for Bug 702439</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe id="i"></iframe>
+<script class="testbody" type="text/javascript">
+var javascript_link_ran = false;
+
+// check that the script in the child frame's javascript: URL ran
+function checkResult()
+{
+ is(javascript_link_ran, true,
+ "javascript URL didn't execute");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+document.getElementById('i').src = 'file_subframe_run_js_if_allowed.html';
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_upgrade_insecure.html b/dom/security/test/csp/test_upgrade_insecure.html
new file mode 100644
index 000000000..a2b99b8db
--- /dev/null
+++ b/dom/security/test/csp/test_upgrade_insecure.html
@@ -0,0 +1,181 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1139297 - Implement CSP upgrade-insecure-requests directive</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * We load resources (img, script, sytle, etc) over *http* and make sure
+ * that all the resources get upgraded to use >> https << when the
+ * csp-directive "upgrade-insecure-requests" is specified. We further
+ * test that subresources within nested contexts (iframes) get upgraded
+ * and also test the handling of server side redirects.
+ *
+ * In detail:
+ * We perform an XHR request to the *.sjs file which is processed async on
+ * the server and waits till all the requests were processed by the server.
+ * Once the server received all the different requests, the server responds
+ * to the initial XHR request with an array of results which must match
+ * the expected results from each test, making sure that all requests
+ * received by the server (*.sjs) were actually *https* requests.
+ */
+
+const UPGRADE_POLICY =
+ "upgrade-insecure-requests;" + // upgrade all http requests to https
+ "block-all-mixed-content;" + // upgrade should be enforced before block-all.
+ "default-src https: wss: 'unsafe-inline';" + // only allow https: and wss:
+ "form-action https:;"; // explicit, no fallback to default-src
+
+const UPGRADE_POLICY_NO_DEFAULT_SRC =
+ "upgrade-insecure-requests;" + // upgrade all http requests to https
+ "script-src 'unsafe-inline' *"; // we have to whitelist the inline scripts
+ // in the test.
+const NO_UPGRADE_POLICY =
+ "default-src http: ws: 'unsafe-inline';" + // allow http:// and ws://
+ "form-action http:;"; // explicit, no fallback to default-src
+
+var tests = [
+ { // (1) test that all requests within an >> https << page get updated
+ policy: UPGRADE_POLICY,
+ topLevelScheme: "https://",
+ description: "upgrade all requests on toplevel https",
+ deliveryMethod: "header",
+ results: [
+ "iframe-ok", "script-ok", "img-ok", "img-redir-ok", "font-ok", "xhr-ok", "style-ok",
+ "media-ok", "object-ok", "form-ok", "websocket-ok", "nested-img-ok"
+ ]
+ },
+ { // (2) test that all requests within an >> http << page get updated
+ policy: UPGRADE_POLICY,
+ topLevelScheme: "http://",
+ description: "upgrade all requests on toplevel http",
+ deliveryMethod: "header",
+ results: [
+ "iframe-ok", "script-ok", "img-ok", "img-redir-ok", "font-ok", "xhr-ok", "style-ok",
+ "media-ok", "object-ok", "form-ok", "websocket-ok", "nested-img-ok"
+ ]
+ },
+ { // (3) test that all requests within an >> http << page get updated, but do
+ // not specify a default-src directive.
+ policy: UPGRADE_POLICY_NO_DEFAULT_SRC,
+ topLevelScheme: "http://",
+ description: "upgrade all requests on toplevel http where default-src is not specified",
+ deliveryMethod: "header",
+ results: [
+ "iframe-ok", "script-ok", "img-ok", "img-redir-ok", "font-ok", "xhr-ok", "style-ok",
+ "media-ok", "object-ok", "form-ok", "websocket-ok", "nested-img-ok"
+ ]
+ },
+ { // (4) test that no requests get updated if >> upgrade-insecure-requests << is not used
+ policy: NO_UPGRADE_POLICY,
+ topLevelScheme: "http://",
+ description: "do not upgrade any requests on toplevel http",
+ deliveryMethod: "header",
+ results: [
+ "iframe-error", "script-error", "img-error", "img-redir-error", "font-error",
+ "xhr-error", "style-error", "media-error", "object-error", "form-error",
+ "websocket-error", "nested-img-error"
+ ]
+ },
+ { // (5) test that all requests within an >> https << page using meta CSP get updated
+ // policy: UPGRADE_POLICY, that test uses UPGRADE_POLICY within
+ // file_upgrade_insecure_meta.html
+ // no need to define it within that object.
+ topLevelScheme: "https://",
+ description: "upgrade all requests on toplevel https using meta csp",
+ deliveryMethod: "meta",
+ results: [
+ "iframe-ok", "script-ok", "img-ok", "img-redir-ok", "font-ok", "xhr-ok", "style-ok",
+ "media-ok", "object-ok", "form-ok", "websocket-ok", "nested-img-ok"
+ ]
+ },
+];
+
+var counter = 0;
+var curTest;
+
+function loadTestPage() {
+ curTest = tests[counter++];
+ var src = curTest.topLevelScheme + "example.com/tests/dom/security/test/csp/file_testserver.sjs?file=";
+ if (curTest.deliveryMethod === "header") {
+ // append the file that should be served
+ src += escape("tests/dom/security/test/csp/file_upgrade_insecure.html");
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(curTest.policy);
+ }
+ else {
+ src += escape("tests/dom/security/test/csp/file_upgrade_insecure_meta.html");
+ // no csp here, since it's in the meta element
+ }
+ document.getElementById("testframe").src = src;
+}
+
+function finishTest() {
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+}
+
+function checkResults(result) {
+ // try to find the expected result within the results array
+ var index = curTest.results.indexOf(result);
+ isnot(index, -1, curTest.description + " (result: " + result + ")");
+
+ // take the element out the array and continue till the results array is empty
+ if (index != -1) {
+ curTest.results.splice(index, 1);
+ }
+ // lets check if we are expecting more results to bubble up
+ if (curTest.results.length > 0) {
+ return;
+ }
+ // lets see if we ran all the tests
+ if (counter == tests.length) {
+ finishTest();
+ return;
+ }
+ // otherwise it's time to run the next test
+ runNextTest();
+}
+
+// a postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin' to bubble up results back to this main page.
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ checkResults(event.data.result);
+}
+
+function runNextTest() {
+ // sends an xhr request to the server which is processed async, which only
+ // returns after the server has received all the expected requests.
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "file_upgrade_insecure_server.sjs?queryresult");
+ myXHR.onload = function(e) {
+ var results = myXHR.responseText.split(",");
+ for (var index in results) {
+ checkResults(results[index]);
+ }
+ }
+ myXHR.onerror = function(e) {
+ ok(false, "could not query results from server (" + e.message + ")");
+ finishTest();
+ }
+ myXHR.send();
+
+ // give it some time and run the testpage
+ SimpleTest.executeSoon(loadTestPage);
+}
+
+SimpleTest.waitForExplicitFinish();
+runNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_upgrade_insecure_cors.html b/dom/security/test/csp/test_upgrade_insecure_cors.html
new file mode 100644
index 000000000..af296983c
--- /dev/null
+++ b/dom/security/test/csp/test_upgrade_insecure_cors.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1139297 - Implement CSP upgrade-insecure-requests directive</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * We load a page serving two XHR requests (including being redirected);
+ * one that should not require CORS and one that should require cors, in particular:
+ *
+ * Test 1:
+ * Main page: https://test1.example.com
+ * XHR request: http://test1.example.com
+ * Redirect to: http://test1.example.com
+ * Description: Upgrade insecure should upgrade from http to https and also
+ * surpress CORS for that case.
+ *
+ * Test 2:
+ * Main page: https://test1.example.com
+ * XHR request: http://test1.example.com
+ * Redirect to: http://test1.example.com:443
+ * Description: Upgrade insecure should upgrade from http to https and also
+ * prevent CORS for that case.
+ * Note: If redirecting to a different port, then CORS *should* be enforced (unless
+ * it's port 443). Unfortunately we can't test that because of the setup of our
+ * *.sjs files; they only are able to listen to port 443, see:
+ * http://mxr.mozilla.org/mozilla-central/source/build/pgo/server-locations.txt#98
+ *
+ * Test 3:
+ * Main page: https://test1.example.com
+ * XHR request: http://test2.example.com
+ * Redirect to: http://test1.example.com
+ * Description: Upgrade insecure should *not* prevent CORS since
+ * the page performs a cross origin xhr.
+ *
+ */
+
+const CSP_POLICY = "upgrade-insecure-requests; script-src 'unsafe-inline'";
+var tests = 3;
+
+function loadTest() {
+ var src = "https://test1.example.com/tests/dom/security/test/csp/file_testserver.sjs?file=";
+ // append the file that should be served
+ src += escape("tests/dom/security/test/csp/file_upgrade_insecure_cors.html")
+ // append the CSP that should be used to serve the file
+ src += "&csp=" + escape(CSP_POLICY);
+ document.getElementById("testframe").src = src;
+}
+
+function checkResult(result) {
+ if (result === "test1-no-cors-ok" ||
+ result === "test2-no-cors-diffport-ok" ||
+ result === "test3-cors-ok") {
+ ok(true, "'upgrade-insecure-requests' acknowledges CORS (" + result + ")");
+ }
+ else {
+ ok(false, "'upgrade-insecure-requests' acknowledges CORS (" + result + ")");
+ }
+ if (--tests > 0) {
+ return;
+ }
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+}
+
+// a postMessage handler that is used to bubble up results from
+// within the iframe.
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ checkResult(event.data);
+}
+
+SimpleTest.waitForExplicitFinish();
+loadTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_upgrade_insecure_docwrite_iframe.html b/dom/security/test/csp/test_upgrade_insecure_docwrite_iframe.html
new file mode 100644
index 000000000..822158bd7
--- /dev/null
+++ b/dom/security/test/csp/test_upgrade_insecure_docwrite_iframe.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1273430 - Test CSP upgrade-insecure-requests for doc.write(iframe)</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * Load an iframe which ships with a CSP of upgrade-insecure-requests.
+ * Within that iframe a script performs doc.write(iframe) using an
+ * *http* URL. Make sure, the URL is upgraded to *https*.
+ *
+ * +-----------------------------------------+
+ * | |
+ * | http(s); csp: upgrade-insecure-requests | |
+ * | +---------------------------------+ |
+ * | | | |
+ * | | doc.write(<iframe src='http'>); | <--------- upgrade to https
+ * | | | |
+ * | +---------------------------------+ |
+ * | |
+ * +-----------------------------------------+
+ *
+ */
+
+const TEST_FRAME_URL =
+ "https://example.com/tests/dom/security/test/csp/file_upgrade_insecure_docwrite_iframe.sjs?testframe";
+
+// important: the RESULT should have a scheme of *https*
+const RESULT =
+ "https://example.com/tests/dom/security/test/csp/file_upgrade_insecure_docwrite_iframe.sjs?docwriteframe";
+
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ is(event.data.result, RESULT, "doc.write(iframe) of http should be upgraded to https!");
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+}
+
+// start the test
+SimpleTest.waitForExplicitFinish();
+var testframe = document.getElementById("testframe");
+testframe.src = TEST_FRAME_URL;
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_upgrade_insecure_referrer.html b/dom/security/test/csp/test_upgrade_insecure_referrer.html
new file mode 100644
index 000000000..890c57335
--- /dev/null
+++ b/dom/security/test/csp/test_upgrade_insecure_referrer.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1139297 - Implement CSP upgrade-insecure-requests directive</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * We load a page that makes use of the CSP referrer directive as well
+ * as upgrade-insecure-requests. The page loads an image over http.
+ * The test makes sure the request gets upgraded to https and the
+ * correct referrer gets sent.
+ */
+
+var tests = [
+ {
+ query: "test1",
+ description: "upgrade insecure request with 'referrer = origin' (CSP in header)",
+ result: "http://example.com/"
+ },
+ {
+ query: "test2",
+ description: "upgrade insecure request with 'referrer = no-referrer' (CSP in header)",
+ result: ""
+ },
+ {
+ query: "test3",
+ description: "upgrade insecure request with 'referrer = origin' (Meta CSP)",
+ result: "http://example.com/"
+ },
+ {
+ query: "test4",
+ description: "upgrade insecure request with 'referrer = no-referrer' (Meta CSP)",
+ result: ""
+ }
+];
+
+var counter = 0;
+var curTest;
+
+function loadTestPage() {
+ curTest = tests[counter++];
+ var src = "http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_referrer.sjs?";
+ // append the query
+ src += curTest.query;
+ document.getElementById("testframe").src = src;
+}
+
+function runNextTest() {
+ // sends a request to the server which is processed async and returns
+ // once the server received the expected image request
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "file_upgrade_insecure_referrer_server.sjs?queryresult");
+ myXHR.onload = function(e) {
+ is(myXHR.responseText, curTest.result, curTest.description);
+ if (counter == tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+ // move on to the next test by setting off another query request.
+ runNextTest();
+ }
+ myXHR.onerror = function(e) {
+ ok(false, "could not query results from server (" + e.message + ")");
+ SimpleTest.finish();
+ }
+ myXHR.send();
+
+ // give it some time and load the testpage
+ SimpleTest.executeSoon(loadTestPage);
+}
+
+SimpleTest.waitForExplicitFinish();
+runNextTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_upgrade_insecure_reporting.html b/dom/security/test/csp/test_upgrade_insecure_reporting.html
new file mode 100644
index 000000000..967654179
--- /dev/null
+++ b/dom/security/test/csp/test_upgrade_insecure_reporting.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1139297 - Implement CSP upgrade-insecure-requests directive</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * We load an https page which includes an http image. We make sure that
+ * the image request gets upgraded to https but also make sure that a report
+ * is sent when a CSP report only is used which only allows https requests.
+ */
+
+var expectedResults = 2;
+
+function finishTest() {
+ // let's wait till the image was loaded and the report was received
+ if (--expectedResults > 0) {
+ return;
+ }
+ window.removeEventListener("message", receiveMessage, false);
+ SimpleTest.finish();
+}
+
+function runTest() {
+ // (1) Lets send off an XHR request which will return once the server receives
+ // the violation report from the report only policy.
+ var myXHR = new XMLHttpRequest();
+ myXHR.open("GET", "file_upgrade_insecure_reporting_server.sjs?queryresult");
+ myXHR.onload = function(e) {
+ is(myXHR.responseText, "report-ok", "csp-report was sent correctly");
+ finishTest();
+ }
+ myXHR.onerror = function(e) {
+ ok(false, "could not query result for csp-report from server (" + e.message + ")");
+ finishTest();
+ }
+ myXHR.send();
+
+ // (2) We load a page that is served using a CSP and a CSP report only which loads
+ // an image over http.
+ SimpleTest.executeSoon(function() {
+ document.getElementById("testframe").src =
+ "https://example.com/tests/dom/security/test/csp/file_upgrade_insecure_reporting_server.sjs?toplevel";
+ });
+}
+
+// a postMessage handler that is used by sandboxed iframes without
+// 'allow-same-origin' to bubble up results back to this main page.
+window.addEventListener("message", receiveMessage, false);
+function receiveMessage(event) {
+ // (3) make sure the image was correctly loaded
+ is(event.data.result, "img-ok", "upgraded insecure image load from http -> https");
+ finishTest();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/general/bug1277803.html b/dom/security/test/general/bug1277803.html
new file mode 100644
index 000000000..c8033551a
--- /dev/null
+++ b/dom/security/test/general/bug1277803.html
@@ -0,0 +1,11 @@
+<html>
+
+<head>
+ <link rel='icon' href='favicon_bug1277803.ico'>
+</head>
+
+<body>
+Nothing to see here...
+</body>
+
+</html>
diff --git a/dom/security/test/general/chrome.ini b/dom/security/test/general/chrome.ini
new file mode 100644
index 000000000..94bf1ef05
--- /dev/null
+++ b/dom/security/test/general/chrome.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+ favicon_bug1277803.ico
+ bug1277803.html
+
+[test_bug1277803.xul]
+skip-if = os == 'android'
diff --git a/dom/security/test/general/favicon_bug1277803.ico b/dom/security/test/general/favicon_bug1277803.ico
new file mode 100644
index 000000000..d44438903
--- /dev/null
+++ b/dom/security/test/general/favicon_bug1277803.ico
Binary files differ
diff --git a/dom/security/test/general/file_block_script_wrong_mime_server.sjs b/dom/security/test/general/file_block_script_wrong_mime_server.sjs
new file mode 100644
index 000000000..d6d27796c
--- /dev/null
+++ b/dom/security/test/general/file_block_script_wrong_mime_server.sjs
@@ -0,0 +1,34 @@
+// Custom *.sjs specifically for the needs of:
+// Bug 1288361 - Block scripts with wrong MIME type
+
+"use strict";
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+const WORKER = `
+ onmessage = function(event) {
+ postMessage("worker-loaded");
+ };`;
+
+function handleRequest(request, response) {
+ const query = new URLSearchParams(request.queryString);
+
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // Set MIME type
+ response.setHeader("Content-Type", query.get("mime"), false);
+
+ // Deliver response
+ switch (query.get("type")) {
+ case "script":
+ response.write("");
+ break;
+ case "worker":
+ response.write(WORKER);
+ break;
+ case "worker-import":
+ response.write(`importScripts("file_block_script_wrong_mime_server.sjs?type=script&mime=${query.get("mime")}");`);
+ response.write(WORKER);
+ break;
+ }
+}
diff --git a/dom/security/test/general/file_contentpolicytype_targeted_link_iframe.sjs b/dom/security/test/general/file_contentpolicytype_targeted_link_iframe.sjs
new file mode 100644
index 000000000..f0084410a
--- /dev/null
+++ b/dom/security/test/general/file_contentpolicytype_targeted_link_iframe.sjs
@@ -0,0 +1,46 @@
+// custom *.sjs for Bug 1255240
+
+const TEST_FRAME = `
+ <!DOCTYPE HTML>
+ <html>
+ <head><meta charset='utf-8'></head>
+ <body>
+ <a id='testlink' target='innerframe' href='file_contentpolicytype_targeted_link_iframe.sjs?innerframe'>click me</a>
+ <iframe name='innerframe'></iframe>
+ <script type='text/javascript'>
+ var link = document.getElementById('testlink');
+ testlink.click();
+ </script>
+ </body>
+ </html> `;
+
+const INNER_FRAME = `
+ <!DOCTYPE HTML>
+ <html>
+ <head><meta charset='utf-8'></head>
+ hello world!
+ </body>
+ </html>`;
+
+function handleRequest(request, response)
+{
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+
+ var queryString = request.queryString;
+
+ if (queryString === "testframe") {
+ response.write(TEST_FRAME);
+ return;
+ }
+
+ if (queryString === "innerframe") {
+ response.write(INNER_FRAME);
+ return;
+ }
+
+ // we should never get here, but just in case
+ // return something unexpected
+ response.write("do'h");
+}
diff --git a/dom/security/test/general/file_nosniff_testserver.sjs b/dom/security/test/general/file_nosniff_testserver.sjs
new file mode 100644
index 000000000..0cf168a3c
--- /dev/null
+++ b/dom/security/test/general/file_nosniff_testserver.sjs
@@ -0,0 +1,60 @@
+"use strict";
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+const SCRIPT = "var foo = 24;";
+const CSS = "body { background-color: green; }";
+
+// small red image
+const IMG = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+ "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==");
+
+function handleRequest(request, response) {
+ const query = new URLSearchParams(request.queryString);
+
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // set the nosniff header
+ response.setHeader("X-Content-Type-Options", " NoSniFF , foo ", false);
+
+ if (query.has("cssCorrectType")) {
+ response.setHeader("Content-Type", "teXt/cSs", false);
+ response.write(CSS);
+ return;
+ }
+
+ if (query.has("cssWrongType")) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(CSS);
+ return;
+ }
+
+ if (query.has("scriptCorrectType")) {
+ response.setHeader("Content-Type", "appLIcation/jAvaScriPt;blah", false);
+ response.write(SCRIPT);
+ return;
+ }
+
+ if (query.has("scriptWrongType")) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(SCRIPT);
+ return;
+ }
+
+ if (query.has("imgCorrectType")) {
+ response.setHeader("Content-Type", "iMaGe/pnG;blah", false);
+ response.write(IMG);
+ return;
+ }
+
+ if (query.has("imgWrongType")) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(IMG);
+ return;
+ }
+
+ // we should never get here, but just in case
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("do'h");
+}
diff --git a/dom/security/test/general/mochitest.ini b/dom/security/test/general/mochitest.ini
new file mode 100644
index 000000000..70c0c9fb6
--- /dev/null
+++ b/dom/security/test/general/mochitest.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ file_contentpolicytype_targeted_link_iframe.sjs
+ file_nosniff_testserver.sjs
+ file_block_script_wrong_mime_server.sjs
+
+[test_contentpolicytype_targeted_link_iframe.html]
+[test_nosniff.html]
+[test_block_script_wrong_mime.html]
diff --git a/dom/security/test/general/test_block_script_wrong_mime.html b/dom/security/test/general/test_block_script_wrong_mime.html
new file mode 100644
index 000000000..f4da9c577
--- /dev/null
+++ b/dom/security/test/general/test_block_script_wrong_mime.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1288361 - Block scripts with incorrect MIME type</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+
+const MIMETypes = [
+ ["application/javascript", true],
+ ["text/javascript", true],
+
+ ["audio/mpeg", false],
+ ["audio/", false],
+ ["image/jpeg", false],
+ ["image/", false],
+ ["video/mpeg", false],
+ ["video/", false],
+ ["text/csv", false],
+];
+
+// <script src="">
+function testScript([mime, shouldLoad]) {
+ return new Promise((resolve, reject) => {
+ let script = document.createElement("script");
+ script.onload = () => {
+ document.body.removeChild(script);
+ ok(shouldLoad, `script with mime '${mime}' should load`);
+ resolve();
+ };
+ script.onerror = () => {
+ document.body.removeChild(script);
+ ok(!shouldLoad, `script with wrong mime '${mime}' should be blocked`);
+ resolve();
+ };
+ script.src = "file_block_script_wrong_mime_server.sjs?type=script&mime="+mime;
+ document.body.appendChild(script);
+ });
+}
+
+// new Worker()
+function testWorker([mime, shouldLoad]) {
+ return new Promise((resolve, reject) => {
+ let worker = new Worker("file_block_script_wrong_mime_server.sjs?type=worker&mime="+mime);
+ worker.onmessage = (event) => {
+ ok(shouldLoad, `worker with mime '${mime}' should load`)
+ is(event.data, "worker-loaded", "worker should send correct message");
+ resolve();
+ };
+ worker.onerror = (error) => {
+ ok(!shouldLoad, `worker with wrong mime '${mime}' should be blocked`);
+ let msg = error.message;
+ ok(msg.match(/^NetworkError/) || msg.match(/Failed to load worker script/),
+ "should gets correct error message");
+ error.preventDefault();
+ resolve();
+ }
+ worker.postMessage("dummy");
+ });
+}
+
+// new Worker() with importScripts()
+function testWorkerImportScripts([mime, shouldLoad]) {
+ return new Promise((resolve, reject) => {
+ let worker = new Worker("file_block_script_wrong_mime_server.sjs?type=worker-import&mime="+mime);
+ worker.onmessage = (event) => {
+ ok(shouldLoad, `worker/importScripts with mime '${mime}' should load`)
+ is(event.data, "worker-loaded", "worker should send correct message");
+ resolve();
+ };
+ worker.onerror = (error) => {
+ ok(!shouldLoad, `worker/importScripts with wrong mime '${mime}' should be blocked`);
+ let msg = error.message;
+ ok(msg.match(/^NetworkError/) || msg.match(/Failed to load worker script/),
+ "should gets correct error message");
+ error.preventDefault();
+ resolve();
+ }
+ worker.postMessage("dummy");
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({set: [["security.block_script_with_wrong_mime", true]]}, function() {
+ Promise.all(MIMETypes.map(testScript)).then(() => {
+ return Promise.all(MIMETypes.map(testWorker));
+ }).then(() => {
+ return Promise.all(MIMETypes.map(testWorkerImportScripts));
+ }).then(() => {
+ SpecialPowers.popPrefEnv(SimpleTest.finish);
+ });
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/general/test_bug1277803.xul b/dom/security/test/general/test_bug1277803.xul
new file mode 100644
index 000000000..a62285f8a
--- /dev/null
+++ b/dom/security/test/general/test_bug1277803.xul
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Bug 1277803 test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="runTest();">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.requestCompleteLog();
+ let Ci = Components.interfaces;
+ let Cc = Components.classes;
+ let Cu = Components.utils;
+ let makeURI = Cu.import("resource://gre/modules/BrowserUtils.jsm", {}).BrowserUtils.makeURI;
+
+ const BASE_URI = "http://mochi.test:8888/chrome/dom/security/test/general/";
+ const FAVICON_URI = BASE_URI + "favicon_bug1277803.ico";
+ const LOADING_URI = BASE_URI + "bug1277803.html";
+ let testWindow; //will be used to trigger favicon load
+
+ let securityManager = Cc["@mozilla.org/scriptsecuritymanager;1"].
+ getService(Ci.nsIScriptSecurityManager);
+ let expectedPrincipal = securityManager.createCodebasePrincipal(makeURI(LOADING_URI), {});
+ let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance();
+
+ // We expect 2 favicon loads, one from PlacesUIUtils.loadFavicon and one
+ // from XUL:image loads.
+ let requestXUL = false;
+ let requestPlaces = false;
+
+ function runTest() {
+ // Register our observer to intercept favicon requests.
+ let os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ let observer = {
+ observe: function(aSubject, aTopic, aData)
+ {
+ // Make sure this is a favicon request.
+ let httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ if (FAVICON_URI != httpChannel.URI.spec) {
+ return;
+ }
+
+ // Ensure the topic is the one we set an observer for.
+ is(aTopic, "http-on-modify-request", "Expected observer topic");
+
+ // Check for the correct loadingPrincipal, triggeringPrincipal.
+ let triggeringPrincipal = httpChannel.loadInfo.triggeringPrincipal;
+ let loadingPrincipal = httpChannel.loadInfo.loadingPrincipal;
+
+ if (loadingPrincipal.equals(systemPrincipal)) {
+ // This is the favicon loading from XUL, which will have the system
+ // principal as its loading principal and have a content principal
+ // as its triggering principal.
+ ok(triggeringPrincipal.equals(expectedPrincipal),
+ "Correct triggeringPrincipal for favicon from XUL.");
+ requestXUL = true;
+ } else if (loadingPrincipal.equals(expectedPrincipal)) {
+ // This is the favicon loading from Places, which will have a
+ // content principal as its loading principal and triggering
+ // principal.
+ ok(triggeringPrincipal.equals(expectedPrincipal),
+ "Correct triggeringPrincipal for favicon from Places.");
+ requestPlaces = true;
+ } else {
+ ok(false, "An unexpected favicon request.")
+ }
+
+ // Cleanup after ourselves...
+ if (requestXUL && requestPlaces) {
+ os.removeObserver(this, "http-on-modify-request");
+ SimpleTest.finish();
+ }
+ }
+ }
+ os.addObserver(observer, "http-on-modify-request", false);
+
+ // Now that the observer is set up, trigger a favicon load with navigation
+ testWindow = window.open(LOADING_URI);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.registerCleanupFunction(function() {
+ if (testWindow) {
+ testWindow.close();
+ }
+ });
+ ]]></script>
+
+ <browser type="content-primary" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html b/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html
new file mode 100644
index 000000000..7b1ab72dc
--- /dev/null
+++ b/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1255240 - Test content policy types within content policies for targeted links in iframes</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * Let's load a link into a targeted iframe and make sure the content policy
+ * type used for content policy checks is of TYPE_SUBDOCUMENT.
+ */
+
+const Cc = SpecialPowers.Cc;
+const Ci = SpecialPowers.Ci;
+
+const EXPECTED_CONTENT_TYPE = Ci.nsIContentPolicy.TYPE_SUBDOCUMENT;
+const EXPECTED_URL =
+ "http://mochi.test:8888/tests/dom/security/test/general/file_contentpolicytype_targeted_link_iframe.sjs?innerframe";
+const TEST_FRAME_URL =
+ "file_contentpolicytype_targeted_link_iframe.sjs?testframe";
+
+// ----- START Content Policy implementation for the test
+var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+
+const POLICYNAME = "@mozilla.org/iframetestpolicy;1";
+const POLICYID = SpecialPowers.wrap(SpecialPowers.Components)
+ .ID("{6cc95ef3-40e1-4d59-87f0-86f100373227}");
+
+var policy = {
+ // nsISupports implementation
+ QueryInterface: function(iid) {
+ iid = SpecialPowers.wrap(iid);
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIFactory) ||
+ iid.equals(Ci.nsIContentPolicy))
+ return this;
+
+ throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ // nsIFactory implementation
+ createInstance: function(outer, iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsIContentPolicy implementation
+ shouldLoad: function(contentType, contentLocation, requestOrigin,
+ context, mimeTypeGuess, extra) {
+
+ // make sure we get the right amount of content policy calls
+ // e.g. about:blank also gets chrcked by content policies
+ if (contentLocation.asciiSpec === EXPECTED_URL) {
+ is(contentType, EXPECTED_CONTENT_TYPE,
+ "content policy type should TYPESUBDOCUMENT");
+ categoryManager.deleteCategoryEntry("content-policy", POLICYNAME, false);
+ SimpleTest.finish();
+ }
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+
+ shouldProcess: function(contentType, contentLocation, requestOrigin,
+ context, mimeTypeGuess, extra) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+}
+policy = SpecialPowers.wrapCallbackObject(policy);
+
+// Register content policy
+var componentManager = SpecialPowers.wrap(SpecialPowers.Components).manager
+ .QueryInterface(Ci.nsIComponentRegistrar);
+
+componentManager.registerFactory(POLICYID, "Test content policy", POLICYNAME, policy);
+categoryManager.addCategoryEntry("content-policy", POLICYNAME, POLICYNAME, false, true);
+
+// ----- END Content Policy implementation for the test
+
+
+// start the test
+SimpleTest.waitForExplicitFinish();
+var testframe = document.getElementById("testframe");
+testframe.src = TEST_FRAME_URL;
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/general/test_nosniff.html b/dom/security/test/general/test_nosniff.html
new file mode 100644
index 000000000..197251e68
--- /dev/null
+++ b/dom/security/test/general/test_nosniff.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 471020 - Add X-Content-Type-Options: nosniff support to Firefox</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+ <!-- add the two css tests -->
+ <link rel="stylesheet" id="cssCorrectType">
+ <link rel="stylesheet" id="cssWrongType">
+</head>
+<body>
+
+<!-- add the two script tests -->
+<script id="scriptCorrectType"></script>
+<script id="scriptWrongType"></script>
+
+<!-- add the two img tests -->
+<img id="imgCorrectType" />
+<img id="imgWrongType" />
+
+<script class="testbody" type="text/javascript">
+/* Description of the test:
+ * We load 2 css files, 2 script files and 2 image files, where
+ * the sever either responds with the right mime type or
+ * the wrong mime type for each test.
+ */
+
+SimpleTest.waitForExplicitFinish();
+const NUM_TESTS = 6;
+
+var testCounter = 0;
+function checkFinish() {
+ testCounter++;
+ if (testCounter === NUM_TESTS) {
+ SimpleTest.finish();
+ }
+}
+
+SpecialPowers.pushPrefEnv({set: [["security.xcto_nosniff_block_images", true]]}, function() {
+
+ // 1) Test CSS with correct mime type
+ var cssCorrectType = document.getElementById("cssCorrectType");
+ cssCorrectType.onload = function() {
+ ok(true, "style nosniff correct type should load");
+ checkFinish();
+ }
+ cssCorrectType.onerror = function() {
+ ok(false, "style nosniff correct type should load");
+ checkFinish();
+ }
+ cssCorrectType.href = "file_nosniff_testserver.sjs?cssCorrectType";
+
+ // 2) Test CSS with wrong mime type
+ var cssWrongType = document.getElementById("cssWrongType");
+ cssWrongType.onload = function() {
+ ok(false, "style nosniff wrong type should not load");
+ checkFinish();
+ }
+ cssWrongType.onerror = function() {
+ ok(true, "style nosniff wrong type should not load");
+ checkFinish();
+ }
+ cssWrongType.href = "file_nosniff_testserver.sjs?cssWrongType";
+
+ // 3) Test SCRIPT with correct mime type
+ var scriptCorrectType = document.getElementById("scriptCorrectType");
+ scriptCorrectType.onload = function() {
+ ok(true, "script nosniff correct type should load");
+ checkFinish();
+ }
+ scriptCorrectType.onerror = function() {
+ ok(false, "script nosniff correct type should load");
+ checkFinish();
+ }
+ scriptCorrectType.src = "file_nosniff_testserver.sjs?scriptCorrectType";
+
+ // 4) Test SCRIPT with wrong mime type
+ var scriptWrongType = document.getElementById("scriptWrongType");
+ scriptWrongType.onload = function() {
+ ok(false, "script nosniff wrong type should not load");
+ checkFinish();
+ }
+ scriptWrongType.onerror = function() {
+ ok(true, "script nosniff wrong type should not load");
+ checkFinish();
+ }
+ scriptWrongType.src = "file_nosniff_testserver.sjs?scriptWrongType";
+
+ // 5) Test IMG with correct mime type
+ var imgCorrectType = document.getElementById("imgCorrectType");
+ imgCorrectType.onload = function() {
+ ok(true, "img nosniff correct type should load");
+ checkFinish();
+ }
+ imgCorrectType.onerror = function() {
+ ok(false, "img nosniff correct type should load");
+ checkFinish();
+ }
+ imgCorrectType.src = "file_nosniff_testserver.sjs?imgCorrectType";
+
+ // 6) Test IMG with wrong mime type
+ var imgWrongType = document.getElementById("imgWrongType");
+ imgWrongType.onload = function() {
+ ok(false, "img nosniff wrong type should not load");
+ checkFinish();
+ }
+ imgWrongType.onerror = function() {
+ ok(true, "img nosniff wrong type should not load");
+ checkFinish();
+ }
+ imgWrongType.src = "file_nosniff_testserver.sjs?imgWrongType";
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/gtest/TestCSPParser.cpp b/dom/security/test/gtest/TestCSPParser.cpp
new file mode 100644
index 000000000..fafa7b5d9
--- /dev/null
+++ b/dom/security/test/gtest/TestCSPParser.cpp
@@ -0,0 +1,1132 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form 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/. */
+
+#include "gtest/gtest.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#ifndef MOZILLA_INTERNAL_API
+// some of the includes make use of internal string types
+#define nsAString_h___
+#define nsString_h___
+#define nsStringFwd_h___
+#define nsReadableUtils_h___
+class nsACString;
+class nsAString;
+class nsAFlatString;
+class nsAFlatCString;
+class nsAdoptingString;
+class nsAdoptingCString;
+class nsXPIDLString;
+template<class T> class nsReadingIterator;
+#endif
+
+#include "nsIContentSecurityPolicy.h"
+#include "nsNetUtil.h"
+#include "nsIScriptSecurityManager.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+
+#ifndef MOZILLA_INTERNAL_API
+#undef nsString_h___
+#undef nsAString_h___
+#undef nsReadableUtils_h___
+#endif
+
+/*
+ * Testing the parser is non trivial, especially since we can not call
+ * parser functionality directly in compiled code tests.
+ * All the tests (except the fuzzy tests at the end) follow the same schemata:
+ * a) create an nsIContentSecurityPolicy object
+ * b) set the selfURI in SetRequestContext
+ * c) append one or more policies by calling AppendPolicy
+ * d) check if the policy count is correct by calling GetPolicyCount
+ * e) compare the result of the policy with the expected output
+ * using the struct PolicyTest;
+ *
+ * In general we test:
+ * a) policies that the parser should accept
+ * b) policies that the parser should reject
+ * c) policies that are randomly generated (fuzzy tests)
+ *
+ * Please note that fuzzy tests are *DISABLED* by default and shold only
+ * be run *OFFLINE* whenever code in nsCSPParser changes.
+ * To run fuzzy tests, flip RUN_OFFLINE_TESTS to 1.
+ *
+ */
+
+#define RUN_OFFLINE_TESTS 0
+
+/*
+ * Offline tests are separated in three different groups:
+ * * TestFuzzyPolicies - complete random ASCII input
+ * * TestFuzzyPoliciesIncDir - a directory name followed by random ASCII
+ * * TestFuzzyPoliciesIncDirLimASCII - a directory name followed by limited ASCII
+ * which represents more likely user input.
+ *
+ * We run each of this categories |kFuzzyRuns| times.
+ */
+
+#if RUN_OFFLINE_TESTS
+static const uint32_t kFuzzyRuns = 10000;
+#endif
+
+// For fuzzy testing we actually do not care about the output,
+// we just want to make sure that the parser can handle random
+// input, therefore we use kFuzzyExpectedPolicyCount to return early.
+static const uint32_t kFuzzyExpectedPolicyCount = 111;
+
+static const uint32_t kMaxPolicyLength = 96;
+
+struct PolicyTest
+{
+ char policy[kMaxPolicyLength];
+ char expectedResult[kMaxPolicyLength];
+};
+
+nsresult runTest(uint32_t aExpectedPolicyCount, // this should be 0 for policies which should fail to parse
+ const char* aPolicy,
+ const char* aExpectedResult) {
+
+ nsresult rv;
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we init the csp with http://www.selfuri.com
+ nsCOMPtr<nsIURI> selfURI;
+ rv = NS_NewURI(getter_AddRefs(selfURI), "http://www.selfuri.com");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> selfURIPrincipal;
+ // Can't use BasePrincipal::CreateCodebasePrincipal here
+ // because the symbol is not visible here
+ rv = secman->GetCodebasePrincipal(selfURI, getter_AddRefs(selfURIPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create a CSP object
+ nsCOMPtr<nsIContentSecurityPolicy> csp =
+ do_CreateInstance(NS_CSPCONTEXT_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // for testing the parser we only need to set a principal which is needed
+ // to translate the keyword 'self' into an actual URI.
+ rv = csp->SetRequestContext(nullptr, selfURIPrincipal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // append a policy
+ nsString policyStr;
+ policyStr.AssignASCII(aPolicy);
+ rv = csp->AppendPolicy(policyStr, false, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // when executing fuzzy tests we do not care about the actual output
+ // of the parser, we just want to make sure that the parser is not crashing.
+ if (aExpectedPolicyCount == kFuzzyExpectedPolicyCount) {
+ return NS_OK;
+ }
+
+ // verify that the expected number of policies exists
+ uint32_t actualPolicyCount;
+ rv = csp->GetPolicyCount(&actualPolicyCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (actualPolicyCount != aExpectedPolicyCount) {
+ EXPECT_TRUE(false) <<
+ "Actual policy count not equal to expected policy count (" <<
+ actualPolicyCount << " != " << aExpectedPolicyCount <<
+ ") for policy: " << aPolicy;
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // if the expected policy count is 0, we can return, because
+ // we can not compare any output anyway. Used when parsing
+ // errornous policies.
+ if (aExpectedPolicyCount == 0) {
+ return NS_OK;
+ }
+
+ // compare the parsed policy against the expected result
+ nsString parsedPolicyStr;
+ // checking policy at index 0, which is the one what we appended.
+ rv = csp->GetPolicyString(0, parsedPolicyStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!NS_ConvertUTF16toUTF8(parsedPolicyStr).EqualsASCII(aExpectedResult)) {
+ EXPECT_TRUE(false) <<
+ "Actual policy does not match expected policy (" <<
+ NS_ConvertUTF16toUTF8(parsedPolicyStr).get() << " != " <<
+ aExpectedResult << ")";
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+// ============================= run Tests ========================
+
+nsresult runTestSuite(const PolicyTest* aPolicies,
+ uint32_t aPolicyCount,
+ uint32_t aExpectedPolicyCount) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ bool experimentalEnabledCache = false;
+ bool strictDynamicEnabledCache = false;
+ if (prefs)
+ {
+ prefs->GetBoolPref("security.csp.experimentalEnabled", &experimentalEnabledCache);
+ prefs->SetBoolPref("security.csp.experimentalEnabled", true);
+
+ prefs->GetBoolPref("security.csp.enableStrictDynamic", &strictDynamicEnabledCache);
+ prefs->SetBoolPref("security.csp.enableStrictDynamic", true);
+ }
+
+ for (uint32_t i = 0; i < aPolicyCount; i++) {
+ rv = runTest(aExpectedPolicyCount, aPolicies[i].policy, aPolicies[i].expectedResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (prefs) {
+ prefs->SetBoolPref("security.csp.experimentalEnabled", experimentalEnabledCache);
+ prefs->SetBoolPref("security.csp.enableStrictDynamic", strictDynamicEnabledCache);
+ }
+
+ return NS_OK;
+}
+
+// ============================= TestDirectives ========================
+
+TEST(CSPParser, Directives)
+{
+ static const PolicyTest policies[] =
+ {
+ { "default-src http://www.example.com",
+ "default-src http://www.example.com" },
+ { "script-src http://www.example.com",
+ "script-src http://www.example.com" },
+ { "object-src http://www.example.com",
+ "object-src http://www.example.com" },
+ { "style-src http://www.example.com",
+ "style-src http://www.example.com" },
+ { "img-src http://www.example.com",
+ "img-src http://www.example.com" },
+ { "media-src http://www.example.com",
+ "media-src http://www.example.com" },
+ { "frame-src http://www.example.com",
+ "frame-src http://www.example.com" },
+ { "font-src http://www.example.com",
+ "font-src http://www.example.com" },
+ { "connect-src http://www.example.com",
+ "connect-src http://www.example.com" },
+ { "report-uri http://www.example.com",
+ "report-uri http://www.example.com/" },
+ { "script-src 'nonce-correctscriptnonce'",
+ "script-src 'nonce-correctscriptnonce'" },
+ { "script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='",
+ "script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='" },
+ { "referrer no-referrer",
+ "referrer no-referrer" },
+ { "require-sri-for script style",
+ "require-sri-for script style"},
+ { "script-src 'nonce-foo' 'unsafe-inline' ",
+ "script-src 'nonce-foo' 'unsafe-inline'" },
+ { "script-src 'nonce-foo' 'strict-dynamic' 'unsafe-inline' https: ",
+ "script-src 'nonce-foo' 'strict-dynamic' 'unsafe-inline' https:" },
+ { "default-src 'sha256-siVR8' 'strict-dynamic' 'unsafe-inline' https: ",
+ "default-src 'sha256-siVR8' 'unsafe-inline' https:" },
+ };
+
+ uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)));
+}
+
+// ============================= TestKeywords ========================
+
+TEST(CSPParser, Keywords)
+{
+ static const PolicyTest policies[] =
+ {
+ { "script-src 'self'",
+ "script-src http://www.selfuri.com" },
+ { "script-src 'unsafe-inline'",
+ "script-src 'unsafe-inline'" },
+ { "script-src 'unsafe-eval'",
+ "script-src 'unsafe-eval'" },
+ { "script-src 'unsafe-inline' 'unsafe-eval'",
+ "script-src 'unsafe-inline' 'unsafe-eval'" },
+ { "script-src 'none'",
+ "script-src 'none'" },
+ { "img-src 'none'; script-src 'unsafe-eval' 'unsafe-inline'; default-src 'self'",
+ "img-src 'none'; script-src 'unsafe-eval' 'unsafe-inline'; default-src http://www.selfuri.com" },
+ };
+
+ uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)));
+}
+
+// ============================= TestIgnoreUpperLowerCasePolicies ========================
+
+TEST(CSPParser, IgnoreUpperLowerCasePolicies)
+{
+ static const PolicyTest policies[] =
+ {
+ { "script-src 'SELF'",
+ "script-src http://www.selfuri.com" },
+ { "sCriPt-src 'Unsafe-Inline'",
+ "script-src 'unsafe-inline'" },
+ { "SCRIPT-src 'unsafe-eval'",
+ "script-src 'unsafe-eval'" },
+ { "default-SRC 'unsafe-inline' 'unsafe-eval'",
+ "default-src 'unsafe-inline' 'unsafe-eval'" },
+ { "script-src 'NoNe'",
+ "script-src 'none'" },
+ { "img-sRc 'noNe'; scrIpt-src 'unsafe-EVAL' 'UNSAFE-inline'; deFAULT-src 'Self'",
+ "img-src 'none'; script-src 'unsafe-eval' 'unsafe-inline'; default-src http://www.selfuri.com" },
+ { "default-src HTTP://www.example.com",
+ "default-src http://www.example.com" },
+ { "default-src HTTP://WWW.EXAMPLE.COM",
+ "default-src http://www.example.com" },
+ { "default-src HTTPS://*.example.COM",
+ "default-src https://*.example.com" },
+ { "script-src 'none' test.com;",
+ "script-src http://test.com" },
+ { "script-src 'NoNCE-correctscriptnonce'",
+ "script-src 'nonce-correctscriptnonce'" },
+ { "script-src 'NoncE-NONCENEEDSTOBEUPPERCASE'",
+ "script-src 'nonce-NONCENEEDSTOBEUPPERCASE'" },
+ { "script-src 'SHA256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='",
+ "script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='" },
+ { "refERRer No-refeRRer",
+ "referrer no-referrer" },
+ { "upgrade-INSECURE-requests",
+ "upgrade-insecure-requests" },
+ { "sanDBox alloW-foRMs",
+ "sandbox allow-forms"},
+ { "require-SRI-for sCript stYle",
+ "require-sri-for script style"},
+ };
+
+ uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)));
+}
+
+// ============================= TestPaths ========================
+
+TEST(CSPParser, Paths)
+{
+ static const PolicyTest policies[] =
+ {
+ { "script-src http://www.example.com",
+ "script-src http://www.example.com" },
+ { "script-src http://www.example.com/",
+ "script-src http://www.example.com/" },
+ { "script-src http://www.example.com/path-1",
+ "script-src http://www.example.com/path-1" },
+ { "script-src http://www.example.com/path-1/",
+ "script-src http://www.example.com/path-1/" },
+ { "script-src http://www.example.com/path-1/path_2",
+ "script-src http://www.example.com/path-1/path_2" },
+ { "script-src http://www.example.com/path-1/path_2/",
+ "script-src http://www.example.com/path-1/path_2/" },
+ { "script-src http://www.example.com/path-1/path_2/file.js",
+ "script-src http://www.example.com/path-1/path_2/file.js" },
+ { "script-src http://www.example.com/path-1/path_2/file_1.js",
+ "script-src http://www.example.com/path-1/path_2/file_1.js" },
+ { "script-src http://www.example.com/path-1/path_2/file-2.js",
+ "script-src http://www.example.com/path-1/path_2/file-2.js" },
+ { "script-src http://www.example.com/path-1/path_2/f.js",
+ "script-src http://www.example.com/path-1/path_2/f.js" },
+ { "script-src http://www.example.com:88",
+ "script-src http://www.example.com:88" },
+ { "script-src http://www.example.com:88/",
+ "script-src http://www.example.com:88/" },
+ { "script-src http://www.example.com:88/path-1",
+ "script-src http://www.example.com:88/path-1" },
+ { "script-src http://www.example.com:88/path-1/",
+ "script-src http://www.example.com:88/path-1/" },
+ { "script-src http://www.example.com:88/path-1/path_2",
+ "script-src http://www.example.com:88/path-1/path_2" },
+ { "script-src http://www.example.com:88/path-1/path_2/",
+ "script-src http://www.example.com:88/path-1/path_2/" },
+ { "script-src http://www.example.com:88/path-1/path_2/file.js",
+ "script-src http://www.example.com:88/path-1/path_2/file.js" },
+ { "script-src http://www.example.com:*",
+ "script-src http://www.example.com:*" },
+ { "script-src http://www.example.com:*/",
+ "script-src http://www.example.com:*/" },
+ { "script-src http://www.example.com:*/path-1",
+ "script-src http://www.example.com:*/path-1" },
+ { "script-src http://www.example.com:*/path-1/",
+ "script-src http://www.example.com:*/path-1/" },
+ { "script-src http://www.example.com:*/path-1/path_2",
+ "script-src http://www.example.com:*/path-1/path_2" },
+ { "script-src http://www.example.com:*/path-1/path_2/",
+ "script-src http://www.example.com:*/path-1/path_2/" },
+ { "script-src http://www.example.com:*/path-1/path_2/file.js",
+ "script-src http://www.example.com:*/path-1/path_2/file.js" },
+ { "script-src http://www.example.com#foo",
+ "script-src http://www.example.com" },
+ { "script-src http://www.example.com?foo=bar",
+ "script-src http://www.example.com" },
+ { "script-src http://www.example.com:8888#foo",
+ "script-src http://www.example.com:8888" },
+ { "script-src http://www.example.com:8888?foo",
+ "script-src http://www.example.com:8888" },
+ { "script-src http://www.example.com/#foo",
+ "script-src http://www.example.com/" },
+ { "script-src http://www.example.com/?foo",
+ "script-src http://www.example.com/" },
+ { "script-src http://www.example.com/path-1/file.js#foo",
+ "script-src http://www.example.com/path-1/file.js" },
+ { "script-src http://www.example.com/path-1/file.js?foo",
+ "script-src http://www.example.com/path-1/file.js" },
+ { "script-src http://www.example.com/path-1/file.js?foo#bar",
+ "script-src http://www.example.com/path-1/file.js" },
+ { "report-uri http://www.example.com/",
+ "report-uri http://www.example.com/" },
+ { "report-uri http://www.example.com:8888/asdf",
+ "report-uri http://www.example.com:8888/asdf" },
+ { "report-uri http://www.example.com:8888/path_1/path_2",
+ "report-uri http://www.example.com:8888/path_1/path_2" },
+ { "report-uri http://www.example.com:8888/path_1/path_2/report.sjs&301",
+ "report-uri http://www.example.com:8888/path_1/path_2/report.sjs&301" },
+ { "report-uri /examplepath",
+ "report-uri http://www.selfuri.com/examplepath" },
+ { "connect-src http://www.example.com/foo%3Bsessionid=12%2C34",
+ "connect-src http://www.example.com/foo;sessionid=12,34" },
+ { "connect-src http://www.example.com/foo%3bsessionid=12%2c34",
+ "connect-src http://www.example.com/foo;sessionid=12,34" },
+ { "connect-src http://test.com/pathIncludingAz19-._~!$&'()*+=:@",
+ "connect-src http://test.com/pathIncludingAz19-._~!$&'()*+=:@" },
+ { "script-src http://www.example.com:88/.js",
+ "script-src http://www.example.com:88/.js" },
+ { "script-src https://foo.com/_abc/abc_/_/_a_b_c_",
+ "script-src https://foo.com/_abc/abc_/_/_a_b_c_" }
+ };
+
+ uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)));
+}
+
+// ============================= TestSimplePolicies ========================
+
+TEST(CSPParser, SimplePolicies)
+{
+ static const PolicyTest policies[] =
+ {
+ { "default-src *",
+ "default-src *" },
+ { "default-src https:",
+ "default-src https:" },
+ { "default-src https://*",
+ "default-src https://*" },
+ { "default-src *:*",
+ "default-src http://*:*" },
+ { "default-src *:80",
+ "default-src http://*:80" },
+ { "default-src http://*:80",
+ "default-src http://*:80" },
+ { "default-src javascript:",
+ "default-src javascript:" },
+ { "default-src data:",
+ "default-src data:" },
+ { "script-src 'unsafe-eval' 'unsafe-inline' http://www.example.com",
+ "script-src 'unsafe-eval' 'unsafe-inline' http://www.example.com" },
+ { "object-src 'self'",
+ "object-src http://www.selfuri.com" },
+ { "style-src http://www.example.com 'self'",
+ "style-src http://www.example.com http://www.selfuri.com" },
+ { "media-src http://www.example.com http://www.test.com",
+ "media-src http://www.example.com http://www.test.com" },
+ { "connect-src http://www.test.com example.com *.other.com;",
+ "connect-src http://www.test.com http://example.com http://*.other.com"},
+ { "connect-src example.com *.other.com",
+ "connect-src http://example.com http://*.other.com"},
+ { "style-src *.other.com example.com",
+ "style-src http://*.other.com http://example.com"},
+ { "default-src 'self'; img-src *;",
+ "default-src http://www.selfuri.com; img-src *" },
+ { "object-src media1.example.com media2.example.com *.cdn.example.com;",
+ "object-src http://media1.example.com http://media2.example.com http://*.cdn.example.com" },
+ { "script-src trustedscripts.example.com",
+ "script-src http://trustedscripts.example.com" },
+ { "script-src 'self' ; default-src trustedscripts.example.com",
+ "script-src http://www.selfuri.com; default-src http://trustedscripts.example.com" },
+ { "default-src 'none'; report-uri http://localhost:49938/test",
+ "default-src 'none'; report-uri http://localhost:49938/test" },
+ { "default-src app://{app-host-is-uid}",
+ "default-src app://{app-host-is-uid}" },
+ { " ; default-src abc",
+ "default-src http://abc" },
+ { " ; ; ; ; default-src abc ; ; ; ;",
+ "default-src http://abc" },
+ { "script-src 'none' 'none' 'none';",
+ "script-src 'none'" },
+ { "script-src http://www.example.com/path-1//",
+ "script-src http://www.example.com/path-1//" },
+ { "script-src http://www.example.com/path-1//path_2",
+ "script-src http://www.example.com/path-1//path_2" },
+ { "default-src 127.0.0.1",
+ "default-src http://127.0.0.1" },
+ { "default-src 127.0.0.1:*",
+ "default-src http://127.0.0.1:*" },
+ { "default-src -; ",
+ "default-src http://-" },
+ { "script-src 1",
+ "script-src http://1" },
+ { "upgrade-insecure-requests",
+ "upgrade-insecure-requests" },
+ { "upgrade-insecure-requests https:",
+ "upgrade-insecure-requests" },
+ { "sandbox allow-scripts allow-forms ",
+ "sandbox allow-scripts allow-forms" },
+ };
+
+ uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)));
+}
+
+// ============================= TestPoliciesWithInvalidSrc ========================
+
+TEST(CSPParser, PoliciesWithInvalidSrc)
+{
+ static const PolicyTest policies[] =
+ {
+ { "script-src 'self'; SCRIPT-SRC http://www.example.com",
+ "script-src http://www.selfuri.com" },
+ { "script-src 'none' test.com; script-src example.com",
+ "script-src http://test.com" },
+ { "default-src **",
+ "default-src 'none'" },
+ { "default-src 'self",
+ "default-src 'none'" },
+ { "default-src 'unsafe-inlin' ",
+ "default-src 'none'" },
+ { "default-src */",
+ "default-src 'none'" },
+ { "default-src",
+ "default-src 'none'" },
+ { "default-src 'unsafe-inlin' ",
+ "default-src 'none'" },
+ { "default-src :88",
+ "default-src 'none'" },
+ { "script-src abc::::::88",
+ "script-src 'none'" },
+ { "script-src *.*:*",
+ "script-src 'none'" },
+ { "img-src *::88",
+ "img-src 'none'" },
+ { "object-src http://localhost:",
+ "object-src 'none'" },
+ { "script-src test..com",
+ "script-src 'none'" },
+ { "script-src sub1.sub2.example+",
+ "script-src 'none'" },
+ { "script-src http://www.example.com//",
+ "script-src 'none'" },
+ { "script-src http://www.example.com:88path-1/",
+ "script-src 'none'" },
+ { "script-src http://www.example.com:88//",
+ "script-src 'none'" },
+ { "script-src http://www.example.com:88//path-1",
+ "script-src 'none'" },
+ { "script-src http://www.example.com:88//path-1",
+ "script-src 'none'" },
+ { "script-src http://www.example.com:88.js",
+ "script-src 'none'" },
+ { "script-src http://www.example.com:*.js",
+ "script-src 'none'" },
+ { "script-src http://www.example.com:*.",
+ "script-src 'none'" },
+ { "connect-src http://www.example.com/foo%zz;",
+ "connect-src 'none'" },
+ { "script-src https://foo.com/%$",
+ "script-src 'none'" },
+ { "require-SRI-for script elephants",
+ "require-sri-for script"},
+ { "sandbox foo",
+ "sandbox"},
+ };
+
+ // amount of tests - 1, because the latest should be ignored.
+ uint32_t policyCount = (sizeof(policies) / sizeof(PolicyTest)) -1;
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)));
+}
+
+// ============================= TestBadPolicies ========================
+
+TEST(CSPParser, BadPolicies)
+{
+ static const PolicyTest policies[] =
+ {
+ { "script-sr 'self", "" },
+ { "", "" },
+ { "; ; ; ; ; ; ;", "" },
+ { "defaut-src asdf", "" },
+ { "default-src: aaa", "" },
+ { "asdf http://test.com", ""},
+ { "referrer", ""},
+ { "referrer foo", ""},
+ { "require-sri-for", ""},
+ { "require-sri-for foo", ""},
+ { "report-uri", ""},
+ { "report-uri http://:foo", ""},
+ };
+
+ uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(policies, policyCount, 0)));
+}
+
+// ============================= TestGoodGeneratedPolicies ========================
+
+TEST(CSPParser, GoodGeneratedPolicies)
+{
+ static const PolicyTest policies[] =
+ {
+ { "default-src 'self'; img-src *",
+ "default-src http://www.selfuri.com; img-src *" },
+ { "report-uri /policy",
+ "report-uri http://www.selfuri.com/policy"},
+ { "img-src *",
+ "img-src *" },
+ { "media-src foo.bar",
+ "media-src http://foo.bar" },
+ { "frame-src *.bar",
+ "frame-src http://*.bar" },
+ { "font-src com",
+ "font-src http://com" },
+ { "connect-src f00b4r.com",
+ "connect-src http://f00b4r.com" },
+ { "default-src {app-url-is-uid}",
+ "default-src http://{app-url-is-uid}" },
+ { "script-src *.a.b.c",
+ "script-src http://*.a.b.c" },
+ { "object-src *.b.c",
+ "object-src http://*.b.c" },
+ { "style-src a.b.c",
+ "style-src http://a.b.c" },
+ { "img-src a.com",
+ "img-src http://a.com" },
+ { "media-src http://abc.com",
+ "media-src http://abc.com" },
+ { "frame-src a2-c.com",
+ "frame-src http://a2-c.com" },
+ { "font-src https://a.com",
+ "font-src https://a.com" },
+ { "connect-src *.a.com",
+ "connect-src http://*.a.com" },
+ { "default-src a.com:23",
+ "default-src http://a.com:23" },
+ { "script-src https://a.com:200",
+ "script-src https://a.com:200" },
+ { "object-src data:",
+ "object-src data:" },
+ { "style-src javascript:",
+ "style-src javascript:" },
+ { "img-src {app-host-is-uid}",
+ "img-src http://{app-host-is-uid}" },
+ { "media-src app://{app-host-is-uid}",
+ "media-src app://{app-host-is-uid}" },
+ { "frame-src https://foobar.com:443",
+ "frame-src https://foobar.com:443" },
+ { "font-src https://a.com:443",
+ "font-src https://a.com:443" },
+ { "connect-src http://a.com:80",
+ "connect-src http://a.com:80" },
+ { "default-src http://foobar.com",
+ "default-src http://foobar.com" },
+ { "script-src https://foobar.com",
+ "script-src https://foobar.com" },
+ { "object-src https://{app-host-is-uid}",
+ "object-src https://{app-host-is-uid}" },
+ { "style-src 'none'",
+ "style-src 'none'" },
+ { "img-src foo.bar:21 https://ras.bar",
+ "img-src http://foo.bar:21 https://ras.bar" },
+ { "media-src http://foo.bar:21 https://ras.bar:443",
+ "media-src http://foo.bar:21 https://ras.bar:443" },
+ { "frame-src http://self.com:80",
+ "frame-src http://self.com:80" },
+ { "font-src http://self.com",
+ "font-src http://self.com" },
+ { "connect-src https://foo.com http://bar.com:88",
+ "connect-src https://foo.com http://bar.com:88" },
+ { "default-src * https://bar.com 'none'",
+ "default-src * https://bar.com" },
+ { "script-src *.foo.com",
+ "script-src http://*.foo.com" },
+ { "object-src http://b.com",
+ "object-src http://b.com" },
+ { "style-src http://bar.com:88",
+ "style-src http://bar.com:88" },
+ { "img-src https://bar.com:88",
+ "img-src https://bar.com:88" },
+ { "media-src http://bar.com:443",
+ "media-src http://bar.com:443" },
+ { "frame-src https://foo.com:88",
+ "frame-src https://foo.com:88" },
+ { "font-src http://foo.com",
+ "font-src http://foo.com" },
+ { "connect-src http://x.com:23",
+ "connect-src http://x.com:23" },
+ { "default-src http://barbaz.com",
+ "default-src http://barbaz.com" },
+ { "script-src http://somerandom.foo.com",
+ "script-src http://somerandom.foo.com" },
+ { "default-src *",
+ "default-src *" },
+ { "style-src http://bar.com:22",
+ "style-src http://bar.com:22" },
+ { "img-src https://foo.com:443",
+ "img-src https://foo.com:443" },
+ { "script-src https://foo.com; ",
+ "script-src https://foo.com" },
+ { "img-src bar.com:*",
+ "img-src http://bar.com:*" },
+ { "font-src https://foo.com:400",
+ "font-src https://foo.com:400" },
+ { "connect-src http://bar.com:400",
+ "connect-src http://bar.com:400" },
+ { "default-src http://evil.com",
+ "default-src http://evil.com" },
+ { "script-src https://evil.com:100",
+ "script-src https://evil.com:100" },
+ { "default-src bar.com; script-src https://foo.com",
+ "default-src http://bar.com; script-src https://foo.com" },
+ { "default-src 'self'; script-src 'self' https://*:*",
+ "default-src http://www.selfuri.com; script-src http://www.selfuri.com https://*:*" },
+ { "img-src http://self.com:34",
+ "img-src http://self.com:34" },
+ { "media-src http://subd.self.com:34",
+ "media-src http://subd.self.com:34" },
+ { "default-src 'none'",
+ "default-src 'none'" },
+ { "connect-src http://self",
+ "connect-src http://self" },
+ { "default-src http://foo",
+ "default-src http://foo" },
+ { "script-src http://foo:80",
+ "script-src http://foo:80" },
+ { "object-src http://bar",
+ "object-src http://bar" },
+ { "style-src http://three:80",
+ "style-src http://three:80" },
+ { "img-src https://foo:400",
+ "img-src https://foo:400" },
+ { "media-src https://self:34",
+ "media-src https://self:34" },
+ { "frame-src https://bar",
+ "frame-src https://bar" },
+ { "font-src http://three:81",
+ "font-src http://three:81" },
+ { "connect-src https://three:81",
+ "connect-src https://three:81" },
+ { "script-src http://self.com:80/foo",
+ "script-src http://self.com:80/foo" },
+ { "object-src http://self.com/foo",
+ "object-src http://self.com/foo" },
+ { "report-uri /report.py",
+ "report-uri http://www.selfuri.com/report.py"},
+ { "img-src http://foo.org:34/report.py",
+ "img-src http://foo.org:34/report.py" },
+ { "media-src foo/bar/report.py",
+ "media-src http://foo/bar/report.py" },
+ { "report-uri /",
+ "report-uri http://www.selfuri.com/"},
+ { "font-src https://self.com/report.py",
+ "font-src https://self.com/report.py" },
+ { "connect-src https://foo.com/report.py",
+ "connect-src https://foo.com/report.py" },
+ { "default-src *; report-uri http://www.reporturi.com/",
+ "default-src *; report-uri http://www.reporturi.com/" },
+ { "default-src http://first.com",
+ "default-src http://first.com" },
+ { "script-src http://second.com",
+ "script-src http://second.com" },
+ { "object-src http://third.com",
+ "object-src http://third.com" },
+ { "style-src https://foobar.com:4443",
+ "style-src https://foobar.com:4443" },
+ { "img-src http://foobar.com:4443",
+ "img-src http://foobar.com:4443" },
+ { "media-src bar.com",
+ "media-src http://bar.com" },
+ { "frame-src http://bar.com",
+ "frame-src http://bar.com" },
+ { "font-src http://self.com/",
+ "font-src http://self.com/" },
+ { "script-src 'self'",
+ "script-src http://www.selfuri.com" },
+ { "default-src http://self.com/foo.png",
+ "default-src http://self.com/foo.png" },
+ { "script-src http://self.com/foo.js",
+ "script-src http://self.com/foo.js" },
+ { "object-src http://bar.com/foo.js",
+ "object-src http://bar.com/foo.js" },
+ { "style-src http://FOO.COM",
+ "style-src http://foo.com" },
+ { "img-src HTTP",
+ "img-src http://http" },
+ { "media-src http",
+ "media-src http://http" },
+ { "frame-src 'SELF'",
+ "frame-src http://www.selfuri.com" },
+ { "DEFAULT-src 'self';",
+ "default-src http://www.selfuri.com" },
+ { "default-src 'self' http://FOO.COM",
+ "default-src http://www.selfuri.com http://foo.com" },
+ { "default-src 'self' HTTP://foo.com",
+ "default-src http://www.selfuri.com http://foo.com" },
+ { "default-src 'NONE'",
+ "default-src 'none'" },
+ { "script-src policy-uri ",
+ "script-src http://policy-uri" },
+ { "img-src 'self'; ",
+ "img-src http://www.selfuri.com" },
+ { "frame-ancestors foo-bar.com",
+ "frame-ancestors http://foo-bar.com" },
+ { "frame-ancestors http://a.com",
+ "frame-ancestors http://a.com" },
+ { "frame-ancestors 'self'",
+ "frame-ancestors http://www.selfuri.com" },
+ { "frame-ancestors http://self.com:88",
+ "frame-ancestors http://self.com:88" },
+ { "frame-ancestors http://a.b.c.d.e.f.g.h.i.j.k.l.x.com",
+ "frame-ancestors http://a.b.c.d.e.f.g.h.i.j.k.l.x.com" },
+ { "frame-ancestors https://self.com:34",
+ "frame-ancestors https://self.com:34" },
+ { "default-src 'none'; frame-ancestors 'self'",
+ "default-src 'none'; frame-ancestors http://www.selfuri.com" },
+ { "frame-ancestors http://self:80",
+ "frame-ancestors http://self:80" },
+ { "frame-ancestors http://self.com/bar",
+ "frame-ancestors http://self.com/bar" },
+ { "default-src 'self'; frame-ancestors 'self'",
+ "default-src http://www.selfuri.com; frame-ancestors http://www.selfuri.com" },
+ { "frame-ancestors http://bar.com/foo.png",
+ "frame-ancestors http://bar.com/foo.png" },
+ };
+
+ uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)));
+}
+
+// ============================= TestBadGeneratedPolicies ========================
+
+TEST(CSPParser, BadGeneratedPolicies)
+{
+ static const PolicyTest policies[] =
+ {
+ { "foo.*.bar", ""},
+ { "foo!bar.com", ""},
+ { "x.*.a.com", ""},
+ { "a#2-c.com", ""},
+ { "http://foo.com:bar.com:23", ""},
+ { "f!oo.bar", ""},
+ { "ht!ps://f-oo.bar", ""},
+ { "https://f-oo.bar:3f", ""},
+ { "**", ""},
+ { "*a", ""},
+ { "http://username:password@self.com/foo", ""},
+ { "http://other:pass1@self.com/foo", ""},
+ { "http://user1:pass1@self.com/foo", ""},
+ { "http://username:password@self.com/bar", ""},
+ };
+
+ uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(policies, policyCount, 0)));
+}
+
+// ============ TestGoodGeneratedPoliciesForPathHandling ============
+
+TEST(CSPParser, GoodGeneratedPoliciesForPathHandling)
+{
+ // Once bug 808292 (Implement path-level host-source matching to CSP)
+ // lands we have to update the expected output to include the parsed path
+
+ static const PolicyTest policies[] =
+ {
+ { "img-src http://test1.example.com",
+ "img-src http://test1.example.com" },
+ { "img-src http://test1.example.com/",
+ "img-src http://test1.example.com/" },
+ { "img-src http://test1.example.com/path-1",
+ "img-src http://test1.example.com/path-1" },
+ { "img-src http://test1.example.com/path-1/",
+ "img-src http://test1.example.com/path-1/" },
+ { "img-src http://test1.example.com/path-1/path_2/",
+ "img-src http://test1.example.com/path-1/path_2/" },
+ { "img-src http://test1.example.com/path-1/path_2/file.js",
+ "img-src http://test1.example.com/path-1/path_2/file.js" },
+ { "img-src http://test1.example.com/path-1/path_2/file_1.js",
+ "img-src http://test1.example.com/path-1/path_2/file_1.js" },
+ { "img-src http://test1.example.com/path-1/path_2/file-2.js",
+ "img-src http://test1.example.com/path-1/path_2/file-2.js" },
+ { "img-src http://test1.example.com/path-1/path_2/f.js",
+ "img-src http://test1.example.com/path-1/path_2/f.js" },
+ { "img-src http://test1.example.com/path-1/path_2/f.oo.js",
+ "img-src http://test1.example.com/path-1/path_2/f.oo.js" },
+ { "img-src test1.example.com",
+ "img-src http://test1.example.com" },
+ { "img-src test1.example.com/",
+ "img-src http://test1.example.com/" },
+ { "img-src test1.example.com/path-1",
+ "img-src http://test1.example.com/path-1" },
+ { "img-src test1.example.com/path-1/",
+ "img-src http://test1.example.com/path-1/" },
+ { "img-src test1.example.com/path-1/path_2/",
+ "img-src http://test1.example.com/path-1/path_2/" },
+ { "img-src test1.example.com/path-1/path_2/file.js",
+ "img-src http://test1.example.com/path-1/path_2/file.js" },
+ { "img-src test1.example.com/path-1/path_2/file_1.js",
+ "img-src http://test1.example.com/path-1/path_2/file_1.js" },
+ { "img-src test1.example.com/path-1/path_2/file-2.js",
+ "img-src http://test1.example.com/path-1/path_2/file-2.js" },
+ { "img-src test1.example.com/path-1/path_2/f.js",
+ "img-src http://test1.example.com/path-1/path_2/f.js" },
+ { "img-src test1.example.com/path-1/path_2/f.oo.js",
+ "img-src http://test1.example.com/path-1/path_2/f.oo.js" },
+ { "img-src *.example.com",
+ "img-src http://*.example.com" },
+ { "img-src *.example.com/",
+ "img-src http://*.example.com/" },
+ { "img-src *.example.com/path-1",
+ "img-src http://*.example.com/path-1" },
+ { "img-src *.example.com/path-1/",
+ "img-src http://*.example.com/path-1/" },
+ { "img-src *.example.com/path-1/path_2/",
+ "img-src http://*.example.com/path-1/path_2/" },
+ { "img-src *.example.com/path-1/path_2/file.js",
+ "img-src http://*.example.com/path-1/path_2/file.js" },
+ { "img-src *.example.com/path-1/path_2/file_1.js",
+ "img-src http://*.example.com/path-1/path_2/file_1.js" },
+ { "img-src *.example.com/path-1/path_2/file-2.js",
+ "img-src http://*.example.com/path-1/path_2/file-2.js" },
+ { "img-src *.example.com/path-1/path_2/f.js",
+ "img-src http://*.example.com/path-1/path_2/f.js" },
+ { "img-src *.example.com/path-1/path_2/f.oo.js",
+ "img-src http://*.example.com/path-1/path_2/f.oo.js" },
+ { "img-src test1.example.com:80",
+ "img-src http://test1.example.com:80" },
+ { "img-src test1.example.com:80/",
+ "img-src http://test1.example.com:80/" },
+ { "img-src test1.example.com:80/path-1",
+ "img-src http://test1.example.com:80/path-1" },
+ { "img-src test1.example.com:80/path-1/",
+ "img-src http://test1.example.com:80/path-1/" },
+ { "img-src test1.example.com:80/path-1/path_2",
+ "img-src http://test1.example.com:80/path-1/path_2" },
+ { "img-src test1.example.com:80/path-1/path_2/",
+ "img-src http://test1.example.com:80/path-1/path_2/" },
+ { "img-src test1.example.com:80/path-1/path_2/file.js",
+ "img-src http://test1.example.com:80/path-1/path_2/file.js" },
+ { "img-src test1.example.com:80/path-1/path_2/f.ile.js",
+ "img-src http://test1.example.com:80/path-1/path_2/f.ile.js" },
+ { "img-src test1.example.com:*",
+ "img-src http://test1.example.com:*" },
+ { "img-src test1.example.com:*/",
+ "img-src http://test1.example.com:*/" },
+ { "img-src test1.example.com:*/path-1",
+ "img-src http://test1.example.com:*/path-1" },
+ { "img-src test1.example.com:*/path-1/",
+ "img-src http://test1.example.com:*/path-1/" },
+ { "img-src test1.example.com:*/path-1/path_2",
+ "img-src http://test1.example.com:*/path-1/path_2" },
+ { "img-src test1.example.com:*/path-1/path_2/",
+ "img-src http://test1.example.com:*/path-1/path_2/" },
+ { "img-src test1.example.com:*/path-1/path_2/file.js",
+ "img-src http://test1.example.com:*/path-1/path_2/file.js" },
+ { "img-src test1.example.com:*/path-1/path_2/f.ile.js",
+ "img-src http://test1.example.com:*/path-1/path_2/f.ile.js" },
+ { "img-src http://test1.example.com/abc//",
+ "img-src http://test1.example.com/abc//" },
+ { "img-src https://test1.example.com/abc/def//",
+ "img-src https://test1.example.com/abc/def//" },
+ { "img-src https://test1.example.com/abc/def/ghi//",
+ "img-src https://test1.example.com/abc/def/ghi//" },
+ { "img-src http://test1.example.com:80/abc//",
+ "img-src http://test1.example.com:80/abc//" },
+ { "img-src https://test1.example.com:80/abc/def//",
+ "img-src https://test1.example.com:80/abc/def//" },
+ { "img-src https://test1.example.com:80/abc/def/ghi//",
+ "img-src https://test1.example.com:80/abc/def/ghi//" },
+ { "img-src https://test1.example.com/abc////////////def/",
+ "img-src https://test1.example.com/abc////////////def/" },
+ { "img-src https://test1.example.com/abc////////////",
+ "img-src https://test1.example.com/abc////////////" },
+ };
+
+ uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)));
+}
+
+// ============ TestBadGeneratedPoliciesForPathHandling ============
+
+TEST(CSPParser, BadGeneratedPoliciesForPathHandling)
+{
+ static const PolicyTest policies[] =
+ {
+ { "img-src test1.example.com:88path-1/",
+ "img-src 'none'" },
+ { "img-src test1.example.com:80.js",
+ "img-src 'none'" },
+ { "img-src test1.example.com:*.js",
+ "img-src 'none'" },
+ { "img-src test1.example.com:*.",
+ "img-src 'none'" },
+ { "img-src http://test1.example.com//",
+ "img-src 'none'" },
+ { "img-src http://test1.example.com:80//",
+ "img-src 'none'" },
+ { "img-src http://test1.example.com:80abc",
+ "img-src 'none'" },
+ };
+
+ uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)));
+}
+
+// ============================= TestFuzzyPolicies ========================
+
+// Use a policy, eliminate one character at a time,
+// and feed it as input to the parser.
+
+TEST(CSPParser, ShorteningPolicies)
+{
+ char pol[] = "default-src http://www.sub1.sub2.example.com:88/path1/path2/ 'unsafe-inline' 'none'";
+ uint32_t len = static_cast<uint32_t>(sizeof(pol));
+
+ PolicyTest testPol[1];
+ memset(&testPol[0].policy, '\0', kMaxPolicyLength * sizeof(char));
+
+ while (--len) {
+ memset(&testPol[0].policy, '\0', kMaxPolicyLength * sizeof(char));
+ memcpy(&testPol[0].policy, &pol, len * sizeof(char));
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(testPol, 1,
+ kFuzzyExpectedPolicyCount)));
+ }
+}
+
+// ============================= TestFuzzyPolicies ========================
+
+// We generate kFuzzyRuns inputs by (pseudo) randomly picking from the 128
+// ASCII characters; feed them to the parser and verfy that the parser
+// handles the input gracefully.
+//
+// Please note, that by using srand(0) we get deterministic results!
+
+#if RUN_OFFLINE_TESTS
+
+TEST(CSPParser, FuzzyPolicies)
+{
+ // init srand with 0 so we get same results
+ srand(0);
+
+ PolicyTest testPol[1];
+ memset(&testPol[0].policy, '\0', kMaxPolicyLength);
+
+ for (uint32_t index = 0; index < kFuzzyRuns; index++) {
+ // randomly select the length of the next policy
+ uint32_t polLength = rand() % kMaxPolicyLength;
+ // reset memory of the policy string
+ memset(&testPol[0].policy, '\0', kMaxPolicyLength * sizeof(char));
+
+ for (uint32_t i = 0; i < polLength; i++) {
+ // fill the policy array with random ASCII chars
+ testPol[0].policy[i] = static_cast<char>(rand() % 128);
+ }
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(testPol, 1,
+ kFuzzyExpectedPolicyCount)));
+ }
+}
+
+#endif
+
+// ============================= TestFuzzyPoliciesIncDir ========================
+
+// In a similar fashion as in TestFuzzyPolicies, we again (pseudo) randomly
+// generate input for the parser, but this time also include a valid directive
+// followed by the random input.
+
+#if RUN_OFFLINE_TESTS
+
+TEST(CSPParser, FuzzyPoliciesIncDir)
+{
+ // init srand with 0 so we get same results
+ srand(0);
+
+ PolicyTest testPol[1];
+ memset(&testPol[0].policy, '\0', kMaxPolicyLength);
+
+ char defaultSrc[] = "default-src ";
+ int defaultSrcLen = sizeof(defaultSrc) - 1;
+ // copy default-src into the policy array
+ memcpy(&testPol[0].policy, &defaultSrc, (defaultSrcLen * sizeof(char)));
+
+ for (uint32_t index = 0; index < kFuzzyRuns; index++) {
+ // randomly select the length of the next policy
+ uint32_t polLength = rand() % (kMaxPolicyLength - defaultSrcLen);
+ // reset memory of the policy string, but leave default-src.
+ memset((&(testPol[0].policy) + (defaultSrcLen * sizeof(char))),
+ '\0', (kMaxPolicyLength - defaultSrcLen) * sizeof(char));
+
+ // do not start at index 0 so we do not overwrite 'default-src'
+ for (uint32_t i = defaultSrcLen; i < polLength; i++) {
+ // fill the policy array with random ASCII chars
+ testPol[0].policy[i] = static_cast<char>(rand() % 128);
+ }
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(testPol, 1,
+ kFuzzyExpectedPolicyCount)));
+ }
+}
+
+#endif
+
+// ============================= TestFuzzyPoliciesIncDirLimASCII ==============
+
+// Same as TestFuzzyPoliciesIncDir() but using limited ASCII,
+// which represents more likely input.
+
+#if RUN_OFFLINE_TESTS
+
+TEST(CSPParser, FuzzyPoliciesIncDirLimASCII)
+{
+ char input[] = "1234567890" \
+ "abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWZYZ" \
+ "!@#^&*()-+_=";
+
+ // init srand with 0 so we get same results
+ srand(0);
+
+ PolicyTest testPol[1];
+ memset(&testPol[0].policy, '\0', kMaxPolicyLength);
+
+ char defaultSrc[] = "default-src ";
+ int defaultSrcLen = sizeof(defaultSrc) - 1;
+ // copy default-src into the policy array
+ memcpy(&testPol[0].policy, &defaultSrc, (defaultSrcLen * sizeof(char)));
+
+ for (uint32_t index = 0; index < kFuzzyRuns; index++) {
+ // randomly select the length of the next policy
+ uint32_t polLength = rand() % (kMaxPolicyLength - defaultSrcLen);
+ // reset memory of the policy string, but leave default-src.
+ memset((&(testPol[0].policy) + (defaultSrcLen * sizeof(char))),
+ '\0', (kMaxPolicyLength - defaultSrcLen) * sizeof(char));
+
+ // do not start at index 0 so we do not overwrite 'default-src'
+ for (uint32_t i = defaultSrcLen; i < polLength; i++) {
+ // fill the policy array with chars from the pre-defined input
+ uint32_t inputIndex = rand() % sizeof(input);
+ testPol[0].policy[i] = input[inputIndex];
+ }
+ ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(testPol, 1,
+ kFuzzyExpectedPolicyCount)));
+ }
+}
+#endif
+
diff --git a/dom/security/test/gtest/moz.build b/dom/security/test/gtest/moz.build
new file mode 100644
index 000000000..e927e7bfa
--- /dev/null
+++ b/dom/security/test/gtest/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form 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/.
+
+UNIFIED_SOURCES += [
+ 'TestCSPParser.cpp',
+]
+
+FINAL_LIBRARY = 'xul-gtest'
diff --git a/dom/security/test/hsts/browser.ini b/dom/security/test/hsts/browser.ini
new file mode 100644
index 000000000..ae75031df
--- /dev/null
+++ b/dom/security/test/hsts/browser.ini
@@ -0,0 +1,19 @@
+[DEFAULT]
+skip-if = debug # bug 1311599, bug 1311239, etc
+support-files =
+ head.js
+ file_priming-top.html
+ file_testserver.sjs
+ file_1x1.png
+ file_priming.js
+ file_stylesheet.css
+
+[browser_hsts-priming_allow_active.js]
+[browser_hsts-priming_block_active.js]
+[browser_hsts-priming_hsts_after_mixed.js]
+[browser_hsts-priming_allow_display.js]
+[browser_hsts-priming_block_display.js]
+[browser_hsts-priming_block_active_css.js]
+[browser_hsts-priming_block_active_with_redir_same.js]
+[browser_hsts-priming_no-duplicates.js]
+[browser_hsts-priming_cache-timeout.js]
diff --git a/dom/security/test/hsts/browser_hsts-priming_allow_active.js b/dom/security/test/hsts/browser_hsts-priming_allow_active.js
new file mode 100644
index 000000000..a932b31b3
--- /dev/null
+++ b/dom/security/test/hsts/browser_hsts-priming_allow_active.js
@@ -0,0 +1,24 @@
+/*
+ * Description of the test:
+ * Check that HSTS priming occurs correctly with mixed content when active
+ * content is allowed.
+ */
+'use strict';
+
+//jscs:disable
+add_task(function*() {
+ //jscs:enable
+ Services.obs.addObserver(Observer, "console-api-log-event", false);
+ Services.obs.addObserver(Observer, "http-on-examine-response", false);
+ registerCleanupFunction(do_cleanup);
+
+ let which = "allow_active";
+
+ SetupPrefTestEnvironment(which);
+
+ for (let server of Object.keys(test_servers)) {
+ yield execute_test(server, test_settings[which].mimetype);
+ }
+
+ SpecialPowers.popPrefEnv();
+});
diff --git a/dom/security/test/hsts/browser_hsts-priming_allow_display.js b/dom/security/test/hsts/browser_hsts-priming_allow_display.js
new file mode 100644
index 000000000..06546ca65
--- /dev/null
+++ b/dom/security/test/hsts/browser_hsts-priming_allow_display.js
@@ -0,0 +1,24 @@
+/*
+ * Description of the test:
+ * Check that HSTS priming occurs correctly with mixed content when display
+ * content is allowed.
+ */
+'use strict';
+
+//jscs:disable
+add_task(function*() {
+ //jscs:enable
+ Services.obs.addObserver(Observer, "console-api-log-event", false);
+ Services.obs.addObserver(Observer, "http-on-examine-response", false);
+ registerCleanupFunction(do_cleanup);
+
+ let which = "allow_display";
+
+ SetupPrefTestEnvironment(which);
+
+ for (let server of Object.keys(test_servers)) {
+ yield execute_test(server, test_settings[which].mimetype);
+ }
+
+ SpecialPowers.popPrefEnv();
+});
diff --git a/dom/security/test/hsts/browser_hsts-priming_block_active.js b/dom/security/test/hsts/browser_hsts-priming_block_active.js
new file mode 100644
index 000000000..a5478b185
--- /dev/null
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active.js
@@ -0,0 +1,24 @@
+/*
+ * Description of the test:
+ * Check that HSTS priming occurs correctly with mixed content when active
+ * content is blocked.
+ */
+'use strict';
+
+//jscs:disable
+add_task(function*() {
+ //jscs:enable
+ Services.obs.addObserver(Observer, "console-api-log-event", false);
+ Services.obs.addObserver(Observer, "http-on-examine-response", false);
+ registerCleanupFunction(do_cleanup);
+
+ let which = "block_active";
+
+ SetupPrefTestEnvironment(which);
+
+ for (let server of Object.keys(test_servers)) {
+ yield execute_test(server, test_settings[which].mimetype);
+ }
+
+ SpecialPowers.popPrefEnv();
+});
diff --git a/dom/security/test/hsts/browser_hsts-priming_block_active_css.js b/dom/security/test/hsts/browser_hsts-priming_block_active_css.js
new file mode 100644
index 000000000..340d11483
--- /dev/null
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active_css.js
@@ -0,0 +1,24 @@
+/*
+ * Description of the test:
+ * Check that HSTS priming occurs correctly with mixed content when active
+ * content is blocked for css.
+ */
+'use strict';
+
+//jscs:disable
+add_task(function*() {
+ //jscs:enable
+ Services.obs.addObserver(Observer, "console-api-log-event", false);
+ Services.obs.addObserver(Observer, "http-on-examine-response", false);
+ registerCleanupFunction(do_cleanup);
+
+ let which = "block_active_css";
+
+ SetupPrefTestEnvironment(which);
+
+ for (let server of Object.keys(test_servers)) {
+ yield execute_test(server, test_settings[which].mimetype);
+ }
+
+ SpecialPowers.popPrefEnv();
+});
diff --git a/dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js b/dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js
new file mode 100644
index 000000000..130a3d5ec
--- /dev/null
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js
@@ -0,0 +1,24 @@
+/*
+ * Description of the test:
+ * Check that HSTS priming occurs correctly with mixed content when active
+ * content is blocked and redirect to the same host should still upgrade.
+ */
+'use strict';
+
+//jscs:disable
+add_task(function*() {
+ //jscs:enable
+ Services.obs.addObserver(Observer, "console-api-log-event", false);
+ Services.obs.addObserver(Observer, "http-on-examine-response", false);
+ registerCleanupFunction(do_cleanup);
+
+ let which = "block_active_with_redir_same";
+
+ SetupPrefTestEnvironment(which);
+
+ for (let server of Object.keys(test_servers)) {
+ yield execute_test(server, test_settings[which].mimetype);
+ }
+
+ SpecialPowers.popPrefEnv();
+});
diff --git a/dom/security/test/hsts/browser_hsts-priming_block_display.js b/dom/security/test/hsts/browser_hsts-priming_block_display.js
new file mode 100644
index 000000000..4eca62718
--- /dev/null
+++ b/dom/security/test/hsts/browser_hsts-priming_block_display.js
@@ -0,0 +1,24 @@
+/*
+ * Description of the test:
+ * Check that HSTS priming occurs correctly with mixed content when display
+ * content is blocked.
+ */
+'use strict';
+
+//jscs:disable
+add_task(function*() {
+ //jscs:enable
+ Services.obs.addObserver(Observer, "console-api-log-event", false);
+ Services.obs.addObserver(Observer, "http-on-examine-response", false);
+ registerCleanupFunction(do_cleanup);
+
+ let which = "block_display";
+
+ SetupPrefTestEnvironment(which);
+
+ for (let server of Object.keys(test_servers)) {
+ yield execute_test(server, test_settings[which].mimetype);
+ }
+
+ SpecialPowers.popPrefEnv();
+});
diff --git a/dom/security/test/hsts/browser_hsts-priming_cache-timeout.js b/dom/security/test/hsts/browser_hsts-priming_cache-timeout.js
new file mode 100644
index 000000000..5416a71d2
--- /dev/null
+++ b/dom/security/test/hsts/browser_hsts-priming_cache-timeout.js
@@ -0,0 +1,36 @@
+/*
+ * Description of the test:
+ * Test that the network.hsts_priming.cache_timeout preferene causes the cache
+ * to timeout
+ */
+'use strict';
+
+//jscs:disable
+add_task(function*() {
+ //jscs:enable
+ Observer.add_observers(Services);
+ registerCleanupFunction(do_cleanup);
+
+ let which = "block_display";
+
+ SetupPrefTestEnvironment(which, [["security.mixed_content.hsts_priming_cache_timeout", 1]]);
+
+ yield execute_test("no-ssl", test_settings[which].mimetype);
+
+ let pre_promise = performance.now();
+
+ while ((performance.now() - pre_promise) < 2000) {
+ yield new Promise(function (resolve) {
+ setTimeout(resolve, 2000);
+ });
+ }
+
+ // clear the fact that we saw a priming request
+ test_settings[which].priming = {};
+
+ yield execute_test("no-ssl", test_settings[which].mimetype);
+ is(test_settings[which].priming["no-ssl"], true,
+ "Correctly send a priming request after expiration.");
+
+ SpecialPowers.popPrefEnv();
+});
diff --git a/dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js b/dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js
new file mode 100644
index 000000000..89ea6fbeb
--- /dev/null
+++ b/dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js
@@ -0,0 +1,24 @@
+/*
+ * Description of the test:
+ * Check that HSTS priming occurs correctly with mixed content when the
+ * mixed-content blocks before HSTS.
+ */
+'use strict';
+
+//jscs:disable
+add_task(function*() {
+ //jscs:enable
+ Services.obs.addObserver(Observer, "console-api-log-event", false);
+ Services.obs.addObserver(Observer, "http-on-examine-response", false);
+ registerCleanupFunction(do_cleanup);
+
+ let which = "hsts_after_mixed";
+
+ SetupPrefTestEnvironment(which);
+
+ for (let server of Object.keys(test_servers)) {
+ yield execute_test(server, test_settings[which].mimetype);
+ }
+
+ SpecialPowers.popPrefEnv();
+});
diff --git a/dom/security/test/hsts/browser_hsts-priming_no-duplicates.js b/dom/security/test/hsts/browser_hsts-priming_no-duplicates.js
new file mode 100644
index 000000000..3846fe4f0
--- /dev/null
+++ b/dom/security/test/hsts/browser_hsts-priming_no-duplicates.js
@@ -0,0 +1,30 @@
+/*
+ * Description of the test:
+ * Only one request should be sent per host, even if we run the test more
+ * than once.
+ */
+'use strict';
+
+//jscs:disable
+add_task(function*() {
+ //jscs:enable
+ Observer.add_observers(Services);
+ registerCleanupFunction(do_cleanup);
+
+ let which = "block_display";
+
+ SetupPrefTestEnvironment(which);
+
+ for (let server of Object.keys(test_servers)) {
+ yield execute_test(server, test_settings[which].mimetype);
+ }
+
+ test_settings[which].priming = {};
+
+ // run the tests twice to validate the cache is being used
+ for (let server of Object.keys(test_servers)) {
+ yield execute_test(server, test_settings[which].mimetype);
+ }
+
+ SpecialPowers.popPrefEnv();
+});
diff --git a/dom/security/test/hsts/file_1x1.png b/dom/security/test/hsts/file_1x1.png
new file mode 100644
index 000000000..1ba31ba1a
--- /dev/null
+++ b/dom/security/test/hsts/file_1x1.png
Binary files differ
diff --git a/dom/security/test/hsts/file_priming-top.html b/dom/security/test/hsts/file_priming-top.html
new file mode 100644
index 000000000..b1d1bfa40
--- /dev/null
+++ b/dom/security/test/hsts/file_priming-top.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1246540</title>
+ <meta http-equiv='content-type' content="text/html;charset=utf-8" />
+</head>
+<body>
+ <p id="display"></p>
+ <div id="content" style="visibility: hidden">
+ </div>
+
+<script type="text/javascript">
+/*
+ * Description of the test:
+ * Attempt to load an insecure resource. If the resource responds to HSTS
+ * priming with an STS header, the load should continue securely.
+ * If it does not, the load should continue be blocked or continue insecurely.
+ */
+
+function parse_query_string() {
+ var q = {};
+ document.location.search.substr(1).
+ split('&').forEach(function (item, idx, ar) {
+ let [k, v] = item.split('=');
+ q[k] = unescape(v);
+ });
+ return q;
+}
+
+var args = parse_query_string();
+
+var subresources = {
+ css: { mimetype: 'text/css', file: 'file_stylesheet.css' },
+ img: { mimetype: 'image/png', file: 'file_1x1.png' },
+ script: { mimetype: 'text/javascript', file: 'file_priming.js' },
+};
+
+function handler(ev) {
+ console.log("HSTS_PRIMING: Blocked "+args.id);
+}
+
+function loadCss(src) {
+ let head = document.getElementsByTagName("head")[0];
+ let link = document.createElement("link");
+ link.setAttribute("rel", "stylesheet");
+ link.setAttribute("type", subresources[args.type].mimetype);
+ link.setAttribute("href", src);
+ head.appendChild(link);
+}
+
+function loadResource(src) {
+ let content = document.getElementById("content");
+ let testElem = document.createElement(args.type);
+ testElem.setAttribute("id", args.id);
+ testElem.setAttribute("charset", "UTF-8");
+ testElem.onerror = handler;
+ content.appendChild(testElem);
+ testElem.src = src;
+}
+
+function loadTest() {
+ let subresource = subresources[args.type];
+
+ let src = "http://"
+ + args.host
+ + "/browser/dom/security/test/hsts/file_testserver.sjs"
+ + "?file=" +escape("browser/dom/security/test/hsts/" + subresource.file)
+ + "&primer=" + escape(args.id)
+ + "&mimetype=" + escape(subresource.mimetype)
+ ;
+ if (args.type == 'css') {
+ loadCss(src);
+ return;
+ }
+
+ loadResource(src);
+}
+
+// start running the tests
+loadTest();
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/hsts/file_priming.js b/dom/security/test/hsts/file_priming.js
new file mode 100644
index 000000000..023022da6
--- /dev/null
+++ b/dom/security/test/hsts/file_priming.js
@@ -0,0 +1,4 @@
+function completed() {
+ return;
+}
+completed();
diff --git a/dom/security/test/hsts/file_stylesheet.css b/dom/security/test/hsts/file_stylesheet.css
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/dom/security/test/hsts/file_stylesheet.css
diff --git a/dom/security/test/hsts/file_testserver.sjs b/dom/security/test/hsts/file_testserver.sjs
new file mode 100644
index 000000000..d5cd6b17a
--- /dev/null
+++ b/dom/security/test/hsts/file_testserver.sjs
@@ -0,0 +1,66 @@
+// SJS file for HSTS mochitests
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+function loadFromFile(path) {
+ // Load the HTML to return in the response from file.
+ // Since it's relative to the cwd of the test runner, we start there and
+ // append to get to the actual path of the file.
+ var testFile =
+ Components.classes["@mozilla.org/file/directory_service;1"].
+ getService(Components.interfaces.nsIProperties).
+ get("CurWorkD", Components.interfaces.nsILocalFile);
+ var dirs = path.split("/");
+ for (var i = 0; i < dirs.length; i++) {
+ testFile.append(dirs[i]);
+ }
+ var testFileStream =
+ Components.classes["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Components.interfaces.nsIFileInputStream);
+ testFileStream.init(testFile, -1, 0, 0);
+ var test = NetUtil.readInputStreamToString(testFileStream, testFileStream.available());
+ return test;
+}
+
+function handleRequest(request, response)
+{
+ const query = new URLSearchParams(request.queryString);
+
+ redir = query.get('redir');
+ if (redir == 'same') {
+ query.delete("redir");
+ response.setStatus(302);
+ let newURI = request.uri;
+ newURI.queryString = query.serialize();
+ response.setHeader("Location", newURI.spec)
+ }
+
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ // if we have a priming header, check for required behavior
+ // and set header appropriately
+ if (request.hasHeader('Upgrade-Insecure-Requests')) {
+ var expected = query.get('primer');
+ if (expected == 'prime-hsts') {
+ // set it for 5 minutes
+ response.setHeader("Strict-Transport-Security", "max-age="+(60*5), false);
+ } else if (expected == 'reject-upgrade') {
+ response.setHeader("Strict-Transport-Security", "max-age=0", false);
+ }
+ response.write('');
+ return;
+ }
+
+ var file = query.get('file');
+ if (file) {
+ var mimetype = unescape(query.get('mimetype'));
+ response.setHeader("Content-Type", mimetype, false);
+ response.write(loadFromFile(unescape(file)));
+ return;
+ }
+
+ response.setHeader("Content-Type", "application/json", false);
+ response.write('{}');
+}
diff --git a/dom/security/test/hsts/head.js b/dom/security/test/hsts/head.js
new file mode 100644
index 000000000..362b36444
--- /dev/null
+++ b/dom/security/test/hsts/head.js
@@ -0,0 +1,308 @@
+/*
+ * Description of the tests:
+ * Check that HSTS priming occurs correctly with mixed content
+ *
+ * This test uses three hostnames, each of which treats an HSTS priming
+ * request differently.
+ * * no-ssl never returns an ssl response
+ * * reject-upgrade returns an ssl response, but with no STS header
+ * * prime-hsts returns an ssl response with the appropriate STS header
+ *
+ * For each server, test that it response appropriately when the we allow
+ * or block active or display content, as well as when we send an hsts priming
+ * request, but do not change the order of mixed-content and HSTS.
+ *
+ * Test use http-on-examine-response, so must be run in browser context.
+ */
+'use strict';
+
+var TOP_URI = "https://example.com/browser/dom/security/test/hsts/file_priming-top.html";
+
+var test_servers = {
+ // a test server that does not support TLS
+ 'no-ssl': {
+ host: 'example.co.jp',
+ response: false,
+ id: 'no-ssl',
+ },
+ // a test server which does not support STS upgrade
+ 'reject-upgrade': {
+ host: 'example.org',
+ response: true,
+ id: 'reject-upgrade',
+ },
+ // a test server when sends an STS header when priming
+ 'prime-hsts': {
+ host: 'test1.example.com',
+ response: true,
+ id: 'prime-hsts'
+ },
+};
+
+var test_settings = {
+ // mixed active content is allowed, priming will upgrade
+ allow_active: {
+ block_active: false,
+ block_display: false,
+ use_hsts: true,
+ send_hsts_priming: true,
+ type: 'script',
+ result: {
+ 'no-ssl': 'insecure',
+ 'reject-upgrade': 'insecure',
+ 'prime-hsts': 'secure',
+ },
+ },
+ // mixed active content is blocked, priming will upgrade
+ block_active: {
+ block_active: true,
+ block_display: false,
+ use_hsts: true,
+ send_hsts_priming: true,
+ type: 'script',
+ result: {
+ 'no-ssl': 'blocked',
+ 'reject-upgrade': 'blocked',
+ 'prime-hsts': 'secure',
+ },
+ },
+ // keep the original order of mixed-content and HSTS, but send
+ // priming requests
+ hsts_after_mixed: {
+ block_active: true,
+ block_display: false,
+ use_hsts: false,
+ send_hsts_priming: true,
+ type: 'script',
+ result: {
+ 'no-ssl': 'blocked',
+ 'reject-upgrade': 'blocked',
+ 'prime-hsts': 'blocked',
+ },
+ },
+ // mixed display content is allowed, priming will upgrade
+ allow_display: {
+ block_active: true,
+ block_display: false,
+ use_hsts: true,
+ send_hsts_priming: true,
+ type: 'img',
+ result: {
+ 'no-ssl': 'insecure',
+ 'reject-upgrade': 'insecure',
+ 'prime-hsts': 'secure',
+ },
+ },
+ // mixed display content is blocked, priming will upgrade
+ block_display: {
+ block_active: true,
+ block_display: true,
+ use_hsts: true,
+ send_hsts_priming: true,
+ type: 'img',
+ result: {
+ 'no-ssl': 'blocked',
+ 'reject-upgrade': 'blocked',
+ 'prime-hsts': 'secure',
+ },
+ },
+ // mixed active content is blocked, priming will upgrade (css)
+ block_active_css: {
+ block_active: true,
+ block_display: false,
+ use_hsts: true,
+ send_hsts_priming: true,
+ type: 'css',
+ result: {
+ 'no-ssl': 'blocked',
+ 'reject-upgrade': 'blocked',
+ 'prime-hsts': 'secure',
+ },
+ },
+ // mixed active content is blocked, priming will upgrade
+ // redirect to the same host
+ block_active_with_redir_same: {
+ block_active: true,
+ block_display: false,
+ use_hsts: true,
+ send_hsts_priming: true,
+ type: 'script',
+ redir: 'same',
+ result: {
+ 'no-ssl': 'blocked',
+ 'reject-upgrade': 'blocked',
+ 'prime-hsts': 'secure',
+ },
+ },
+}
+// track which test we are on
+var which_test = "";
+
+const Observer = {
+ observe: function (subject, topic, data) {
+ switch (topic) {
+ case 'console-api-log-event':
+ return Observer.console_api_log_event(subject, topic, data);
+ case 'http-on-examine-response':
+ return Observer.http_on_examine_response(subject, topic, data);
+ case 'http-on-modify-request':
+ return Observer.http_on_modify_request(subject, topic, data);
+ }
+ throw "Can't handle topic "+topic;
+ },
+ add_observers: function (services) {
+ services.obs.addObserver(Observer, "console-api-log-event", false);
+ services.obs.addObserver(Observer, "http-on-examine-response", false);
+ services.obs.addObserver(Observer, "http-on-modify-request", false);
+ },
+ // When a load is blocked which results in an error event within a page, the
+ // test logs to the console.
+ console_api_log_event: function (subject, topic, data) {
+ var message = subject.wrappedJSObject.arguments[0];
+ // when we are blocked, this will match the message we sent to the console,
+ // ignore everything else.
+ var re = RegExp(/^HSTS_PRIMING: Blocked ([-\w]+).*$/);
+ if (!re.test(message)) {
+ return;
+ }
+
+ let id = message.replace(re, '$1');
+ let curTest =test_servers[id];
+
+ if (!curTest) {
+ ok(false, "HSTS priming got a console message blocked, "+
+ "but doesn't match expectations "+id+" (msg="+message);
+ return;
+ }
+
+ is("blocked", test_settings[which_test].result[curTest.id], "HSTS priming "+
+ which_test+":"+curTest.id+" expected "+
+ test_settings[which_test].result[curTest.id]+", got blocked");
+ test_settings[which_test].finished[curTest.id] = "blocked";
+ },
+ get_current_test: function(uri) {
+ for (let item in test_servers) {
+ let re = RegExp('https?://'+test_servers[item].host);
+ if (re.test(uri)) {
+ return test_servers[item];
+ }
+ }
+ return null;
+ },
+ http_on_modify_request: function (subject, topic, data) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ if (channel.requestMethod != 'HEAD') {
+ return;
+ }
+
+ let curTest = this.get_current_test(channel.URI.asciiSpec);
+
+ if (!curTest) {
+ return;
+ }
+
+ ok(!(curTest.id in test_settings[which_test].priming), "Already saw a priming request for " + curTest.id);
+ test_settings[which_test].priming[curTest.id] = true;
+ },
+ // When we see a response come back, peek at the response and test it is secure
+ // or insecure as needed. Addtionally, watch the response for priming requests.
+ http_on_examine_response: function (subject, topic, data) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ let curTest = this.get_current_test(channel.URI.asciiSpec);
+
+ if (!curTest) {
+ return;
+ }
+
+ let result = (channel.URI.asciiSpec.startsWith('https:')) ? "secure" : "insecure";
+
+ // This is a priming request, go ahead and validate we were supposed to see
+ // a response from the server
+ if (channel.requestMethod == 'HEAD') {
+ is(true, curTest.response, "HSTS priming response found " + curTest.id);
+ return;
+ }
+
+ // This is the response to our query, make sure it matches
+ is(result, test_settings[which_test].result[curTest.id],
+ "HSTS priming result " + which_test + ":" + curTest.id);
+ test_settings[which_test].finished[curTest.id] = result;
+ },
+};
+
+// opens `uri' in a new tab and focuses it.
+// returns the newly opened tab
+function openTab(uri) {
+ let tab = gBrowser.addTab(uri);
+
+ // select tab and make sure its browser is focused
+ gBrowser.selectedTab = tab;
+ tab.ownerDocument.defaultView.focus();
+
+ return tab;
+}
+
+function clear_sts_data() {
+ for (let test in test_servers) {
+ SpecialPowers.cleanUpSTSData('http://'+test_servers[test].host);
+ }
+}
+
+function do_cleanup() {
+ clear_sts_data();
+
+ Services.obs.removeObserver(Observer, "console-api-log-event");
+ Services.obs.removeObserver(Observer, "http-on-examine-response");
+}
+
+function SetupPrefTestEnvironment(which, additional_prefs) {
+ which_test = which;
+ clear_sts_data();
+
+ var settings = test_settings[which];
+ // priming counts how many priming requests we saw
+ settings.priming = {};
+ // priming counts how many tests were finished
+ settings.finished= {};
+
+ var prefs = [["security.mixed_content.block_active_content",
+ settings.block_active],
+ ["security.mixed_content.block_display_content",
+ settings.block_display],
+ ["security.mixed_content.use_hsts",
+ settings.use_hsts],
+ ["security.mixed_content.send_hsts_priming",
+ settings.send_hsts_priming]];
+
+ if (additional_prefs) {
+ for (let idx in additional_prefs) {
+ prefs.push(additional_prefs[idx]);
+ }
+ }
+
+ console.log("prefs=%s", prefs);
+
+ SpecialPowers.pushPrefEnv({'set': prefs});
+}
+
+// make the top-level test uri
+function build_test_uri(base_uri, host, test_id, type) {
+ return base_uri +
+ "?host=" + escape(host) +
+ "&id=" + escape(test_id) +
+ "&type=" + escape(type);
+}
+
+// open a new tab, load the test, and wait for it to finish
+function execute_test(test, mimetype) {
+ var src = build_test_uri(TOP_URI, test_servers[test].host,
+ test, test_settings[which_test].type);
+
+ let tab = openTab(src);
+ test_servers[test]['tab'] = tab;
+
+ let browser = gBrowser.getBrowserForTab(tab);
+ yield BrowserTestUtils.browserLoaded(browser);
+
+ yield BrowserTestUtils.removeTab(tab);
+}
diff --git a/dom/security/test/mixedcontentblocker/file_bug803225_test_mailto.html b/dom/security/test/mixedcontentblocker/file_bug803225_test_mailto.html
new file mode 100644
index 000000000..f1459d366
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/file_bug803225_test_mailto.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for Mixed Content Blocker - Mailto Protocol Compose Page
+https://bugzilla.mozilla.org/show_bug.cgi?id=803225
+-->
+<head> <meta charset="utf-8">
+</head>
+<body>
+Hello
+<script>window.close();</script>
+</body>
+</html>
diff --git a/dom/security/test/mixedcontentblocker/file_frameNavigation.html b/dom/security/test/mixedcontentblocker/file_frameNavigation.html
new file mode 100644
index 000000000..fd9ea2317
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/file_frameNavigation.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for Mixed Content Blocker related to navigating children, grandchildren, etc
+https://bugzilla.mozilla.org/show_bug.cgi?id=840388
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Mixed Content Frame Navigation</title>
+</head>
+<body>
+<div id="testContent"></div>
+
+<script>
+ var baseUrlHttps = "https://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html";
+
+ // For tests that require setTimeout, set the maximum polling time to 50 x 100ms = 5 seconds.
+ var MAX_COUNT = 50;
+ var TIMEOUT_INTERVAL = 100;
+
+ var testContent = document.getElementById("testContent");
+
+ // Test 1: Navigate secure iframe to insecure iframe on an insecure page
+ var iframe_test1 = document.createElement("iframe");
+ var counter_test1 = 0;
+ iframe_test1.src = baseUrlHttps + "?insecurePage_navigate_child";
+ iframe_test1.setAttribute("id", "test1");
+ iframe_test1.onerror = function() {
+ parent.postMessage({"test": "insecurePage_navigate_child", "msg": "got an onerror alert when loading or navigating testing iframe"}, "http://mochi.test:8888");
+ };
+ testContent.appendChild(iframe_test1);
+
+ function navigationStatus(iframe_test1)
+ {
+ // When the page is navigating, it goes through about:blank and we will get a permission denied for loc.
+ // Catch that specific exception and return
+ try {
+ var loc = document.getElementById("test1").contentDocument.location;
+ } catch(e) {
+ if (e.name === "SecurityError") {
+ // We received an exception we didn't expect.
+ throw e;
+ }
+ counter_test1++;
+ return;
+ }
+ if (loc == "http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?insecurePage_navigate_child_response") {
+ return;
+ }
+ else {
+ if(counter_test1 < MAX_COUNT) {
+ counter_test1++;
+ setTimeout(navigationStatus, TIMEOUT_INTERVAL, iframe_test1);
+ }
+ else {
+ // After we have called setTimeout the maximum number of times, assume navigating the iframe is blocked
+ parent.postMessage({"test": "insecurePage_navigate_child", "msg": "navigating to insecure iframe blocked on insecure page"}, "http://mochi.test:8888");
+ }
+ }
+ }
+
+ setTimeout(navigationStatus, TIMEOUT_INTERVAL, iframe_test1);
+
+ // Test 2: Navigate secure grandchild iframe to insecure grandchild iframe on a page that has no secure parents
+ var iframe_test2 = document.createElement("iframe");
+ iframe_test2.src = "http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_grandchild.html"
+ iframe_test2.onerror = function() {
+ parent.postMessage({"test": "insecurePage_navigate_grandchild", "msg": "got an on error alert when loading or navigating testing iframe"}, "http://mochi.test:8888");
+ };
+ testContent.appendChild(iframe_test2);
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/mixedcontentblocker/file_frameNavigation_blankTarget.html b/dom/security/test/mixedcontentblocker/file_frameNavigation_blankTarget.html
new file mode 100644
index 000000000..5cfd95984
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/file_frameNavigation_blankTarget.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for Mixed Content Blocker - Opening link with _blank target in an https iframe.
+https://bugzilla.mozilla.org/show_bug.cgi?id=841850
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Mixed Content Frame Navigation</title>
+</head>
+<body>
+<a href="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?blankTarget" id="blankTarget" target="_blank">Go to http site</a>
+
+<script>
+ var blankTarget = document.getElementById("blankTarget");
+ blankTarget.click();
+
+ var os = SpecialPowers.Cc["@mozilla.org/observer-service;1"].
+ getService(SpecialPowers.Components.interfaces.nsIObserverService);
+ var observer = {
+ observe: function(subject, topic, data) {
+ if(topic == "content-document-global-created" && data =="http://example.com") {
+ parent.parent.postMessage({"test": "blankTarget", "msg": "opened an http link with target=_blank from a secure page"}, "http://mochi.test:8888");
+ os.removeObserver(observer, "content-document-global-created");
+ }
+ }
+ }
+ os.addObserver(observer, "content-document-global-created", false);
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/mixedcontentblocker/file_frameNavigation_grandchild.html b/dom/security/test/mixedcontentblocker/file_frameNavigation_grandchild.html
new file mode 100644
index 000000000..1034991da
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/file_frameNavigation_grandchild.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for Mixed Content Blocker - Navigating Grandchild frames when a secure parent doesn't exist
+https://bugzilla.mozilla.org/show_bug.cgi?id=840388
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Mixed Content Frame Navigation</title>
+</head>
+<body>
+<iframe src="https://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?insecurePage_navigate_grandchild" id="child"></iframe>
+
+<script>
+ // For tests that require setTimeout, set the maximum polling time to 100 x 100ms = 10 seconds.
+ var MAX_COUNT = 50;
+ var TIMEOUT_INTERVAL = 100;
+ var counter = 0;
+
+ var child = document.getElementById("child");
+ function navigationStatus(child)
+ {
+ // When the page is navigating, it goes through about:blank and we will get a permission denied for loc.
+ // Catch that specific exception and return
+ try {
+ var loc;
+ if (child.contentDocument) {
+ loc = child.contentDocument.location;
+ }
+ } catch(e) {
+ if (e.message && e.message.indexOf("Permission denied to access property") == -1) {
+ // We received an exception we didn't expect.
+ throw e;
+ }
+ counter++;
+ return;
+ }
+ if (loc == "http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?insecurePage_navigate_grandchild_response") {
+ return;
+ }
+ else {
+ if(counter < MAX_COUNT) {
+ counter++;
+ setTimeout(navigationStatus, TIMEOUT_INTERVAL, child);
+ }
+ else {
+ // After we have called setTimeout the maximum number of times, assume navigating the iframe is blocked
+ parent.parent.postMessage({"test": "insecurePage_navigate_grandchild", "msg": "navigating to insecure grandchild iframe blocked on insecure page"}, "http://mochi.test:8888");
+ }
+ }
+ }
+
+ setTimeout(navigationStatus, TIMEOUT_INTERVAL, child);
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html b/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html
new file mode 100644
index 000000000..62b24f37d
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div id="content"></div>
+<script>
+ // get the case from the query string
+ var type = location.search.substring(1);
+
+ switch (type) {
+ case "insecurePage_navigate_child":
+ document.getElementById("content").innerHTML =
+ '<a href="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?insecurePage_navigate_child_response" id="link">Testing\<\/a>';
+ document.getElementById("link").click();
+ break;
+
+ case "insecurePage_navigate_child_response":
+ parent.parent.postMessage({"test": "insecurePage_navigate_child", "msg": "navigated to insecure iframe on insecure page"}, "http://mochi.test:8888");
+ document.getElementById("content").innerHTML = "Navigated from secure to insecure frame on an insecure page";
+ break;
+
+ case "insecurePage_navigate_grandchild":
+ document.getElementById("content").innerHTML =
+ '<a href="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?insecurePage_navigate_grandchild_response" id="link">Testing\<\/a>';
+ // If we don't reflow before clicking the link, the test will fail intermittently. The reason is still unknown. We'll track this issue in bug 1259715.
+ requestAnimationFrame(function() {
+ setTimeout(function() {
+ document.getElementById("link").click();
+ }, 0);
+ });
+ break;
+
+ case "insecurePage_navigate_grandchild_response":
+ parent.parent.parent.postMessage({"test": "insecurePage_navigate_grandchild", "msg": "navigated to insecure grandchild iframe on insecure page"}, "http://mochi.test:8888");
+ document.getElementById("content").innerHTML = "Navigated from secure to insecure grandchild frame on an insecure page";
+ break;
+
+ case "securePage_navigate_child":
+ document.getElementById("content").innerHTML =
+ '<a href="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?securePage_navigate_child_response" id="link">Testing\<\/a>';
+ document.getElementById("link").click();
+ break;
+
+ case "securePage_navigate_child_response":
+ document.getElementById("content").innerHTML = "<p>Navigated from secure to insecure frame on a secure page</p>";
+ parent.parent.postMessage({"test": "securePage_navigate_child", "msg": "navigated to insecure iframe on secure page"}, "http://mochi.test:8888");
+ break;
+
+ case "securePage_navigate_grandchild":
+ document.getElementById("content").innerHTML=
+ '<a href="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?securePage_navigate_grandchild_response" id="link">Testing\<\/a>';
+ document.getElementById("link").click();
+ break;
+
+ case "securePage_navigate_grandchild_response":
+ dump("\nNavigated to grandchild iframe from secure location to insecure location. About to post message to the top page.\n");
+ parent.parent.parent.postMessage({"test": "securePage_navigate_grandchild", "msg": "navigated to insecure grandchild iframe on secure page"}, "http://mochi.test:8888");
+ dump("\npostMessage to parent attempted.\n");
+ document.getElementById("content").innerHTML = "<p>Navigated from secure to insecure grandchild frame on a secure page</p>";
+ break;
+
+ case "blankTarget":
+ window.close();
+ break;
+
+ default:
+ document.getElementById("content").innerHTML = "Hello";
+ break;
+ }
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/mixedcontentblocker/file_frameNavigation_secure.html b/dom/security/test/mixedcontentblocker/file_frameNavigation_secure.html
new file mode 100644
index 000000000..ea8462c39
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/file_frameNavigation_secure.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for Mixed Content Blocker related to navigating children, grandchildren, etc
+https://bugzilla.mozilla.org/show_bug.cgi?id=840388
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Mixed Content Frame Navigation</title>
+</head>
+<body>
+<div id="testContent"></div>
+
+<script>
+ var baseUrl = "https://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html";
+
+ // For tests that require setTimeout, set the maximum polling time to 50 x 100ms = 5 seconds.
+ var MAX_COUNT = 50;
+ var TIMEOUT_INTERVAL = 100;
+
+ var testContent = document.getElementById("testContent");
+
+ // Test 1: Navigate secure iframe to insecure iframe on a secure page
+ var iframe_test1 = document.createElement("iframe");
+ var counter_test1 = 0;
+ iframe_test1.setAttribute("id", "test1");
+ iframe_test1.src = baseUrl + "?securePage_navigate_child";
+ iframe_test1.onerror = function() {
+ parent.postMessage({"test": "securePage_navigate_child", "msg": "got an onerror event when loading or navigating testing iframe"}, "http://mochi.test:8888");
+ };
+ testContent.appendChild(iframe_test1);
+
+ function navigationStatus(iframe_test1)
+ {
+ // When the page is navigating, it goes through about:blank and we will get a permission denied for loc.
+ // Catch that specific exception and return
+ try {
+ var loc = document.getElementById("test1").contentDocument.location;
+ } catch(e) {
+ if (e.name === "SecurityError") {
+ // We received an exception we didn't expect.
+ throw e;
+ }
+ counter_test1++;
+ return;
+ }
+ if (loc == "http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?insecurePage_navigate_child_response") {
+ return;
+ } else {
+ if(counter_test1 < MAX_COUNT) {
+ counter_test1++;
+ setTimeout(navigationStatus, TIMEOUT_INTERVAL, iframe_test1);
+ }
+ else {
+ // After we have called setTimeout the maximum number of times, assume navigating the iframe is blocked
+ parent.postMessage({"test": "securePage_navigate_child", "msg": "navigating to insecure iframe blocked on secure page"}, "http://mochi.test:8888");
+ }
+ }
+ }
+
+ setTimeout(navigationStatus, TIMEOUT_INTERVAL, iframe_test1);
+
+ // Test 2: Open an http page in a new tab from a link click with target=_blank.
+ var iframe_test3 = document.createElement("iframe");
+ iframe_test3.src = "https://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_blankTarget.html";
+ iframe_test3.onerror = function() {
+ parent.postMessage({"test": "blankTarget", "msg": "got an onerror event when loading or navigating testing iframe"}, "http://mochi.test:8888");
+ };
+ testContent.appendChild(iframe_test3);
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/mixedcontentblocker/file_frameNavigation_secure_grandchild.html b/dom/security/test/mixedcontentblocker/file_frameNavigation_secure_grandchild.html
new file mode 100644
index 000000000..f7f3c4086
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/file_frameNavigation_secure_grandchild.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for Mixed Content Blocker - Navigating Grandchild Frames when a secure parent exists
+https://bugzilla.mozilla.org/show_bug.cgi?id=840388
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Mixed Content Frame Navigation</title>
+</head>
+<body>
+
+<iframe src="https://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?securePage_navigate_grandchild" id="child"></iframe>
+<script>
+ // For tests that require setTimeout, set the maximum polling time to 50 x 100ms = 5 seconds.
+ var MAX_COUNT = 50;
+ var TIMEOUT_INTERVAL = 100;
+ var counter = 0;
+
+ var child = document.getElementById("child");
+ function navigationStatus(child)
+ {
+ var loc;
+ // When the page is navigating, it goes through about:blank and we will get a permission denied for loc.
+ // Catch that specific exception and return
+ try {
+ loc = document.getElementById("child").contentDocument.location;
+ } catch(e) {
+ if (e.message && e.message.indexOf("Permission denied to access property") == -1) {
+ // We received an exception we didn't expect.
+ throw e;
+ }
+ counter++;
+ return;
+ }
+ if (loc == "http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_innermost.html?securePage_navigate_grandchild_response") {
+ return;
+ }
+ else {
+ if(counter < MAX_COUNT) {
+ counter++;
+ setTimeout(navigationStatus, TIMEOUT_INTERVAL, child);
+ }
+ else {
+ // After we have called setTimeout the maximum number of times, assume navigating the iframe is blocked
+ dump("\nThe current location of the grandchild iframe is: "+loc+".\n");
+ dump("\nWe have past the maximum timeout. Navigating a grandchild iframe from an https location to an http location on a secure page failed. We are about to post message to the top level page\n");
+ parent.parent.postMessage({"test": "securePage_navigate_grandchild", "msg": "navigating to insecure grandchild iframe blocked on secure page"}, "http://mochi.test:8888");
+ dump("\nAttempted postMessage\n");
+ }
+ }
+ }
+
+ setTimeout(navigationStatus, TIMEOUT_INTERVAL, child);
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/mixedcontentblocker/file_main.html b/dom/security/test/mixedcontentblocker/file_main.html
new file mode 100644
index 000000000..ade5eefdb
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/file_main.html
@@ -0,0 +1,261 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for Mixed Content Blocker
+https://bugzilla.mozilla.org/show_bug.cgi?id=62178
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 62178</title>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="testContent"></div>
+
+<!-- types the Mixed Content Blocker can block
+ /*
+ switch (aContentType) {
+ case nsIContentPolicy::TYPE_OBJECT:
+ case nsIContentPolicy::TYPE_SCRIPT:
+ case nsIContentPolicy::TYPE_STYLESHEET:
+ case nsIContentPolicy::TYPE_SUBDOCUMENT:
+ case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
+
+ case nsIContentPolicy::TYPE_FONT: - NO TEST:
+ Load events for external fonts are not detectable by javascript.
+ case nsIContentPolicy::TYPE_WEBSOCKET: - NO TEST:
+ websocket connections over https require an encrypted websocket protocol (wss:)
+
+ case nsIContentPolicy::TYPE_IMAGE:
+ case nsIContentPolicy::TYPE_IMAGESET:
+ case nsIContentPolicy::TYPE_MEDIA:
+ case nsIContentPolicy::TYPE_PING:
+ our ping implementation is off by default and does not comply with the current spec (bug 786347)
+ case nsIContentPolicy::TYPE_BEACON:
+
+ }
+ */
+-->
+
+<script>
+ var baseUrl = "http://example.com/tests/dom/security/test/mixedcontentblocker/file_server.sjs";
+
+ //For tests that require setTimeout, set the maximum polling time to 100 x 100ms = 10 seconds.
+ var MAX_COUNT = 100;
+ var TIMEOUT_INTERVAL = 100;
+
+ var testContent = document.getElementById("testContent");
+
+ /* Part 1: Mixed Script tests */
+
+ // Test 1a: insecure object
+ var object = document.createElement("object");
+ object.data = baseUrl + "?type=object";
+ object.type = "application/x-test";
+ object.width = "200";
+ object.height = "200";
+
+ testContent.appendChild(object);
+
+ var objectCount = 0;
+
+ function objectStatus(object) {
+ // Expose our privileged bits on the object
+ object = SpecialPowers.wrap(object);
+
+ if (object.displayedType != SpecialPowers.Ci.nsIObjectLoadingContent.TYPE_NULL) {
+ //object loaded
+ parent.postMessage({"test": "object", "msg": "insecure object loaded"}, "http://mochi.test:8888");
+ }
+ else {
+ if(objectCount < MAX_COUNT) {
+ objectCount++;
+ setTimeout(objectStatus, TIMEOUT_INTERVAL, object);
+ }
+ else {
+ //After we have called setTimeout the maximum number of times, assume object is blocked
+ parent.postMessage({"test": "object", "msg": "insecure object blocked"}, "http://mochi.test:8888");
+ }
+ }
+ }
+
+ // object does not have onload and onerror events. Hence we need a setTimeout to check the object's status
+ setTimeout(objectStatus, TIMEOUT_INTERVAL, object);
+
+ // Test 1b: insecure script
+ var script = document.createElement("script");
+ var scriptLoad = false;
+ var scriptCount = 0;
+ script.src = baseUrl + "?type=script";
+ script.onload = function() {
+ parent.postMessage({"test": "script", "msg": "insecure script loaded"}, "http://mochi.test:8888");
+ scriptLoad = true;
+ }
+ testContent.appendChild(script);
+
+ function scriptStatus(script)
+ {
+ if(scriptLoad) {
+ return;
+ }
+ else {
+ if(scriptCount < MAX_COUNT) {
+ scriptCount++;
+ setTimeout(scriptStatus, TIMEOUT_INTERVAL, script);
+ }
+ else {
+ //After we have called setTimeout the maximum number of times, assume script is blocked
+ parent.postMessage({"test": "script", "msg": "insecure script blocked"}, "http://mochi.test:8888");
+ }
+ }
+ }
+
+ // scripts blocked by Content Policy's do not have onerror events (see bug 789856). Hence we need a setTimeout to check the script's status
+ setTimeout(scriptStatus, TIMEOUT_INTERVAL, script);
+
+
+ // Test 1c: insecure stylesheet
+ var cssStyleSheet = document.createElement("link");
+ cssStyleSheet.rel = "stylesheet";
+ cssStyleSheet.href = baseUrl + "?type=stylesheet";
+ cssStyleSheet.type = "text/css";
+ testContent.appendChild(cssStyleSheet);
+
+ var styleCount = 0;
+
+ function styleStatus(cssStyleSheet) {
+ if( cssStyleSheet.sheet || cssStyleSheet.styleSheet || cssStyleSheet.innerHTML ) {
+ parent.postMessage({"test": "stylesheet", "msg": "insecure stylesheet loaded"}, "http://mochi.test:8888");
+ }
+ else {
+ if(styleCount < MAX_COUNT) {
+ styleCount++;
+ setTimeout(styleStatus, TIMEOUT_INTERVAL, cssStyleSheet);
+ }
+ else {
+ //After we have called setTimeout the maximum number of times, assume stylesheet is blocked
+ parent.postMessage({"test": "stylesheet", "msg": "insecure stylesheet blocked"}, "http://mochi.test:8888");
+ }
+ }
+ }
+
+ // link does not have onload and onerror events. Hence we need a setTimeout to check the link's status
+ window.setTimeout(styleStatus, TIMEOUT_INTERVAL, cssStyleSheet);
+
+ // Test 1d: insecure iframe
+ var iframe = document.createElement("iframe");
+ iframe.src = baseUrl + "?type=iframe";
+ iframe.onload = function() {
+ parent.postMessage({"test": "iframe", "msg": "insecure iframe loaded"}, "http://mochi.test:8888");
+ }
+ iframe.onerror = function() {
+ parent.postMessage({"test": "iframe", "msg": "insecure iframe blocked"}, "http://mochi.test:8888");
+ };
+ testContent.appendChild(iframe);
+
+
+ // Test 1e: insecure xhr
+ var xhr = new XMLHttpRequest;
+ try {
+ xhr.open("GET", baseUrl + "?type=xhr", true);
+ xhr.send();
+ xhr.onloadend = function (oEvent) {
+ if (xhr.status == 200) {
+ parent.postMessage({"test": "xhr", "msg": "insecure xhr loaded"}, "http://mochi.test:8888");
+ }
+ else {
+ parent.postMessage({"test": "xhr", "msg": "insecure xhr blocked"}, "http://mochi.test:8888");
+ }
+ }
+ } catch(ex) {
+ parent.postMessage({"test": "xhr", "msg": "insecure xhr blocked"}, "http://mochi.test:8888");
+ }
+
+ /* Part 2: Mixed Display tests */
+
+ // Shorthand for all image test variants
+ function imgHandlers(img, test) {
+ img.onload = function () {
+ parent.postMessage({"test": test, "msg": "insecure image loaded"}, "http://mochi.test:8888");
+ }
+ img.onerror = function() {
+ parent.postMessage({"test": test, "msg": "insecure image blocked"}, "http://mochi.test:8888");
+ }
+ }
+
+ // Test 2a: insecure image
+ var img = document.createElement("img");
+ img.src = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+ imgHandlers(img, "image");
+ // We don't need to append the image to the document. Doing so causes the image test to run twice.
+
+ // Test 2b: insecure media
+ var media = document.createElement("video");
+ media.src = "http://mochi.test:8888/tests/dom/media/test/320x240.ogv?" + Math.floor((Math.random()*1000)+1);
+ media.width = "320";
+ media.height = "200";
+ media.type = "video/ogg";
+ media.onloadeddata = function() {
+ parent.postMessage({"test": "media", "msg": "insecure media loaded"}, "http://mochi.test:8888");
+ }
+ media.onerror = function() {
+ parent.postMessage({"test": "media", "msg": "insecure media blocked"}, "http://mochi.test:8888");
+ }
+ // We don't need to append the video to the document. Doing so causes the image test to run twice.
+
+ /* Part 3: Mixed Active Tests for Image srcset */
+
+ // Test 3a: image with srcset
+ var imgA = document.createElement("img");
+ imgA.srcset = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+ imgHandlers(imgA, "imageSrcset");
+
+ // Test 3b: image with srcset, using fallback from src, should still use imageset policy
+ var imgB = document.createElement("img");
+ imgB.srcset = " ";
+ imgB.src = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+ imgHandlers(imgB, "imageSrcsetFallback");
+
+ // Test 3c: image in <picture>
+ var imgC = document.createElement("img");
+ var pictureC = document.createElement("picture");
+ var sourceC = document.createElement("source");
+ sourceC.srcset = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+ pictureC.appendChild(sourceC);
+ pictureC.appendChild(imgC);
+ imgHandlers(imgC, "imagePicture");
+
+ // Test 3d: Loaded basic image switching to a <picture>, loading
+ // same source, should still redo the request with new
+ // policy.
+ var imgD = document.createElement("img");
+ imgD.src = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+ imgD.onload = imgD.onerror = function() {
+ // Whether or not it loads, we want to now append it to a picture and observe
+ var pictureD = document.createElement("picture");
+ var sourceD = document.createElement("source");
+ sourceD.srcset = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+ pictureD.appendChild(sourceD);
+ pictureD.appendChild(imgD);
+ imgHandlers(imgD, "imageJoinPicture");
+ }
+
+ // Test 3e: img load from <picture> source reverts to img.src as it
+ // is removed -- the new request should revert to mixed
+ // display policy
+ var imgE = document.createElement("img");
+ var pictureE = document.createElement("picture");
+ var sourceE = document.createElement("source");
+ sourceE.srcset = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+ pictureE.appendChild(sourceE);
+ pictureE.appendChild(imgE);
+ imgE.src = "http://mochi.test:8888/tests/image/test/mochitest/blue.png";
+ imgE.onload = imgE.onerror = function() {
+ // Whether or not it loads, remove it from the picture and observe
+ pictureE.removeChild(imgE)
+ imgHandlers(imgE, "imageLeavePicture");
+ }
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/mixedcontentblocker/file_main_bug803225.html b/dom/security/test/mixedcontentblocker/file_main_bug803225.html
new file mode 100644
index 000000000..e657be256
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/file_main_bug803225.html
@@ -0,0 +1,182 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for Mixed Content Blocker - Allowed Protocols
+https://bugzilla.mozilla.org/show_bug.cgi?id=803225
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 62178</title>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="testContent"></div>
+
+<!-- Test additional schemes the Mixed Content Blocker should not block
+ "about" protocol URIs that are URI_SAFE_FOR_UNTRUSTED_CONTENT (moz-safe-about; see nsAboutProtocolHandler::NewURI
+ "data",
+ "javascript",
+ "mailto",
+ "resource",
+ "moz-icon",
+ "wss"
+-->
+
+<script>
+
+ //For tests that require setTimeout, set the timeout interval
+ var TIMEOUT_INTERVAL = 100;
+
+ var testContent = document.getElementById("testContent");
+
+ // Test 1 & 2: about and javascript protcols within an iframe
+ var data = Array(2,2);
+ var protocols = [
+ ["about", ""], //When no source is specified, the frame gets a source of about:blank
+ ["javascript", "javascript:document.open();document.write='<h1>SUCCESS</h1>';document.close();"],
+ ];
+ for(var i=0; i < protocols.length; i++)
+ {
+ var generic_frame = document.createElement("iframe");
+ generic_frame.src = protocols[i][1];
+ generic_frame.name="generic_protocol";
+
+ generic_frame.onload = function(i) {
+ data = {"test": protocols[i][0], "msg": "resource with " + protocols[i][0] + " protocol loaded"};
+ parent.postMessage(data, "http://mochi.test:8888");
+ }.bind(generic_frame, i)
+
+ generic_frame.onerror = function(i) {
+ data = {"test": protocols[i][0], "msg": "resource with " + protocols[i][0] + " protocol did not load"};
+ parent.postMessage(data, "http://mochi.test:8888");
+ }.bind(generic_frame, i);
+
+ testContent.appendChild(generic_frame, i);
+ }
+
+ // Test 3: for resource within a script tag
+ // Note: the script we load throws an exception, but the script element's
+ // onload listener is called after we successfully fetch the script,
+ // independently of whether it throws an exception.
+ var resource_script=document.createElement("script");
+ resource_script.src = "resource://gre/modules/XPCOMUtils.jsm";
+ resource_script.name = "resource_protocol";
+ resource_script.onload = function() {
+ parent.postMessage({"test": "resource", "msg": "resource with resource protocol loaded"}, "http://mochi.test:8888");
+ }
+ resource_script.onerror = function() {
+ parent.postMessage({"test": "resource", "msg": "resource with resource protocol did not load"}, "http://mochi.test:8888");
+ }
+
+ testContent.appendChild(resource_script);
+
+ // Test 4: moz-icon within an img tag
+ var image=document.createElement("img");
+ image.src = "moz-icon://dummy.exe?size=16";
+ image.onload = function() {
+ parent.postMessage({"test": "mozicon", "msg": "resource with mozicon protocol loaded"}, "http://mochi.test:8888");
+ }
+ image.onerror = function() {
+ parent.postMessage({"test": "mozicon", "msg": "resource with mozicon protocol did not load"}, "http://mochi.test:8888");
+ }
+ // We don't need to append the image to the document. Doing so causes the image test to run twice.
+
+ // Test 5: about unsafe protocol within an iframe
+ var unsafe_about_frame = document.createElement("iframe");
+ unsafe_about_frame.src = "about:config";
+ unsafe_about_frame.name = "unsafe_about_protocol";
+ unsafe_about_frame.onload = function() {
+ parent.postMessage({"test": "unsafe_about", "msg": "resource with unsafe about protocol loaded"}, "http://mochi.test:8888");
+ }
+ unsafe_about_frame.onerror = function() {
+ parent.postMessage({"test": "unsafe_about", "msg": "resource with unsafe about protocol did not load"}, "http://mochi.test:8888");
+ }
+ testContent.appendChild(unsafe_about_frame);
+
+ // Test 6: data protocol within a script tag
+ var x = 2;
+ var newscript = document.createElement("script");
+ newscript.src= "data:text/javascript,var x = 4;";
+ newscript.onload = function() {
+ parent.postMessage({"test": "data_protocol", "msg": "resource with data protocol loaded"}, "http://mochi.test:8888");
+ }
+ newscript.onerror = function() {
+ parent.postMessage({"test": "data_protocol", "msg": "resource with data protocol did not load"}, "http://mochi.test:8888");
+ }
+ testContent.appendChild(newscript);
+
+ // Test 7: mailto protocol
+ let mm = SpecialPowers.loadChromeScript(function launchHandler() {
+ var { classes: Cc, interfaces: Ci } = Components;
+ var ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var webHandler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(Ci.nsIWebHandlerApp);
+ webHandler.name = "Web Handler";
+ webHandler.uriTemplate = "http://example.com/tests/dom/security/test/mixedcontentblocker/file_bug803225_test_mailto.html?s=%";
+
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ Services.ppmm.addMessageListener("Test:content-ready", function contentReadyListener() {
+ Services.ppmm.removeMessageListener("Test:content-ready", contentReadyListener);
+ sendAsyncMessage("Test:content-ready-forward");
+ Services.ppmm.removeDelayedProcessScript(pScript);
+ })
+
+ var pScript = "data:,new " + function () {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ var observer = {
+ observe: function(subject, topic, data) {
+ if (topic == "content-document-global-created" && data == "http://example.com") {
+ sendAsyncMessage("Test:content-ready");
+ os.removeObserver(observer, "content-document-global-created");
+ }
+ }
+ };
+ os.addObserver(observer, "content-document-global-created", false);
+ }
+
+ Services.ppmm.loadProcessScript(pScript, true);
+
+ var uri = ioService.newURI("mailto:foo@bar.com", null, null);
+ webHandler.launchWithURI(uri);
+ });
+
+ var mailto = false;
+
+ mm.addMessageListener("Test:content-ready-forward", function contentReadyListener() {
+ mm.removeMessageListener("Test:content-ready-forward", contentReadyListener);
+ mailto = true;
+ parent.postMessage({"test": "mailto", "msg": "resource with mailto protocol loaded"}, "http://mochi.test:8888");
+ });
+
+ function mailtoProtocolStatus() {
+ if(!mailto) {
+ //There is no onerror event associated with the WebHandler, and hence we need a setTimeout to check the status
+ setTimeout(mailtoProtocolStatus, TIMEOUT_INTERVAL);
+ }
+ }
+
+ mailtoProtocolStatus();
+
+ // Test 8: wss protocol
+ var wss;
+ wss = new WebSocket("wss://example.com/tests/dom/security/test/mixedcontentblocker/file_main_bug803225_websocket");
+
+ var status_wss = "started";
+ wss.onopen = function(e) {
+ status_wss = "opened";
+ wss.close();
+ }
+ wss.onclose = function(e) {
+ if(status_wss == "opened") {
+ parent.postMessage({"test": "wss", "msg": "resource with wss protocol loaded"}, "http://mochi.test:8888");
+ } else {
+ parent.postMessage({"test": "wss", "msg": "resource with wss protocol did not load"}, "http://mochi.test:8888");
+ }
+ }
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/mixedcontentblocker/file_main_bug803225_websocket_wsh.py b/dom/security/test/mixedcontentblocker/file_main_bug803225_websocket_wsh.py
new file mode 100644
index 000000000..8c33c6b10
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/file_main_bug803225_websocket_wsh.py
@@ -0,0 +1,7 @@
+from mod_pywebsocket import msgutil
+
+def web_socket_do_extra_handshake(request):
+ pass
+
+def web_socket_transfer_data(request):
+ resp = ""
diff --git a/dom/security/test/mixedcontentblocker/file_server.sjs b/dom/security/test/mixedcontentblocker/file_server.sjs
new file mode 100644
index 000000000..58dffc565
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/file_server.sjs
@@ -0,0 +1,45 @@
+
+function handleRequest(request, response)
+{
+ // get the Content-Type to serve from the query string
+ var contentType = null;
+ request.queryString.split('&').forEach( function (val) {
+ var [name, value] = val.split('=');
+ if (name == "type") {
+ contentType = unescape(value);
+ }
+ });
+
+ // avoid confusing cache behaviors
+ response.setHeader("Cache-Control", "no-cache", false);
+
+ switch (contentType) {
+ case "iframe":
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("frame content");
+ break;
+
+ case "script":
+ response.setHeader("Content-Type", "application/javascript", false);
+ break;
+
+ case "stylesheet":
+ response.setHeader("Content-Type", "text/css", false);
+ break;
+
+ case "object":
+ response.setHeader("Content-Type", "application/x-test", false);
+ break;
+
+ case "xhr":
+ response.setHeader("Content-Type", "text/xml", false);
+ response.setHeader("Access-Control-Allow-Origin", "https://example.com");
+ response.write('<?xml version="1.0" encoding="UTF-8" ?><test></test>');
+ break;
+
+ default:
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("<html><body>Hello World</body></html>");
+ break;
+ }
+}
diff --git a/dom/security/test/mixedcontentblocker/mochitest.ini b/dom/security/test/mixedcontentblocker/mochitest.ini
new file mode 100644
index 000000000..94c17f9b4
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/mochitest.ini
@@ -0,0 +1,23 @@
+[DEFAULT]
+tags = mcb
+support-files =
+ file_bug803225_test_mailto.html
+ file_frameNavigation.html
+ file_frameNavigation_blankTarget.html
+ file_frameNavigation_grandchild.html
+ file_frameNavigation_innermost.html
+ file_frameNavigation_secure.html
+ file_frameNavigation_secure_grandchild.html
+ file_main.html
+ file_main_bug803225.html
+ file_main_bug803225_websocket_wsh.py
+ file_server.sjs
+ !/dom/media/test/320x240.ogv
+ !/image/test/mochitest/blue.png
+
+[test_main.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_bug803225.html]
+skip-if = toolkit == 'android' #TIMED_OUT
+[test_frameNavigation.html]
+skip-if = toolkit == 'android' #TIMED_OUT
diff --git a/dom/security/test/mixedcontentblocker/test_bug803225.html b/dom/security/test/mixedcontentblocker/test_bug803225.html
new file mode 100644
index 000000000..13c52762d
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/test_bug803225.html
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Testing Whitelist of Resource Scheme for Mixed Content Blocker
+https://bugzilla.mozilla.org/show_bug.cgi?id=803225
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 803225</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <script>
+ var counter = 0;
+ var settings = [ [true, true], [true, false], [false, true], [false, false] ];
+
+ var blockActive;
+ var blockDisplay;
+
+ //Cycle through 4 different preference settings.
+ function changePrefs(callback) {
+ let newPrefs = [["security.mixed_content.block_display_content", settings[counter][0]],
+ ["security.mixed_content.block_active_content", settings[counter][1]]];
+
+ SpecialPowers.pushPrefEnv({"set": newPrefs}, function () {
+ blockDisplay = SpecialPowers.getBoolPref("security.mixed_content.block_display_content");
+ blockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content");
+ counter++;
+ callback();
+ });
+ }
+
+ var testsToRun = {
+ /* https - Tests already run as part of bug 62178. */
+ about: false,
+ mozicon: false,
+ resource: false,
+ unsafe_about: false,
+ data_protocol: false,
+ javascript: false,
+ mailto: false,
+ wss: false,
+ };
+
+ function log(msg) {
+ document.getElementById("log").textContent += "\n" + msg;
+ }
+
+ function reloadFrame() {
+ document.getElementById('framediv').innerHTML = '<iframe id="testHarness" src="https://example.com/tests/dom/security/test/mixedcontentblocker/file_main_bug803225.html"></iframe>';
+ }
+
+ function checkTestsCompleted() {
+ for (var prop in testsToRun) {
+ // some test hasn't run yet so we're not done
+ if (!testsToRun[prop])
+ return;
+ }
+ //if the testsToRun are all completed, change the pref and run the tests again until we have cycled through all the prefs.
+ if(counter < 4) {
+ for (var prop in testsToRun) {
+ testsToRun[prop] = false;
+ }
+ //call to change the preferences
+ changePrefs(function() {
+ log("\nblockDisplay set to "+blockDisplay+", blockActive set to "+blockActive+".");
+ reloadFrame();
+ });
+ }
+ else {
+ SimpleTest.finish();
+ }
+ }
+
+ var firstTest = true;
+
+ function receiveMessage(event) {
+ if(firstTest) {
+ log("blockDisplay set to "+blockDisplay+", blockActive set to "+blockActive+".");
+ firstTest = false;
+ }
+
+ log("test: "+event.data.test+", msg: "+event.data.msg + " logging message.");
+ // test that the load type matches the pref for this type of content
+ // (i.e. active vs. display)
+
+ switch(event.data.test) {
+
+ /* Mixed Script tests */
+ case "about":
+ ok(event.data.msg == "resource with about protocol loaded", "resource with about protocol did not load");
+ testsToRun["about"] = true;
+ break;
+
+ case "resource":
+ ok(event.data.msg == "resource with resource protocol loaded", "resource with resource protocol did not load");
+ testsToRun["resource"] = true;
+ break;
+
+ case "mozicon":
+ ok(event.data.msg == "resource with mozicon protocol loaded", "resource with mozicon protocol did not load");
+ testsToRun["mozicon"] = true;
+ break;
+
+ case "unsafe_about":
+ // This one should not load
+ ok(event.data.msg == "resource with unsafe about protocol did not load", "resource with unsafe about protocol loaded");
+ testsToRun["unsafe_about"] = true;
+ break;
+
+ case "data_protocol":
+ ok(event.data.msg == "resource with data protocol loaded", "resource with data protocol did not load");
+ testsToRun["data_protocol"] = true;
+ break;
+
+ case "javascript":
+ ok(event.data.msg == "resource with javascript protocol loaded", "resource with javascript protocol did not load");
+ testsToRun["javascript"] = true;
+ break;
+
+ case "wss":
+ ok(event.data.msg == "resource with wss protocol loaded", "resource with wss protocol did not load");
+ testsToRun["wss"] = true;
+ break;
+
+ case "mailto":
+ ok(event.data.msg == "resource with mailto protocol loaded", "resource with mailto protocol did not load");
+ testsToRun["mailto"] = true;
+ break;
+ }
+ checkTestsCompleted();
+ }
+
+ function startTest() {
+ //Set the first set of settings (true, true) and increment the counter.
+ changePrefs(function() {
+ // listen for a messages from the mixed content test harness
+ window.addEventListener("message", receiveMessage, false);
+
+ reloadFrame();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</head>
+
+<body onload='startTest()'>
+ <div id="framediv"></div>
+ <pre id="log"></pre>
+</body>
+</html>
diff --git a/dom/security/test/mixedcontentblocker/test_frameNavigation.html b/dom/security/test/mixedcontentblocker/test_frameNavigation.html
new file mode 100644
index 000000000..5b3ae50b0
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/test_frameNavigation.html
@@ -0,0 +1,127 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for Mixed Content Blocker
+https://bugzilla.mozilla.org/show_bug.cgi?id=840388
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 840388</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <script>
+ var counter = 0;
+ var origBlockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content");
+
+ SpecialPowers.setBoolPref("security.mixed_content.block_active_content", true);
+ var blockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content");
+
+
+ var testsToRunInsecure = {
+ insecurePage_navigate_child: false,
+ insecurePage_navigate_grandchild: false,
+ };
+
+ var testsToRunSecure = {
+ securePage_navigate_child: false,
+ blankTarget: false,
+ };
+
+ function log(msg) {
+ document.getElementById("log").textContent += "\n" + msg;
+ }
+
+ var secureTestsStarted = false;
+ function checkTestsCompleted() {
+ for (var prop in testsToRunInsecure) {
+ // some test hasn't run yet so we're not done
+ if (!testsToRunInsecure[prop])
+ return;
+ }
+ // If we are here, all the insecure tests have run.
+ // If we haven't changed the iframe to run the secure tests, change it now.
+ if (!secureTestsStarted) {
+ document.getElementById('testing_frame').src = "https://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation_secure.html";
+ secureTestsStarted = true;
+ }
+ for (var prop in testsToRunSecure) {
+ // some test hasn't run yet so we're not done
+ if (!testsToRunSecure[prop])
+ return;
+ }
+ //if the secure and insecure testsToRun are all completed, change the block mixed active content pref and run the tests again.
+ if(counter < 1) {
+ for (var prop in testsToRunSecure) {
+ testsToRunSecure[prop] = false;
+ }
+ for (var prop in testsToRunInsecure) {
+ testsToRunInsecure[prop] = false;
+ }
+ //call to change the preferences
+ counter++;
+ SpecialPowers.setBoolPref("security.mixed_content.block_active_content", false);
+ blockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content");
+ log("blockActive set to "+blockActive+".");
+ secureTestsStarted = false;
+ document.getElementById('framediv').innerHTML = '<iframe src="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation.html" id="testing_frame"></iframe>';
+ }
+ else {
+ //set the prefs back to what they were set to originally
+ SpecialPowers.setBoolPref("security.mixed_content.block_active_content", origBlockActive);
+ SimpleTest.finish();
+ }
+ }
+
+ var firstTestDebugMessage = true;
+
+ // listen for a messages from the mixed content test harness
+ window.addEventListener("message", receiveMessage, false);
+ function receiveMessage(event) {
+ if(firstTestDebugMessage) {
+ log("blockActive set to "+blockActive);
+ firstTestDebugMessage = false;
+ }
+
+ log("test: "+event.data.test+", msg: "+event.data.msg + ".");
+ // test that the load type matches the pref for this type of content
+ // (i.e. active vs. display)
+
+ switch(event.data.test) {
+
+ case "insecurePage_navigate_child":
+ is(event.data.msg, "navigated to insecure iframe on insecure page", "navigating to insecure iframe blocked on insecure page");
+ testsToRunInsecure["insecurePage_navigate_child"] = true;
+ break;
+
+ case "insecurePage_navigate_grandchild":
+ is(event.data.msg, "navigated to insecure grandchild iframe on insecure page", "navigating to insecure grandchild iframe blocked on insecure page");
+ testsToRunInsecure["insecurePage_navigate_grandchild"] = true;
+ break;
+
+ case "securePage_navigate_child":
+ ok(blockActive == (event.data.msg == "navigating to insecure iframe blocked on secure page"), "navigated to insecure iframe on secure page");
+ testsToRunSecure["securePage_navigate_child"] = true;
+ break;
+
+ case "blankTarget":
+ is(event.data.msg, "opened an http link with target=_blank from a secure page", "couldn't open an http link in a new window from a secure page");
+ testsToRunSecure["blankTarget"] = true;
+ break;
+
+ }
+ checkTestsCompleted();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</head>
+
+<body>
+ <div id="framediv">
+ <iframe src="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation.html" id="testing_frame"></iframe>
+ </div>
+
+ <pre id="log"></pre>
+</body>
+</html>
diff --git a/dom/security/test/mixedcontentblocker/test_main.html b/dom/security/test/mixedcontentblocker/test_main.html
new file mode 100644
index 000000000..d2bc9dc7e
--- /dev/null
+++ b/dom/security/test/mixedcontentblocker/test_main.html
@@ -0,0 +1,187 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for Mixed Content Blocker
+https://bugzilla.mozilla.org/show_bug.cgi?id=62178
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Tests for Bug 62178</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <script>
+ SpecialPowers.setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
+
+ var counter = 0;
+ var settings = [ [true, true], [true, false], [false, true], [false, false] ];
+
+ var blockActive;
+ var blockDisplay;
+
+ //Cycle through 4 different preference settings.
+ function changePrefs(otherPrefs, callback) {
+ let basePrefs = [["security.mixed_content.block_display_content", settings[counter][0]],
+ ["security.mixed_content.block_active_content", settings[counter][1]]];
+ let newPrefs = basePrefs.concat(otherPrefs);
+
+ SpecialPowers.pushPrefEnv({"set": newPrefs}, function () {
+ blockDisplay = SpecialPowers.getBoolPref("security.mixed_content.block_display_content");
+ blockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content");
+ counter++;
+ callback();
+ });
+ }
+
+ var testsToRun = {
+ iframe: false,
+ image: false,
+ imageSrcset: false,
+ imageSrcsetFallback: false,
+ imagePicture: false,
+ imageJoinPicture: false,
+ imageLeavePicture: false,
+ script: false,
+ stylesheet: false,
+ object: false,
+ media: false,
+ xhr: false,
+ };
+
+ function log(msg) {
+ document.getElementById("log").textContent += "\n" + msg;
+ }
+
+ function reloadFrame() {
+ document.getElementById('framediv').innerHTML = '<iframe id="testHarness" src="https://example.com/tests/dom/security/test/mixedcontentblocker/file_main.html"></iframe>';
+ }
+
+ function checkTestsCompleted() {
+ for (var prop in testsToRun) {
+ // some test hasn't run yet so we're not done
+ if (!testsToRun[prop])
+ return;
+ }
+ //if the testsToRun are all completed, chnage the pref and run the tests again until we have cycled through all the prefs.
+ if(counter < 4) {
+ for (var prop in testsToRun) {
+ testsToRun[prop] = false;
+ }
+ //call to change the preferences
+ changePrefs([], function() {
+ log("\nblockDisplay set to "+blockDisplay+", blockActive set to "+blockActive+".");
+ reloadFrame();
+ });
+ }
+ else {
+ SimpleTest.finish();
+ }
+ }
+
+ var firstTest = true;
+
+ function receiveMessage(event) {
+ if(firstTest) {
+ log("blockActive set to "+blockActive+", blockDisplay set to "+blockDisplay+".");
+ firstTest = false;
+ }
+
+ log("test: "+event.data.test+", msg: "+event.data.msg + " logging message.");
+ // test that the load type matches the pref for this type of content
+ // (i.e. active vs. display)
+
+ switch(event.data.test) {
+
+ /* Mixed Script tests */
+ case "iframe":
+ ok(blockActive == (event.data.msg == "insecure iframe blocked"), "iframe did not follow block_active_content pref");
+ testsToRun["iframe"] = true;
+ break;
+
+ case "object":
+ ok(blockActive == (event.data.msg == "insecure object blocked"), "object did not follow block_active_content pref");
+ testsToRun["object"] = true;
+ break;
+
+ case "script":
+ ok(blockActive == (event.data.msg == "insecure script blocked"), "script did not follow block_active_content pref");
+ testsToRun["script"] = true;
+ break;
+
+ case "stylesheet":
+ ok(blockActive == (event.data.msg == "insecure stylesheet blocked"), "stylesheet did not follow block_active_content pref");
+ testsToRun["stylesheet"] = true;
+ break;
+
+ case "xhr":
+ ok(blockActive == (event.data.msg == "insecure xhr blocked"), "xhr did not follow block_active_content pref");
+ testsToRun["xhr"] = true;
+ break;
+
+ /* Mixed Display tests */
+ case "image":
+ //test that the image load matches the pref for display content
+ ok(blockDisplay == (event.data.msg == "insecure image blocked"), "image did not follow block_display_content pref");
+ testsToRun["image"] = true;
+ break;
+
+ case "media":
+ ok(blockDisplay == (event.data.msg == "insecure media blocked"), "media did not follow block_display_content pref");
+ testsToRun["media"] = true;
+ break;
+
+ /* Images using the "imageset" policy, from <img srcset> and <picture>, do not get the mixed display exception */
+ case "imageSrcset":
+ ok(blockActive == (event.data.msg == "insecure image blocked"), "imageSrcset did not follow block_active_content pref");
+ testsToRun["imageSrcset"] = true;
+ break;
+
+ case "imageSrcsetFallback":
+ ok(blockActive == (event.data.msg == "insecure image blocked"), "imageSrcsetFallback did not follow block_active_content pref");
+ testsToRun["imageSrcsetFallback"] = true;
+ break;
+
+ case "imagePicture":
+ ok(blockActive == (event.data.msg == "insecure image blocked"), "imagePicture did not follow block_active_content pref");
+ testsToRun["imagePicture"] = true;
+ break;
+
+ case "imageJoinPicture":
+ ok(blockActive == (event.data.msg == "insecure image blocked"), "imageJoinPicture did not follow block_active_content pref");
+ testsToRun["imageJoinPicture"] = true;
+ break;
+
+ // Should return to mixed display mode
+ case "imageLeavePicture":
+ ok(blockDisplay == (event.data.msg == "insecure image blocked"), "imageLeavePicture did not follow block_display_content pref");
+ testsToRun["imageLeavePicture"] = true;
+ break;
+
+ }
+ checkTestsCompleted();
+ }
+
+ function startTest() {
+ // Set prefs to use mixed-content before HSTS
+ SpecialPowers.pushPrefEnv({'set': [["security.mixed_content.use_hsts", false],
+ ["security.mixed_content.send_hsts_priming", false]]});
+ //Set the first set of mixed content settings and increment the counter.
+ changePrefs([], function() {
+ //listen for a messages from the mixed content test harness
+ window.addEventListener("message", receiveMessage, false);
+
+ //Kick off test
+ reloadFrame();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ </script>
+</head>
+
+<body onload='startTest()'>
+ <div id="framediv"></div>
+ <pre id="log"></pre>
+</body>
+</html>
diff --git a/dom/security/test/moz.build b/dom/security/test/moz.build
new file mode 100644
index 000000000..ddb4e9b89
--- /dev/null
+++ b/dom/security/test/moz.build
@@ -0,0 +1,31 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form 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/.
+
+XPCSHELL_TESTS_MANIFESTS += [
+ 'unit/xpcshell.ini',
+]
+
+TEST_DIRS += [
+ 'gtest',
+]
+
+MOCHITEST_MANIFESTS += [
+ 'cors/mochitest.ini',
+ 'csp/mochitest.ini',
+ 'general/mochitest.ini',
+ 'mixedcontentblocker/mochitest.ini',
+ 'sri/mochitest.ini',
+]
+
+MOCHITEST_CHROME_MANIFESTS += [
+ 'general/chrome.ini',
+]
+
+BROWSER_CHROME_MANIFESTS += [
+ 'contentverifier/browser.ini',
+ 'csp/browser.ini',
+ 'hsts/browser.ini',
+]
diff --git a/dom/security/test/sri/file_bug_1271796.css b/dom/security/test/sri/file_bug_1271796.css
new file mode 100644
index 000000000..c0928f2cf
--- /dev/null
+++ b/dom/security/test/sri/file_bug_1271796.css
@@ -0,0 +1,2 @@
+/*! Simple test for bug 1271796 */
+p::before { content: "\2014"; }
diff --git a/dom/security/test/sri/iframe_csp_directive_style_imports.html b/dom/security/test/sri/iframe_csp_directive_style_imports.html
new file mode 100644
index 000000000..0abea7854
--- /dev/null
+++ b/dom/security/test/sri/iframe_csp_directive_style_imports.html
@@ -0,0 +1,6 @@
+<!-- file should be loaded (text is blue), but subsequent files shouldn't (text is red) -->
+<link rel="stylesheet" href="style_importing.css"
+ integrity="sha384-m5Q2GOhAtLrdiv6rCmxY3GjEFMVInALcdTyDnEddUUiDH2uQvJSX5GSJYQiatpTK"
+ onload="parent.postMessage('finish', '*');"
+ onerror="parent.postMessage('finish', '*');">
+<p id="text-for-import-test">blue text</p>
diff --git a/dom/security/test/sri/iframe_csp_directive_style_imports.html^headers^ b/dom/security/test/sri/iframe_csp_directive_style_imports.html^headers^
new file mode 100644
index 000000000..0a6ccba79
--- /dev/null
+++ b/dom/security/test/sri/iframe_csp_directive_style_imports.html^headers^
@@ -0,0 +1 @@
+content-security-policy: require-sri-for script style
diff --git a/dom/security/test/sri/iframe_require-sri-for_main.html b/dom/security/test/sri/iframe_require-sri-for_main.html
new file mode 100644
index 000000000..467c699c7
--- /dev/null
+++ b/dom/security/test/sri/iframe_require-sri-for_main.html
@@ -0,0 +1,47 @@
+<script>
+ window.hasCORSLoaded = false; // set through script_crossdomain1.js
+</script>
+
+<!-- script tag cors-enabled. should be loaded -->
+<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain1.js"
+ crossorigin=""
+ integrity="sha512-9Tv2DL1fHvmPQa1RviwKleE/jq72jgxj8XGLyWn3H6Xp/qbtfK/jZINoPFAv2mf0Nn1TxhZYMFULAbzJNGkl4Q=="
+ onload="parent.postMessage('good_sriLoaded', '*');"></script>
+
+<!-- script tag cors but not using SRI. should trigger onerror -->
+<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain5.js"
+ onload="parent.postMessage('bad_nonsriLoaded', '*');"
+ onerror="parent.postMessage('good_nonsriBlocked', '*');"></script>
+
+<!-- svg:script tag with cors but not using SRI. should trigger onerror -->
+<svg xmlns="http://www.w3.org/2000/svg">
+ <script xlink:href="http://example.com/tests/dom/security/test/sri/script_crossdomain3.js"
+ onload="parent.postMessage('bad_svg_nonsriLoaded', '*');"
+ onerror="parent.postMessage('good_svg_nonsriBlocked', '*');"></script>
+ ></script>
+</svg>
+
+<!-- stylesheet with cors and integrity. it should just load fine. -->
+<link rel="stylesheet" href="style1.css"
+ integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
+ onload="parent.postMessage('good_sriLoaded', '*');">
+
+<!-- stylesheet not using SRI, should trigger onerror -->
+<link rel="stylesheet" href="style3.css"
+ onload="parent.postMessage('bad_nonsriLoaded', '*');"
+ onerror="parent.postMessage('good_nonsriBlocked', '*');">
+
+
+<p id="black-text">black text</p>
+<script>
+ // this worker should not load,
+ // given that we can not provide integrity metadata through the constructor
+ w = new Worker("rsf_worker.js");
+ w.onerror = function(e) {
+ if (typeof w == "object") {
+ parent.postMessage("finish", '*');
+ } else {
+ parent.postMessage("error", "*")
+ }
+ }
+</script>
diff --git a/dom/security/test/sri/iframe_require-sri-for_main.html^headers^ b/dom/security/test/sri/iframe_require-sri-for_main.html^headers^
new file mode 100644
index 000000000..0a6ccba79
--- /dev/null
+++ b/dom/security/test/sri/iframe_require-sri-for_main.html^headers^
@@ -0,0 +1 @@
+content-security-policy: require-sri-for script style
diff --git a/dom/security/test/sri/iframe_require-sri-for_no_csp.html b/dom/security/test/sri/iframe_require-sri-for_no_csp.html
new file mode 100644
index 000000000..435b32ea3
--- /dev/null
+++ b/dom/security/test/sri/iframe_require-sri-for_no_csp.html
@@ -0,0 +1,5 @@
+<script>
+ w = new Worker("rsf_csp_worker.js");
+ // use the handler function in the parent frame (test_require-sri-for_csp_directive.html)
+ w.onmessage = parent.handler;
+</script>
diff --git a/dom/security/test/sri/iframe_script_crossdomain.html b/dom/security/test/sri/iframe_script_crossdomain.html
new file mode 100644
index 000000000..a752c7e79
--- /dev/null
+++ b/dom/security/test/sri/iframe_script_crossdomain.html
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+
+<script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ window.hasCORSLoaded = false;
+ window.hasNonCORSLoaded = false;
+
+ function good_nonsriLoaded() {
+ ok(true, "Non-eligible non-SRI resource was loaded correctly.");
+ }
+ function bad_nonsriBlocked() {
+ ok(false, "Non-eligible non-SRI resources should be loaded!");
+ }
+
+ function good_nonCORSInvalidBlocked() {
+ ok(true, "A non-CORS resource with invalid metadata was correctly blocked.");
+ }
+ function bad_nonCORSInvalidLoaded() {
+ ok(false, "Non-CORS resources with invalid metadata should be blocked!");
+ }
+
+ window.onerrorCalled = false;
+ window.onloadCalled = false;
+
+ function bad_onloadCalled() {
+ window.onloadCalled = true;
+ }
+
+ function good_onerrorCalled() {
+ window.onerrorCalled = true;
+ }
+
+ function good_incorrect301Blocked() {
+ ok(true, "A non-CORS load with incorrect hash redirected to a different origin was blocked correctly.");
+ }
+ function bad_incorrect301Loaded() {
+ ok(false, "Non-CORS loads with incorrect hashes redirecting to a different origin should be blocked!");
+ }
+
+ function good_correct301Blocked() {
+ ok(true, "A non-CORS load with correct hash redirected to a different origin was blocked correctly.");
+ }
+ function bad_correct301Loaded() {
+ ok(false, "Non-CORS loads with correct hashes redirecting to a different origin should be blocked!");
+ }
+
+ function good_correctDataLoaded() {
+ ok(true, "Since data: URLs are same-origin, they should be loaded.");
+ }
+ function bad_correctDataBlocked() {
+ todo(false, "We should not block scripts in data: URIs!");
+ }
+ function good_correctDataCORSLoaded() {
+ ok(true, "A data: URL with a CORS load was loaded correctly.");
+ }
+ function bad_correctDataCORSBlocked() {
+ ok(false, "We should not BLOCK scripts!");
+ }
+
+ window.onload = function() {
+ SimpleTest.finish()
+ }
+</script>
+
+<!-- cors-enabled. should be loaded -->
+<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain1.js"
+ crossorigin=""
+ integrity="sha512-9Tv2DL1fHvmPQa1RviwKleE/jq72jgxj8XGLyWn3H6Xp/qbtfK/jZINoPFAv2mf0Nn1TxhZYMFULAbzJNGkl4Q=="></script>
+
+<!-- not cors-enabled. should be blocked -->
+<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain2.js"
+ crossorigin="anonymous"
+ integrity="sha256-ntgU2U1xv7HfK1XWMTSWz6vJkyVtGzMrIAxQkux1I94="
+ onload="bad_onloadCalled()"
+ onerror="good_onerrorCalled()"></script>
+
+<!-- non-cors but not actually using SRI. should trigger onload -->
+<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain3.js"
+ integrity=" "
+ onload="good_nonsriLoaded()"
+ onerror="bad_nonsriBlocked()"></script>
+
+<!-- non-cors with invalid metadata -->
+<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain4.js"
+ integrity="sha256-bogus"
+ onload="bad_nonCORSInvalidLoaded()"
+ onerror="good_nonCORSInvalidBlocked()"></script>
+
+<!-- non-cors that's same-origin initially but redirected to another origin -->
+<script src="script_301.js"
+ integrity="sha384-invalid"
+ onerror="good_incorrect301Blocked()"
+ onload="bad_incorrect301Loaded()"></script>
+
+<!-- non-cors that's same-origin initially but redirected to another origin -->
+<script src="script_301.js"
+ integrity="sha384-1NpiDI6decClMaTWSCAfUjTdx1BiOffsCPgH4lW5hCLwmHk0VyV/g6B9Sw2kD2K3"
+ onerror="good_correct301Blocked()"
+ onload="bad_correct301Loaded()"></script>
+
+<!-- data: URLs are same-origin -->
+<script src="data:,console.log('data:valid');"
+ integrity="sha256-W5I4VIN+mCwOfR9kDbvWoY1UOVRXIh4mKRN0Nz0ookg="
+ onerror="bad_correctDataBlocked()"
+ onload="good_correctDataLoaded()"></script>
+
+<!-- not cors-enabled with data: URLs. should trigger onload -->
+<script src="data:,console.log('data:valid');"
+ crossorigin="anonymous"
+ integrity="sha256-W5I4VIN+mCwOfR9kDbvWoY1UOVRXIh4mKRN0Nz0ookg="
+ onerror="bad_correctDataCORSBlocked()"
+ onload="good_correctDataCORSLoaded()"></script>
+
+<script>
+ ok(window.hasCORSLoaded, "CORS-enabled resource with a correct hash");
+ ok(!window.hasNonCORSLoaded, "Correct hash, but non-CORS, should be blocked");
+ ok(!window.onloadCalled, "Failed loads should not call onload when they're cross-domain");
+ ok(window.onerrorCalled, "Failed loads should call onerror when they're cross-domain");
+</script>
+</body>
+</html>
diff --git a/dom/security/test/sri/iframe_script_sameorigin.html b/dom/security/test/sri/iframe_script_sameorigin.html
new file mode 100644
index 000000000..b891db547
--- /dev/null
+++ b/dom/security/test/sri/iframe_script_sameorigin.html
@@ -0,0 +1,249 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ SimpleTest.finish();
+ }
+ </script>
+ <script>
+ function good_correctHashLoaded() {
+ ok(true, "A script was correctly loaded when integrity matched")
+ }
+ function bad_correctHashBlocked() {
+ ok(false, "We should load scripts with hashes that match!");
+ }
+
+ function good_correctHashArrayLoaded() {
+ ok(true, "A script was correctly loaded when one of the hashes in the integrity attribute matched")
+ }
+ function bad_correctHashArrayBlocked() {
+ ok(false, "We should load scripts with at least one hash that match!");
+ }
+
+ function good_emptyIntegrityLoaded() {
+ ok(true, "A script was correctly loaded when the integrity attribute was empty")
+ }
+ function bad_emptyIntegrityBlocked() {
+ ok(false, "We should load scripts with empty integrity attributes!");
+ }
+
+ function good_whitespaceIntegrityLoaded() {
+ ok(true, "A script was correctly loaded when the integrity attribute only contained whitespace")
+ }
+ function bad_whitespaceIntegrityBlocked() {
+ ok(false, "We should load scripts with integrity attributes containing only whitespace!");
+ }
+
+ function good_incorrectHashBlocked() {
+ ok(true, "A script was correctly blocked, because the hash digest was wrong");
+ }
+ function bad_incorrectHashLoaded() {
+ ok(false, "We should not load scripts with hashes that do not match the content!");
+ }
+
+ function good_incorrectHashArrayBlocked() {
+ ok(true, "A script was correctly blocked, because all the hashes were wrong");
+ }
+ function bad_incorrectHashArrayLoaded() {
+ ok(false, "We should not load scripts when none of the hashes match the content!");
+ }
+
+ function good_incorrectHashLengthBlocked() {
+ ok(true, "A script was correctly blocked, because the hash length was wrong");
+ }
+ function bad_incorrectHashLengthLoaded() {
+ ok(false, "We should not load scripts with hashes that don't have the right length!");
+ }
+
+ function bad_incorrectHashFunctionBlocked() {
+ ok(false, "We should load scripts with invalid/unsupported hash functions!");
+ }
+ function good_incorrectHashFunctionLoaded() {
+ ok(true, "A script was correctly loaded, despite the hash function being invalid/unsupported.");
+ }
+
+ function bad_missingHashFunctionBlocked() {
+ ok(false, "We should load scripts with missing hash functions!");
+ }
+ function good_missingHashFunctionLoaded() {
+ ok(true, "A script was correctly loaded, despite a missing hash function.");
+ }
+
+ function bad_missingHashValueBlocked() {
+ ok(false, "We should load scripts with missing hash digests!");
+ }
+ function good_missingHashValueLoaded() {
+ ok(true, "A script was correctly loaded, despite the missing hash digest.");
+ }
+
+ function good_401Blocked() {
+ ok(true, "A script was not loaded because of 401 response.");
+ }
+ function bad_401Loaded() {
+ ok(false, "We should nt load scripts with a 401 response!");
+ }
+
+ function good_valid302Loaded() {
+ ok(true, "A script was loaded successfully despite a 302 response.");
+ }
+ function bad_valid302Blocked() {
+ ok(false, "We should load scripts with a 302 response and the right hash!");
+ }
+
+ function good_invalid302Blocked() {
+ ok(true, "A script was blocked successfully after a 302 response.");
+ }
+ function bad_invalid302Loaded() {
+ ok(false, "We should not load scripts with a 302 response and the wrong hash!");
+ }
+
+ function good_validBlobLoaded() {
+ ok(true, "A script was loaded successfully from a blob: URL.");
+ }
+ function bad_validBlobBlocked() {
+ ok(false, "We should load scripts using blob: URLs with the right hash!");
+ }
+
+ function good_invalidBlobBlocked() {
+ ok(true, "A script was blocked successfully from a blob: URL.");
+ }
+ function bad_invalidBlobLoaded() {
+ ok(false, "We should not load scripts using blob: URLs with the wrong hash!");
+ }
+</script>
+</head>
+<body>
+ <!-- valid hash. should trigger onload -->
+ <!-- the hash value comes from running this command:
+ cat script.js | openssl dgst -sha256 -binary | openssl enc -base64 -A
+ -->
+ <script src="script.js"
+ integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+ onerror="bad_correctHashBlocked()"
+ onload="good_correctHashLoaded()"></script>
+
+ <!-- valid sha512 hash. should trigger onload -->
+ <script src="script.js"
+ integrity="sha512-mzSqH+vC6qrXX46JX2WEZ0FtY/lGj/5+5yYCBlk0jfYHLm0vP6XgsURbq83mwMApsnwbDLXdgjp5J8E93GT6Mw==?ignore=this"
+ onerror="bad_correctHashBlocked()"
+ onload="good_correctHashLoaded()"></script>
+
+ <!-- one valid sha256 hash. should trigger onload -->
+ <script src="script.js"
+ integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+ onerror="bad_correctHashArrayBlocked()"
+ onload="good_correctHashArrayLoaded()"></script>
+
+ <!-- empty integrity. should trigger onload -->
+ <script src="script.js"
+ integrity=""
+ onerror="bad_emptyIntegrityBlocked()"
+ onload="good_emptyIntegrityLoaded()"></script>
+
+ <!-- whitespace integrity. should trigger onload -->
+ <script src="script.js"
+ integrity="
+
+"
+ onerror="bad_whitespaceIntegrityBlocked()"
+ onload="good_whitespaceIntegrityLoaded()"></script>
+
+ <!-- invalid sha256 hash but valid sha384 hash. should trigger onload -->
+ <script src="script.js"
+ integrity="sha256-bogus sha384-zDCkvKOHXk8mM6Nk07oOGXGME17PA4+ydFw+hq0r9kgF6ZDYFWK3fLGPEy7FoOAo?"
+ onerror="bad_correctHashBlocked()"
+ onload="good_correctHashLoaded()"></script>
+
+ <!-- valid sha256 and invalid sha384. should trigger onerror -->
+ <script src="script.js"
+ integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha384-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+ onerror="good_incorrectHashLengthBlocked()"
+ onload="bad_incorrectHashLengthLoaded()"></script>
+
+ <!-- invalid hash. should trigger onerror -->
+ <script src="script.js"
+ integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+ onerror="good_incorrectHashBlocked()"
+ onload="bad_incorrectHashLoaded()"></script>
+
+ <!-- invalid hashes. should trigger onerror -->
+ <script src="script.js"
+ integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-ZkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-zkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+ onerror="good_incorrectHashBlocked()"
+ onload="bad_incorrectHashLoaded()"></script>
+
+ <!-- invalid hash function. should trigger onload -->
+ <script src="script.js"
+ integrity="rot13-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+ onerror="bad_incorrectHashFunctionBlocked()"
+ onload="good_incorrectHashFunctionLoaded()"></script>
+
+ <!-- missing hash function. should trigger onload -->
+ <script src="script.js"
+ integrity="RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+ onerror="bad_missingHashFunctionBlocked()"
+ onload="good_missingHashFunctionLoaded()"></script>
+
+ <!-- missing hash value. should trigger onload -->
+ <script src="script.js"
+ integrity="sha512-"
+ onerror="bad_missingHashValueBlocked()"
+ onload="good_missingHashValueLoaded()"></script>
+
+ <!-- 401 response. should trigger onerror -->
+ <script src="script_401.js"
+ integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+ onerror="good_401Blocked()"
+ onload="bad_401Loaded()"></script>
+
+ <!-- valid sha256 after a redirection. should trigger onload -->
+ <script src="script_302.js"
+ integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+ onerror="bad_valid302Blocked()"
+ onload="good_valid302Loaded()"></script>
+
+ <!-- invalid sha256 after a redirection. should trigger onerror -->
+ <script src="script_302.js"
+ integrity="sha256-JSi74NSN8WQNr9syBGmNg2APJp9PnHUO5ioZo5hmIiQ="
+ onerror="good_invalid302Blocked()"
+ onload="bad_invalid302Loaded()"></script>
+
+ <!-- valid sha256 for a blob: URL -->
+ <script>
+ var blob = new Blob(["console.log('blob:valid');"],
+ {type:"application/javascript"});
+ var script = document.createElement('script');
+ script.setAttribute('src', URL.createObjectURL(blob));
+ script.setAttribute('integrity', 'sha256-AwLdXiGfCqOxOXDPUim73G8NVEL34jT0IcQR/tqv/GQ=');
+ script.onerror = bad_validBlobBlocked;
+ script.onload = good_validBlobLoaded;
+ var head = document.getElementsByTagName('head').item(0);
+ head.appendChild(script);
+ </script>
+
+ <!-- invalid sha256 for a blob: URL -->
+ <script>
+ var blob = new Blob(["console.log('blob:invalid');"],
+ {type:"application/javascript"});
+ var script = document.createElement('script');
+ script.setAttribute('src', URL.createObjectURL(blob));
+ script.setAttribute('integrity', 'sha256-AwLdXiGfCqOxOXDPUim73G8NVEL34jT0IcQR/tqv/GQ=');
+ script.onerror = good_invalidBlobBlocked;
+ script.onload = bad_invalidBlobLoaded;
+ var head = document.getElementsByTagName('head').item(0);
+ head.appendChild(script);
+ </script>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/sri/iframe_sri_disabled.html b/dom/security/test/sri/iframe_sri_disabled.html
new file mode 100644
index 000000000..9fb10293a
--- /dev/null
+++ b/dom/security/test/sri/iframe_sri_disabled.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ SimpleTest.finish();
+ }
+ </script>
+ <script>
+ function good_correctHashLoaded() {
+ ok(true, "A script was correctly loaded when integrity matched")
+ }
+ function bad_correctHashBlocked() {
+ ok(false, "We should load scripts with hashes that match!");
+ }
+
+ function good_incorrectHashLoaded() {
+ ok(true, "A script was correctly loaded despite the incorrect hash because SRI is disabled.");
+ }
+ function bad_incorrectHashBlocked() {
+ ok(false, "We should load scripts with hashes that do not match the content when SRI is disabled!");
+ }
+
+ function good_correctStyleHashLoaded() {
+ ok(true, "A stylesheet was correctly loaded when integrity matched")
+ }
+ function bad_correctStyleHashBlocked() {
+ ok(false, "We should load stylesheets with hashes that match!");
+ }
+
+ function good_incorrectStyleHashLoaded() {
+ ok(true, "A stylesheet was correctly loaded despite the incorrect hash because SRI is disabled.");
+ }
+ function bad_incorrectStyleHashBlocked() {
+ ok(false, "We should load stylesheets with hashes that do not match the content when SRI is disabled!");
+ }
+ </script>
+
+ <!-- valid sha256 hash. should trigger onload -->
+ <link rel="stylesheet" href="style1.css?disabled"
+ integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
+ onerror="bad_correctStyleHashBlocked()"
+ onload="good_correctStyleHashLoaded()">
+
+ <!-- invalid sha256 hash. should trigger onerror -->
+ <link rel="stylesheet" href="style2.css?disabled"
+ integrity="sha256-bogus"
+ onerror="bad_incorrectStyleHashBlocked()"
+ onload="good_incorrectStyleHashLoaded()">
+</head>
+<body>
+ <!-- valid hash. should trigger onload -->
+ <script src="script.js"
+ integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+ onerror="bad_correctHashBlocked()"
+ onload="good_correctHashLoaded()"></script>
+
+ <!-- invalid hash. should trigger onerror -->
+ <script src="script.js"
+ integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
+ onerror="bad_incorrectHashBlocked()"
+ onload="good_incorrectHashLoaded()"></script>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/sri/iframe_style_crossdomain.html b/dom/security/test/sri/iframe_style_crossdomain.html
new file mode 100644
index 000000000..22c8593b5
--- /dev/null
+++ b/dom/security/test/sri/iframe_style_crossdomain.html
@@ -0,0 +1,117 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ function check_styles() {
+ var redText = document.getElementById('red-text');
+ var greenText = document.getElementById('green-text');
+ var blueText = document.getElementById('blue-text');
+ var redTextColor = window.getComputedStyle(redText, null).getPropertyValue('color');
+ var greenTextColor = window.getComputedStyle(greenText, null).getPropertyValue('color');
+ var blueTextColor = window.getComputedStyle(blueText, null).getPropertyValue('color');
+ ok(redTextColor == 'rgb(255, 0, 0)', "The first part should be red.");
+ ok(greenTextColor == 'rgb(0, 255, 0)', "The second part should be green.");
+ ok(blueTextColor == 'rgb(0, 0, 255)', "The third part should be blue.");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ check_styles();
+ SimpleTest.finish();
+ }
+ </script>
+ <script>
+ function good_correctHashCORSLoaded() {
+ ok(true, "A CORS cross-domain stylesheet with correct hash was correctly loaded.");
+ }
+ function bad_correctHashCORSBlocked() {
+ ok(false, "We should load CORS cross-domain stylesheets with hashes that match!");
+ }
+ function good_correctHashBlocked() {
+ ok(true, "A non-CORS cross-domain stylesheet with correct hash was correctly blocked.");
+ }
+ function bad_correctHashLoaded() {
+ ok(false, "We should block non-CORS cross-domain stylesheets with hashes that match!");
+ }
+
+ function good_incorrectHashBlocked() {
+ ok(true, "A non-CORS cross-domain stylesheet with incorrect hash was correctly blocked.");
+ }
+ function bad_incorrectHashLoaded() {
+ ok(false, "We should load non-CORS cross-domain stylesheets with incorrect hashes!");
+ }
+
+ function bad_correctDataBlocked() {
+ ok(false, "We should not block non-CORS cross-domain stylesheets in data: URI!");
+ }
+ function good_correctDataLoaded() {
+ ok(true, "A non-CORS cross-domain stylesheet with data: URI was correctly loaded.");
+ }
+ function bad_correctDataCORSBlocked() {
+ ok(false, "We should not block CORS stylesheets in data: URI!");
+ }
+ function good_correctDataCORSLoaded() {
+ ok(true, "A CORS stylesheet with data: URI was correctly loaded.");
+ }
+
+ function good_correctHashOpaqueBlocked() {
+ ok(true, "A non-CORS(Opaque) cross-domain stylesheet with correct hash was correctly blocked.");
+ }
+ function bad_correctHashOpaqueLoaded() {
+ ok(false, "We should not load non-CORS(Opaque) cross-domain stylesheets with correct hashes!");
+ }
+ </script>
+
+ <!-- valid CORS sha256 hash -->
+ <link rel="stylesheet" href="http://example.com/tests/dom/security/test/sri/style1.css"
+ crossorigin="anonymous"
+ integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
+ onerror="bad_correctHashCORSBlocked()"
+ onload="good_correctHashCORSLoaded()">
+
+ <!-- valid non-CORS sha256 hash -->
+ <link rel="stylesheet" href="style_301.css"
+ integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
+ onerror="good_correctHashBlocked()"
+ onload="bad_correctHashLoaded()">
+
+ <!-- invalid non-CORS sha256 hash -->
+ <link rel="stylesheet" href="style_301.css?again"
+ integrity="sha256-bogus"
+ onerror="good_incorrectHashBlocked()"
+ onload="bad_incorrectHashLoaded()">
+
+ <!-- valid non-CORS sha256 hash in a data: URL -->
+ <link rel="stylesheet" href="data:text/css,.green-text{color:rgb(0, 255, 0)}"
+ integrity="sha256-EhVtGGyovvffvYdhyqJxUJ/ekam7zlxxo46iM13cwP0="
+ onerror="bad_correctDataBlocked()"
+ onload="good_correctDataLoaded()">
+
+ <!-- valid CORS sha256 hash in a data: URL -->
+ <link rel="stylesheet" href="data:text/css,.blue-text{color:rgb(0, 0, 255)}"
+ crossorigin="anonymous"
+ integrity="sha256-m0Fs2hNSyPOn1030Dp+c8pJFHNmwpeTbB+8J/DcqLss="
+ onerror="bad_correctDataCORSBlocked()"
+ onload="good_correctDataCORSLoaded()">
+
+ <!-- valid non-CORS sha256 hash -->
+ <link rel="stylesheet" href="http://example.com/tests/dom/security/test/sri/style1.css"
+ integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
+ onerror="good_correctHashOpaqueBlocked()"
+ onload="bad_correctHashOpaqueLoaded()">
+</head>
+<body>
+<p><span id="red-text">This should be red</span> but
+ <span id="green-text" class="green-text">this should be green</span> and
+ <span id="blue-text" class="blue-text">this should be blue</span></p>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/sri/iframe_style_sameorigin.html b/dom/security/test/sri/iframe_style_sameorigin.html
new file mode 100644
index 000000000..d994e7c2b
--- /dev/null
+++ b/dom/security/test/sri/iframe_style_sameorigin.html
@@ -0,0 +1,164 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ function check_styles() {
+ var redText = document.getElementById('red-text');
+ var blueText = document.getElementById('blue-text-element');
+ var blackText1 = document.getElementById('black-text');
+ var blackText2 = document.getElementById('black-text-2');
+ var redTextColor = window.getComputedStyle(redText, null).getPropertyValue('color');
+ var blueTextColor = window.getComputedStyle(blueText, null).getPropertyValue('color');
+ var blackTextColor1 = window.getComputedStyle(blackText1, null).getPropertyValue('color');
+ var blackTextColor2 = window.getComputedStyle(blackText2, null).getPropertyValue('color');
+ ok(redTextColor == 'rgb(255, 0, 0)', "The first part should be red.");
+ ok(blueTextColor == 'rgb(0, 0, 255)', "The second part should be blue.");
+ ok(blackTextColor1 == 'rgb(0, 0, 0)', "The second last part should still be black.");
+ ok(blackTextColor2 == 'rgb(0, 0, 0)', "The last part should still be black.");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ window.onload = function() {
+ check_styles();
+ SimpleTest.finish();
+ }
+ </script>
+ <script>
+ function good_correctHashLoaded() {
+ ok(true, "A stylesheet was correctly loaded when integrity matched");
+ }
+ function bad_correctHashBlocked() {
+ ok(false, "We should load stylesheets with hashes that match!");
+ }
+
+ function good_emptyIntegrityLoaded() {
+ ok(true, "A stylesheet was correctly loaded when the integrity attribute was empty");
+ }
+ function bad_emptyIntegrityBlocked() {
+ ok(false, "We should load stylesheets with empty integrity attributes!");
+ }
+
+ function good_incorrectHashBlocked() {
+ ok(true, "A stylesheet was correctly blocked, because the hash digest was wrong");
+ }
+ function bad_incorrectHashLoaded() {
+ ok(false, "We should not load stylesheets with hashes that do not match the content!");
+ }
+
+ function good_validBlobLoaded() {
+ ok(true, "A stylesheet was loaded successfully from a blob: URL with the right hash.");
+ }
+ function bad_validBlobBlocked() {
+ ok(false, "We should load stylesheets using blob: URLs with the right hash!");
+ }
+ function good_invalidBlobBlocked() {
+ ok(true, "A stylesheet was blocked successfully from a blob: URL with an invalid hash.");
+ }
+ function bad_invalidBlobLoaded() {
+ ok(false, "We should not load stylesheets using blob: URLs when they have the wrong hash!");
+ }
+
+ function good_correctUTF8HashLoaded() {
+ ok(true, "A UTF8 stylesheet was correctly loaded when integrity matched");
+ }
+ function bad_correctUTF8HashBlocked() {
+ ok(false, "We should load UTF8 stylesheets with hashes that match!");
+ }
+ function good_correctUTF8BOMHashLoaded() {
+ ok(true, "A UTF8 stylesheet (with BOM) was correctly loaded when integrity matched");
+ }
+ function bad_correctUTF8BOMHashBlocked() {
+ ok(false, "We should load UTF8 (with BOM) stylesheets with hashes that match!");
+ }
+ function good_correctUTF8ishHashLoaded() {
+ ok(true, "A UTF8ish stylesheet was correctly loaded when integrity matched");
+ }
+ function bad_correctUTF8ishHashBlocked() {
+ ok(false, "We should load UTF8ish stylesheets with hashes that match!");
+ }
+ </script>
+
+ <!-- valid sha256 hash. should trigger onload -->
+ <link rel="stylesheet" href="style1.css"
+ integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
+ onerror="bad_correctHashBlocked()"
+ onload="good_correctHashLoaded()">
+
+ <!-- empty metadata. should trigger onload -->
+ <link rel="stylesheet" href="style2.css"
+ integrity=""
+ onerror="bad_emptyIntegrityBlocked()"
+ onload="good_emptyIntegrityLoaded()">
+
+ <!-- invalid sha256 hash. should trigger onerror -->
+ <link rel="stylesheet" href="style3.css"
+ integrity="sha256-bogus"
+ onerror="good_incorrectHashBlocked()"
+ onload="bad_incorrectHashLoaded()">
+
+ <!-- valid sha384 hash of a utf8 file. should trigger onload -->
+ <link rel="stylesheet" href="style4.css"
+ integrity="sha384-13rt+j7xMDLhohLukb7AZx8lDGS3hkahp0IoeuyvxSNVPyc1QQmTDcwXGhQZjoMH"
+ onerror="bad_correctUTF8HashBlocked()"
+ onload="good_correctUTF8HashLoaded()">
+
+ <!-- valid sha384 hash of a utf8 file with a BOM. should trigger onload -->
+ <link rel="stylesheet" href="style5.css"
+ integrity="sha384-udAqVKPIHf/OD1isAYKrgzsog/3Q6lSEL2nKhtLSTmHryiae0+y6x1akeTzEF446"
+ onerror="bad_correctUTF8BOMHashBlocked()"
+ onload="good_correctUTF8BOMHashLoaded()">
+
+ <!-- valid sha384 hash of a utf8 file with the wrong charset. should trigger onload -->
+ <link rel="stylesheet" href="style6.css"
+ integrity="sha384-Xli4ROFoVGCiRgXyl7y8jv5Vm2yuqj+8tkNL3cUI7AHaCocna75JLs5xID437W6C"
+ onerror="bad_correctUTF8ishHashBlocked()"
+ onload="good_correctUTF8ishHashLoaded()">
+</head>
+<body>
+
+<!-- valid sha256 for a blob: URL -->
+<script>
+ var blob = new Blob(['.blue-text{color:blue}'],
+ {type: 'text/css'});
+ var link = document.createElement('link');
+ link.rel = 'stylesheet';
+ link.href = window.URL.createObjectURL(blob);
+ link.setAttribute('integrity', 'sha256-/F+EMVnTWYJOAzN5n7/21idiydu6nRi33LZOISZtwOM=');
+ link.onerror = bad_validBlobBlocked;
+ link.onload = good_validBlobLoaded;
+ document.body.appendChild(link);
+</script>
+
+<!-- invalid sha256 for a blob: URL -->
+<script>
+ var blob = new Blob(['.black-text{color:blue}'],
+ {type: 'text/css'});
+ var link = document.createElement('link');
+ link.rel = 'stylesheet';
+ link.href = window.URL.createObjectURL(blob);
+ link.setAttribute('integrity', 'sha256-/F+EMVnTWYJOAzN5n7/21idiydu6nRi33LZOISZtwOM=');
+ link.onerror = good_invalidBlobBlocked;
+ link.onload = bad_invalidBlobLoaded;
+ document.body.appendChild(link);
+</script>
+
+<p><span id="red-text">This should be red </span>,
+ <span id="purple-text">this should be purple</span>,
+ <span id="brown-text">this should be brown</span>,
+ <span id="orange-text">this should be orange</span>, and
+ <span class="blue-text" id="blue-text-element">this should be blue.</span>
+ However, <span id="black-text">this should stay black</span> and
+ <span class="black-text" id="black-text-2">this should also stay black.</span>
+</p>
+
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/security/test/sri/mochitest.ini b/dom/security/test/sri/mochitest.ini
new file mode 100644
index 000000000..e6e2e2fc3
--- /dev/null
+++ b/dom/security/test/sri/mochitest.ini
@@ -0,0 +1,57 @@
+[DEFAULT]
+support-files =
+ file_bug_1271796.css
+ iframe_csp_directive_style_imports.html
+ iframe_csp_directive_style_imports.html^headers^
+ iframe_require-sri-for_main.html
+ iframe_require-sri-for_main.html^headers^
+ iframe_require-sri-for_no_csp.html
+ iframe_script_crossdomain.html
+ iframe_script_sameorigin.html
+ iframe_sri_disabled.html
+ iframe_style_crossdomain.html
+ iframe_style_sameorigin.html
+ rsf_csp_worker.js
+ rsf_csp_worker.js^headers^
+ rsf_imported.js
+ rsf_worker.js
+ script_crossdomain1.js
+ script_crossdomain1.js^headers^
+ script_crossdomain2.js
+ script_crossdomain3.js
+ script_crossdomain3.js^headers^
+ script_crossdomain4.js
+ script_crossdomain4.js^headers^
+ script_crossdomain5.js
+ script_crossdomain5.js^headers^
+ script.js
+ script.js^headers^
+ script_301.js
+ script_301.js^headers^
+ script_302.js
+ script_302.js^headers^
+ script_401.js
+ script_401.js^headers^
+ style1.css
+ style1.css^headers^
+ style2.css
+ style3.css
+ style4.css
+ style4.css^headers^
+ style5.css
+ style6.css
+ style6.css^headers^
+ style_301.css
+ style_301.css^headers^
+ style_importing.css
+ style_imported.css
+
+[test_script_sameorigin.html]
+[test_script_crossdomain.html]
+[test_sri_disabled.html]
+[test_style_crossdomain.html]
+[test_style_sameorigin.html]
+[test_require-sri-for_csp_directive.html]
+[test_require-sri-for_csp_directive_disabled.html]
+[test_bug_1271796.html]
+[test_csp_directive_style_imports.html]
diff --git a/dom/security/test/sri/rsf_csp_worker.js b/dom/security/test/sri/rsf_csp_worker.js
new file mode 100644
index 000000000..553fdf92b
--- /dev/null
+++ b/dom/security/test/sri/rsf_csp_worker.js
@@ -0,0 +1,9 @@
+postMessage("good_worker_could_load");
+try {
+ importScripts('rsf_imported.js');
+} catch(e) {
+ postMessage("good_worker_after_importscripts");
+}
+finally {
+ postMessage("finish");
+}
diff --git a/dom/security/test/sri/rsf_csp_worker.js^headers^ b/dom/security/test/sri/rsf_csp_worker.js^headers^
new file mode 100644
index 000000000..0a6ccba79
--- /dev/null
+++ b/dom/security/test/sri/rsf_csp_worker.js^headers^
@@ -0,0 +1 @@
+content-security-policy: require-sri-for script style
diff --git a/dom/security/test/sri/rsf_imported.js b/dom/security/test/sri/rsf_imported.js
new file mode 100644
index 000000000..33f54b7a0
--- /dev/null
+++ b/dom/security/test/sri/rsf_imported.js
@@ -0,0 +1 @@
+postMessage('bad_worker_could_load_via_importScripts');
diff --git a/dom/security/test/sri/rsf_spawn_CSPd_worker.js b/dom/security/test/sri/rsf_spawn_CSPd_worker.js
new file mode 100644
index 000000000..652c77100
--- /dev/null
+++ b/dom/security/test/sri/rsf_spawn_CSPd_worker.js
@@ -0,0 +1,3 @@
+w = new Worker("rsf_csp_worker.js");
+// use the handler function in test_require-sri-for_csp_directive.html
+w.onmessage = parent.handler;
diff --git a/dom/security/test/sri/rsf_worker.js b/dom/security/test/sri/rsf_worker.js
new file mode 100644
index 000000000..553d3deee
--- /dev/null
+++ b/dom/security/test/sri/rsf_worker.js
@@ -0,0 +1,2 @@
+parent.postMessage('bad_worker_could_load', '*');
+importScripts('rsf_imported.js');
diff --git a/dom/security/test/sri/script.js b/dom/security/test/sri/script.js
new file mode 100644
index 000000000..8fd8f96b2
--- /dev/null
+++ b/dom/security/test/sri/script.js
@@ -0,0 +1 @@
+var load=true;
diff --git a/dom/security/test/sri/script.js^headers^ b/dom/security/test/sri/script.js^headers^
new file mode 100644
index 000000000..b77232d81
--- /dev/null
+++ b/dom/security/test/sri/script.js^headers^
@@ -0,0 +1 @@
+Cache-control: public
diff --git a/dom/security/test/sri/script_301.js b/dom/security/test/sri/script_301.js
new file mode 100644
index 000000000..9a95de77c
--- /dev/null
+++ b/dom/security/test/sri/script_301.js
@@ -0,0 +1 @@
+var load=false;
diff --git a/dom/security/test/sri/script_301.js^headers^ b/dom/security/test/sri/script_301.js^headers^
new file mode 100644
index 000000000..efbfb7334
--- /dev/null
+++ b/dom/security/test/sri/script_301.js^headers^
@@ -0,0 +1,2 @@
+HTTP 301 Moved Permanently
+Location: http://example.com/tests/dom/security/test/sri/script_crossdomain5.js
diff --git a/dom/security/test/sri/script_302.js b/dom/security/test/sri/script_302.js
new file mode 100644
index 000000000..9a95de77c
--- /dev/null
+++ b/dom/security/test/sri/script_302.js
@@ -0,0 +1 @@
+var load=false;
diff --git a/dom/security/test/sri/script_302.js^headers^ b/dom/security/test/sri/script_302.js^headers^
new file mode 100644
index 000000000..05a545a6a
--- /dev/null
+++ b/dom/security/test/sri/script_302.js^headers^
@@ -0,0 +1,2 @@
+HTTP 302 Found
+Location: /tests/dom/security/test/sri/script.js
diff --git a/dom/security/test/sri/script_401.js b/dom/security/test/sri/script_401.js
new file mode 100644
index 000000000..8fd8f96b2
--- /dev/null
+++ b/dom/security/test/sri/script_401.js
@@ -0,0 +1 @@
+var load=true;
diff --git a/dom/security/test/sri/script_401.js^headers^ b/dom/security/test/sri/script_401.js^headers^
new file mode 100644
index 000000000..889fbe081
--- /dev/null
+++ b/dom/security/test/sri/script_401.js^headers^
@@ -0,0 +1,2 @@
+HTTP 401 Authorization Required
+Cache-control: public
diff --git a/dom/security/test/sri/script_crossdomain1.js b/dom/security/test/sri/script_crossdomain1.js
new file mode 100644
index 000000000..1f17a6db2
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain1.js
@@ -0,0 +1,4 @@
+/*
+ * this file should be loaded, because it has CORS enabled.
+*/
+window.hasCORSLoaded = true;
diff --git a/dom/security/test/sri/script_crossdomain1.js^headers^ b/dom/security/test/sri/script_crossdomain1.js^headers^
new file mode 100644
index 000000000..3a6a85d89
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain1.js^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: http://mochi.test:8888
diff --git a/dom/security/test/sri/script_crossdomain2.js b/dom/security/test/sri/script_crossdomain2.js
new file mode 100644
index 000000000..4b0208ab3
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain2.js
@@ -0,0 +1,5 @@
+/*
+ * this file should not be loaded, because it does not have CORS
+ * enabled.
+ */
+window.hasNonCORSLoaded = true;
diff --git a/dom/security/test/sri/script_crossdomain3.js b/dom/security/test/sri/script_crossdomain3.js
new file mode 100644
index 000000000..eed05d59b
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain3.js
@@ -0,0 +1 @@
+// This script intentionally left blank
diff --git a/dom/security/test/sri/script_crossdomain3.js^headers^ b/dom/security/test/sri/script_crossdomain3.js^headers^
new file mode 100644
index 000000000..3a6a85d89
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain3.js^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: http://mochi.test:8888
diff --git a/dom/security/test/sri/script_crossdomain4.js b/dom/security/test/sri/script_crossdomain4.js
new file mode 100644
index 000000000..eed05d59b
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain4.js
@@ -0,0 +1 @@
+// This script intentionally left blank
diff --git a/dom/security/test/sri/script_crossdomain4.js^headers^ b/dom/security/test/sri/script_crossdomain4.js^headers^
new file mode 100644
index 000000000..3a6a85d89
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain4.js^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: http://mochi.test:8888
diff --git a/dom/security/test/sri/script_crossdomain5.js b/dom/security/test/sri/script_crossdomain5.js
new file mode 100644
index 000000000..eed05d59b
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain5.js
@@ -0,0 +1 @@
+// This script intentionally left blank
diff --git a/dom/security/test/sri/script_crossdomain5.js^headers^ b/dom/security/test/sri/script_crossdomain5.js^headers^
new file mode 100644
index 000000000..cb762eff8
--- /dev/null
+++ b/dom/security/test/sri/script_crossdomain5.js^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/dom/security/test/sri/style1.css b/dom/security/test/sri/style1.css
new file mode 100644
index 000000000..c7ab9ecff
--- /dev/null
+++ b/dom/security/test/sri/style1.css
@@ -0,0 +1,3 @@
+#red-text {
+ color: red;
+}
diff --git a/dom/security/test/sri/style1.css^headers^ b/dom/security/test/sri/style1.css^headers^
new file mode 100644
index 000000000..3a6a85d89
--- /dev/null
+++ b/dom/security/test/sri/style1.css^headers^
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: http://mochi.test:8888
diff --git a/dom/security/test/sri/style2.css b/dom/security/test/sri/style2.css
new file mode 100644
index 000000000..9eece75e5
--- /dev/null
+++ b/dom/security/test/sri/style2.css
@@ -0,0 +1 @@
+; A valid but somewhat uninteresting stylesheet
diff --git a/dom/security/test/sri/style3.css b/dom/security/test/sri/style3.css
new file mode 100644
index 000000000..b64fa3b74
--- /dev/null
+++ b/dom/security/test/sri/style3.css
@@ -0,0 +1,3 @@
+#black-text {
+ color: green;
+}
diff --git a/dom/security/test/sri/style4.css b/dom/security/test/sri/style4.css
new file mode 100644
index 000000000..eab83656e
--- /dev/null
+++ b/dom/security/test/sri/style4.css
@@ -0,0 +1,4 @@
+/* François was here. */
+#purple-text {
+ color: purple;
+}
diff --git a/dom/security/test/sri/style4.css^headers^ b/dom/security/test/sri/style4.css^headers^
new file mode 100644
index 000000000..e13897f15
--- /dev/null
+++ b/dom/security/test/sri/style4.css^headers^
@@ -0,0 +1 @@
+Content-Type: text/css; charset=utf-8
diff --git a/dom/security/test/sri/style5.css b/dom/security/test/sri/style5.css
new file mode 100644
index 000000000..5d59134cc
--- /dev/null
+++ b/dom/security/test/sri/style5.css
@@ -0,0 +1,4 @@
+/* François was here. */
+#orange-text {
+ color: orange;
+}
diff --git a/dom/security/test/sri/style6.css b/dom/security/test/sri/style6.css
new file mode 100644
index 000000000..569557694
--- /dev/null
+++ b/dom/security/test/sri/style6.css
@@ -0,0 +1,4 @@
+/* François was here. */
+#brown-text {
+ color: brown;
+}
diff --git a/dom/security/test/sri/style6.css^headers^ b/dom/security/test/sri/style6.css^headers^
new file mode 100644
index 000000000..d866aa522
--- /dev/null
+++ b/dom/security/test/sri/style6.css^headers^
@@ -0,0 +1 @@
+Content-Type: text/css; charset=iso-8859-8
diff --git a/dom/security/test/sri/style_301.css b/dom/security/test/sri/style_301.css
new file mode 100644
index 000000000..c7ab9ecff
--- /dev/null
+++ b/dom/security/test/sri/style_301.css
@@ -0,0 +1,3 @@
+#red-text {
+ color: red;
+}
diff --git a/dom/security/test/sri/style_301.css^headers^ b/dom/security/test/sri/style_301.css^headers^
new file mode 100644
index 000000000..c5b78ee04
--- /dev/null
+++ b/dom/security/test/sri/style_301.css^headers^
@@ -0,0 +1,2 @@
+HTTP 301 Moved Permanently
+Location: http://example.com/tests/dom/security/test/sri/style1.css
diff --git a/dom/security/test/sri/style_imported.css b/dom/security/test/sri/style_imported.css
new file mode 100644
index 000000000..4469e8d78
--- /dev/null
+++ b/dom/security/test/sri/style_imported.css
@@ -0,0 +1,6 @@
+#text-for-import-test {
+ color: red;
+}
+#text-for-import-test::before {
+ content: 'Test failed';
+}
diff --git a/dom/security/test/sri/style_importing.css b/dom/security/test/sri/style_importing.css
new file mode 100644
index 000000000..985dd3467
--- /dev/null
+++ b/dom/security/test/sri/style_importing.css
@@ -0,0 +1,4 @@
+/* neither of them should load. trying multiple cases*/
+@import url("style_imported.css");
+@import 'style_imported.css';
+#text-for-import-test { color: blue; }
diff --git a/dom/security/test/sri/test_bug_1271796.html b/dom/security/test/sri/test_bug_1271796.html
new file mode 100644
index 000000000..1d639c9cb
--- /dev/null
+++ b/dom/security/test/sri/test_bug_1271796.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ function good_shouldLoadEncodingProblem() {
+ ok(true, "Problematically encoded file correctly loaded.")
+ };
+ function bad_shouldntEncounterBug1271796() {
+ ok(false, "Problematically encoded should load!")
+ }
+ window.onload = function() {
+ SimpleTest.finish();
+ }
+ </script>
+ <link rel="stylesheet" href="file_bug_1271796.css" crossorigin="anonymous"
+ integrity="sha384-8Xl0mTN4S2QZ5xeliG1sd4Ar9o1xMw6JoJy9RNjyHGQDha7GiLxo8l1llwLVgTNG"
+ onload="good_shouldLoadEncodingProblem();"
+ onerror="bad_shouldntEncounterBug1271796();">
+</head>
+<body>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1271796">Bug 1271796</a><br>
+<p>This text is prepended by emdash if css has loaded</p>
+</body>
+</html>
diff --git a/dom/security/test/sri/test_csp_directive_style_imports.html b/dom/security/test/sri/test_csp_directive_style_imports.html
new file mode 100644
index 000000000..f293e2412
--- /dev/null
+++ b/dom/security/test/sri/test_csp_directive_style_imports.html
@@ -0,0 +1,42 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for SRI require-sri-for CSP directive</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265318">Mozilla Bug 1265318</a><br>
+<iframe style="width:200px;height:200px;" id="test_frame"></iframe><br>
+</body>
+<script type="application/javascript">
+ var finished = 0;
+ SpecialPowers.setBoolPref("security.csp.experimentalEnabled", true);
+ SimpleTest.waitForExplicitFinish();
+ function handler(event) {
+ console.log(event);
+ switch (event.data) {
+ case 'finish':
+ // need finish message from iframe_require-sri-for_main onload event and
+ // from iframe_require-sri-for_no_csp, which spawns a Worker
+ var importText = frame.contentDocument.getElementById('text-for-import-test');
+ var importColor = frame.contentWindow.getComputedStyle(importText, null).getPropertyValue('color');
+ ok(importColor == 'rgb(0, 0, 255)', "The import should not work without integrity. The text is now red, but should not.");
+ removeEventListener('message', handler);
+ SimpleTest.finish();
+ break;
+ default:
+ ok(false, 'Something is wrong here');
+ break;
+ }
+ }
+ addEventListener("message", handler);
+ // This frame has a CSP that requires SRI
+ var frame = document.getElementById("test_frame");
+ frame.src = "iframe_csp_directive_style_imports.html";
+</script>
+</html>
diff --git a/dom/security/test/sri/test_require-sri-for_csp_directive.html b/dom/security/test/sri/test_require-sri-for_csp_directive.html
new file mode 100644
index 000000000..ef1b3603f
--- /dev/null
+++ b/dom/security/test/sri/test_require-sri-for_csp_directive.html
@@ -0,0 +1,76 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for SRI require-sri-for CSP directive</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265318">Mozilla Bug 1265318</a><br>
+<iframe style="width:200px;height:200px;" id="test_frame"></iframe><br>
+<iframe style="width:200px;height:200px;" id="test_frame_no_csp"></iframe>
+</body>
+<script type="application/javascript">
+ var finished = 0;
+ SpecialPowers.setBoolPref("security.csp.experimentalEnabled", true);
+ SimpleTest.waitForExplicitFinish();
+ function handler(event) {
+ switch (event.data) {
+ case 'good_sriLoaded':
+ ok(true, "Eligible SRI resources was correctly loaded.");
+ break;
+ case 'bad_nonsriLoaded':
+ ok(false, "Eligible non-SRI resource should be blocked by the CSP!");
+ break;
+ case 'good_nonsriBlocked':
+ ok(true, "Eligible non-SRI resources was correctly blocked by the CSP.");
+ break;
+ case 'bad_svg_nonsriLoaded':
+ ok(false, 'Eligible non-SRI resource should be blocked by the CSP.');
+ break;
+ case 'good_svg_nonsriBlocked':
+ ok(true, 'Eligible non-SRI svg script was correctly blocked by the CSP.');
+ break;
+ case 'bad_worker_could_load':
+ ok(false, 'require-sri-for failed to block loading a Worker with no integrity metadata.');
+ break;
+ case 'good_worker_could_load':
+ ok(true, "Loaded a worker that has require-sri-for set (but its parent doesnt).")
+ break;
+ case 'bad_worker_could_load_via_importScripts':
+ ok(false, 'require-sri-for failed to block loading importScript in a worker though we require SRI via CSP');
+ break;
+ case 'good_worker_after_importscripts':
+ ok(true, 'Worker continued after failed importScript due to require-sri-for');
+ break;
+ case 'finish':
+ finished++;
+ if (finished > 1) {
+ // need finish message from iframe_require-sri-for_main onload event and
+ // from iframe_require-sri-for_no_csp, which spawns a Worker
+ var blackText = frame.contentDocument.getElementById('black-text');
+ var blackTextColor = frame.contentWindow.getComputedStyle(blackText, null).getPropertyValue('color');
+ ok(blackTextColor == 'rgb(0, 0, 0)', "The second part should not be black.");
+ removeEventListener('message', handler);
+ SimpleTest.finish();
+ }
+ break;
+ default:
+ ok(false, 'Something is wrong here');
+ break;
+ }
+ }
+ addEventListener("message", handler);
+ // This frame has a CSP that requires SRI
+ var frame = document.getElementById("test_frame");
+ frame.src = "iframe_require-sri-for_main.html";
+ // This frame has no CSP to require SRI.
+ // Used for testing require-sri-for in a Worker.
+ var frame_no_csp = document.getElementById("test_frame_no_csp");
+ frame_no_csp.src = "iframe_require-sri-for_no_csp.html";
+</script>
+</html>
diff --git a/dom/security/test/sri/test_require-sri-for_csp_directive_disabled.html b/dom/security/test/sri/test_require-sri-for_csp_directive_disabled.html
new file mode 100644
index 000000000..b833fe11c
--- /dev/null
+++ b/dom/security/test/sri/test_require-sri-for_csp_directive_disabled.html
@@ -0,0 +1,46 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for diabled SRI require-sri-for CSP directive</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265318">Mozilla Bug 1265318</a>
+<iframe style="width:200px;height:200px;" id="test_frame"></iframe>
+</body>
+<script type="application/javascript">
+ SpecialPowers.setBoolPref("security.csp.experimentalEnabled", false);
+ SimpleTest.waitForExplicitFinish();
+ function handler(event) {
+ switch (event.data) {
+ case 'good_sriLoaded':
+ ok(true, "Eligible SRI resources was correctly loaded.");
+ break;
+ case 'bad_nonsriLoaded':
+ ok(true, "Eligible non-SRI resource should be blocked by the CSP!");
+ break;
+ case 'good_nonsriBlocked':
+ ok(false, "Eligible non-SRI resources was correctly blocked by the CSP.");
+ break;
+ case 'finish':
+ var blackText = frame.contentDocument.getElementById('black-text');
+ var blackTextColor = frame.contentWindow.getComputedStyle(blackText, null).getPropertyValue('color');
+ ok(blackTextColor != 'rgb(0, 0, 0)', "The second part should still be black.");
+ removeEventListener('message', handler);
+ SimpleTest.finish();
+ break;
+ default:
+ ok(false, 'Something is wrong here');
+ break;
+ }
+ }
+ addEventListener("message", handler);
+ var frame = document.getElementById("test_frame");
+ frame.src = "iframe_require-sri-for_main.html";
+</script>
+</html>
diff --git a/dom/security/test/sri/test_script_crossdomain.html b/dom/security/test/sri/test_script_crossdomain.html
new file mode 100644
index 000000000..327ea4247
--- /dev/null
+++ b/dom/security/test/sri/test_script_crossdomain.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Cross-domain script tests for Bug 992096</title>
+ <script>
+ SpecialPowers.setBoolPref("security.sri.enable", true);
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
+<div>
+ <iframe src="iframe_script_crossdomain.html" height="100%" width="90%" frameborder="0"></iframe>
+</div>
+</body>
+</html>
diff --git a/dom/security/test/sri/test_script_sameorigin.html b/dom/security/test/sri/test_script_sameorigin.html
new file mode 100644
index 000000000..df890d5f9
--- /dev/null
+++ b/dom/security/test/sri/test_script_sameorigin.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Same-origin script tests for Bug 992096</title>
+ <script>
+ SpecialPowers.setBoolPref("security.sri.enable", true);
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
+<div>
+ <iframe src="iframe_script_sameorigin.html" height="100%" width="90%" frameborder="0"></iframe>
+</div>
+</body>
+</html>
diff --git a/dom/security/test/sri/test_sri_disabled.html b/dom/security/test/sri/test_sri_disabled.html
new file mode 100644
index 000000000..4661235b1
--- /dev/null
+++ b/dom/security/test/sri/test_sri_disabled.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>security.sri.enable tests for Bug 992096</title>
+ <script>
+ SpecialPowers.setBoolPref("security.sri.enable", false);
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
+<div>
+ <iframe src="iframe_sri_disabled.html" height="100%" width="90%" frameborder="0"></iframe>
+</div>
+</body>
+</html>
diff --git a/dom/security/test/sri/test_style_crossdomain.html b/dom/security/test/sri/test_style_crossdomain.html
new file mode 100644
index 000000000..6bf4b2de1
--- /dev/null
+++ b/dom/security/test/sri/test_style_crossdomain.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Cross-domain stylesheet tests for Bug 1196740</title>
+ <script>
+ SpecialPowers.setBoolPref("security.sri.enable", true);
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1196740">Mozilla Bug 1196740</a>
+<div>
+ <iframe src="iframe_style_crossdomain.html" height="100%" width="90%" frameborder="0"></iframe>
+</div>
+</body>
+</html>
diff --git a/dom/security/test/sri/test_style_sameorigin.html b/dom/security/test/sri/test_style_sameorigin.html
new file mode 100644
index 000000000..fb1d3f78d
--- /dev/null
+++ b/dom/security/test/sri/test_style_sameorigin.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Same-origin stylesheet tests for Bug 992096</title>
+ <script>
+ SpecialPowers.setBoolPref("security.sri.enable", true);
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
+<div>
+ <iframe src="iframe_style_sameorigin.html" height="100%" width="90%" frameborder="0"></iframe>
+</div>
+</body>
+</html>
diff --git a/dom/security/test/unit/test_csp_reports.js b/dom/security/test/unit/test_csp_reports.js
new file mode 100644
index 000000000..6c88fb1e1
--- /dev/null
+++ b/dom/security/test/unit/test_csp_reports.js
@@ -0,0 +1,231 @@
+/* This Source Code Form 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/. */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+Cu.import('resource://gre/modules/NetUtil.jsm');
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://testing-common/httpd.js");
+
+var httpServer = new HttpServer();
+httpServer.start(-1);
+var testsToFinish = 0;
+
+var principal;
+
+const REPORT_SERVER_PORT = httpServer.identity.primaryPort;
+const REPORT_SERVER_URI = "http://localhost";
+const REPORT_SERVER_PATH = "/report";
+
+/**
+ * Construct a callback that listens to a report submission and either passes
+ * or fails a test based on what it gets.
+ */
+function makeReportHandler(testpath, message, expectedJSON) {
+ return function(request, response) {
+ // we only like "POST" submissions for reports!
+ if (request.method !== "POST") {
+ do_throw("violation report should be a POST request");
+ return;
+ }
+
+ // check content-type of report is "application/csp-report"
+ var contentType = request.hasHeader("Content-Type")
+ ? request.getHeader("Content-Type") : undefined;
+ if (contentType !== "application/csp-report") {
+ do_throw("violation report should have the 'application/csp-report' " +
+ "content-type, when in fact it is " + contentType.toString())
+ }
+
+ // obtain violation report
+ var reportObj = JSON.parse(
+ NetUtil.readInputStreamToString(
+ request.bodyInputStream,
+ request.bodyInputStream.available()));
+
+ // dump("GOT REPORT:\n" + JSON.stringify(reportObj) + "\n");
+ // dump("TESTPATH: " + testpath + "\n");
+ // dump("EXPECTED: \n" + JSON.stringify(expectedJSON) + "\n\n");
+
+ for (var i in expectedJSON)
+ do_check_eq(expectedJSON[i], reportObj['csp-report'][i]);
+
+ testsToFinish--;
+ httpServer.registerPathHandler(testpath, null);
+ if (testsToFinish < 1)
+ httpServer.stop(do_test_finished);
+ else
+ do_test_finished();
+ };
+}
+
+/**
+ * Everything created by this assumes it will cause a report. If you want to
+ * add a test here that will *not* cause a report to go out, you're gonna have
+ * to make sure the test cleans up after itself.
+ */
+function makeTest(id, expectedJSON, useReportOnlyPolicy, callback) {
+ testsToFinish++;
+ do_test_pending();
+
+ // set up a new CSP instance for each test.
+ var csp = Cc["@mozilla.org/cspcontext;1"]
+ .createInstance(Ci.nsIContentSecurityPolicy);
+ var policy = "default-src 'none'; " +
+ "report-uri " + REPORT_SERVER_URI +
+ ":" + REPORT_SERVER_PORT +
+ "/test" + id;
+ var selfuri = NetUtil.newURI(REPORT_SERVER_URI +
+ ":" + REPORT_SERVER_PORT +
+ "/foo/self");
+
+ dump("Created test " + id + " : " + policy + "\n\n");
+
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ principal = ssm.createCodebasePrincipal(selfuri, {});
+ csp.setRequestContext(null, principal);
+
+ // Load up the policy
+ // set as report-only if that's the case
+ csp.appendPolicy(policy, useReportOnlyPolicy, false);
+
+ // prime the report server
+ var handler = makeReportHandler("/test" + id, "Test " + id, expectedJSON);
+ httpServer.registerPathHandler("/test" + id, handler);
+
+ //trigger the violation
+ callback(csp);
+}
+
+function run_test() {
+ var selfuri = NetUtil.newURI(REPORT_SERVER_URI +
+ ":" + REPORT_SERVER_PORT +
+ "/foo/self");
+
+ // test that inline script violations cause a report.
+ makeTest(0, {"blocked-uri": "self"}, false,
+ function(csp) {
+ let inlineOK = true;
+ inlineOK = csp.getAllowsInline(Ci.nsIContentPolicy.TYPE_SCRIPT,
+ "", // aNonce
+ false, // aParserCreated
+ "", // aContent
+ 0); // aLineNumber
+
+ // this is not a report only policy, so it better block inline scripts
+ do_check_false(inlineOK);
+ });
+
+ // test that eval violations cause a report.
+ makeTest(1, {"blocked-uri": "self",
+ // JSON script-sample is UTF8 encoded
+ "script-sample" : "\xc2\xa3\xc2\xa5\xc2\xb5\xe5\x8c\x97\xf0\xa0\x9d\xb9"}, false,
+ function(csp) {
+ let evalOK = true, oReportViolation = {'value': false};
+ evalOK = csp.getAllowsEval(oReportViolation);
+
+ // this is not a report only policy, so it better block eval
+ do_check_false(evalOK);
+ // ... and cause reports to go out
+ do_check_true(oReportViolation.value);
+
+ if (oReportViolation.value) {
+ // force the logging, since the getter doesn't.
+ csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL,
+ selfuri.asciiSpec,
+ // sending UTF-16 script sample to make sure
+ // csp report in JSON is not cut-off, please
+ // note that JSON is UTF8 encoded.
+ "\u00a3\u00a5\u00b5\u5317\ud841\udf79",
+ 1);
+ }
+ });
+
+ makeTest(2, {"blocked-uri": "http://blocked.test"}, false,
+ function(csp) {
+ // shouldLoad creates and sends out the report here.
+ csp.shouldLoad(Ci.nsIContentPolicy.TYPE_SCRIPT,
+ NetUtil.newURI("http://blocked.test/foo.js"),
+ null, null, null, null);
+ });
+
+ // test that inline script violations cause a report in report-only policy
+ makeTest(3, {"blocked-uri": "self"}, true,
+ function(csp) {
+ let inlineOK = true;
+ inlineOK = csp.getAllowsInline(Ci.nsIContentPolicy.TYPE_SCRIPT,
+ "", // aNonce
+ false, // aParserCreated
+ "", // aContent
+ 0); // aLineNumber
+
+ // this is a report only policy, so it better allow inline scripts
+ do_check_true(inlineOK);
+ });
+
+ // test that eval violations cause a report in report-only policy
+ makeTest(4, {"blocked-uri": "self"}, true,
+ function(csp) {
+ let evalOK = true, oReportViolation = {'value': false};
+ evalOK = csp.getAllowsEval(oReportViolation);
+
+ // this is a report only policy, so it better allow eval
+ do_check_true(evalOK);
+ // ... but still cause reports to go out
+ do_check_true(oReportViolation.value);
+
+ if (oReportViolation.value) {
+ // force the logging, since the getter doesn't.
+ csp.logViolationDetails(Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT,
+ selfuri.asciiSpec,
+ "script sample",
+ 4);
+ }
+ });
+
+ // test that only the uri's scheme is reported for globally unique identifiers
+ makeTest(5, {"blocked-uri": "data"}, false,
+ function(csp) {
+ var base64data =
+ "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" +
+ "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
+ // shouldLoad creates and sends out the report here.
+ csp.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE,
+ NetUtil.newURI("data:image/png;base64," + base64data),
+ null, null, null, null);
+ });
+
+ // test that only the uri's scheme is reported for globally unique identifiers
+ makeTest(6, {"blocked-uri": "intent"}, false,
+ function(csp) {
+ // shouldLoad creates and sends out the report here.
+ csp.shouldLoad(Ci.nsIContentPolicy.TYPE_SUBDOCUMENT,
+ NetUtil.newURI("intent://mymaps.com/maps?um=1&ie=UTF-8&fb=1&sll"),
+ null, null, null, null);
+ });
+
+ // test fragment removal
+ var selfSpec = REPORT_SERVER_URI + ":" + REPORT_SERVER_PORT + "/foo/self/foo.js";
+ makeTest(7, {"blocked-uri": selfSpec}, false,
+ function(csp) {
+ var uri = NetUtil
+ // shouldLoad creates and sends out the report here.
+ csp.shouldLoad(Ci.nsIContentPolicy.TYPE_SCRIPT,
+ NetUtil.newURI(selfSpec + "#bar"),
+ null, null, null, null);
+ });
+
+ // test scheme of ftp:
+ makeTest(8, {"blocked-uri": "ftp://blocked.test"}, false,
+ function(csp) {
+ // shouldLoad creates and sends out the report here.
+ csp.shouldLoad(Ci.nsIContentPolicy.TYPE_SCRIPT,
+ NetUtil.newURI("ftp://blocked.test/profile.png"),
+ null, null, null, null);
+ });
+}
diff --git a/dom/security/test/unit/test_csp_upgrade_insecure_request_header.js b/dom/security/test/unit/test_csp_upgrade_insecure_request_header.js
new file mode 100644
index 000000000..8be873234
--- /dev/null
+++ b/dom/security/test/unit/test_csp_upgrade_insecure_request_header.js
@@ -0,0 +1,107 @@
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+
+// Since this test creates a TYPE_DOCUMENT channel via javascript, it will
+// end up using the wrong LoadInfo constructor. Setting this pref will disable
+// the ContentPolicyType assertion in the constructor.
+prefs.setBoolPref("network.loadinfo.skip_type_assertion", true);
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+ return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver = null;
+var channel = null;
+var curTest = null;
+var testpath = "/footpath";
+
+var tests = [
+ {
+ description: "should not set request header for TYPE_OTHER",
+ expectingHeader: false,
+ contentType: Ci.nsIContentPolicy.TYPE_OTHER
+ },
+ {
+ description: "should set request header for TYPE_DOCUMENT",
+ expectingHeader: true,
+ contentType: Ci.nsIContentPolicy.TYPE_DOCUMENT
+ },
+ {
+ description: "should set request header for TYPE_SUBDOCUMENT",
+ expectingHeader: true,
+ contentType: Ci.nsIContentPolicy.TYPE_SUBDOCUMENT
+ },
+ {
+ description: "should not set request header for TYPE_IMG",
+ expectingHeader: false,
+ contentType: Ci.nsIContentPolicy.TYPE_IMG
+ },
+];
+
+function ChannelListener() {
+}
+
+ChannelListener.prototype = {
+ onStartRequest: function(request, context) { },
+ onDataAvailable: function(request, context, stream, offset, count) {
+ do_throw("Should not get any data!");
+ },
+ onStopRequest: function(request, context, status) {
+ var upgrade_insecure_header = false;
+ try {
+ if (request.getRequestHeader("Upgrade-Insecure-Requests")) {
+ upgrade_insecure_header = true;
+ }
+ }
+ catch (e) {
+ // exception is thrown if header is not available on the request
+ }
+ // debug
+ // dump("executing test: " + curTest.description);
+ do_check_eq(upgrade_insecure_header, curTest.expectingHeader)
+ run_next_test();
+ },
+};
+
+function setupChannel(aContentType) {
+ var chan = NetUtil.newChannel({
+ uri: URL + testpath,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: aContentType
+ });
+ chan.QueryInterface(Ci.nsIHttpChannel);
+ chan.requestMethod = "GET";
+ return chan;
+}
+
+function serverHandler(metadata, response) {
+ // no need to perform anything here
+}
+
+function run_next_test() {
+ curTest = tests.shift();
+ if (!curTest) {
+ httpserver.stop(do_test_finished);
+ return;
+ }
+ channel = setupChannel(curTest.contentType);
+ channel.asyncOpen2(new ChannelListener());
+}
+
+function run_test() {
+ // set up the test environment
+ httpserver = new HttpServer();
+ httpserver.registerPathHandler(testpath, serverHandler);
+ httpserver.start(-1);
+
+ run_next_test();
+ do_test_pending();
+}
diff --git a/dom/security/test/unit/test_isOriginPotentiallyTrustworthy.js b/dom/security/test/unit/test_isOriginPotentiallyTrustworthy.js
new file mode 100644
index 000000000..7de8faa8f
--- /dev/null
+++ b/dom/security/test/unit/test_isOriginPotentiallyTrustworthy.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests the "Is origin potentially trustworthy?" logic from
+ * <https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy>.
+ */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gScriptSecurityManager",
+ "@mozilla.org/scriptsecuritymanager;1",
+ "nsIScriptSecurityManager");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager",
+ "@mozilla.org/contentsecuritymanager;1",
+ "nsIContentSecurityManager");
+
+var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+prefs.setCharPref("dom.securecontext.whitelist", "example.net,example.org");
+
+add_task(function* test_isOriginPotentiallyTrustworthy() {
+ for (let [uriSpec, expectedResult] of [
+ ["http://example.com/", false],
+ ["https://example.com/", true],
+ ["http://localhost/", true],
+ ["http://127.0.0.1/", true],
+ ["file:///", true],
+ ["resource:///", true],
+ ["app://", true],
+ ["moz-extension://", true],
+ ["wss://example.com/", true],
+ ["about:config", false],
+ ["urn:generic", false],
+ ["http://example.net/", true],
+ ["ws://example.org/", true],
+ ["chrome://example.net/content/messenger.xul", false],
+ ]) {
+ let uri = NetUtil.newURI(uriSpec);
+ let principal = gScriptSecurityManager.getCodebasePrincipal(uri);
+ Assert.equal(gContentSecurityManager.isOriginPotentiallyTrustworthy(principal),
+ expectedResult);
+ }
+});
diff --git a/dom/security/test/unit/xpcshell.ini b/dom/security/test/unit/xpcshell.ini
new file mode 100644
index 000000000..7e1d4a0ed
--- /dev/null
+++ b/dom/security/test/unit/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+head =
+tail =
+
+[test_csp_reports.js]
+[test_isOriginPotentiallyTrustworthy.js]
+[test_csp_upgrade_insecure_request_header.js]