xref: /unit/test/test_static.py (revision 2616:ab2896c980ab)
1import os
2import socket
3from pathlib import Path
4
5import pytest
6
7from unit.applications.proto import ApplicationProto
8from unit.utils import waitforfiles
9
10
11client = ApplicationProto()
12
13
14@pytest.fixture(autouse=True)
15def setup_method_fixture(temp_dir):
16    assets_dir = f'{temp_dir}/assets'
17
18    Path(f'{assets_dir}/dir').mkdir(parents=True)
19    Path(f'{assets_dir}/index.html').write_text('0123456789', encoding='utf-8')
20    Path(f'{assets_dir}/README').write_text('readme', encoding='utf-8')
21    Path(f'{assets_dir}/log.log').write_text('[debug]', encoding='utf-8')
22    Path(f'{assets_dir}/dir/file').write_text('blah', encoding='utf-8')
23
24    assert 'success' in client.conf(
25        {
26            "listeners": {"*:8080": {"pass": "routes"}},
27            "routes": [{"action": {"share": f'{assets_dir}$uri'}}],
28            "settings": {
29                "http": {
30                    "static": {"mime_types": {"text/plain": [".log", "README"]}}
31                }
32            },
33        }
34    )
35
36
37def test_static_index(temp_dir):
38    def set_index(index):
39        assert 'success' in client.conf(
40            {"share": f'{temp_dir}/assets$uri', "index": index},
41            'routes/0/action',
42        ), 'configure index'
43
44    set_index('README')
45    assert client.get()['body'] == 'readme', 'index'
46
47    client.conf_delete('routes/0/action/index')
48    assert client.get()['body'] == '0123456789', 'delete index'
49
50    set_index('')
51    assert client.get()['status'] == 404, 'index empty'
52
53
54def test_static_index_default():
55    assert client.get(url='/index.html')['body'] == '0123456789', 'index'
56    assert client.get(url='/')['body'] == '0123456789', 'index 2'
57    assert client.get(url='//')['body'] == '0123456789', 'index 3'
58    assert client.get(url='/.')['body'] == '0123456789', 'index 4'
59    assert client.get(url='/./')['body'] == '0123456789', 'index 5'
60    assert client.get(url='/?blah')['body'] == '0123456789', 'index vars'
61    assert client.get(url='/#blah')['body'] == '0123456789', 'index anchor'
62    assert client.get(url='/dir/')['status'] == 404, 'index not found'
63
64    resp = client.get(url='/index.html/')
65    assert resp['status'] == 404, 'index not found 2 status'
66    assert (
67        resp['headers']['Content-Type'] == 'text/html'
68    ), 'index not found 2 Content-Type'
69
70
71def test_static_index_invalid(skip_alert, temp_dir):
72    skip_alert(r'failed to apply new conf')
73
74    def check_index(index):
75        assert 'error' in client.conf(
76            {"share": f'{temp_dir}/assets$uri', "index": index},
77            'routes/0/action',
78        )
79
80    check_index({})
81    check_index(['index.html', '$blah'])
82
83
84def test_static_large_file(temp_dir):
85    file_size = 32 * 1024 * 1024
86    with open(f'{temp_dir}/assets/large', 'wb') as f:
87        f.seek(file_size - 1)
88        f.write(b'\0')
89
90    assert (
91        len(client.get(url='/large', read_buffer_size=1024 * 1024)['body'])
92        == file_size
93    ), 'large file'
94
95
96def test_static_etag(temp_dir):
97    etag = client.get(url='/')['headers']['ETag']
98    etag_2 = client.get(url='/README')['headers']['ETag']
99
100    assert etag != etag_2, 'different ETag'
101    assert etag == client.get(url='/')['headers']['ETag'], 'same ETag'
102
103    with open(f'{temp_dir}/assets/index.html', 'w', encoding='utf-8') as f:
104        f.write('blah')
105
106    assert etag != client.get(url='/')['headers']['ETag'], 'new ETag'
107
108
109def test_static_redirect():
110    resp = client.get(url='/dir')
111    assert resp['status'] == 301, 'redirect status'
112    assert resp['headers']['Location'] == '/dir/', 'redirect Location'
113    assert 'Content-Type' not in resp['headers'], 'redirect Content-Type'
114
115
116def test_static_space_in_name(temp_dir):
117    assets_dir = f'{temp_dir}/assets'
118
119    Path(f'{assets_dir}/dir/file').rename(f'{assets_dir}/dir/fi le')
120
121    assert waitforfiles(f'{assets_dir}/dir/fi le')
122    assert client.get(url='/dir/fi le')['body'] == 'blah', 'file name'
123
124    Path(f'{assets_dir}/dir').rename(f'{assets_dir}/di r')
125    assert waitforfiles(f'{assets_dir}/di r/fi le')
126    assert client.get(url='/di r/fi le')['body'] == 'blah', 'dir name'
127
128    Path(f'{assets_dir}/di r').rename(f'{assets_dir}/ di r ')
129    assert waitforfiles(f'{assets_dir}/ di r /fi le')
130    assert (
131        client.get(url='/ di r /fi le')['body'] == 'blah'
132    ), 'dir name enclosing'
133
134    assert (
135        client.get(url='/%20di%20r%20/fi le')['body'] == 'blah'
136    ), 'dir encoded'
137    assert client.get(url='/ di r %2Ffi le')['body'] == 'blah', 'slash encoded'
138    assert client.get(url='/ di r /fi%20le')['body'] == 'blah', 'file encoded'
139    assert (
140        client.get(url='/%20di%20r%20%2Ffi%20le')['body'] == 'blah'
141    ), 'encoded'
142    assert (
143        client.get(url='/%20%64%69%20%72%20%2F%66%69%20%6C%65')['body']
144        == 'blah'
145    ), 'encoded 2'
146
147    Path(f'{assets_dir}/ di r /fi le').rename(f'{assets_dir}/ di r / fi le ')
148    assert waitforfiles(f'{assets_dir}/ di r / fi le ')
149    assert (
150        client.get(url='/%20di%20r%20/%20fi%20le%20')['body'] == 'blah'
151    ), 'file name enclosing'
152
153    try:
154        Path(f'{temp_dir}/ф а').touch()
155        utf8 = True
156
157    except KeyboardInterrupt:
158        raise
159
160    except:
161        utf8 = False
162
163    if utf8:
164        Path(f'{assets_dir}/ di r / fi le ').rename(
165            f'{assets_dir}/ di r /фа йл'
166        )
167        assert waitforfiles(f'{assets_dir}/ di r /фа йл')
168        assert client.get(url='/ di r /фа йл')['body'] == 'blah'
169
170        Path(f'{assets_dir}/ di r ').rename(f'{assets_dir}/ди ректория')
171        assert waitforfiles(f'{assets_dir}/ди ректория/фа йл')
172        assert (
173            client.get(url='/ди ректория/фа йл')['body'] == 'blah'
174        ), 'dir name 2'
175
176
177def test_static_unix_socket(temp_dir):
178    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
179    sock.bind(f'{temp_dir}/assets/unix_socket')
180
181    assert client.get(url='/unix_socket')['status'] == 404, 'socket'
182
183    sock.close()
184
185
186def test_static_unix_fifo(temp_dir):
187    os.mkfifo(f'{temp_dir}/assets/fifo')
188
189    assert client.get(url='/fifo')['status'] == 404, 'fifo'
190
191
192def test_static_method():
193    resp = client.head()
194    assert resp['status'] == 200, 'HEAD status'
195    assert resp['body'] == '', 'HEAD empty body'
196
197    assert client.delete()['status'] == 405, 'DELETE'
198    assert client.post()['status'] == 405, 'POST'
199    assert client.put()['status'] == 405, 'PUT'
200
201
202def test_static_path():
203    assert client.get(url='/dir/../dir/file')['status'] == 200, 'relative'
204
205    assert client.get(url='./')['status'] == 400, 'path invalid'
206    assert client.get(url='../')['status'] == 400, 'path invalid 2'
207    assert client.get(url='/..')['status'] == 400, 'path invalid 3'
208    assert client.get(url='../assets/')['status'] == 400, 'path invalid 4'
209    assert client.get(url='/../assets/')['status'] == 400, 'path invalid 5'
210
211
212def test_static_two_clients():
213    sock = client.get(no_recv=True)
214    sock2 = client.get(no_recv=True)
215
216    assert sock.recv(1) == b'H', 'client 1'
217    assert sock2.recv(1) == b'H', 'client 2'
218    assert sock.recv(1) == b'T', 'client 1 again'
219    assert sock2.recv(1) == b'T', 'client 2 again'
220
221    sock.close()
222    sock2.close()
223
224
225def test_static_mime_types():
226    assert 'success' in client.conf(
227        {
228            "text/x-code/x-blah/x-blah": "readme",
229            "text/plain": [".html", ".log", "file"],
230        },
231        'settings/http/static/mime_types',
232    ), 'configure mime_types'
233
234    assert (
235        client.get(url='/README')['headers']['Content-Type']
236        == 'text/x-code/x-blah/x-blah'
237    ), 'mime_types string case insensitive'
238    assert (
239        client.get(url='/index.html')['headers']['Content-Type'] == 'text/plain'
240    ), 'mime_types html'
241    assert (
242        client.get(url='/')['headers']['Content-Type'] == 'text/plain'
243    ), 'mime_types index default'
244    assert (
245        client.get(url='/dir/file')['headers']['Content-Type'] == 'text/plain'
246    ), 'mime_types file in dir'
247
248
249def test_static_mime_types_partial_match():
250    assert 'success' in client.conf(
251        {
252            "text/x-blah": ["ile", "fil", "f", "e", ".file"],
253        },
254        'settings/http/static/mime_types',
255    ), 'configure mime_types'
256    assert 'Content-Type' not in client.get(url='/dir/file'), 'partial match'
257
258
259def test_static_mime_types_reconfigure():
260    assert 'success' in client.conf(
261        {
262            "text/x-code": "readme",
263            "text/plain": [".html", ".log", "file"],
264        },
265        'settings/http/static/mime_types',
266    ), 'configure mime_types'
267
268    assert client.conf_get('settings/http/static/mime_types') == {
269        'text/x-code': 'readme',
270        'text/plain': ['.html', '.log', 'file'],
271    }, 'mime_types get'
272    assert (
273        client.conf_get('settings/http/static/mime_types/text%2Fx-code')
274        == 'readme'
275    ), 'mime_types get string'
276    assert client.conf_get('settings/http/static/mime_types/text%2Fplain') == [
277        '.html',
278        '.log',
279        'file',
280    ], 'mime_types get array'
281    assert (
282        client.conf_get('settings/http/static/mime_types/text%2Fplain/1')
283        == '.log'
284    ), 'mime_types get array element'
285
286    assert 'success' in client.conf_delete(
287        'settings/http/static/mime_types/text%2Fplain/2'
288    ), 'mime_types remove array element'
289    assert (
290        'Content-Type' not in client.get(url='/dir/file')['headers']
291    ), 'mime_types removed'
292
293    assert 'success' in client.conf_post(
294        '"file"', 'settings/http/static/mime_types/text%2Fplain'
295    ), 'mime_types add array element'
296    assert (
297        client.get(url='/dir/file')['headers']['Content-Type'] == 'text/plain'
298    ), 'mime_types reverted'
299
300    assert 'success' in client.conf(
301        '"file"', 'settings/http/static/mime_types/text%2Fplain'
302    ), 'configure mime_types update'
303    assert (
304        client.get(url='/dir/file')['headers']['Content-Type'] == 'text/plain'
305    ), 'mime_types updated'
306    assert (
307        'Content-Type' not in client.get(url='/log.log')['headers']
308    ), 'mime_types updated 2'
309
310    assert 'success' in client.conf(
311        '".log"', 'settings/http/static/mime_types/text%2Fblahblahblah'
312    ), 'configure mime_types create'
313    assert (
314        client.get(url='/log.log')['headers']['Content-Type']
315        == 'text/blahblahblah'
316    ), 'mime_types create'
317
318
319def test_static_mime_types_correct():
320    assert 'error' in client.conf(
321        {"text/x-code": "readme", "text/plain": "readme"},
322        'settings/http/static/mime_types',
323    ), 'mime_types same extensions'
324    assert 'error' in client.conf(
325        {"text/x-code": [".h", ".c"], "text/plain": ".c"},
326        'settings/http/static/mime_types',
327    ), 'mime_types same extensions array'
328    assert 'error' in client.conf(
329        {
330            "text/x-code": [".h", ".c", "readme"],
331            "text/plain": "README",
332        },
333        'settings/http/static/mime_types',
334    ), 'mime_types same extensions case insensitive'
335
336
337@pytest.mark.skip('not yet')
338def test_static_mime_types_invalid(temp_dir):
339    assert 'error' in client.http(
340        b"""PUT /config/settings/http/static/mime_types/%0%00% HTTP/1.1\r
341Host: localhost\r
342Connection: close\r
343Content-Length: 6\r
344\r
345\"blah\"""",
346        raw_resp=True,
347        raw=True,
348        sock_type='unix',
349        addr=f'{temp_dir}/control.unit.sock',
350    ), 'mime_types invalid'
351