xref: /unit/test/test_routing.py (revision 1597:be1483ecf2a0)
1# -*- coding: utf-8 -*-
2import pytest
3
4from unit.applications.proto import TestApplicationProto
5from conftest import option, skip_alert
6
7
8class TestRouting(TestApplicationProto):
9    prerequisites = {'modules': {'python': 'any'}}
10
11    def setup_method(self):
12        super().setup_method()
13
14        assert 'success' in self.conf(
15            {
16                "listeners": {"*:7080": {"pass": "routes"}},
17                "routes": [
18                    {"match": {"method": "GET"}, "action": {"return": 200},}
19                ],
20                "applications": {},
21            }
22        ), 'routing configure'
23
24    def route(self, route):
25        return self.conf([route], 'routes')
26
27    def route_match(self, match):
28        assert 'success' in self.route(
29            {"match": match, "action": {"return": 200}}
30        ), 'route match configure'
31
32    def route_match_invalid(self, match):
33        assert 'error' in self.route(
34            {"match": match, "action": {"return": 200}}
35        ), 'route match configure invalid'
36
37    def host(self, host, status):
38        assert (
39            self.get(headers={'Host': host, 'Connection': 'close'})['status']
40            == status
41        ), 'match host'
42
43    def cookie(self, cookie, status):
44        assert (
45            self.get(
46                headers={
47                    'Host': 'localhost',
48                    'Cookie': cookie,
49                    'Connection': 'close',
50                },
51            )['status']
52            == status
53        ), 'match cookie'
54
55    def test_routes_match_method_positive(self):
56        assert self.get()['status'] == 200, 'GET'
57        assert self.post()['status'] == 404, 'POST'
58
59    def test_routes_match_method_positive_many(self):
60        self.route_match({"method": ["GET", "POST"]})
61
62        assert self.get()['status'] == 200, 'GET'
63        assert self.post()['status'] == 200, 'POST'
64        assert self.delete()['status'] == 404, 'DELETE'
65
66    def test_routes_match_method_negative(self):
67        self.route_match({"method": "!GET"})
68
69        assert self.get()['status'] == 404, 'GET'
70        assert self.post()['status'] == 200, 'POST'
71
72    def test_routes_match_method_negative_many(self):
73        self.route_match({"method": ["!GET", "!POST"]})
74
75        assert self.get()['status'] == 404, 'GET'
76        assert self.post()['status'] == 404, 'POST'
77        assert self.delete()['status'] == 200, 'DELETE'
78
79    def test_routes_match_method_wildcard_left(self):
80        self.route_match({"method": "*ET"})
81
82        assert self.get()['status'] == 200, 'GET'
83        assert self.post()['status'] == 404, 'POST'
84
85    def test_routes_match_method_wildcard_right(self):
86        self.route_match({"method": "GE*"})
87
88        assert self.get()['status'] == 200, 'GET'
89        assert self.post()['status'] == 404, 'POST'
90
91    def test_routes_match_method_wildcard_left_right(self):
92        self.route_match({"method": "*GET*"})
93
94        assert self.get()['status'] == 200, 'GET'
95        assert self.post()['status'] == 404, 'POST'
96
97    def test_routes_match_method_wildcard(self):
98        self.route_match({"method": "*"})
99
100        assert self.get()['status'] == 200, 'GET'
101
102    def test_routes_match_invalid(self):
103        self.route_match_invalid({"method": "**"})
104
105    def test_routes_match_valid(self):
106        self.route_match({"method": "blah*"})
107        self.route_match({"host": "*blah*blah"})
108        self.route_match({"host": "blah*blah*blah"})
109        self.route_match({"host": "blah*blah*"})
110
111    def test_routes_match_empty_exact(self):
112        self.route_match({"uri": ""})
113        assert self.get()['status'] == 404
114
115        self.route_match({"uri": "/"})
116        assert self.get()['status'] == 200
117        assert self.get(url='/blah')['status'] == 404
118
119    def test_routes_match_negative(self):
120        self.route_match({"uri": "!"})
121        assert self.get()['status'] == 404
122
123        self.route_match({"uri": "!/"})
124        assert self.get()['status'] == 404
125        assert self.get(url='/blah')['status'] == 200
126
127        self.route_match({"uri": "!*blah"})
128        assert self.get()['status'] == 200
129        assert self.get(url='/bla')['status'] == 200
130        assert self.get(url='/blah')['status'] == 404
131        assert self.get(url='/blah1')['status'] == 200
132
133        self.route_match({"uri": "!/blah*1*"})
134        assert self.get()['status'] == 200
135        assert self.get(url='/blah')['status'] == 200
136        assert self.get(url='/blah1')['status'] == 404
137        assert self.get(url='/blah12')['status'] == 404
138        assert self.get(url='/blah2')['status'] == 200
139
140    def test_routes_match_wildcard_middle(self):
141        self.route_match({"host": "ex*le"})
142
143        self.host('example', 200)
144        self.host('www.example', 404)
145        self.host('example.com', 404)
146        self.host('exampl', 404)
147
148    def test_routes_match_method_case_insensitive(self):
149        self.route_match({"method": "get"})
150
151        assert self.get()['status'] == 200, 'GET'
152
153    def test_routes_match_wildcard_left_case_insensitive(self):
154        self.route_match({"method": "*get"})
155        assert self.get()['status'] == 200, 'GET'
156
157        self.route_match({"method": "*et"})
158        assert self.get()['status'] == 200, 'GET'
159
160    def test_routes_match_wildcard_middle_case_insensitive(self):
161        self.route_match({"method": "g*t"})
162
163        assert self.get()['status'] == 200, 'GET'
164
165    def test_routes_match_wildcard_right_case_insensitive(self):
166        self.route_match({"method": "get*"})
167        assert self.get()['status'] == 200, 'GET'
168
169        self.route_match({"method": "ge*"})
170        assert self.get()['status'] == 200, 'GET'
171
172    def test_routes_match_wildcard_substring_case_insensitive(self):
173        self.route_match({"method": "*et*"})
174
175        assert self.get()['status'] == 200, 'GET'
176
177    def test_routes_match_wildcard_left_case_sensitive(self):
178        self.route_match({"uri": "*blah"})
179
180        assert self.get(url='/blah')['status'] == 200, '/blah'
181        assert self.get(url='/BLAH')['status'] == 404, '/BLAH'
182
183    def test_routes_match_wildcard_middle_case_sensitive(self):
184        self.route_match({"uri": "/b*h"})
185
186        assert self.get(url='/blah')['status'] == 200, '/blah'
187        assert self.get(url='/BLAH')['status'] == 404, '/BLAH'
188
189    def test_route_match_wildcards_ordered(self):
190        self.route_match({"uri": "/a*x*y*"})
191
192        assert self.get(url='/axy')['status'] == 200, '/axy'
193        assert self.get(url='/ayx')['status'] == 404, '/ayx'
194
195    def test_route_match_wildcards_adjust_start(self):
196        self.route_match({"uri": "/bla*bla*"})
197
198        assert self.get(url='/bla_foo')['status'] == 404, '/bla_foo'
199
200    def test_route_match_wildcards_adjust_start_substr(self):
201        self.route_match({"uri": "*bla*bla*"})
202
203        assert self.get(url='/bla_foo')['status'] == 404, '/bla_foo'
204
205    def test_route_match_wildcards_adjust_end(self):
206        self.route_match({"uri": "/bla*bla"})
207
208        assert self.get(url='/foo_bla')['status'] == 404, '/foo_bla'
209
210    def test_routes_match_wildcard_right_case_sensitive(self):
211        self.route_match({"uri": "/bla*"})
212
213        assert self.get(url='/blah')['status'] == 200, '/blah'
214        assert self.get(url='/BLAH')['status'] == 404, '/BLAH'
215
216    def test_routes_match_wildcard_substring_case_sensitive(self):
217        self.route_match({"uri": "*bla*"})
218
219        assert self.get(url='/blah')['status'] == 200, '/blah'
220        assert self.get(url='/BLAH')['status'] == 404, '/BLAH'
221
222    def test_routes_match_many_wildcard_substrings_case_sensitive(self):
223        self.route_match({"uri": "*a*B*c*"})
224
225        assert self.get(url='/blah-a-B-c-blah')['status'] == 200
226        assert self.get(url='/a-B-c')['status'] == 200
227        assert self.get(url='/aBc')['status'] == 200
228        assert self.get(url='/aBCaBbc')['status'] == 200
229        assert self.get(url='/ABc')['status'] == 404
230
231    def test_routes_pass_encode(self):
232        def check_pass(path, name):
233            assert 'success' in self.conf(
234                {
235                    "listeners": {"*:7080": {"pass": "applications/" + path}},
236                    "applications": {
237                        name: {
238                            "type": "python",
239                            "processes": {"spare": 0},
240                            "path": option.test_dir + '/python/empty',
241                            "working_directory": option.test_dir
242                            + '/python/empty',
243                            "module": "wsgi",
244                        }
245                    },
246                }
247            )
248
249            assert self.get()['status'] == 200
250
251        check_pass("%25", "%")
252        check_pass("blah%2Fblah", "blah/blah")
253        check_pass("%2Fblah%2F%2Fblah%2F", "/blah//blah/")
254        check_pass("%20blah%252Fblah%7E", " blah%2Fblah~")
255
256        def check_pass_error(path, name):
257            assert 'error' in self.conf(
258                {
259                    "listeners": {"*:7080": {"pass": "applications/" + path}},
260                    "applications": {
261                        name: {
262                            "type": "python",
263                            "processes": {"spare": 0},
264                            "path": option.test_dir + '/python/empty',
265                            "working_directory": option.test_dir
266                            + '/python/empty',
267                            "module": "wsgi",
268                        }
269                    },
270                }
271            )
272
273        check_pass_error("%", "%")
274        check_pass_error("%1", "%1")
275
276    def test_routes_absent(self):
277        self.conf(
278            {
279                "listeners": {"*:7081": {"pass": "applications/empty"}},
280                "applications": {
281                    "empty": {
282                        "type": "python",
283                        "processes": {"spare": 0},
284                        "path": option.test_dir + '/python/empty',
285                        "working_directory": option.test_dir
286                        + '/python/empty',
287                        "module": "wsgi",
288                    }
289                },
290            }
291        )
292
293        assert self.get(port=7081)['status'] == 200, 'routes absent'
294
295    def test_routes_pass_invalid(self):
296        assert 'error' in self.conf(
297            {"pass": "routes/blah"}, 'listeners/*:7080'
298        ), 'routes invalid'
299
300    def test_route_empty(self):
301        assert 'success' in self.conf(
302            {
303                "listeners": {"*:7080": {"pass": "routes/main"}},
304                "routes": {"main": []},
305                "applications": {},
306            }
307        ), 'route empty configure'
308
309        assert self.get()['status'] == 404, 'route empty'
310
311    def test_routes_route_empty(self):
312        assert 'success' in self.conf(
313            {}, 'listeners'
314        ), 'routes empty listeners configure'
315
316        assert 'success' in self.conf({}, 'routes'), 'routes empty configure'
317
318    def test_routes_route_match_absent(self):
319        assert 'success' in self.conf(
320            [{"action": {"return": 200}}], 'routes'
321        ), 'route match absent configure'
322
323        assert self.get()['status'] == 200, 'route match absent'
324
325    def test_routes_route_action_absent(self):
326        skip_alert(r'failed to apply new conf')
327
328        assert 'error' in self.conf(
329            [{"match": {"method": "GET"}}], 'routes'
330        ), 'route pass absent configure'
331
332    def test_routes_route_pass(self):
333        assert 'success' in self.conf(
334            {
335                "applications": {
336                    "app": {
337                        "type": "python",
338                        "processes": {"spare": 0},
339                        "path": "/app",
340                        "module": "wsgi",
341                    }
342                },
343                "upstreams": {
344                    "one": {
345                        "servers": {
346                            "127.0.0.1:7081": {},
347                            "127.0.0.1:7082": {},
348                        },
349                    },
350                    "two": {
351                        "servers": {
352                            "127.0.0.1:7081": {},
353                            "127.0.0.1:7082": {},
354                        },
355                    },
356                },
357            }
358        )
359
360        assert 'success' in self.conf(
361            [{"action": {"pass": "routes"}}], 'routes'
362        )
363        assert 'success' in self.conf(
364            [{"action": {"pass": "applications/app"}}], 'routes'
365        )
366        assert 'success' in self.conf(
367            [{"action": {"pass": "upstreams/one"}}], 'routes'
368        )
369
370    def test_routes_route_pass_absent(self):
371        assert 'error' in self.conf(
372            [{"match": {"method": "GET"}, "action": {}}], 'routes'
373        ), 'route pass absent configure'
374
375    def test_routes_route_pass_invalid(self):
376        assert 'success' in self.conf(
377            {
378                "applications": {
379                    "app": {
380                        "type": "python",
381                        "processes": {"spare": 0},
382                        "path": "/app",
383                        "module": "wsgi",
384                    }
385                },
386                "upstreams": {
387                    "one": {
388                        "servers": {
389                            "127.0.0.1:7081": {},
390                            "127.0.0.1:7082": {},
391                        },
392                    },
393                    "two": {
394                        "servers": {
395                            "127.0.0.1:7081": {},
396                            "127.0.0.1:7082": {},
397                        },
398                    },
399                },
400            }
401        )
402
403        assert 'error' in self.conf(
404            [{"action": {"pass": "blah"}}], 'routes'
405        ), 'route pass invalid'
406        assert 'error' in self.conf(
407            [{"action": {"pass": "routes/blah"}}], 'routes'
408        ), 'route pass routes invalid'
409        assert 'error' in self.conf(
410            [{"action": {"pass": "applications/blah"}}], 'routes'
411        ), 'route pass applications invalid'
412        assert 'error' in self.conf(
413            [{"action": {"pass": "upstreams/blah"}}], 'routes'
414        ), 'route pass upstreams invalid'
415
416    def test_routes_action_unique(self):
417        assert 'success' in self.conf(
418            {
419                "listeners": {
420                    "*:7080": {"pass": "routes"},
421                    "*:7081": {"pass": "applications/app"},
422                },
423                "routes": [{"action": {"proxy": "http://127.0.0.1:7081"}}],
424                "applications": {
425                    "app": {
426                        "type": "python",
427                        "processes": {"spare": 0},
428                        "path": "/app",
429                        "module": "wsgi",
430                    }
431                },
432            }
433        )
434
435        assert 'error' in self.conf(
436            {"proxy": "http://127.0.0.1:7081", "share": self.temp_dir},
437            'routes/0/action',
438        ), 'proxy share'
439        assert 'error' in self.conf(
440            {"proxy": "http://127.0.0.1:7081", "pass": "applications/app",},
441            'routes/0/action',
442        ), 'proxy pass'
443        assert 'error' in self.conf(
444            {"share": self.temp_dir, "pass": "applications/app"},
445            'routes/0/action',
446        ), 'share pass'
447
448    def test_routes_rules_two(self):
449        assert 'success' in self.conf(
450            [
451                {"match": {"method": "GET"}, "action": {"return": 200}},
452                {"match": {"method": "POST"}, "action": {"return": 201}},
453            ],
454            'routes',
455        ), 'rules two configure'
456
457        assert self.get()['status'] == 200, 'rules two match first'
458        assert self.post()['status'] == 201, 'rules two match second'
459
460    def test_routes_two(self):
461        assert 'success' in self.conf(
462            {
463                "listeners": {"*:7080": {"pass": "routes/first"}},
464                "routes": {
465                    "first": [
466                        {
467                            "match": {"method": "GET"},
468                            "action": {"pass": "routes/second"},
469                        }
470                    ],
471                    "second": [
472                        {
473                            "match": {"host": "localhost"},
474                            "action": {"return": 200},
475                        }
476                    ],
477                },
478                "applications": {},
479            }
480        ), 'routes two configure'
481
482        assert self.get()['status'] == 200, 'routes two'
483
484    def test_routes_match_host_positive(self):
485        self.route_match({"host": "localhost"})
486
487        assert self.get()['status'] == 200, 'localhost'
488        self.host('localhost.', 200)
489        self.host('localhost.', 200)
490        self.host('.localhost', 404)
491        self.host('www.localhost', 404)
492        self.host('localhost1', 404)
493
494    @pytest.mark.skip('not yet')
495    def test_routes_match_host_absent(self):
496        self.route_match({"host": "localhost"})
497
498        assert (
499            self.get(headers={'Connection': 'close'})['status'] == 400
500        ), 'match host absent'
501
502    def test_routes_match_host_ipv4(self):
503        self.route_match({"host": "127.0.0.1"})
504
505        self.host('127.0.0.1', 200)
506        self.host('127.0.0.1:7080', 200)
507
508    def test_routes_match_host_ipv6(self):
509        self.route_match({"host": "[::1]"})
510
511        self.host('[::1]', 200)
512        self.host('[::1]:7080', 200)
513
514    def test_routes_match_host_positive_many(self):
515        self.route_match({"host": ["localhost", "example.com"]})
516
517        assert self.get()['status'] == 200, 'localhost'
518        self.host('example.com', 200)
519
520    def test_routes_match_host_positive_and_negative(self):
521        self.route_match({"host": ["*example.com", "!www.example.com"]})
522
523        assert self.get()['status'] == 404, 'localhost'
524        self.host('example.com', 200)
525        self.host('www.example.com', 404)
526        self.host('!www.example.com', 200)
527
528    def test_routes_match_host_positive_and_negative_wildcard(self):
529        self.route_match({"host": ["*example*", "!www.example*"]})
530
531        self.host('example.com', 200)
532        self.host('www.example.com', 404)
533
534    def test_routes_match_host_case_insensitive(self):
535        self.route_match({"host": "Example.com"})
536
537        self.host('example.com', 200)
538        self.host('EXAMPLE.COM', 200)
539
540    def test_routes_match_host_port(self):
541        self.route_match({"host": "example.com"})
542
543        self.host('example.com:7080', 200)
544
545    def test_routes_match_host_empty(self):
546        self.route_match({"host": ""})
547
548        self.host('', 200)
549        assert (
550            self.get(http_10=True, headers={})['status'] == 200
551        ), 'match host empty 2'
552        assert self.get()['status'] == 404, 'match host empty 3'
553
554    def test_routes_match_uri_positive(self):
555        self.route_match({"uri": ["/blah", "/slash/"]})
556
557        assert self.get()['status'] == 404, '/'
558        assert self.get(url='/blah')['status'] == 200, '/blah'
559        assert self.get(url='/blah#foo')['status'] == 200, '/blah#foo'
560        assert self.get(url='/blah?var')['status'] == 200, '/blah?var'
561        assert self.get(url='//blah')['status'] == 200, '//blah'
562        assert self.get(url='/slash/foo/../')['status'] == 200, 'relative'
563        assert self.get(url='/slash/./')['status'] == 200, '/slash/./'
564        assert self.get(url='/slash//.//')['status'] == 200, 'adjacent slashes'
565        assert self.get(url='/%')['status'] == 400, 'percent'
566        assert self.get(url='/%1')['status'] == 400, 'percent digit'
567        assert self.get(url='/%A')['status'] == 400, 'percent letter'
568        assert self.get(url='/slash/.?args')['status'] == 200, 'dot args'
569        assert self.get(url='/slash/.#frag')['status'] == 200, 'dot frag'
570        assert (
571            self.get(url='/slash/foo/..?args')['status'] == 200
572        ), 'dot dot args'
573        assert (
574            self.get(url='/slash/foo/..#frag')['status'] == 200
575        ), 'dot dot frag'
576        assert self.get(url='/slash/.')['status'] == 200, 'trailing dot'
577        assert (
578            self.get(url='/slash/foo/..')['status'] == 200
579        ), 'trailing dot dot'
580
581    def test_routes_match_uri_case_sensitive(self):
582        self.route_match({"uri": "/BLAH"})
583
584        assert self.get(url='/blah')['status'] == 404, '/blah'
585        assert self.get(url='/BlaH')['status'] == 404, '/BlaH'
586        assert self.get(url='/BLAH')['status'] == 200, '/BLAH'
587
588    def test_routes_match_uri_normalize(self):
589        self.route_match({"uri": "/blah"})
590
591        assert self.get(url='/%62%6c%61%68')['status'] == 200, 'normalize'
592
593    def test_routes_match_empty_array(self):
594        self.route_match({"uri": []})
595
596        assert self.get(url='/blah')['status'] == 200, 'empty array'
597
598    def test_routes_reconfigure(self):
599        assert 'success' in self.conf([], 'routes'), 'redefine'
600        assert self.get()['status'] == 404, 'redefine request'
601
602        assert 'success' in self.conf(
603            [{"action": {"return": 200}}], 'routes'
604        ), 'redefine 2'
605        assert self.get()['status'] == 200, 'redefine request 2'
606
607        assert 'success' in self.conf([], 'routes'), 'redefine 3'
608        assert self.get()['status'] == 404, 'redefine request 3'
609
610        assert 'success' in self.conf(
611            {
612                "listeners": {"*:7080": {"pass": "routes/main"}},
613                "routes": {"main": [{"action": {"return": 200}}]},
614                "applications": {},
615            }
616        ), 'redefine 4'
617        assert self.get()['status'] == 200, 'redefine request 4'
618
619        assert 'success' in self.conf_delete('routes/main/0'), 'redefine 5'
620        assert self.get()['status'] == 404, 'redefine request 5'
621
622        assert 'success' in self.conf_post(
623            {"action": {"return": 200}}, 'routes/main'
624        ), 'redefine 6'
625        assert self.get()['status'] == 200, 'redefine request 6'
626
627        assert 'error' in self.conf(
628            {"action": {"return": 200}}, 'routes/main/2'
629        ), 'redefine 7'
630        assert 'success' in self.conf(
631            {"action": {"return": 201}}, 'routes/main/1'
632        ), 'redefine 8'
633
634        assert len(self.conf_get('routes/main')) == 2, 'redefine conf 8'
635        assert self.get()['status'] == 200, 'redefine request 8'
636
637    def test_routes_edit(self):
638        self.route_match({"method": "GET"})
639
640        assert self.get()['status'] == 200, 'routes edit GET'
641        assert self.post()['status'] == 404, 'routes edit POST'
642
643        assert 'success' in self.conf_post(
644            {"match": {"method": "POST"}, "action": {"return": 200}}, 'routes',
645        ), 'routes edit configure 2'
646        assert 'GET' == self.conf_get(
647            'routes/0/match/method'
648        ), 'routes edit configure 2 check'
649        assert 'POST' == self.conf_get(
650            'routes/1/match/method'
651        ), 'routes edit configure 2 check 2'
652
653        assert self.get()['status'] == 200, 'routes edit GET 2'
654        assert self.post()['status'] == 200, 'routes edit POST 2'
655
656        assert 'success' in self.conf_delete(
657            'routes/0'
658        ), 'routes edit configure 3'
659
660        assert self.get()['status'] == 404, 'routes edit GET 3'
661        assert self.post()['status'] == 200, 'routes edit POST 3'
662
663        assert 'error' in self.conf_delete(
664            'routes/1'
665        ), 'routes edit configure invalid'
666        assert 'error' in self.conf_delete(
667            'routes/-1'
668        ), 'routes edit configure invalid 2'
669        assert 'error' in self.conf_delete(
670            'routes/blah'
671        ), 'routes edit configure invalid 3'
672
673        assert self.get()['status'] == 404, 'routes edit GET 4'
674        assert self.post()['status'] == 200, 'routes edit POST 4'
675
676        assert 'success' in self.conf_delete(
677            'routes/0'
678        ), 'routes edit configure 5'
679
680        assert self.get()['status'] == 404, 'routes edit GET 5'
681        assert self.post()['status'] == 404, 'routes edit POST 5'
682
683        assert 'success' in self.conf_post(
684            {"match": {"method": "POST"}, "action": {"return": 200}}, 'routes',
685        ), 'routes edit configure 6'
686
687        assert self.get()['status'] == 404, 'routes edit GET 6'
688        assert self.post()['status'] == 200, 'routes edit POST 6'
689
690        assert 'success' in self.conf(
691            {
692                "listeners": {"*:7080": {"pass": "routes/main"}},
693                "routes": {"main": [{"action": {"return": 200}}]},
694                "applications": {},
695            }
696        ), 'route edit configure 7'
697
698        assert 'error' in self.conf_delete(
699            'routes/0'
700        ), 'routes edit configure invalid 4'
701        assert 'error' in self.conf_delete(
702            'routes/main'
703        ), 'routes edit configure invalid 5'
704
705        assert self.get()['status'] == 200, 'routes edit GET 7'
706
707        assert 'success' in self.conf_delete(
708            'listeners/*:7080'
709        ), 'route edit configure 8'
710        assert 'success' in self.conf_delete(
711            'routes/main'
712        ), 'route edit configure 9'
713
714    def test_match_edit(self):
715        skip_alert(r'failed to apply new conf')
716
717        self.route_match({"method": ["GET", "POST"]})
718
719        assert self.get()['status'] == 200, 'match edit GET'
720        assert self.post()['status'] == 200, 'match edit POST'
721        assert self.put()['status'] == 404, 'match edit PUT'
722
723        assert 'success' in self.conf_post(
724            '\"PUT\"', 'routes/0/match/method'
725        ), 'match edit configure 2'
726        assert ['GET', 'POST', 'PUT'] == self.conf_get(
727            'routes/0/match/method'
728        ), 'match edit configure 2 check'
729
730        assert self.get()['status'] == 200, 'match edit GET 2'
731        assert self.post()['status'] == 200, 'match edit POST 2'
732        assert self.put()['status'] == 200, 'match edit PUT 2'
733
734        assert 'success' in self.conf_delete(
735            'routes/0/match/method/1'
736        ), 'match edit configure 3'
737        assert ['GET', 'PUT'] == self.conf_get(
738            'routes/0/match/method'
739        ), 'match edit configure 3 check'
740
741        assert self.get()['status'] == 200, 'match edit GET 3'
742        assert self.post()['status'] == 404, 'match edit POST 3'
743        assert self.put()['status'] == 200, 'match edit PUT 3'
744
745        assert 'success' in self.conf_delete(
746            'routes/0/match/method/1'
747        ), 'match edit configure 4'
748        assert ['GET'] == self.conf_get(
749            'routes/0/match/method'
750        ), 'match edit configure 4 check'
751
752        assert self.get()['status'] == 200, 'match edit GET 4'
753        assert self.post()['status'] == 404, 'match edit POST 4'
754        assert self.put()['status'] == 404, 'match edit PUT 4'
755
756        assert 'error' in self.conf_delete(
757            'routes/0/match/method/1'
758        ), 'match edit configure invalid'
759        assert 'error' in self.conf_delete(
760            'routes/0/match/method/-1'
761        ), 'match edit configure invalid 2'
762        assert 'error' in self.conf_delete(
763            'routes/0/match/method/blah'
764        ), 'match edit configure invalid 3'
765        assert ['GET'] == self.conf_get(
766            'routes/0/match/method'
767        ), 'match edit configure 5 check'
768
769        assert self.get()['status'] == 200, 'match edit GET 5'
770        assert self.post()['status'] == 404, 'match edit POST 5'
771        assert self.put()['status'] == 404, 'match edit PUT 5'
772
773        assert 'success' in self.conf_delete(
774            'routes/0/match/method/0'
775        ), 'match edit configure 6'
776        assert [] == self.conf_get(
777            'routes/0/match/method'
778        ), 'match edit configure 6 check'
779
780        assert self.get()['status'] == 200, 'match edit GET 6'
781        assert self.post()['status'] == 200, 'match edit POST 6'
782        assert self.put()['status'] == 200, 'match edit PUT 6'
783
784        assert 'success' in self.conf(
785            '"GET"', 'routes/0/match/method'
786        ), 'match edit configure 7'
787
788        assert self.get()['status'] == 200, 'match edit GET 7'
789        assert self.post()['status'] == 404, 'match edit POST 7'
790        assert self.put()['status'] == 404, 'match edit PUT 7'
791
792        assert 'error' in self.conf_delete(
793            'routes/0/match/method/0'
794        ), 'match edit configure invalid 5'
795        assert 'error' in self.conf(
796            {}, 'routes/0/action'
797        ), 'match edit configure invalid 6'
798
799        assert 'success' in self.conf(
800            {}, 'routes/0/match'
801        ), 'match edit configure 8'
802
803        assert self.get()['status'] == 200, 'match edit GET 8'
804
805    def test_routes_match_rules(self):
806        self.route_match({"method": "GET", "host": "localhost", "uri": "/"})
807
808        assert self.get()['status'] == 200, 'routes match rules'
809
810    def test_routes_loop(self):
811        assert 'success' in self.route(
812            {"match": {"uri": "/"}, "action": {"pass": "routes"}}
813        ), 'routes loop configure'
814
815        assert self.get()['status'] == 500, 'routes loop'
816
817    def test_routes_match_headers(self):
818        self.route_match({"headers": {"host": "localhost"}})
819
820        assert self.get()['status'] == 200, 'match headers'
821        self.host('Localhost', 200)
822        self.host('localhost.com', 404)
823        self.host('llocalhost', 404)
824        self.host('host', 404)
825
826    def test_routes_match_headers_multiple(self):
827        self.route_match({"headers": {"host": "localhost", "x-blah": "test"}})
828
829        assert self.get()['status'] == 404, 'match headers multiple'
830        assert (
831            self.get(
832                headers={
833                    "Host": "localhost",
834                    "X-blah": "test",
835                    "Connection": "close",
836                }
837            )['status']
838            == 200
839        ), 'match headers multiple 2'
840
841        assert (
842            self.get(
843                headers={
844                    "Host": "localhost",
845                    "X-blah": "",
846                    "Connection": "close",
847                }
848            )['status']
849            == 404
850        ), 'match headers multiple 3'
851
852    def test_routes_match_headers_multiple_values(self):
853        self.route_match({"headers": {"x-blah": "test"}})
854
855        assert (
856            self.get(
857                headers={
858                    "Host": "localhost",
859                    "X-blah": ["test", "test", "test"],
860                    "Connection": "close",
861                }
862            )['status']
863            == 200
864        ), 'match headers multiple values'
865        assert (
866            self.get(
867                headers={
868                    "Host": "localhost",
869                    "X-blah": ["test", "blah", "test"],
870                    "Connection": "close",
871                }
872            )['status']
873            == 404
874        ), 'match headers multiple values 2'
875        assert (
876            self.get(
877                headers={
878                    "Host": "localhost",
879                    "X-blah": ["test", "", "test"],
880                    "Connection": "close",
881                }
882            )['status']
883            == 404
884        ), 'match headers multiple values 3'
885
886    def test_routes_match_headers_multiple_rules(self):
887        self.route_match({"headers": {"x-blah": ["test", "blah"]}})
888
889        assert self.get()['status'] == 404, 'match headers multiple rules'
890        assert (
891            self.get(
892                headers={
893                    "Host": "localhost",
894                    "X-blah": "test",
895                    "Connection": "close",
896                }
897            )['status']
898            == 200
899        ), 'match headers multiple rules 2'
900        assert (
901            self.get(
902                headers={
903                    "Host": "localhost",
904                    "X-blah": "blah",
905                    "Connection": "close",
906                }
907            )['status']
908            == 200
909        ), 'match headers multiple rules 3'
910        assert (
911            self.get(
912                headers={
913                    "Host": "localhost",
914                    "X-blah": ["test", "blah", "test"],
915                    "Connection": "close",
916                }
917            )['status']
918            == 200
919        ), 'match headers multiple rules 4'
920
921        assert (
922            self.get(
923                headers={
924                    "Host": "localhost",
925                    "X-blah": ["blah", ""],
926                    "Connection": "close",
927                }
928            )['status']
929            == 404
930        ), 'match headers multiple rules 5'
931
932    def test_routes_match_headers_case_insensitive(self):
933        self.route_match({"headers": {"X-BLAH": "TEST"}})
934
935        assert (
936            self.get(
937                headers={
938                    "Host": "localhost",
939                    "x-blah": "test",
940                    "Connection": "close",
941                }
942            )['status']
943            == 200
944        ), 'match headers case insensitive'
945
946    def test_routes_match_headers_invalid(self):
947        self.route_match_invalid({"headers": ["blah"]})
948        self.route_match_invalid({"headers": {"foo": ["bar", {}]}})
949        self.route_match_invalid({"headers": {"": "blah"}})
950
951    def test_routes_match_headers_empty_rule(self):
952        self.route_match({"headers": {"host": ""}})
953
954        assert self.get()['status'] == 404, 'localhost'
955        self.host('', 200)
956
957    def test_routes_match_headers_empty(self):
958        self.route_match({"headers": {}})
959        assert self.get()['status'] == 200, 'empty'
960
961        self.route_match({"headers": []})
962        assert self.get()['status'] == 200, 'empty 2'
963
964    def test_routes_match_headers_rule_array_empty(self):
965        self.route_match({"headers": {"blah": []}})
966
967        assert self.get()['status'] == 404, 'array empty'
968        assert (
969            self.get(
970                headers={
971                    "Host": "localhost",
972                    "blah": "foo",
973                    "Connection": "close",
974                }
975            )['status']
976            == 200
977        ), 'match headers rule array empty 2'
978
979    def test_routes_match_headers_array(self):
980        self.route_match(
981            {
982                "headers": [
983                    {"x-header1": "foo*"},
984                    {"x-header2": "bar"},
985                    {"x-header3": ["foo", "bar"]},
986                    {"x-header1": "bar", "x-header4": "foo"},
987                ]
988            }
989        )
990
991        assert self.get()['status'] == 404, 'match headers array'
992        assert (
993            self.get(
994                headers={
995                    "Host": "localhost",
996                    "x-header1": "foo123",
997                    "Connection": "close",
998                }
999            )['status']
1000            == 200
1001        ), 'match headers array 2'
1002        assert (
1003            self.get(
1004                headers={
1005                    "Host": "localhost",
1006                    "x-header2": "bar",
1007                    "Connection": "close",
1008                }
1009            )['status']
1010            == 200
1011        ), 'match headers array 3'
1012        assert (
1013            self.get(
1014                headers={
1015                    "Host": "localhost",
1016                    "x-header3": "bar",
1017                    "Connection": "close",
1018                }
1019            )['status']
1020            == 200
1021        ), 'match headers array 4'
1022        assert (
1023            self.get(
1024                headers={
1025                    "Host": "localhost",
1026                    "x-header1": "bar",
1027                    "Connection": "close",
1028                }
1029            )['status']
1030            == 404
1031        ), 'match headers array 5'
1032        assert (
1033            self.get(
1034                headers={
1035                    "Host": "localhost",
1036                    "x-header1": "bar",
1037                    "x-header4": "foo",
1038                    "Connection": "close",
1039                }
1040            )['status']
1041            == 200
1042        ), 'match headers array 6'
1043
1044        assert 'success' in self.conf_delete(
1045            'routes/0/match/headers/1'
1046        ), 'match headers array configure 2'
1047
1048        assert (
1049            self.get(
1050                headers={
1051                    "Host": "localhost",
1052                    "x-header2": "bar",
1053                    "Connection": "close",
1054                }
1055            )['status']
1056            == 404
1057        ), 'match headers array 7'
1058        assert (
1059            self.get(
1060                headers={
1061                    "Host": "localhost",
1062                    "x-header3": "foo",
1063                    "Connection": "close",
1064                }
1065            )['status']
1066            == 200
1067        ), 'match headers array 8'
1068
1069    def test_routes_match_arguments(self):
1070        self.route_match({"arguments": {"foo": "bar"}})
1071
1072        assert self.get()['status'] == 404, 'args'
1073        assert self.get(url='/?foo=bar')['status'] == 200, 'args 2'
1074        assert self.get(url='/?foo=bar1')['status'] == 404, 'args 3'
1075        assert self.get(url='/?1foo=bar')['status'] == 404, 'args 4'
1076        assert self.get(url='/?Foo=bar')['status'] == 404, 'case'
1077        assert self.get(url='/?foo=Bar')['status'] == 404, 'case 2'
1078
1079    def test_routes_match_arguments_chars(self):
1080        chars = (
1081            " !\"%23$%25%26'()*%2B,-./0123456789:;<%3D>?@"
1082            "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
1083        )
1084
1085        chars_enc = ""
1086        for h1 in ["2", "3", "4", "5", "6", "7"]:
1087            for h2 in [
1088                "0",
1089                "1",
1090                "2",
1091                "3",
1092                "4",
1093                "5",
1094                "6",
1095                "7",
1096                "8",
1097                "9",
1098                "A",
1099                "B",
1100                "C",
1101                "D",
1102                "E",
1103                "F",
1104            ]:
1105                chars_enc += "%" + h1 + h2
1106        chars_enc = chars_enc[:-3]
1107
1108        def check_args(args, query):
1109            self.route_match({"arguments": args})
1110            assert self.get(url='/?' + query)['status'] == 200
1111
1112        check_args({chars: chars}, chars + '=' + chars)
1113        check_args({chars: chars}, chars + '=' + chars_enc)
1114        check_args({chars: chars}, chars_enc + '=' + chars)
1115        check_args({chars: chars}, chars_enc + '=' + chars_enc)
1116        check_args({chars_enc: chars_enc}, chars + '=' + chars)
1117        check_args({chars_enc: chars_enc}, chars + '=' + chars_enc)
1118        check_args({chars_enc: chars_enc}, chars_enc + '=' + chars)
1119        check_args({chars_enc: chars_enc}, chars_enc + '=' + chars_enc)
1120
1121    def test_routes_match_arguments_empty(self):
1122        self.route_match({"arguments": {}})
1123        assert self.get()['status'] == 200, 'arguments empty'
1124
1125        self.route_match({"arguments": []})
1126        assert self.get()['status'] == 200, 'arguments empty 2'
1127
1128    def test_routes_match_arguments_space(self):
1129        self.route_match({"arguments": {"+fo o%20": "%20b+a r"}})
1130        assert self.get(url='/? fo o = b a r&')['status'] == 200
1131        assert self.get(url='/?+fo+o+=+b+a+r&')['status'] == 200
1132        assert self.get(url='/?%20fo%20o%20=%20b%20a%20r&')['status'] == 200
1133
1134        self.route_match({"arguments": {"%20foo": " bar"}})
1135        assert self.get(url='/? foo= bar')['status'] == 200
1136        assert self.get(url='/?+foo=+bar')['status'] == 200
1137        assert self.get(url='/?%20foo=%20bar')['status'] == 200
1138        assert self.get(url='/?+foo= bar')['status'] == 200
1139        assert self.get(url='/?%20foo=+bar')['status'] == 200
1140
1141    def test_routes_match_arguments_equal(self):
1142        self.route_match({"arguments": {"=": "="}})
1143        assert self.get(url='/?%3D=%3D')['status'] == 200
1144        assert self.get(url='/?%3D==')['status'] == 200
1145        assert self.get(url='/?===')['status'] == 404
1146        assert self.get(url='/?%3D%3D%3D')['status'] == 404
1147        assert self.get(url='/?==%3D')['status'] == 404
1148
1149    def test_routes_match_arguments_enc(self):
1150        self.route_match({"arguments": {"Ю": "н"}})
1151        assert self.get(url='/?%D0%AE=%D0%BD')['status'] == 200
1152        assert self.get(url='/?%d0%ae=%d0%Bd')['status'] == 200
1153
1154    def test_routes_match_arguments_hash(self):
1155        self.route_match({"arguments": {"#": "#"}})
1156        assert self.get(url='/?%23=%23')['status'] == 200
1157        assert self.get(url='/?%23=%23#')['status'] == 200
1158        assert self.get(url='/?#=#')['status'] == 404
1159        assert self.get(url='/?%23=#')['status'] == 404
1160
1161    def test_routes_match_arguments_wildcard(self):
1162        self.route_match({"arguments": {"foo": "*"}})
1163        assert self.get(url='/?foo')['status'] == 200
1164        assert self.get(url='/?foo=')['status'] == 200
1165        assert self.get(url='/?foo=blah')['status'] == 200
1166        assert self.get(url='/?blah=foo')['status'] == 404
1167
1168        self.route_match({"arguments": {"foo": "%25*"}})
1169        assert self.get(url='/?foo=%xx')['status'] == 200
1170
1171        self.route_match({"arguments": {"foo": "%2A*"}})
1172        assert self.get(url='/?foo=*xx')['status'] == 200
1173        assert self.get(url='/?foo=xx')['status'] == 404
1174
1175        self.route_match({"arguments": {"foo": "*%2A"}})
1176        assert self.get(url='/?foo=xx*')['status'] == 200
1177        assert self.get(url='/?foo=xx*x')['status'] == 404
1178
1179        self.route_match({"arguments": {"foo": "1*2"}})
1180        assert self.get(url='/?foo=12')['status'] == 200
1181        assert self.get(url='/?foo=1blah2')['status'] == 200
1182        assert self.get(url='/?foo=1%2A2')['status'] == 200
1183        assert self.get(url='/?foo=x12')['status'] == 404
1184
1185        self.route_match({"arguments": {"foo": "bar*", "%25": "%25"}})
1186        assert self.get(url='/?foo=barxx&%=%')['status'] == 200
1187        assert self.get(url='/?foo=barxx&x%=%')['status'] == 404
1188
1189    def test_routes_match_arguments_negative(self):
1190        self.route_match({"arguments": {"foo": "!%25"}})
1191        assert self.get(url='/?foo=blah')['status'] == 200
1192        assert self.get(url='/?foo=%')['status'] == 404
1193
1194        self.route_match({"arguments": {"foo": "%21blah"}})
1195        assert self.get(url='/?foo=%21blah')['status'] == 200
1196        assert self.get(url='/?foo=!blah')['status'] == 200
1197        assert self.get(url='/?foo=bar')['status'] == 404
1198
1199        self.route_match({"arguments": {"foo": "!!%21*a"}})
1200        assert self.get(url='/?foo=blah')['status'] == 200
1201        assert self.get(url='/?foo=!blah')['status'] == 200
1202        assert self.get(url='/?foo=!!a')['status'] == 404
1203        assert self.get(url='/?foo=!!bla')['status'] == 404
1204
1205    def test_routes_match_arguments_percent(self):
1206        self.route_match({"arguments": {"%25": "%25"}})
1207        assert self.get(url='/?%=%')['status'] == 200
1208        assert self.get(url='/?%25=%25')['status'] == 200
1209        assert self.get(url='/?%25=%')['status'] == 200
1210
1211        self.route_match({"arguments": {"%251": "%252"}})
1212        assert self.get(url='/?%1=%2')['status'] == 200
1213        assert self.get(url='/?%251=%252')['status'] == 200
1214        assert self.get(url='/?%251=%2')['status'] == 200
1215
1216        self.route_match({"arguments": {"%25%21%251": "%25%24%252"}})
1217        assert self.get(url='/?%!%1=%$%2')['status'] == 200
1218        assert self.get(url='/?%25!%251=%25$%252')['status'] == 200
1219        assert self.get(url='/?%25!%1=%$%2')['status'] == 200
1220
1221    def test_routes_match_arguments_ampersand(self):
1222        self.route_match({"arguments": {"foo": "&"}})
1223        assert self.get(url='/?foo=%26')['status'] == 200
1224        assert self.get(url='/?foo=%26&')['status'] == 200
1225        assert self.get(url='/?foo=%26%26')['status'] == 404
1226        assert self.get(url='/?foo=&')['status'] == 404
1227
1228        self.route_match({"arguments": {"&": ""}})
1229        assert self.get(url='/?%26=')['status'] == 200
1230        assert self.get(url='/?%26=&')['status'] == 200
1231        assert self.get(url='/?%26=%26')['status'] == 404
1232        assert self.get(url='/?&=')['status'] == 404
1233
1234    def test_routes_match_arguments_complex(self):
1235        self.route_match({"arguments": {"foo": ""}})
1236
1237        assert self.get(url='/?foo')['status'] == 200, 'complex'
1238        assert self.get(url='/?blah=blah&foo=')['status'] == 200, 'complex 2'
1239        assert self.get(url='/?&&&foo&&&')['status'] == 200, 'complex 3'
1240        assert self.get(url='/?foo&foo=bar&foo')['status'] == 404, 'complex 4'
1241        assert self.get(url='/?foo=&foo')['status'] == 200, 'complex 5'
1242        assert self.get(url='/?&=&foo&==&')['status'] == 200, 'complex 6'
1243        assert self.get(url='/?&=&bar&==&')['status'] == 404, 'complex 7'
1244
1245    def test_routes_match_arguments_multiple(self):
1246        self.route_match({"arguments": {"foo": "bar", "blah": "test"}})
1247
1248        assert self.get()['status'] == 404, 'multiple'
1249        assert (
1250            self.get(url='/?foo=bar&blah=test')['status'] == 200
1251        ), 'multiple 2'
1252        assert self.get(url='/?foo=bar&blah')['status'] == 404, 'multiple 3'
1253        assert (
1254            self.get(url='/?foo=bar&blah=tes')['status'] == 404
1255        ), 'multiple 4'
1256        assert (
1257            self.get(url='/?foo=b%61r&bl%61h=t%65st')['status'] == 200
1258        ), 'multiple 5'
1259
1260    def test_routes_match_arguments_multiple_rules(self):
1261        self.route_match({"arguments": {"foo": ["bar", "blah"]}})
1262
1263        assert self.get()['status'] == 404, 'rules'
1264        assert self.get(url='/?foo=bar')['status'] == 200, 'rules 2'
1265        assert self.get(url='/?foo=blah')['status'] == 200, 'rules 3'
1266        assert (
1267            self.get(url='/?foo=blah&foo=bar&foo=blah')['status'] == 200
1268        ), 'rules 4'
1269        assert (
1270            self.get(url='/?foo=blah&foo=bar&foo=')['status'] == 404
1271        ), 'rules 5'
1272
1273    def test_routes_match_arguments_array(self):
1274        self.route_match(
1275            {
1276                "arguments": [
1277                    {"var1": "val1*"},
1278                    {"var2": "val2"},
1279                    {"var3": ["foo", "bar"]},
1280                    {"var1": "bar", "var4": "foo"},
1281                ]
1282            }
1283        )
1284
1285        assert self.get()['status'] == 404, 'arr'
1286        assert self.get(url='/?var1=val123')['status'] == 200, 'arr 2'
1287        assert self.get(url='/?var2=val2')['status'] == 200, 'arr 3'
1288        assert self.get(url='/?var3=bar')['status'] == 200, 'arr 4'
1289        assert self.get(url='/?var1=bar')['status'] == 404, 'arr 5'
1290        assert self.get(url='/?var1=bar&var4=foo')['status'] == 200, 'arr 6'
1291
1292        assert 'success' in self.conf_delete(
1293            'routes/0/match/arguments/1'
1294        ), 'match arguments array configure 2'
1295
1296        assert self.get(url='/?var2=val2')['status'] == 404, 'arr 7'
1297        assert self.get(url='/?var3=foo')['status'] == 200, 'arr 8'
1298
1299    def test_routes_match_arguments_invalid(self):
1300        # TODO remove it after controller fixed
1301        skip_alert(r'failed to apply new conf')
1302
1303        self.route_match_invalid({"arguments": ["var"]})
1304        self.route_match_invalid({"arguments": [{"var1": {}}]})
1305        self.route_match_invalid({"arguments": {"": "bar"}})
1306        self.route_match_invalid({"arguments": {"foo": "%"}})
1307        self.route_match_invalid({"arguments": {"foo": "%1G"}})
1308        self.route_match_invalid({"arguments": {"%": "bar"}})
1309        self.route_match_invalid({"arguments": {"foo": "%0"}})
1310        self.route_match_invalid({"arguments": {"foo": "%%1F"}})
1311        self.route_match_invalid({"arguments": {"%%1F": ""}})
1312        self.route_match_invalid({"arguments": {"%7%F": ""}})
1313
1314    def test_routes_match_cookies(self):
1315        self.route_match({"cookies": {"foO": "bar"}})
1316
1317        assert self.get()['status'] == 404, 'cookie'
1318        self.cookie('foO=bar', 200)
1319        self.cookie('foO=bar;1', 200)
1320        self.cookie(['foO=bar', 'blah=blah'], 200)
1321        self.cookie('foO=bar; blah=blah', 200)
1322        self.cookie('Foo=bar', 404)
1323        self.cookie('foO=Bar', 404)
1324        self.cookie('foO=bar1', 404)
1325        self.cookie('1foO=bar;', 404)
1326
1327    def test_routes_match_cookies_empty(self):
1328        self.route_match({"cookies": {}})
1329        assert self.get()['status'] == 200, 'cookies empty'
1330
1331        self.route_match({"cookies": []})
1332        assert self.get()['status'] == 200, 'cookies empty 2'
1333
1334    def test_routes_match_cookies_invalid(self):
1335        self.route_match_invalid({"cookies": ["var"]})
1336        self.route_match_invalid({"cookies": [{"foo": {}}]})
1337
1338    def test_routes_match_cookies_multiple(self):
1339        self.route_match({"cookies": {"foo": "bar", "blah": "blah"}})
1340
1341        assert self.get()['status'] == 404, 'multiple'
1342        self.cookie('foo=bar; blah=blah', 200)
1343        self.cookie(['foo=bar', 'blah=blah'], 200)
1344        self.cookie(['foo=bar; blah', 'blah'], 404)
1345        self.cookie(['foo=bar; blah=test', 'blah=blah'], 404)
1346
1347    def test_routes_match_cookies_multiple_values(self):
1348        self.route_match({"cookies": {"blah": "blah"}})
1349
1350        self.cookie(['blah=blah', 'blah=blah', 'blah=blah'], 200)
1351        self.cookie(['blah=blah', 'blah=test', 'blah=blah'], 404)
1352        self.cookie(['blah=blah; blah=', 'blah=blah'], 404)
1353
1354    def test_routes_match_cookies_multiple_rules(self):
1355        self.route_match({"cookies": {"blah": ["test", "blah"]}})
1356
1357        assert self.get()['status'] == 404, 'multiple rules'
1358        self.cookie('blah=test', 200)
1359        self.cookie('blah=blah', 200)
1360        self.cookie(['blah=blah', 'blah=test', 'blah=blah'], 200)
1361        self.cookie(['blah=blah; blah=test', 'blah=blah'], 200)
1362        self.cookie(['blah=blah', 'blah'], 200)  # invalid cookie
1363
1364    def test_routes_match_cookies_array(self):
1365        self.route_match(
1366            {
1367                "cookies": [
1368                    {"var1": "val1*"},
1369                    {"var2": "val2"},
1370                    {"var3": ["foo", "bar"]},
1371                    {"var1": "bar", "var4": "foo"},
1372                ]
1373            }
1374        )
1375
1376        assert self.get()['status'] == 404, 'cookies array'
1377        self.cookie('var1=val123', 200)
1378        self.cookie('var2=val2', 200)
1379        self.cookie(' var2=val2 ', 200)
1380        self.cookie('var3=bar', 200)
1381        self.cookie('var3=bar;', 200)
1382        self.cookie('var1=bar', 404)
1383        self.cookie('var1=bar; var4=foo;', 200)
1384        self.cookie(['var1=bar', 'var4=foo'], 200)
1385
1386        assert 'success' in self.conf_delete(
1387            'routes/0/match/cookies/1'
1388        ), 'match cookies array configure 2'
1389
1390        self.cookie('var2=val2', 404)
1391        self.cookie('var3=foo', 200)
1392
1393    def test_routes_match_scheme(self):
1394        self.route_match({"scheme": "http"})
1395        self.route_match({"scheme": "https"})
1396        self.route_match({"scheme": "HtTp"})
1397        self.route_match({"scheme": "HtTpS"})
1398
1399    def test_routes_match_scheme_invalid(self):
1400        self.route_match_invalid({"scheme": ["http"]})
1401        self.route_match_invalid({"scheme": "ftp"})
1402        self.route_match_invalid({"scheme": "ws"})
1403        self.route_match_invalid({"scheme": "*"})
1404        self.route_match_invalid({"scheme": ""})
1405
1406    def test_routes_source_port(self):
1407        def sock_port():
1408            _, sock = self.http(b'', start=True, raw=True, no_recv=True)
1409            port = sock.getsockname()[1]
1410            return (sock, port)
1411
1412        sock, port = sock_port()
1413        sock2, port2 = sock_port()
1414
1415        self.route_match({"source": "127.0.0.1:" + str(port)})
1416        assert self.get(sock=sock)['status'] == 200, 'exact'
1417        assert self.get(sock=sock2)['status'] == 404, 'exact 2'
1418
1419        sock, port = sock_port()
1420        sock2, port2 = sock_port()
1421
1422        self.route_match({"source": "!127.0.0.1:" + str(port)})
1423        assert self.get(sock=sock)['status'] == 404, 'negative'
1424        assert self.get(sock=sock2)['status'] == 200, 'negative 2'
1425
1426        sock, port = sock_port()
1427        sock2, port2 = sock_port()
1428
1429        self.route_match({"source": ["*:" + str(port), "!127.0.0.1"]})
1430        assert self.get(sock=sock)['status'] == 404, 'negative 3'
1431        assert self.get(sock=sock2)['status'] == 404, 'negative 4'
1432
1433        sock, port = sock_port()
1434        sock2, port2 = sock_port()
1435
1436        self.route_match(
1437            {"source": "127.0.0.1:" + str(port) + "-" + str(port)}
1438        )
1439        assert self.get(sock=sock)['status'] == 200, 'range single'
1440        assert self.get(sock=sock2)['status'] == 404, 'range single 2'
1441
1442        socks = [
1443            sock_port(),
1444            sock_port(),
1445            sock_port(),
1446            sock_port(),
1447            sock_port(),
1448        ]
1449        socks.sort(key=lambda sock: sock[1])
1450
1451        self.route_match(
1452            {
1453                "source": "127.0.0.1:"
1454                + str(socks[1][1])  # second port number
1455                + "-"
1456                + str(socks[3][1])  # fourth port number
1457            }
1458        )
1459        assert self.get(sock=socks[0][0])['status'] == 404, 'range'
1460        assert self.get(sock=socks[1][0])['status'] == 200, 'range 2'
1461        assert self.get(sock=socks[2][0])['status'] == 200, 'range 3'
1462        assert self.get(sock=socks[3][0])['status'] == 200, 'range 4'
1463        assert self.get(sock=socks[4][0])['status'] == 404, 'range 5'
1464
1465        socks = [
1466            sock_port(),
1467            sock_port(),
1468            sock_port(),
1469        ]
1470        socks.sort(key=lambda sock: sock[1])
1471
1472        self.route_match(
1473            {
1474                "source": [
1475                    "127.0.0.1:" + str(socks[0][1]),
1476                    "127.0.0.1:" + str(socks[2][1]),
1477                ]
1478            }
1479        )
1480        assert self.get(sock=socks[0][0])['status'] == 200, 'array'
1481        assert self.get(sock=socks[1][0])['status'] == 404, 'array 2'
1482        assert self.get(sock=socks[2][0])['status'] == 200, 'array 3'
1483
1484    def test_routes_source_addr(self):
1485        assert 'success' in self.conf(
1486            {"*:7080": {"pass": "routes"}, "[::1]:7081": {"pass": "routes"},},
1487            'listeners',
1488        ), 'source listeners configure'
1489
1490        def get_ipv6():
1491            return self.get(sock_type='ipv6', port=7081)
1492
1493        self.route_match({"source": "127.0.0.1"})
1494        assert self.get()['status'] == 200, 'exact'
1495        assert get_ipv6()['status'] == 404, 'exact ipv6'
1496
1497        self.route_match({"source": ["127.0.0.1"]})
1498        assert self.get()['status'] == 200, 'exact 2'
1499        assert get_ipv6()['status'] == 404, 'exact 2 ipv6'
1500
1501        self.route_match({"source": "!127.0.0.1"})
1502        assert self.get()['status'] == 404, 'exact neg'
1503        assert get_ipv6()['status'] == 200, 'exact neg ipv6'
1504
1505        self.route_match({"source": "127.0.0.2"})
1506        assert self.get()['status'] == 404, 'exact 3'
1507        assert get_ipv6()['status'] == 404, 'exact 3 ipv6'
1508
1509        self.route_match({"source": "127.0.0.1-127.0.0.1"})
1510        assert self.get()['status'] == 200, 'range single'
1511        assert get_ipv6()['status'] == 404, 'range single ipv6'
1512
1513        self.route_match({"source": "127.0.0.2-127.0.0.2"})
1514        assert self.get()['status'] == 404, 'range single 2'
1515        assert get_ipv6()['status'] == 404, 'range single 2 ipv6'
1516
1517        self.route_match({"source": "127.0.0.2-127.0.0.3"})
1518        assert self.get()['status'] == 404, 'range'
1519        assert get_ipv6()['status'] == 404, 'range ipv6'
1520
1521        self.route_match({"source": "127.0.0.1-127.0.0.2"})
1522        assert self.get()['status'] == 200, 'range 2'
1523        assert get_ipv6()['status'] == 404, 'range 2 ipv6'
1524
1525        self.route_match({"source": "127.0.0.0-127.0.0.2"})
1526        assert self.get()['status'] == 200, 'range 3'
1527        assert get_ipv6()['status'] == 404, 'range 3 ipv6'
1528
1529        self.route_match({"source": "127.0.0.0-127.0.0.1"})
1530        assert self.get()['status'] == 200, 'range 4'
1531        assert get_ipv6()['status'] == 404, 'range 4 ipv6'
1532
1533        self.route_match({"source": "126.0.0.0-127.0.0.0"})
1534        assert self.get()['status'] == 404, 'range 5'
1535        assert get_ipv6()['status'] == 404, 'range 5 ipv6'
1536
1537        self.route_match({"source": "126.126.126.126-127.0.0.2"})
1538        assert self.get()['status'] == 200, 'range 6'
1539        assert get_ipv6()['status'] == 404, 'range 6 ipv6'
1540
1541    def test_routes_source_ipv6(self):
1542        assert 'success' in self.conf(
1543            {
1544                "[::1]:7080": {"pass": "routes"},
1545                "127.0.0.1:7081": {"pass": "routes"},
1546            },
1547            'listeners',
1548        ), 'source listeners configure'
1549
1550        self.route_match({"source": "::1"})
1551        assert self.get(sock_type='ipv6')['status'] == 200, 'exact'
1552        assert self.get(port=7081)['status'] == 404, 'exact ipv4'
1553
1554        self.route_match({"source": ["::1"]})
1555        assert self.get(sock_type='ipv6')['status'] == 200, 'exact 2'
1556        assert self.get(port=7081)['status'] == 404, 'exact 2 ipv4'
1557
1558        self.route_match({"source": "!::1"})
1559        assert self.get(sock_type='ipv6')['status'] == 404, 'exact neg'
1560        assert self.get(port=7081)['status'] == 200, 'exact neg ipv4'
1561
1562        self.route_match({"source": "::2"})
1563        assert self.get(sock_type='ipv6')['status'] == 404, 'exact 3'
1564        assert self.get(port=7081)['status'] == 404, 'exact 3 ipv4'
1565
1566        self.route_match({"source": "::1-::1"})
1567        assert self.get(sock_type='ipv6')['status'] == 200, 'range'
1568        assert self.get(port=7081)['status'] == 404, 'range ipv4'
1569
1570        self.route_match({"source": "::2-::2"})
1571        assert self.get(sock_type='ipv6')['status'] == 404, 'range 2'
1572        assert self.get(port=7081)['status'] == 404, 'range 2 ipv4'
1573
1574        self.route_match({"source": "::2-::3"})
1575        assert self.get(sock_type='ipv6')['status'] == 404, 'range 3'
1576        assert self.get(port=7081)['status'] == 404, 'range 3 ipv4'
1577
1578        self.route_match({"source": "::1-::2"})
1579        assert self.get(sock_type='ipv6')['status'] == 200, 'range 4'
1580        assert self.get(port=7081)['status'] == 404, 'range 4 ipv4'
1581
1582        self.route_match({"source": "::0-::2"})
1583        assert self.get(sock_type='ipv6')['status'] == 200, 'range 5'
1584        assert self.get(port=7081)['status'] == 404, 'range 5 ipv4'
1585
1586        self.route_match({"source": "::0-::1"})
1587        assert self.get(sock_type='ipv6')['status'] == 200, 'range 6'
1588        assert self.get(port=7081)['status'] == 404, 'range 6 ipv4'
1589
1590    def test_routes_source_cidr(self):
1591        assert 'success' in self.conf(
1592            {"*:7080": {"pass": "routes"}, "[::1]:7081": {"pass": "routes"},},
1593            'listeners',
1594        ), 'source listeners configure'
1595
1596        def get_ipv6():
1597            return self.get(sock_type='ipv6', port=7081)
1598
1599        self.route_match({"source": "127.0.0.1/32"})
1600        assert self.get()['status'] == 200, '32'
1601        assert get_ipv6()['status'] == 404, '32 ipv6'
1602
1603        self.route_match({"source": "127.0.0.0/32"})
1604        assert self.get()['status'] == 404, '32 2'
1605        assert get_ipv6()['status'] == 404, '32 2 ipv6'
1606
1607        self.route_match({"source": "127.0.0.0/31"})
1608        assert self.get()['status'] == 200, '31'
1609        assert get_ipv6()['status'] == 404, '31 ipv6'
1610
1611        self.route_match({"source": "0.0.0.0/1"})
1612        assert self.get()['status'] == 200, '1'
1613        assert get_ipv6()['status'] == 404, '1 ipv6'
1614
1615        self.route_match({"source": "0.0.0.0/0"})
1616        assert self.get()['status'] == 200, '0'
1617        assert get_ipv6()['status'] == 404, '0 ipv6'
1618
1619    def test_routes_source_cidr_ipv6(self):
1620        assert 'success' in self.conf(
1621            {
1622                "[::1]:7080": {"pass": "routes"},
1623                "127.0.0.1:7081": {"pass": "routes"},
1624            },
1625            'listeners',
1626        ), 'source listeners configure'
1627
1628        self.route_match({"source": "::1/128"})
1629        assert self.get(sock_type='ipv6')['status'] == 200, '128'
1630        assert self.get(port=7081)['status'] == 404, '128 ipv4'
1631
1632        self.route_match({"source": "::0/128"})
1633        assert self.get(sock_type='ipv6')['status'] == 404, '128 2'
1634        assert self.get(port=7081)['status'] == 404, '128 ipv4'
1635
1636        self.route_match({"source": "::0/127"})
1637        assert self.get(sock_type='ipv6')['status'] == 200, '127'
1638        assert self.get(port=7081)['status'] == 404, '127 ipv4'
1639
1640        self.route_match({"source": "::0/32"})
1641        assert self.get(sock_type='ipv6')['status'] == 200, '32'
1642        assert self.get(port=7081)['status'] == 404, '32 ipv4'
1643
1644        self.route_match({"source": "::0/1"})
1645        assert self.get(sock_type='ipv6')['status'] == 200, '1'
1646        assert self.get(port=7081)['status'] == 404, '1 ipv4'
1647
1648        self.route_match({"source": "::/0"})
1649        assert self.get(sock_type='ipv6')['status'] == 200, '0'
1650        assert self.get(port=7081)['status'] == 404, '0 ipv4'
1651
1652    def test_routes_source_unix(self):
1653        addr = self.temp_dir + '/sock'
1654
1655        assert 'success' in self.conf(
1656            {"unix:" + addr: {"pass": "routes"}}, 'listeners'
1657        ), 'source listeners configure'
1658
1659        self.route_match({"source": "!0.0.0.0/0"})
1660        assert (
1661            self.get(sock_type='unix', addr=addr)['status'] == 200
1662        ), 'unix ipv4'
1663
1664        self.route_match({"source": "!::/0"})
1665        assert (
1666            self.get(sock_type='unix', addr=addr)['status'] == 200
1667        ), 'unix ipv6'
1668
1669    def test_routes_match_source(self):
1670        self.route_match({"source": "::"})
1671        self.route_match(
1672            {
1673                "source": [
1674                    "127.0.0.1",
1675                    "192.168.0.10:8080",
1676                    "192.168.0.11:8080-8090",
1677                ]
1678            }
1679        )
1680        self.route_match(
1681            {
1682                "source": [
1683                    "10.0.0.0/8",
1684                    "10.0.0.0/7:1000",
1685                    "10.0.0.0/32:8080-8090",
1686                ]
1687            }
1688        )
1689        self.route_match(
1690            {
1691                "source": [
1692                    "10.0.0.0-10.0.0.1",
1693                    "10.0.0.0-11.0.0.0:1000",
1694                    "127.0.0.0-127.0.0.255:8080-8090",
1695                ]
1696            }
1697        )
1698        self.route_match(
1699            {"source": ["2001::", "[2002::]:8000", "[2003::]:8080-8090"]}
1700        )
1701        self.route_match(
1702            {
1703                "source": [
1704                    "2001::-200f:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
1705                    "[fe08::-feff::]:8000",
1706                    "[fff0::-fff0::10]:8080-8090",
1707                ]
1708            }
1709        )
1710        self.route_match(
1711            {
1712                "source": [
1713                    "2001::/16",
1714                    "[0ff::/64]:8000",
1715                    "[fff0:abcd:ffff:ffff:ffff::/128]:8080-8090",
1716                ]
1717            }
1718        )
1719        self.route_match({"source": "*:0-65535"})
1720        assert self.get()['status'] == 200, 'source any'
1721
1722    def test_routes_match_source_invalid(self):
1723        self.route_match_invalid({"source": "127"})
1724        self.route_match_invalid({"source": "256.0.0.1"})
1725        self.route_match_invalid({"source": "127.0.0."})
1726        self.route_match_invalid({"source": " 127.0.0.1"})
1727        self.route_match_invalid({"source": "127.0.0.1:"})
1728        self.route_match_invalid({"source": "127.0.0.1/"})
1729        self.route_match_invalid({"source": "11.0.0.0/33"})
1730        self.route_match_invalid({"source": "11.0.0.0/65536"})
1731        self.route_match_invalid({"source": "11.0.0.0-10.0.0.0"})
1732        self.route_match_invalid({"source": "11.0.0.0:3000-2000"})
1733        self.route_match_invalid({"source": ["11.0.0.0:3000-2000"]})
1734        self.route_match_invalid({"source": "[2001::]:3000-2000"})
1735        self.route_match_invalid({"source": "2001::-2000::"})
1736        self.route_match_invalid({"source": "2001::/129"})
1737        self.route_match_invalid({"source": "::FFFFF"})
1738        self.route_match_invalid({"source": "[::1]:"})
1739        self.route_match_invalid({"source": "[:::]:7080"})
1740        self.route_match_invalid({"source": "*:"})
1741        self.route_match_invalid({"source": "*:1-a"})
1742        self.route_match_invalid({"source": "*:65536"})
1743
1744    def test_routes_match_destination(self):
1745        assert 'success' in self.conf(
1746            {"*:7080": {"pass": "routes"}, "*:7081": {"pass": "routes"}},
1747            'listeners',
1748        ), 'listeners configure'
1749
1750        self.route_match({"destination": "*:7080"})
1751        assert self.get()['status'] == 200, 'dest'
1752        assert self.get(port=7081)['status'] == 404, 'dest 2'
1753
1754        self.route_match({"destination": ["127.0.0.1:7080"]})
1755        assert self.get()['status'] == 200, 'dest 3'
1756        assert self.get(port=7081)['status'] == 404, 'dest 4'
1757
1758        self.route_match({"destination": "!*:7080"})
1759        assert self.get()['status'] == 404, 'dest neg'
1760        assert self.get(port=7081)['status'] == 200, 'dest neg 2'
1761
1762        self.route_match({"destination": ['!*:7080', '!*:7081']})
1763        assert self.get()['status'] == 404, 'dest neg 3'
1764        assert self.get(port=7081)['status'] == 404, 'dest neg 4'
1765
1766        self.route_match({"destination": ['!*:7081', '!*:7082']})
1767        assert self.get()['status'] == 200, 'dest neg 5'
1768
1769        self.route_match({"destination": ['*:7080', '!*:7080']})
1770        assert self.get()['status'] == 404, 'dest neg 6'
1771
1772        self.route_match(
1773            {"destination": ['127.0.0.1:7080', '*:7081', '!*:7080']}
1774        )
1775        assert self.get()['status'] == 404, 'dest neg 7'
1776        assert self.get(port=7081)['status'] == 200, 'dest neg 8'
1777
1778        self.route_match({"destination": ['!*:7081', '!*:7082', '*:7083']})
1779        assert self.get()['status'] == 404, 'dest neg 9'
1780
1781        self.route_match(
1782            {"destination": ['*:7081', '!127.0.0.1:7080', '*:7080']}
1783        )
1784        assert self.get()['status'] == 404, 'dest neg 10'
1785        assert self.get(port=7081)['status'] == 200, 'dest neg 11'
1786
1787        assert 'success' in self.conf_delete(
1788            'routes/0/match/destination/0'
1789        ), 'remove destination rule'
1790        assert self.get()['status'] == 404, 'dest neg 12'
1791        assert self.get(port=7081)['status'] == 404, 'dest neg 13'
1792
1793        assert 'success' in self.conf_delete(
1794            'routes/0/match/destination/0'
1795        ), 'remove destination rule 2'
1796        assert self.get()['status'] == 200, 'dest neg 14'
1797        assert self.get(port=7081)['status'] == 404, 'dest neg 15'
1798
1799        assert 'success' in self.conf_post(
1800            "\"!127.0.0.1\"", 'routes/0/match/destination'
1801        ), 'add destination rule'
1802        assert self.get()['status'] == 404, 'dest neg 16'
1803        assert self.get(port=7081)['status'] == 404, 'dest neg 17'
1804
1805    def test_routes_match_destination_proxy(self):
1806        assert 'success' in self.conf(
1807            {
1808                "listeners": {
1809                    "*:7080": {"pass": "routes/first"},
1810                    "*:7081": {"pass": "routes/second"},
1811                },
1812                "routes": {
1813                    "first": [{"action": {"proxy": "http://127.0.0.1:7081"}}],
1814                    "second": [
1815                        {
1816                            "match": {"destination": ["127.0.0.1:7081"]},
1817                            "action": {"return": 200},
1818                        }
1819                    ],
1820                },
1821                "applications": {},
1822            }
1823        ), 'proxy configure'
1824
1825        assert self.get()['status'] == 200, 'proxy'
1826