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