Project

General

Profile

Download (6.46 KB) Statistics
| Branch: | Tag: | Revision:

haketilo / test / haketilo_test / unit / test_content.py @ fd9f2fc4

1
# SPDX-License-Identifier: CC0-1.0
2

    
3
"""
4
Haketilo unit tests - main content script
5
"""
6

    
7
# This file is part of Haketilo
8
#
9
# Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org>
10
#
11
# This program is free software: you can redistribute it and/or modify
12
# it under the terms of the CC0 1.0 Universal License as published by
13
# the Creative Commons Corporation.
14
#
15
# This program is distributed in the hope that it will be useful,
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
# CC0 1.0 Universal License for more details.
19

    
20
import pytest
21
import json
22
from selenium.webdriver.support.ui import WebDriverWait
23

    
24
from ..script_loader import load_script
25

    
26
# From:
27
# https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/contentScripts/register
28
# it is unclear whether the dynamically-registered content script is guaranteed
29
# to be always executed after statically-registered ones. We want to test both
30
# cases, so we'll make the mocked dynamic content script execute before
31
# content.js on http:// pages and after it on https:// pages.
32
dynamic_script = \
33
    ''';
34
    this.haketilo_secret        = "abracadabra";
35
    this.haketilo_pattern_tree  = {};
36
    this.haketilo_default_allow = false;
37

    
38
    if (this.haketilo_content_script_main)
39
        this.haketilo_content_script_main();
40
    '''
41

    
42
content_script = \
43
    '''
44
    /* Mock dynamic content script - case 'before'. */
45
    if (/dynamic_before/.test(document.URL)) {
46
        %s;
47
    }
48

    
49
    /* Place amalgamated content.js here. */
50
    %s;
51

    
52
    /* Rest of mocks */
53

    
54
    function mock_decide_policy() {
55
        nonce = "12345";
56
        return {
57
            allow: false,
58
            mapping: "what-is-programmers-favorite-drinking-place",
59
            payload: {identifier: "foo-bar"},
60
            nonce,
61
            csp: "prefetch-src 'none'; script-src-attr 'none'; script-src 'nonce-12345'; script-src-elem 'nonce-12345';"
62
        };
63
    }
64

    
65
    async function mock_payload_error([type, res_id]) {
66
        if (type === "indexeddb_files")
67
            return {error: {haketilo_error_type: "missing", id: res_id}};
68
    }
69

    
70
    async function mock_payload_ok([type, res_id]) {
71
        if (type === "indexeddb_files")
72
            return {files: [1, 2].map(n => `window.hak_injected_${n} = ${n};`)};
73
    }
74

    
75
    if (/payload_error/.test(document.URL)) {
76
        browser.runtime.sendMessage = mock_payload_error;
77
        decide_policy = mock_decide_policy;
78
    } else if (/payload_ok/.test(document.URL)) {
79
        browser.runtime.sendMessage = mock_payload_ok;
80
        decide_policy = mock_decide_policy;
81
    }
82
    /* Otherwise, script blocking policy without payload to inject is used. */
83

    
84
    const data_to_verify = {};
85
    function data_set(prop, val) {
86
        data_to_verify[prop] = val;
87
        window.wrappedJSObject.data_to_verify = JSON.stringify(data_to_verify);
88
    }
89

    
90
    repo_query_cacher.start = () => data_set("cacher_started", true);
91

    
92
    enforce_blocking = policy => data_set("enforcing", policy);
93

    
94
    browser.runtime.onMessage.addListener = async function (listener_cb) {
95
        await new Promise(cb => setTimeout(cb, 10));
96

    
97
        /* Mock a good request. */
98
        const set_good = val => data_set("good_request_result", val);
99
        data_set("good_request_returned",
100
                 !!listener_cb(["page_info"], {}, val => set_good(val)));
101

    
102
        /* Mock a bad request. */
103
        const set_bad = val => data_set("bad_request_result", val);
104
        data_set("bad_request_returned",
105
                 !!listener_cb(["???"], {}, val => set_bad(val)));
106
    }
107

    
108
    /* main() call - normally present in content.js, inside '#IF !UNIT_TEST'. */
109
    main();
110

    
111
    /* Mock dynamic content script - case 'after'. */
112
    if (/#dynamic_after/.test(document.URL)) {
113
        %s;
114
    }
115

    
116
    data_set("script_run_without_errors", true);
117
    ''' % (dynamic_script, load_script('content/content.js'), dynamic_script)
118

    
119
@pytest.mark.ext_data({'content_script': content_script})
120
@pytest.mark.usefixtures('webextension')
121
@pytest.mark.parametrize('target1', ['dynamic_before'])#, 'dynamic_after'])
122
@pytest.mark.parametrize('target2', [
123
    'scripts_blocked',
124
    'payload_error',
125
    'payload_ok'
126
])
127
def test_content_unprivileged_page(driver, execute_in_page, target1, target2):
128
    """
129
    Test functioning of content.js on an page using unprivileged schema (e.g.
130
    'https://' and not 'about:').
131
    """
132
    driver.get(f'https://gotmyowndoma.in/index.html#{target1}-{target2}')
133

    
134
    def get_data(driver):
135
        data = driver.execute_script('return window.data_to_verify;')
136
        return data if 'good_request_result' in data else False
137

    
138
    data = json.loads(WebDriverWait(driver, 10).until(get_data))
139

    
140
    assert 'gotmyowndoma.in' in data['good_request_result']['url']
141
    assert 'bad_request_result' not in data
142

    
143
    assert data['good_request_returned'] == True
144
    assert data['bad_request_returned'] == False
145

    
146
    assert data['cacher_started'] == True
147

    
148
    for obj in (data['good_request_result'], data['enforcing']):
149
        assert obj['allow'] == False
150

    
151
    assert 'error' not in data['enforcing']
152

    
153
    if target2.startswith('payload'):
154
        for obj in (data['good_request_result'], data['enforcing']):
155
            assert obj['payload']['identifier'] == 'foo-bar'
156
            assert 'mapping' in obj
157
    else:
158
        assert 'payload' not in data['enforcing']
159
        assert 'mapping' not in data['enforcing']
160

    
161
    assert data['script_run_without_errors'] == True
162

    
163
    def vars_made_by_payload(driver):
164
        vars_values = driver.execute_script(
165
            'return [1, 2].map(n => window[`hak_injected_${n}`]);'
166
        )
167
        if vars_values != [None, None]:
168
            return vars_values
169

    
170
    if target2 == 'payload_error':
171
        assert data['good_request_result']['error'] == {
172
            'haketilo_error_type': 'missing',
173
            'id': 'foo-bar'
174
        }
175
    elif target2 == 'payload_ok':
176
        vars_values = WebDriverWait(driver, 10).until(vars_made_by_payload)
177
        assert vars_values == [1, 2]
178

    
179
@pytest.mark.ext_data({'content_script': content_script})
180
@pytest.mark.usefixtures('webextension')
181
@pytest.mark.parametrize('target', ['dynamic_before', 'dynamic_after'])
182
def test_content_privileged_page(driver, execute_in_page, target):
183
    """
184
    Test functioning of content.js on an page considered privileged (e.g. a
185
    directory listing at 'file:///').
186
    """
187
    driver.get(f'file:///#{target}')
188
    data = json.loads(driver.execute_script('return window.data_to_verify;'))
189

    
190
    assert data == {'script_run_without_errors': True}
(5-5/25)