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