Project

General

Profile

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

haketilo / test / haketilo_test / world_wide_library.py @ f8dedf60

1
# SPDX-License-Identifier: AGPL-3.0-or-later
2

    
3
"""
4
Our helpful little stand-in for the Internet
5
"""
6

    
7
# This file is part of Haketilo.
8
#
9
# Copyright (C) 2021 jahoti <jahoti@tilde.team>
10
# Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
11
#
12
# This program is free software: you can redistribute it and/or modify
13
# it under the terms of the GNU Affero General Public License as
14
# published by the Free Software Foundation, either version 3 of the
15
# License, or (at your option) any later version.
16
#
17
# This program is distributed in the hope that it will be useful,
18
# but WITHOUT ANY WARRANTY; without even the implied warranty of
19
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
# GNU Affero General Public License for more details.
21
#
22
# You should have received a copy of the GNU Affero General Public License
23
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
24
#
25
#
26
# I, Wojtek Kosior, thereby promise not to sue for violation of this
27
# file's license. Although I request that you do not make use of this code
28
# in a proprietary program, I am not going to enforce this in court.
29

    
30
from hashlib import sha256
31
from pathlib import Path
32
from shutil import rmtree
33
from threading import Lock
34
from uuid import uuid4
35
import json
36
import functools as ft
37
import operator as op
38

    
39
from .misc_constants import here
40
from .unit.utils import * # sample repo data
41

    
42
# TODO: instead of having the entire catalog defined here, make it possible to
43
# add catalog items from within individual test files.
44

    
45
served_scripts = {}
46
served_scripts_lock = Lock()
47

    
48
def start_serving_script(script_text):
49
    """
50
    Register given script so that it is served at
51
    https://serve.scrip.ts/?sha256=<script's_sha256_sum>
52

    
53
    Returns the URL at which script will be served.
54

    
55
    This function lacks thread safety. Might moght consider fixing this if it
56
    turns
57
    """
58
    sha256sum = sha256(script_text.encode()).digest().hex()
59
    served_scripts_lock.acquire()
60
    served_scripts[sha256sum] = script_text
61
    served_scripts_lock.release()
62

    
63
    return f'https://serve.scrip.ts/?sha256={sha256sum}'
64

    
65
def serve_script(command, get_params, post_params):
66
    """
67
    info() callback to pass to request-handling code in server.py. Facilitates
68
    serving scripts that have been registered with start_serving_script().
69
    """
70
    served_scripts_lock.acquire()
71
    try:
72
        script = served_scripts.get(get_params['sha256'][0])
73
    finally:
74
        served_scripts_lock.release()
75
    if script is None:
76
        return 404, {}, b''
77

    
78
    return 200, {'Content-Type': 'application/javascript'}, script
79

    
80
def dump_scripts(directory=(Path.cwd() / 'injected_scripts')):
81
    """
82
    Write all scripts that have been registered with start_serving_script()
83
    under the provided directory. If the directory already exists, it is wiped
84
    beforehand. If it doesn't exist, it is created.
85
    """
86
    directory = Path(directory)
87
    rmtree(directory, ignore_errors=True)
88
    directory.mkdir(parents=True)
89

    
90
    served_scripts_lock.acquire()
91
    for sha256, script in served_scripts.items():
92
        with open(directory / sha256, 'wt') as file:
93
            file.write(script)
94
    served_scripts_lock.release()
95

    
96
some_data = '{"some": "data"}'
97

    
98
# used by handler function of https://counterdoma.in
99
request_counter = 0
100

    
101
def serve_counter(command, get_params, post_params):
102
    global request_counter
103
    request_counter += 1
104
    return (
105
        200,
106
        {'Cache-Control': 'private, max-age=0, no-store'},
107
        json.dumps({'counter': request_counter})
108
    )
109

    
110
# Mock a Hydrilla repository.
111

    
112
make_handler = lambda txt: lambda c, g, p: (200, {}, txt)
113

    
114
# Mock files in the repository.
115
sample_contents = [f'Mi povas manĝi vitron, ĝi ne damaĝas min {i}'
116
                   for i in range(9)]
117
sample_hashes = [sha256(c.encode()).digest().hex() for c in sample_contents]
118

    
119
file_url = ft.partial(op.concat, 'https://hydril.la/file/sha256/')
120

    
121
sample_files_catalog = dict([(file_url(h), make_handler(c))
122
                             for h, c in zip(sample_hashes, sample_contents)])
123

    
124
# Mock resources and mappings in the repository.
125
sample_resource_templates = []
126

    
127
for deps in [(0, 1, 2, 3), (3, 4, 5, 6), (6, 7, 8, 9)]:
128
    letters = [chr(ord('a') + i) for i in deps]
129
    sample_resource_templates.append({
130
        'id_suffix':    ''.join(letters),
131
        'files_count':  deps[0],
132
        'dependencies': [{'identifier': f'resource-{l}'} for l in letters]
133
    })
134

    
135
suffixes = [srt['id_suffix'] for srt in sample_resource_templates]
136
sample_resource_templates.append({
137
    'id_suffix':    '-'.join(suffixes),
138
    'files_count':  2,
139
    'dependencies': [{'identifier': f'resource-{suf}'} for suf in suffixes]
140
})
141

    
142
for i in range(10):
143
    sample_resource_templates.append({
144
        'id_suffix':    chr(ord('a') + i),
145
        'files_count':  i,
146
        'dependencies': []
147
    })
148

    
149
# The one below will generate items with schema still at version 1, so required
150
# mappings will be ignored.
151
sample_resource_templates.append({
152
    'id_suffix':         'a-w-required-mapping-v1',
153
    'files_count':       1,
154
    'dependencies':      [],
155
    'required_mappings': [{'identifier': 'mapping-a'}],
156
    'include_in_query':  False
157
})
158

    
159
sample_resource_templates.append({
160
    'id_suffix':         'a-w-required-mapping-v2',
161
    'files_count':       1,
162
    'dependencies':      [],
163
    'required_mappings': [{'identifier': 'mapping-a'}],
164
    'schema_ver':        '2',
165
    'include_in_query':  False
166
})
167

    
168
sample_resources_catalog = {}
169
sample_mappings_catalog = {}
170
sample_queries = {}
171

    
172
for srt in sample_resource_templates:
173
    resource = make_sample_resource()
174
    resource['identifier']       = f'resource-{srt["id_suffix"]}'
175
    resource['long_name']        = resource['identifier'].upper()
176
    resource['uuid']             = str(uuid4())
177
    resource['dependencies']     = srt['dependencies']
178
    resource['source_copyright'] = []
179
    resource['scripts']          = []
180
    for i in range(srt['files_count']):
181
        file_ref = {'file': f'file_{i}', 'sha256': sample_hashes[i]}
182
        resource[('source_copyright', 'scripts')[i & 1]].append(file_ref)
183

    
184
    resource_versions = [resource['version'], resource['version'].copy()]
185
    resource_versions[1][-1] += 1
186

    
187
    mapping = make_sample_mapping()
188
    mapping['identifier']          = f'mapping-{srt["id_suffix"]}'
189
    mapping['long_name']           = mapping['identifier'].upper()
190
    mapping['uuid']                = str(uuid4())
191
    mapping['source_copyright']    = resource['source_copyright']
192

    
193
    mapping_versions = [mapping['version'], mapping['version'].copy()]
194
    mapping_versions[1][-1] += 1
195

    
196
    sufs = [srt["id_suffix"], *[l for l in srt["id_suffix"] if l.isalpha()]]
197
    patterns = [f'https://example_{suf}.com/*' for suf in set(sufs)]
198
    mapping['payloads'] = {}
199

    
200
    for pat in patterns:
201
        mapping['payloads'][pat] = {'identifier': resource['identifier']}
202

    
203
        if not srt.get('include_in_query', True):
204
            continue
205

    
206
        sample_queries.setdefault(pat.replace('*', 'something'), []).append({
207
            'identifier': mapping['identifier'],
208
            'long_name':  mapping['long_name'],
209
            'version':    mapping_versions[1]
210
        })
211

    
212
    for item in resource, mapping:
213
        if 'required_mappings' in srt:
214
            item['required_mappings'] = srt['required_mappings']
215
        if 'schema_ver' in srt:
216
            item['$schema'] = item['$schema'].replace('1', srt['schema_ver'])
217

    
218
    for item, versions, catalog in [
219
        (resource, resource_versions, sample_resources_catalog),
220
        (mapping,  mapping_versions,  sample_mappings_catalog)
221
    ]:
222
        fmt = f'https://hydril.la/{item["type"]}/{item["identifier"]}%s'
223
        # Make 2 versions of each item so that we can test updates.
224
        for ver in versions:
225
            item['version'] = ver
226
            for fmt_arg in ('.json', '/' + item_version_string(item)):
227
                catalog[fmt % fmt_arg] = make_handler(json.dumps(item))
228

    
229
def serve_query(command, get_params, post_params):
230
    response = {
231
        '$schema': 'https://hydrilla.koszko.org/schemas/api_query_result-1.schema.json',
232
        'generated_by': {
233
            'name': 'human',
234
            'version': 'sapiens-0.8.15'
235
        },
236
        'mappings': sample_queries[get_params['url'][0]]
237
    }
238

    
239
    return (200, {}, json.dumps(response))
240

    
241
sample_queries_catalog = dict([(f'https://hydril.la/{suf}query', serve_query)
242
                               for suf in ('', '1/', '2/', '3/', '4/')])
243

    
244
catalog = {
245
    'http://gotmyowndoma.in':
246
    (302, {'location': 'http://gotmyowndoma.in/index.html'}, None),
247
    'http://gotmyowndoma.in/':
248
    (302, {'location': 'http://gotmyowndoma.in/index.html'}, None),
249
    'http://gotmyowndoma.in/index.html':
250
    (200, {}, here / 'data' / 'pages' / 'gotmyowndomain.html'),
251

    
252
    'https://gotmyowndoma.in':
253
    (302, {'location': 'https://gotmyowndoma.in/index.html'}, None),
254
    'https://gotmyowndoma.in/':
255
    (302, {'location': 'https://gotmyowndoma.in/index.html'}, None),
256
    'https://gotmyowndoma.in/index.html':
257
    (200, {}, here / 'data' / 'pages' / 'gotmyowndomain_https.html'),
258

    
259
    'https://gotmyowndoma.in/scripts_to_block_1.html':
260
    (200, {}, here / 'data' / 'pages' / 'scripts_to_block_1.html'),
261
    'https://gotmyowndoma.in/scripts_to_block_2.xml':
262
    (200, {}, here / 'data' / 'pages' / 'scripts_to_block_2.xml'),
263

    
264
    'https://anotherdoma.in/resource/blocked/by/CORS.json':
265
    lambda command, get_params, post_params: (200, {}, some_data),
266

    
267
    'https://counterdoma.in/': serve_counter,
268

    
269
    'https://serve.scrip.ts/': serve_script,
270

    
271
    'https://site.with.scripts.block.ed':
272
    (302, {'location': 'https://site.with.scripts.block.ed/index.html'}, None),
273
    'https://site.with.scripts.block.ed/':
274
    (302, {'location': 'https://site.with.scripts.block.ed/index.html'}, None),
275
    'https://site.with.scripts.block.ed/index.html':
276
    (200, {}, here / 'data' / 'pages' / 'gotmyowndomain_https.html'),
277

    
278
    'https://site.with.scripts.allow.ed':
279
    (302, {'location': 'https://site.with.scripts.allow.ed/index.html'}, None),
280
    'https://site.with.scripts.allow.ed/':
281
    (302, {'location': 'https://site.with.scripts.allow.ed/index.html'}, None),
282
    'https://site.with.scripts.allow.ed/index.html':
283
    (200, {}, here / 'data' / 'pages' / 'gotmyowndomain_https.html'),
284

    
285
    'https://site.with.paylo.ad':
286
    (302, {'location': 'https://site.with.paylo.ad/index.html'}, None),
287
    'https://site.with.paylo.ad/':
288
    (302, {'location': 'https://site.with.paylo.ad/index.html'}, None),
289
    'https://site.with.paylo.ad/index.html':
290
    (200, {}, here / 'data' / 'pages' / 'gotmyowndomain_https.html'),
291

    
292
    **sample_files_catalog,
293
    **sample_resources_catalog,
294
    **sample_mappings_catalog,
295
    **sample_queries_catalog
296
}
(11-11/11)