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