Project

General

Profile

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

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

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
    haketilo_apis.start     = () => data_set("apis_started", true);
92

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

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

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

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

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

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

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

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

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

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

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

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

    
147
    assert data['cacher_started'] == True
148
    assert data.get('apis_started', False) == (target2 == 'payload_ok')
149

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

    
153
    assert 'error' not in data['enforcing']
154

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

    
163
    assert data['script_run_without_errors'] == True
164

    
165
    def vars_made_by_payload(driver):
166
        vars_values = driver.execute_script(
167
            '''
168
            return [
169
                ...[1, 2].map(n => window[`hak_injected_${n}`]),
170
                window.haketilo_version
171
            ];
172
            ''')
173
        if vars_values != [None, None, None]:
174
            return vars_values
175

    
176
    if target2 == 'payload_error':
177
        assert data['good_request_result']['error'] == {
178
            'haketilo_error_type': 'missing',
179
            'id': 'foo-bar'
180
        }
181
    elif target2 == 'payload_ok':
182
        vars_values = WebDriverWait(driver, 10).until(vars_made_by_payload)
183
        assert vars_values[:2] == [1, 2]
184
        assert type(vars_values[2]) == str
185

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

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