xref: /unit/test/test_http_header.py (revision 2073:bc6ad31ce286)
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(headers={'Host': 'exmaple.com:', 'Connection': 'close'})
329
330        assert resp['status'] == 200, 'Host port empty status'
331        assert (
332            resp['headers']['X-Server-Name'] == 'exmaple.com'
333        ), 'Host port empty SERVER_NAME'
334        assert (
335            resp['headers']['X-Http-Host'] == 'exmaple.com:'
336        ), 'Host port empty HTTP_HOST'
337
338    def test_http_header_host_literal(self):
339        self.load('host')
340
341        resp = self.get(headers={'Host': '127.0.0.1', 'Connection': 'close'})
342
343        assert resp['status'] == 200, 'Host literal status'
344        assert (
345            resp['headers']['X-Server-Name'] == '127.0.0.1'
346        ), 'Host literal SERVER_NAME'
347
348    def test_http_header_host_literal_ipv6(self):
349        self.load('host')
350
351        resp = self.get(headers={'Host': '[::1]:7080', 'Connection': 'close'})
352
353        assert resp['status'] == 200, 'Host literal ipv6 status'
354        assert (
355            resp['headers']['X-Server-Name'] == '[::1]'
356        ), 'Host literal ipv6 SERVER_NAME'
357        assert (
358            resp['headers']['X-Http-Host'] == '[::1]:7080'
359        ), 'Host literal ipv6 HTTP_HOST'
360
361    def test_http_header_host_trailing_period(self):
362        self.load('host')
363
364        resp = self.get(headers={'Host': '127.0.0.1.', 'Connection': 'close'})
365
366        assert resp['status'] == 200, 'Host trailing period status'
367        assert (
368            resp['headers']['X-Server-Name'] == '127.0.0.1'
369        ), 'Host trailing period SERVER_NAME'
370        assert (
371            resp['headers']['X-Http-Host'] == '127.0.0.1.'
372        ), 'Host trailing period HTTP_HOST'
373
374    def test_http_header_host_trailing_period_2(self):
375        self.load('host')
376
377        resp = self.get(headers={'Host': 'EXAMPLE.COM.', 'Connection': 'close'})
378
379        assert resp['status'] == 200, 'Host trailing period 2 status'
380        assert (
381            resp['headers']['X-Server-Name'] == 'example.com'
382        ), 'Host trailing period 2 SERVER_NAME'
383        assert (
384            resp['headers']['X-Http-Host'] == 'EXAMPLE.COM.'
385        ), 'Host trailing period 2 HTTP_HOST'
386
387    def test_http_header_host_case_insensitive(self):
388        self.load('host')
389
390        resp = self.get(headers={'Host': 'EXAMPLE.COM', 'Connection': 'close'})
391
392        assert resp['status'] == 200, 'Host case insensitive'
393        assert (
394            resp['headers']['X-Server-Name'] == 'example.com'
395        ), 'Host case insensitive SERVER_NAME'
396
397    def test_http_header_host_double_dot(self):
398        self.load('empty')
399
400        assert (
401            self.get(headers={'Host': '127.0.0..1', 'Connection': 'close'})[
402                'status'
403            ]
404            == 400
405        ), 'Host double dot'
406
407    def test_http_header_host_slash(self):
408        self.load('empty')
409
410        assert (
411            self.get(headers={'Host': '/localhost', 'Connection': 'close'})[
412                'status'
413            ]
414            == 400
415        ), 'Host slash'
416
417    def test_http_header_host_multiple_fields(self):
418        self.load('empty')
419
420        assert (
421            self.get(
422                headers={
423                    'Host': ['localhost', 'example.com'],
424                    'Connection': 'close',
425                }
426            )['status']
427            == 400
428        ), 'Host multiple fields'
429
430    def test_http_discard_unsafe_fields(self):
431        self.load('header_fields')
432
433        def check_status(header):
434            resp = self.get(
435                headers={
436                    'Host': 'localhost',
437                    header: 'blah',
438                    'Connection': 'close',
439                }
440            )
441
442            assert resp['status'] == 200
443            return resp
444
445        resp = check_status("!Custom-Header")
446        assert 'CUSTOM' not in resp['headers']['All-Headers']
447
448        resp = check_status("Custom_Header")
449        assert 'CUSTOM' not in resp['headers']['All-Headers']
450
451        assert 'success' in self.conf(
452            {'http': {'discard_unsafe_fields': False}},
453            'settings',
454        )
455
456        resp = check_status("!#$%&'*+.^`|~Custom_Header")
457        assert 'CUSTOM' in resp['headers']['All-Headers']
458
459        assert 'success' in self.conf(
460            {'http': {'discard_unsafe_fields': True}},
461            'settings',
462        )
463
464        resp = check_status("!Custom-Header")
465        assert 'CUSTOM' not in resp['headers']['All-Headers']
466
467        resp = check_status("Custom_Header")
468        assert 'CUSTOM' not in resp['headers']['All-Headers']
469