xref: /unit/test/test_http_header.py (revision 1848:4bd548074e2c)
1import pytest
2
3from unit.applications.lang.python import TestApplicationPython
4
5
6class TestHTTPHeader(TestApplicationPython):
7    prerequisites = {'modules': {'python': 'any'}}
8
9    def test_http_header_value_leading_sp(self):
10        self.load('custom_header')
11
12        resp = self.get(
13            headers={
14                'Host': 'localhost',
15                'Custom-Header': ' ,',
16                'Connection': 'close',
17            }
18        )
19
20        assert resp['status'] == 200, 'value leading sp status'
21        assert (
22            resp['headers']['Custom-Header'] == ','
23        ), 'value leading sp custom header'
24
25    def test_http_header_value_leading_htab(self):
26        self.load('custom_header')
27
28        resp = self.get(
29            headers={
30                'Host': 'localhost',
31                'Custom-Header': '\t,',
32                'Connection': 'close',
33            }
34        )
35
36        assert resp['status'] == 200, 'value leading htab status'
37        assert (
38            resp['headers']['Custom-Header'] == ','
39        ), 'value leading htab custom header'
40
41    def test_http_header_value_trailing_sp(self):
42        self.load('custom_header')
43
44        resp = self.get(
45            headers={
46                'Host': 'localhost',
47                'Custom-Header': ', ',
48                'Connection': 'close',
49            }
50        )
51
52        assert resp['status'] == 200, 'value trailing sp status'
53        assert (
54            resp['headers']['Custom-Header'] == ','
55        ), 'value trailing sp custom header'
56
57    def test_http_header_value_trailing_htab(self):
58        self.load('custom_header')
59
60        resp = self.get(
61            headers={
62                'Host': 'localhost',
63                'Custom-Header': ',\t',
64                'Connection': 'close',
65            }
66        )
67
68        assert resp['status'] == 200, 'value trailing htab status'
69        assert (
70            resp['headers']['Custom-Header'] == ','
71        ), 'value trailing htab custom header'
72
73    def test_http_header_value_both_sp(self):
74        self.load('custom_header')
75
76        resp = self.get(
77            headers={
78                'Host': 'localhost',
79                'Custom-Header': ' , ',
80                'Connection': 'close',
81            }
82        )
83
84        assert resp['status'] == 200, 'value both sp status'
85        assert (
86            resp['headers']['Custom-Header'] == ','
87        ), 'value both sp custom header'
88
89    def test_http_header_value_both_htab(self):
90        self.load('custom_header')
91
92        resp = self.get(
93            headers={
94                'Host': 'localhost',
95                'Custom-Header': '\t,\t',
96                'Connection': 'close',
97            }
98        )
99
100        assert resp['status'] == 200, 'value both htab status'
101        assert (
102            resp['headers']['Custom-Header'] == ','
103        ), 'value both htab custom header'
104
105    def test_http_header_value_chars(self):
106        self.load('custom_header')
107
108        resp = self.get(
109            headers={
110                'Host': 'localhost',
111                'Custom-Header': r'(),/:;<=>?@[\]{}\t !#$%&\'*+-.^_`|~',
112                'Connection': 'close',
113            }
114        )
115
116        assert resp['status'] == 200, 'value chars status'
117        assert (
118            resp['headers']['Custom-Header']
119            == r'(),/:;<=>?@[\]{}\t !#$%&\'*+-.^_`|~'
120        ), 'value chars custom header'
121
122    def test_http_header_value_chars_edge(self):
123        self.load('custom_header')
124
125        resp = self.http(
126            b"""GET / HTTP/1.1
127Host: localhost
128Custom-Header: \x20\xFF
129Connection: close
130
131""",
132            raw=True,
133            encoding='latin1',
134        )
135
136        assert resp['status'] == 200, 'value chars edge status'
137        assert resp['headers']['Custom-Header'] == '\xFF', 'value chars edge'
138
139    def test_http_header_value_chars_below(self):
140        self.load('custom_header')
141
142        resp = self.http(
143            b"""GET / HTTP/1.1
144Host: localhost
145Custom-Header: \x1F
146Connection: close
147
148""",
149            raw=True,
150        )
151
152        assert resp['status'] == 400, 'value chars below'
153
154    def test_http_header_field_leading_sp(self):
155        self.load('empty')
156
157        assert (
158            self.get(
159                headers={
160                    'Host': 'localhost',
161                    ' Custom-Header': 'blah',
162                    'Connection': 'close',
163                }
164            )['status']
165            == 400
166        ), 'field leading sp'
167
168    def test_http_header_field_leading_htab(self):
169        self.load('empty')
170
171        assert (
172            self.get(
173                headers={
174                    'Host': 'localhost',
175                    '\tCustom-Header': 'blah',
176                    'Connection': 'close',
177                }
178            )['status']
179            == 400
180        ), 'field leading htab'
181
182    def test_http_header_field_trailing_sp(self):
183        self.load('empty')
184
185        assert (
186            self.get(
187                headers={
188                    'Host': 'localhost',
189                    'Custom-Header ': 'blah',
190                    'Connection': 'close',
191                }
192            )['status']
193            == 400
194        ), 'field trailing sp'
195
196    def test_http_header_field_trailing_htab(self):
197        self.load('empty')
198
199        assert (
200            self.get(
201                headers={
202                    'Host': 'localhost',
203                    'Custom-Header\t': 'blah',
204                    'Connection': 'close',
205                }
206            )['status']
207            == 400
208        ), 'field trailing htab'
209
210    def test_http_header_content_length_big(self):
211        self.load('empty')
212
213        assert (
214            self.post(
215                headers={
216                    'Host': 'localhost',
217                    'Content-Length': str(2 ** 64),
218                    'Connection': 'close',
219                },
220                body='X' * 1000,
221            )['status']
222            == 400
223        ), 'Content-Length big'
224
225    def test_http_header_content_length_negative(self):
226        self.load('empty')
227
228        assert (
229            self.post(
230                headers={
231                    'Host': 'localhost',
232                    'Content-Length': '-100',
233                    'Connection': 'close',
234                },
235                body='X' * 1000,
236            )['status']
237            == 400
238        ), 'Content-Length negative'
239
240    def test_http_header_content_length_text(self):
241        self.load('empty')
242
243        assert (
244            self.post(
245                headers={
246                    'Host': 'localhost',
247                    'Content-Length': 'blah',
248                    'Connection': 'close',
249                },
250                body='X' * 1000,
251            )['status']
252            == 400
253        ), 'Content-Length text'
254
255    def test_http_header_content_length_multiple_values(self):
256        self.load('empty')
257
258        assert (
259            self.post(
260                headers={
261                    'Host': 'localhost',
262                    'Content-Length': '41, 42',
263                    'Connection': 'close',
264                },
265                body='X' * 1000,
266            )['status']
267            == 400
268        ), 'Content-Length multiple value'
269
270    def test_http_header_content_length_multiple_fields(self):
271        self.load('empty')
272
273        assert (
274            self.post(
275                headers={
276                    'Host': 'localhost',
277                    'Content-Length': ['41', '42'],
278                    'Connection': 'close',
279                },
280                body='X' * 1000,
281            )['status']
282            == 400
283        ), 'Content-Length multiple fields'
284
285    @pytest.mark.skip('not yet')
286    def test_http_header_host_absent(self):
287        self.load('host')
288
289        resp = self.get(headers={'Connection': 'close'})
290
291        assert resp['status'] == 400, 'Host absent status'
292
293    def test_http_header_host_empty(self):
294        self.load('host')
295
296        resp = self.get(headers={'Host': '', 'Connection': 'close'})
297
298        assert resp['status'] == 200, 'Host empty status'
299        assert resp['headers']['X-Server-Name'] != '', 'Host empty SERVER_NAME'
300
301    def test_http_header_host_big(self):
302        self.load('empty')
303
304        assert (
305            self.get(headers={'Host': 'X' * 10000, 'Connection': 'close'})[
306                'status'
307            ]
308            == 431
309        ), 'Host big'
310
311    def test_http_header_host_port(self):
312        self.load('host')
313
314        resp = self.get(
315            headers={'Host': 'exmaple.com:7080', 'Connection': 'close'}
316        )
317
318        assert resp['status'] == 200, 'Host port status'
319        assert (
320            resp['headers']['X-Server-Name'] == 'exmaple.com'
321        ), 'Host port SERVER_NAME'
322        assert (
323            resp['headers']['X-Http-Host'] == 'exmaple.com:7080'
324        ), 'Host port HTTP_HOST'
325
326    def test_http_header_host_port_empty(self):
327        self.load('host')
328
329        resp = self.get(
330            headers={'Host': 'exmaple.com:', 'Connection': 'close'}
331        )
332
333        assert resp['status'] == 200, 'Host port empty status'
334        assert (
335            resp['headers']['X-Server-Name'] == 'exmaple.com'
336        ), 'Host port empty SERVER_NAME'
337        assert (
338            resp['headers']['X-Http-Host'] == 'exmaple.com:'
339        ), 'Host port empty HTTP_HOST'
340
341    def test_http_header_host_literal(self):
342        self.load('host')
343
344        resp = self.get(headers={'Host': '127.0.0.1', 'Connection': 'close'})
345
346        assert resp['status'] == 200, 'Host literal status'
347        assert (
348            resp['headers']['X-Server-Name'] == '127.0.0.1'
349        ), 'Host literal SERVER_NAME'
350
351    def test_http_header_host_literal_ipv6(self):
352        self.load('host')
353
354        resp = self.get(headers={'Host': '[::1]:7080', 'Connection': 'close'})
355
356        assert resp['status'] == 200, 'Host literal ipv6 status'
357        assert (
358            resp['headers']['X-Server-Name'] == '[::1]'
359        ), 'Host literal ipv6 SERVER_NAME'
360        assert (
361            resp['headers']['X-Http-Host'] == '[::1]:7080'
362        ), 'Host literal ipv6 HTTP_HOST'
363
364    def test_http_header_host_trailing_period(self):
365        self.load('host')
366
367        resp = self.get(headers={'Host': '127.0.0.1.', 'Connection': 'close'})
368
369        assert resp['status'] == 200, 'Host trailing period status'
370        assert (
371            resp['headers']['X-Server-Name'] == '127.0.0.1'
372        ), 'Host trailing period SERVER_NAME'
373        assert (
374            resp['headers']['X-Http-Host'] == '127.0.0.1.'
375        ), 'Host trailing period HTTP_HOST'
376
377    def test_http_header_host_trailing_period_2(self):
378        self.load('host')
379
380        resp = self.get(
381            headers={'Host': 'EXAMPLE.COM.', 'Connection': 'close'}
382        )
383
384        assert resp['status'] == 200, 'Host trailing period 2 status'
385        assert (
386            resp['headers']['X-Server-Name'] == 'example.com'
387        ), 'Host trailing period 2 SERVER_NAME'
388        assert (
389            resp['headers']['X-Http-Host'] == 'EXAMPLE.COM.'
390        ), 'Host trailing period 2 HTTP_HOST'
391
392    def test_http_header_host_case_insensitive(self):
393        self.load('host')
394
395        resp = self.get(headers={'Host': 'EXAMPLE.COM', 'Connection': 'close'})
396
397        assert resp['status'] == 200, 'Host case insensitive'
398        assert (
399            resp['headers']['X-Server-Name'] == 'example.com'
400        ), 'Host case insensitive SERVER_NAME'
401
402    def test_http_header_host_double_dot(self):
403        self.load('empty')
404
405        assert (
406            self.get(headers={'Host': '127.0.0..1', 'Connection': 'close'})[
407                'status'
408            ]
409            == 400
410        ), 'Host double dot'
411
412    def test_http_header_host_slash(self):
413        self.load('empty')
414
415        assert (
416            self.get(headers={'Host': '/localhost', 'Connection': 'close'})[
417                'status'
418            ]
419            == 400
420        ), 'Host slash'
421
422    def test_http_header_host_multiple_fields(self):
423        self.load('empty')
424
425        assert (
426            self.get(
427                headers={
428                    'Host': ['localhost', 'example.com'],
429                    'Connection': 'close',
430                }
431            )['status']
432            == 400
433        ), 'Host multiple fields'
434
435    def test_http_discard_unsafe_fields(self):
436        self.load('header_fields')
437
438        def check_status(header):
439            resp = self.get(
440                headers={
441                    'Host': 'localhost',
442                    header: 'blah',
443                    'Connection': 'close',
444                }
445            )
446
447            assert resp['status'] == 200
448            return resp
449
450        resp = check_status("!Custom-Header")
451        assert 'CUSTOM' not in resp['headers']['All-Headers']
452
453        resp = check_status("Custom_Header")
454        assert 'CUSTOM' not in resp['headers']['All-Headers']
455
456        assert 'success' in self.conf(
457            {'http': {'discard_unsafe_fields': False}}, 'settings',
458        )
459
460        resp = check_status("!#$%&'*+.^`|~Custom_Header")
461        assert 'CUSTOM' in resp['headers']['All-Headers']
462
463        assert 'success' in self.conf(
464            {'http': {'discard_unsafe_fields': True}}, 'settings',
465        )
466
467        resp = check_status("!Custom-Header")
468        assert 'CUSTOM' not in resp['headers']['All-Headers']
469
470        resp = check_status("Custom_Header")
471        assert 'CUSTOM' not in resp['headers']['All-Headers']
472