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