xref: /unit/test/test_return.py (revision 2616:ab2896c980ab)
1import re
2
3import pytest
4
5from unit.applications.proto import ApplicationProto
6
7client = ApplicationProto()
8
9
10@pytest.fixture(autouse=True)
11def setup_method_fixture():
12    assert 'success' in client.conf(
13        {
14            "listeners": {"*:8080": {"pass": "routes"}},
15            "routes": [{"action": {"return": 200}}],
16            "applications": {},
17        }
18    )
19
20
21def get_resps_sc(req=10):
22    to_send = b"""GET / HTTP/1.1
23Host: localhost
24
25""" * (
26        req - 1
27    )
28
29    to_send += b"""GET / HTTP/1.1
30Host: localhost
31Connection: close
32
33"""
34
35    return client.http(to_send, raw_resp=True, raw=True)
36
37
38def test_return():
39    resp = client.get()
40    assert resp['status'] == 200
41    assert 'Server' in resp['headers']
42    assert 'Date' in resp['headers']
43    assert resp['headers']['Content-Length'] == '0'
44    assert resp['headers']['Connection'] == 'close'
45    assert resp['body'] == '', 'body'
46
47    resp = client.post(body='blah')
48    assert resp['status'] == 200
49    assert resp['body'] == '', 'body'
50
51    resp = get_resps_sc()
52    assert len(re.findall('200 OK', resp)) == 10
53    assert len(re.findall('Connection:', resp)) == 1
54    assert len(re.findall('Connection: close', resp)) == 1
55
56    resp = client.get(http_10=True)
57    assert resp['status'] == 200
58    assert 'Server' in resp['headers']
59    assert 'Date' in resp['headers']
60    assert resp['headers']['Content-Length'] == '0'
61    assert 'Connection' not in resp['headers']
62    assert resp['body'] == '', 'body'
63
64
65def test_return_update():
66    assert 'success' in client.conf('0', 'routes/0/action/return')
67
68    resp = client.get()
69    assert resp['status'] == 0
70    assert resp['body'] == ''
71
72    assert 'success' in client.conf('404', 'routes/0/action/return')
73
74    resp = client.get()
75    assert resp['status'] == 404
76    assert resp['body'] != ''
77
78    assert 'success' in client.conf('598', 'routes/0/action/return')
79
80    resp = client.get()
81    assert resp['status'] == 598
82    assert resp['body'] != ''
83
84    assert 'success' in client.conf('999', 'routes/0/action/return')
85
86    resp = client.get()
87    assert resp['status'] == 999
88    assert resp['body'] == ''
89
90
91def test_return_location():
92    reserved = ":/?#[]@!&'()*+,;="
93    unreserved = (
94        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
95    )
96    unsafe = " \"%<>\\^`{|}"
97    unsafe_enc = "%20%22%25%3C%3E%5C%5E%60%7B%7C%7D"
98
99    def check_location(location, expect=None):
100        if expect is None:
101            expect = location
102
103        assert 'success' in client.conf(
104            {"return": 301, "location": location}, 'routes/0/action'
105        ), 'configure location'
106
107        assert client.get()['headers']['Location'] == expect
108
109    # FAIL: can't specify empty header value.
110    # check_location("")
111
112    check_location(reserved)
113
114    # After first "?" all other "?" encoded.
115    check_location(f'/?{reserved}', "/?:/%3F#[]@!&'()*+,;=")
116    check_location("???", "?%3F%3F")
117
118    # After first "#" all other "?" or "#" encoded.
119    check_location(f'/#{reserved}', "/#:/%3F%23[]@!&'()*+,;=")
120    check_location("##?#?", "#%23%3F%23%3F")
121
122    # After first "?" next "#" not encoded.
123    check_location(f'/?#{reserved}', "/?#:/%3F%23[]@!&'()*+,;=")
124    check_location("??##", "?%3F#%23")
125    check_location("/?##?", "/?#%23%3F")
126
127    # Unreserved never encoded.
128    check_location(unreserved)
129    check_location(f'/{unreserved}?{unreserved}#{unreserved}')
130
131    # Unsafe always encoded.
132    check_location(unsafe, unsafe_enc)
133    check_location(f'?{unsafe}', f'?{unsafe_enc}')
134    check_location(f'#{unsafe}', f'#{unsafe_enc}')
135
136    # %00-%20 and %7F-%FF always encoded.
137    check_location("\u0000\u0018\u001F\u0020\u0021", "%00%18%1F%20!")
138    check_location("\u007F\u0080н\u20BD", "%7F%C2%80%D0%BD%E2%82%BD")
139
140    # Encoded string detection.  If at least one char need to be encoded
141    # then whole string will be encoded.
142    check_location("%20")
143    check_location("/%20?%20#%20")
144    check_location(" %20", "%20%2520")
145    check_location("%20 ", "%2520%20")
146    check_location("/%20?%20#%20 ", "/%2520?%2520#%2520%20")
147
148
149def test_return_location_edit():
150    assert 'success' in client.conf(
151        {"return": 302, "location": "blah"}, 'routes/0/action'
152    ), 'configure init location'
153    assert client.get()['headers']['Location'] == 'blah'
154
155    assert 'success' in client.conf_delete(
156        'routes/0/action/location'
157    ), 'location delete'
158    assert 'Location' not in client.get()['headers']
159
160    assert 'success' in client.conf(
161        '"blah"', 'routes/0/action/location'
162    ), 'location restore'
163    assert client.get()['headers']['Location'] == 'blah'
164
165    assert 'error' in client.conf_post(
166        '"blah"', 'routes/0/action/location'
167    ), 'location method not allowed'
168    assert client.get()['headers']['Location'] == 'blah'
169
170    assert 'success' in client.conf(
171        '"https://${host}${uri}"', 'routes/0/action/location'
172    ), 'location with variables'
173    assert client.get()['headers']['Location'] == 'https://localhost/'
174
175    assert 'success' in client.conf(
176        '"/#$host"', 'routes/0/action/location'
177    ), 'location with encoding and a variable'
178    assert client.get()['headers']['Location'] == '/#localhost'
179
180    assert (
181        client.get(headers={"Host": "#foo?bar", "Connection": "close"})[
182            'headers'
183        ]['Location']
184        == "/#%23foo%3Fbar"
185    ), 'location with a variable with encoding'
186
187    assert 'success' in client.conf(
188        '""', 'routes/0/action/location'
189    ), 'location empty'
190    assert client.get()['headers']['Location'] == ''
191
192    assert 'success' in client.conf(
193        '"${host}"', 'routes/0/action/location'
194    ), 'location empty with variable'
195    assert (
196        client.get(headers={"Host": "", "Connection": "close"})['headers'][
197            'Location'
198        ]
199        == ""
200    ), 'location with empty variable'
201
202
203def test_return_invalid():
204    def check_error(conf):
205        assert 'error' in client.conf(conf, 'routes/0/action')
206
207    check_error({"return": "200"})
208    check_error({"return": []})
209    check_error({"return": 80.1})
210    check_error({"return": 1000})
211    check_error({"return": -1})
212    check_error({"return": 200, "share": "/blah"})
213    check_error({"return": 200, "location": "$hos"})
214    check_error({"return": 200, "location": "$hostblah"})
215
216    assert 'error' in client.conf(
217        '001', 'routes/0/action/return'
218    ), 'leading zero'
219
220    check_error({"return": 301, "location": 0})
221    check_error({"return": 301, "location": []})
222