xref: /unit/test/test_routing.py (revision 1930:fb8b29aac7a1)
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        def check_headers(hds):
1045            hds = dict({"Host": "localhost", "Connection": "close"}, **hds)
1046            assert (
1047                self.get(headers=hds)['status'] == 200
1048            ), 'headers array match'
1049
1050        def check_headers_404(hds):
1051            hds = dict({"Host": "localhost", "Connection": "close"}, **hds)
1052            assert (
1053                self.get(headers=hds)['status'] == 404
1054            ), 'headers array no match'
1055
1056        assert self.get()['status'] == 404, 'match headers array'
1057        check_headers({"x-header1": "foo123"})
1058        check_headers({"x-header2": "bar"})
1059        check_headers({"x-header3": "bar"})
1060        check_headers_404({"x-header1": "bar"})
1061        check_headers({"x-header1": "bar", "x-header4": "foo"})
1062
1063        assert 'success' in self.conf_delete(
1064            'routes/0/match/headers/1'
1065        ), 'match headers array configure 2'
1066
1067        check_headers_404({"x-header2": "bar"})
1068        check_headers({"x-header3": "foo"})
1069
1070    def test_routes_match_arguments(self):
1071        self.route_match({"arguments": {"foo": "bar"}})
1072
1073        assert self.get()['status'] == 404, 'args'
1074        assert self.get(url='/?foo=bar')['status'] == 200, 'args 2'
1075        assert self.get(url='/?foo=bar1')['status'] == 404, 'args 3'
1076        assert self.get(url='/?1foo=bar')['status'] == 404, 'args 4'
1077        assert self.get(url='/?Foo=bar')['status'] == 404, 'case'
1078        assert self.get(url='/?foo=Bar')['status'] == 404, 'case 2'
1079
1080    def test_routes_match_arguments_chars(self):
1081        chars = (
1082            " !\"%23$%25%26'()*%2B,-./0123456789:;<%3D>?@"
1083            "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
1084        )
1085
1086        chars_enc = ""
1087        for h1 in ["2", "3", "4", "5", "6", "7"]:
1088            for h2 in [
1089                "0",
1090                "1",
1091                "2",
1092                "3",
1093                "4",
1094                "5",
1095                "6",
1096                "7",
1097                "8",
1098                "9",
1099                "A",
1100                "B",
1101                "C",
1102                "D",
1103                "E",
1104                "F",
1105            ]:
1106                chars_enc += "%" + h1 + h2
1107        chars_enc = chars_enc[:-3]
1108
1109        def check_args(args, query):
1110            self.route_match({"arguments": args})
1111            assert self.get(url='/?' + query)['status'] == 200
1112
1113        check_args({chars: chars}, chars + '=' + chars)
1114        check_args({chars: chars}, chars + '=' + chars_enc)
1115        check_args({chars: chars}, chars_enc + '=' + chars)
1116        check_args({chars: chars}, chars_enc + '=' + chars_enc)
1117        check_args({chars_enc: chars_enc}, chars + '=' + chars)
1118        check_args({chars_enc: chars_enc}, chars + '=' + chars_enc)
1119        check_args({chars_enc: chars_enc}, chars_enc + '=' + chars)
1120        check_args({chars_enc: chars_enc}, chars_enc + '=' + chars_enc)
1121
1122    def test_routes_match_arguments_empty(self):
1123        self.route_match({"arguments": {}})
1124        assert self.get()['status'] == 200, 'arguments empty'
1125
1126        self.route_match({"arguments": []})
1127        assert self.get()['status'] == 200, 'arguments empty 2'
1128
1129    def test_routes_match_arguments_space(self):
1130        self.route_match({"arguments": {"+fo o%20": "%20b+a r"}})
1131        assert self.get(url='/? fo o = b a r&')['status'] == 200
1132        assert self.get(url='/?+fo+o+=+b+a+r&')['status'] == 200
1133        assert self.get(url='/?%20fo%20o%20=%20b%20a%20r&')['status'] == 200
1134
1135        self.route_match({"arguments": {"%20foo": " bar"}})
1136        assert self.get(url='/? foo= bar')['status'] == 200
1137        assert self.get(url='/?+foo=+bar')['status'] == 200
1138        assert self.get(url='/?%20foo=%20bar')['status'] == 200
1139        assert self.get(url='/?+foo= bar')['status'] == 200
1140        assert self.get(url='/?%20foo=+bar')['status'] == 200
1141
1142    def test_routes_match_arguments_equal(self):
1143        self.route_match({"arguments": {"=": "="}})
1144        assert self.get(url='/?%3D=%3D')['status'] == 200
1145        assert self.get(url='/?%3D==')['status'] == 200
1146        assert self.get(url='/?===')['status'] == 404
1147        assert self.get(url='/?%3D%3D%3D')['status'] == 404
1148        assert self.get(url='/?==%3D')['status'] == 404
1149
1150    def test_routes_match_arguments_enc(self):
1151        self.route_match({"arguments": {"Ю": "н"}})
1152        assert self.get(url='/?%D0%AE=%D0%BD')['status'] == 200
1153        assert self.get(url='/?%d0%ae=%d0%Bd')['status'] == 200
1154
1155    def test_routes_match_arguments_hash(self):
1156        self.route_match({"arguments": {"#": "#"}})
1157        assert self.get(url='/?%23=%23')['status'] == 200
1158        assert self.get(url='/?%23=%23#')['status'] == 200
1159        assert self.get(url='/?#=#')['status'] == 404
1160        assert self.get(url='/?%23=#')['status'] == 404
1161
1162    def test_routes_match_arguments_wildcard(self):
1163        self.route_match({"arguments": {"foo": "*"}})
1164        assert self.get(url='/?foo')['status'] == 200
1165        assert self.get(url='/?foo=')['status'] == 200
1166        assert self.get(url='/?foo=blah')['status'] == 200
1167        assert self.get(url='/?blah=foo')['status'] == 404
1168
1169        self.route_match({"arguments": {"foo": "%25*"}})
1170        assert self.get(url='/?foo=%xx')['status'] == 200
1171
1172        self.route_match({"arguments": {"foo": "%2A*"}})
1173        assert self.get(url='/?foo=*xx')['status'] == 200
1174        assert self.get(url='/?foo=xx')['status'] == 404
1175
1176        self.route_match({"arguments": {"foo": "*%2A"}})
1177        assert self.get(url='/?foo=xx*')['status'] == 200
1178        assert self.get(url='/?foo=xx*x')['status'] == 404
1179
1180        self.route_match({"arguments": {"foo": "1*2"}})
1181        assert self.get(url='/?foo=12')['status'] == 200
1182        assert self.get(url='/?foo=1blah2')['status'] == 200
1183        assert self.get(url='/?foo=1%2A2')['status'] == 200
1184        assert self.get(url='/?foo=x12')['status'] == 404
1185
1186        self.route_match({"arguments": {"foo": "bar*", "%25": "%25"}})
1187        assert self.get(url='/?foo=barxx&%=%')['status'] == 200
1188        assert self.get(url='/?foo=barxx&x%=%')['status'] == 404
1189
1190    def test_routes_match_arguments_negative(self):
1191        self.route_match({"arguments": {"foo": "!"}})
1192        assert self.get(url='/?bar')['status'] == 404
1193        assert self.get(url='/?foo')['status'] == 404
1194        assert self.get(url='/?foo=')['status'] == 404
1195        assert self.get(url='/?foo=%25')['status'] == 200
1196
1197        self.route_match({"arguments": {"foo": "!*"}})
1198        assert self.get(url='/?bar')['status'] == 404
1199        assert self.get(url='/?foo')['status'] == 404
1200        assert self.get(url='/?foo=')['status'] == 404
1201        assert self.get(url='/?foo=blah')['status'] == 404
1202
1203        self.route_match({"arguments": {"foo": "!%25"}})
1204        assert self.get(url='/?foo=blah')['status'] == 200
1205        assert self.get(url='/?foo=%')['status'] == 404
1206
1207        self.route_match({"arguments": {"foo": "%21blah"}})
1208        assert self.get(url='/?foo=%21blah')['status'] == 200
1209        assert self.get(url='/?foo=!blah')['status'] == 200
1210        assert self.get(url='/?foo=bar')['status'] == 404
1211
1212        self.route_match({"arguments": {"foo": "!!%21*a"}})
1213        assert self.get(url='/?foo=blah')['status'] == 200
1214        assert self.get(url='/?foo=!blah')['status'] == 200
1215        assert self.get(url='/?foo=!!a')['status'] == 404
1216        assert self.get(url='/?foo=!!bla')['status'] == 404
1217
1218    def test_routes_match_arguments_percent(self):
1219        self.route_match({"arguments": {"%25": "%25"}})
1220        assert self.get(url='/?%=%')['status'] == 200
1221        assert self.get(url='/?%25=%25')['status'] == 200
1222        assert self.get(url='/?%25=%')['status'] == 200
1223
1224        self.route_match({"arguments": {"%251": "%252"}})
1225        assert self.get(url='/?%1=%2')['status'] == 200
1226        assert self.get(url='/?%251=%252')['status'] == 200
1227        assert self.get(url='/?%251=%2')['status'] == 200
1228
1229        self.route_match({"arguments": {"%25%21%251": "%25%24%252"}})
1230        assert self.get(url='/?%!%1=%$%2')['status'] == 200
1231        assert self.get(url='/?%25!%251=%25$%252')['status'] == 200
1232        assert self.get(url='/?%25!%1=%$%2')['status'] == 200
1233
1234    def test_routes_match_arguments_ampersand(self):
1235        self.route_match({"arguments": {"foo": "&"}})
1236        assert self.get(url='/?foo=%26')['status'] == 200
1237        assert self.get(url='/?foo=%26&')['status'] == 200
1238        assert self.get(url='/?foo=%26%26')['status'] == 404
1239        assert self.get(url='/?foo=&')['status'] == 404
1240
1241        self.route_match({"arguments": {"&": ""}})
1242        assert self.get(url='/?%26=')['status'] == 200
1243        assert self.get(url='/?%26=&')['status'] == 200
1244        assert self.get(url='/?%26=%26')['status'] == 404
1245        assert self.get(url='/?&=')['status'] == 404
1246
1247    def test_routes_match_arguments_complex(self):
1248        self.route_match({"arguments": {"foo": ""}})
1249
1250        assert self.get(url='/?foo')['status'] == 200, 'complex'
1251        assert self.get(url='/?blah=blah&foo=')['status'] == 200, 'complex 2'
1252        assert self.get(url='/?&&&foo&&&')['status'] == 200, 'complex 3'
1253        assert self.get(url='/?foo&foo=bar&foo')['status'] == 404, 'complex 4'
1254        assert self.get(url='/?foo=&foo')['status'] == 200, 'complex 5'
1255        assert self.get(url='/?&=&foo&==&')['status'] == 200, 'complex 6'
1256        assert self.get(url='/?&=&bar&==&')['status'] == 404, 'complex 7'
1257
1258    def test_routes_match_arguments_multiple(self):
1259        self.route_match({"arguments": {"foo": "bar", "blah": "test"}})
1260
1261        assert self.get()['status'] == 404, 'multiple'
1262        assert (
1263            self.get(url='/?foo=bar&blah=test')['status'] == 200
1264        ), 'multiple 2'
1265        assert self.get(url='/?foo=bar&blah')['status'] == 404, 'multiple 3'
1266        assert (
1267            self.get(url='/?foo=bar&blah=tes')['status'] == 404
1268        ), 'multiple 4'
1269        assert (
1270            self.get(url='/?foo=b%61r&bl%61h=t%65st')['status'] == 200
1271        ), 'multiple 5'
1272
1273    def test_routes_match_arguments_multiple_rules(self):
1274        self.route_match({"arguments": {"foo": ["bar", "blah"]}})
1275
1276        assert self.get()['status'] == 404, 'rules'
1277        assert self.get(url='/?foo=bar')['status'] == 200, 'rules 2'
1278        assert self.get(url='/?foo=blah')['status'] == 200, 'rules 3'
1279        assert (
1280            self.get(url='/?foo=blah&foo=bar&foo=blah')['status'] == 200
1281        ), 'rules 4'
1282        assert (
1283            self.get(url='/?foo=blah&foo=bar&foo=')['status'] == 404
1284        ), 'rules 5'
1285
1286    def test_routes_match_arguments_array(self):
1287        self.route_match(
1288            {
1289                "arguments": [
1290                    {"var1": "val1*"},
1291                    {"var2": "val2"},
1292                    {"var3": ["foo", "bar"]},
1293                    {"var1": "bar", "var4": "foo"},
1294                ]
1295            }
1296        )
1297
1298        assert self.get()['status'] == 404, 'arr'
1299        assert self.get(url='/?var1=val123')['status'] == 200, 'arr 2'
1300        assert self.get(url='/?var2=val2')['status'] == 200, 'arr 3'
1301        assert self.get(url='/?var3=bar')['status'] == 200, 'arr 4'
1302        assert self.get(url='/?var1=bar')['status'] == 404, 'arr 5'
1303        assert self.get(url='/?var1=bar&var4=foo')['status'] == 200, 'arr 6'
1304
1305        assert 'success' in self.conf_delete(
1306            'routes/0/match/arguments/1'
1307        ), 'match arguments array configure 2'
1308
1309        assert self.get(url='/?var2=val2')['status'] == 404, 'arr 7'
1310        assert self.get(url='/?var3=foo')['status'] == 200, 'arr 8'
1311
1312    def test_routes_match_arguments_invalid(self):
1313        self.route_match_invalid({"arguments": ["var"]})
1314        self.route_match_invalid({"arguments": [{"var1": {}}]})
1315        self.route_match_invalid({"arguments": {"": "bar"}})
1316        self.route_match_invalid({"arguments": {"foo": "%"}})
1317        self.route_match_invalid({"arguments": {"foo": "%1G"}})
1318        self.route_match_invalid({"arguments": {"%": "bar"}})
1319        self.route_match_invalid({"arguments": {"foo": "%0"}})
1320        self.route_match_invalid({"arguments": {"foo": "%%1F"}})
1321        self.route_match_invalid({"arguments": {"%%1F": ""}})
1322        self.route_match_invalid({"arguments": {"%7%F": ""}})
1323
1324    def test_routes_match_cookies(self):
1325        self.route_match({"cookies": {"foO": "bar"}})
1326
1327        assert self.get()['status'] == 404, 'cookie'
1328        self.cookie('foO=bar', 200)
1329        self.cookie('foO=bar;1', 200)
1330        self.cookie(['foO=bar', 'blah=blah'], 200)
1331        self.cookie('foO=bar; blah=blah', 200)
1332        self.cookie('Foo=bar', 404)
1333        self.cookie('foO=Bar', 404)
1334        self.cookie('foO=bar1', 404)
1335        self.cookie('1foO=bar;', 404)
1336
1337    def test_routes_match_cookies_empty(self):
1338        self.route_match({"cookies": {}})
1339        assert self.get()['status'] == 200, 'cookies empty'
1340
1341        self.route_match({"cookies": []})
1342        assert self.get()['status'] == 200, 'cookies empty 2'
1343
1344    def test_routes_match_cookies_invalid(self):
1345        self.route_match_invalid({"cookies": ["var"]})
1346        self.route_match_invalid({"cookies": [{"foo": {}}]})
1347
1348    def test_routes_match_cookies_multiple(self):
1349        self.route_match({"cookies": {"foo": "bar", "blah": "blah"}})
1350
1351        assert self.get()['status'] == 404, 'multiple'
1352        self.cookie('foo=bar; blah=blah', 200)
1353        self.cookie(['foo=bar', 'blah=blah'], 200)
1354        self.cookie(['foo=bar; blah', 'blah'], 404)
1355        self.cookie(['foo=bar; blah=test', 'blah=blah'], 404)
1356
1357    def test_routes_match_cookies_multiple_values(self):
1358        self.route_match({"cookies": {"blah": "blah"}})
1359
1360        self.cookie(['blah=blah', 'blah=blah', 'blah=blah'], 200)
1361        self.cookie(['blah=blah', 'blah=test', 'blah=blah'], 404)
1362        self.cookie(['blah=blah; blah=', 'blah=blah'], 404)
1363
1364    def test_routes_match_cookies_multiple_rules(self):
1365        self.route_match({"cookies": {"blah": ["test", "blah"]}})
1366
1367        assert self.get()['status'] == 404, 'multiple rules'
1368        self.cookie('blah=test', 200)
1369        self.cookie('blah=blah', 200)
1370        self.cookie(['blah=blah', 'blah=test', 'blah=blah'], 200)
1371        self.cookie(['blah=blah; blah=test', 'blah=blah'], 200)
1372        self.cookie(['blah=blah', 'blah'], 200)  # invalid cookie
1373
1374    def test_routes_match_cookies_array(self):
1375        self.route_match(
1376            {
1377                "cookies": [
1378                    {"var1": "val1*"},
1379                    {"var2": "val2"},
1380                    {"var3": ["foo", "bar"]},
1381                    {"var1": "bar", "var4": "foo"},
1382                ]
1383            }
1384        )
1385
1386        assert self.get()['status'] == 404, 'cookies array'
1387        self.cookie('var1=val123', 200)
1388        self.cookie('var2=val2', 200)
1389        self.cookie(' var2=val2 ', 200)
1390        self.cookie('var3=bar', 200)
1391        self.cookie('var3=bar;', 200)
1392        self.cookie('var1=bar', 404)
1393        self.cookie('var1=bar; var4=foo;', 200)
1394        self.cookie(['var1=bar', 'var4=foo'], 200)
1395
1396        assert 'success' in self.conf_delete(
1397            'routes/0/match/cookies/1'
1398        ), 'match cookies array configure 2'
1399
1400        self.cookie('var2=val2', 404)
1401        self.cookie('var3=foo', 200)
1402
1403    def test_routes_match_scheme(self):
1404        self.route_match({"scheme": "http"})
1405        self.route_match({"scheme": "https"})
1406        self.route_match({"scheme": "HtTp"})
1407        self.route_match({"scheme": "HtTpS"})
1408
1409    def test_routes_match_scheme_invalid(self):
1410        self.route_match_invalid({"scheme": ["http"]})
1411        self.route_match_invalid({"scheme": "ftp"})
1412        self.route_match_invalid({"scheme": "ws"})
1413        self.route_match_invalid({"scheme": "*"})
1414        self.route_match_invalid({"scheme": ""})
1415
1416    def test_routes_source_port(self):
1417        def sock_port():
1418            _, sock = self.http(b'', start=True, raw=True, no_recv=True)
1419            port = sock.getsockname()[1]
1420            return (sock, port)
1421
1422        sock, port = sock_port()
1423        sock2, port2 = sock_port()
1424
1425        self.route_match({"source": "127.0.0.1:" + str(port)})
1426        assert self.get(sock=sock)['status'] == 200, 'exact'
1427        assert self.get(sock=sock2)['status'] == 404, 'exact 2'
1428
1429        sock, port = sock_port()
1430        sock2, port2 = sock_port()
1431
1432        self.route_match({"source": "!127.0.0.1:" + str(port)})
1433        assert self.get(sock=sock)['status'] == 404, 'negative'
1434        assert self.get(sock=sock2)['status'] == 200, 'negative 2'
1435
1436        sock, port = sock_port()
1437        sock2, port2 = sock_port()
1438
1439        self.route_match({"source": ["*:" + str(port), "!127.0.0.1"]})
1440        assert self.get(sock=sock)['status'] == 404, 'negative 3'
1441        assert self.get(sock=sock2)['status'] == 404, 'negative 4'
1442
1443        sock, port = sock_port()
1444        sock2, port2 = sock_port()
1445
1446        self.route_match(
1447            {"source": "127.0.0.1:" + str(port) + "-" + str(port)}
1448        )
1449        assert self.get(sock=sock)['status'] == 200, 'range single'
1450        assert self.get(sock=sock2)['status'] == 404, 'range single 2'
1451
1452        socks = [
1453            sock_port(),
1454            sock_port(),
1455            sock_port(),
1456            sock_port(),
1457            sock_port(),
1458        ]
1459        socks.sort(key=lambda sock: sock[1])
1460
1461        self.route_match(
1462            {
1463                "source": "127.0.0.1:"
1464                + str(socks[1][1])  # second port number
1465                + "-"
1466                + str(socks[3][1])  # fourth port number
1467            }
1468        )
1469        assert self.get(sock=socks[0][0])['status'] == 404, 'range'
1470        assert self.get(sock=socks[1][0])['status'] == 200, 'range 2'
1471        assert self.get(sock=socks[2][0])['status'] == 200, 'range 3'
1472        assert self.get(sock=socks[3][0])['status'] == 200, 'range 4'
1473        assert self.get(sock=socks[4][0])['status'] == 404, 'range 5'
1474
1475        socks = [
1476            sock_port(),
1477            sock_port(),
1478            sock_port(),
1479        ]
1480        socks.sort(key=lambda sock: sock[1])
1481
1482        self.route_match(
1483            {
1484                "source": [
1485                    "127.0.0.1:" + str(socks[0][1]),
1486                    "127.0.0.1:" + str(socks[2][1]),
1487                ]
1488            }
1489        )
1490        assert self.get(sock=socks[0][0])['status'] == 200, 'array'
1491        assert self.get(sock=socks[1][0])['status'] == 404, 'array 2'
1492        assert self.get(sock=socks[2][0])['status'] == 200, 'array 3'
1493
1494    def test_routes_source_addr(self):
1495        assert 'success' in self.conf(
1496            {"*:7080": {"pass": "routes"}, "[::1]:7081": {"pass": "routes"},},
1497            'listeners',
1498        ), 'source listeners configure'
1499
1500        def get_ipv6():
1501            return self.get(sock_type='ipv6', port=7081)
1502
1503        self.route_match({"source": "127.0.0.1"})
1504        assert self.get()['status'] == 200, 'exact'
1505        assert get_ipv6()['status'] == 404, 'exact ipv6'
1506
1507        self.route_match({"source": ["127.0.0.1"]})
1508        assert self.get()['status'] == 200, 'exact 2'
1509        assert get_ipv6()['status'] == 404, 'exact 2 ipv6'
1510
1511        self.route_match({"source": "!127.0.0.1"})
1512        assert self.get()['status'] == 404, 'exact neg'
1513        assert get_ipv6()['status'] == 200, 'exact neg ipv6'
1514
1515        self.route_match({"source": "127.0.0.2"})
1516        assert self.get()['status'] == 404, 'exact 3'
1517        assert get_ipv6()['status'] == 404, 'exact 3 ipv6'
1518
1519        self.route_match({"source": "127.0.0.1-127.0.0.1"})
1520        assert self.get()['status'] == 200, 'range single'
1521        assert get_ipv6()['status'] == 404, 'range single ipv6'
1522
1523        self.route_match({"source": "127.0.0.2-127.0.0.2"})
1524        assert self.get()['status'] == 404, 'range single 2'
1525        assert get_ipv6()['status'] == 404, 'range single 2 ipv6'
1526
1527        self.route_match({"source": "127.0.0.2-127.0.0.3"})
1528        assert self.get()['status'] == 404, 'range'
1529        assert get_ipv6()['status'] == 404, 'range ipv6'
1530
1531        self.route_match({"source": "127.0.0.1-127.0.0.2"})
1532        assert self.get()['status'] == 200, 'range 2'
1533        assert get_ipv6()['status'] == 404, 'range 2 ipv6'
1534
1535        self.route_match({"source": "127.0.0.0-127.0.0.2"})
1536        assert self.get()['status'] == 200, 'range 3'
1537        assert get_ipv6()['status'] == 404, 'range 3 ipv6'
1538
1539        self.route_match({"source": "127.0.0.0-127.0.0.1"})
1540        assert self.get()['status'] == 200, 'range 4'
1541        assert get_ipv6()['status'] == 404, 'range 4 ipv6'
1542
1543        self.route_match({"source": "126.0.0.0-127.0.0.0"})
1544        assert self.get()['status'] == 404, 'range 5'
1545        assert get_ipv6()['status'] == 404, 'range 5 ipv6'
1546
1547        self.route_match({"source": "126.126.126.126-127.0.0.2"})
1548        assert self.get()['status'] == 200, 'range 6'
1549        assert get_ipv6()['status'] == 404, 'range 6 ipv6'
1550
1551    def test_routes_source_ipv6(self):
1552        assert 'success' in self.conf(
1553            {
1554                "[::1]:7080": {"pass": "routes"},
1555                "127.0.0.1:7081": {"pass": "routes"},
1556            },
1557            'listeners',
1558        ), 'source listeners configure'
1559
1560        self.route_match({"source": "::1"})
1561        assert self.get(sock_type='ipv6')['status'] == 200, 'exact'
1562        assert self.get(port=7081)['status'] == 404, 'exact ipv4'
1563
1564        self.route_match({"source": ["::1"]})
1565        assert self.get(sock_type='ipv6')['status'] == 200, 'exact 2'
1566        assert self.get(port=7081)['status'] == 404, 'exact 2 ipv4'
1567
1568        self.route_match({"source": "!::1"})
1569        assert self.get(sock_type='ipv6')['status'] == 404, 'exact neg'
1570        assert self.get(port=7081)['status'] == 200, 'exact neg ipv4'
1571
1572        self.route_match({"source": "::2"})
1573        assert self.get(sock_type='ipv6')['status'] == 404, 'exact 3'
1574        assert self.get(port=7081)['status'] == 404, 'exact 3 ipv4'
1575
1576        self.route_match({"source": "::1-::1"})
1577        assert self.get(sock_type='ipv6')['status'] == 200, 'range'
1578        assert self.get(port=7081)['status'] == 404, 'range ipv4'
1579
1580        self.route_match({"source": "::2-::2"})
1581        assert self.get(sock_type='ipv6')['status'] == 404, 'range 2'
1582        assert self.get(port=7081)['status'] == 404, 'range 2 ipv4'
1583
1584        self.route_match({"source": "::2-::3"})
1585        assert self.get(sock_type='ipv6')['status'] == 404, 'range 3'
1586        assert self.get(port=7081)['status'] == 404, 'range 3 ipv4'
1587
1588        self.route_match({"source": "::1-::2"})
1589        assert self.get(sock_type='ipv6')['status'] == 200, 'range 4'
1590        assert self.get(port=7081)['status'] == 404, 'range 4 ipv4'
1591
1592        self.route_match({"source": "::0-::2"})
1593        assert self.get(sock_type='ipv6')['status'] == 200, 'range 5'
1594        assert self.get(port=7081)['status'] == 404, 'range 5 ipv4'
1595
1596        self.route_match({"source": "::0-::1"})
1597        assert self.get(sock_type='ipv6')['status'] == 200, 'range 6'
1598        assert self.get(port=7081)['status'] == 404, 'range 6 ipv4'
1599
1600    def test_routes_source_cidr(self):
1601        assert 'success' in self.conf(
1602            {"*:7080": {"pass": "routes"}, "[::1]:7081": {"pass": "routes"},},
1603            'listeners',
1604        ), 'source listeners configure'
1605
1606        def get_ipv6():
1607            return self.get(sock_type='ipv6', port=7081)
1608
1609        self.route_match({"source": "127.0.0.1/32"})
1610        assert self.get()['status'] == 200, '32'
1611        assert get_ipv6()['status'] == 404, '32 ipv6'
1612
1613        self.route_match({"source": "127.0.0.0/32"})
1614        assert self.get()['status'] == 404, '32 2'
1615        assert get_ipv6()['status'] == 404, '32 2 ipv6'
1616
1617        self.route_match({"source": "127.0.0.0/31"})
1618        assert self.get()['status'] == 200, '31'
1619        assert get_ipv6()['status'] == 404, '31 ipv6'
1620
1621        self.route_match({"source": "0.0.0.0/1"})
1622        assert self.get()['status'] == 200, '1'
1623        assert get_ipv6()['status'] == 404, '1 ipv6'
1624
1625        self.route_match({"source": "0.0.0.0/0"})
1626        assert self.get()['status'] == 200, '0'
1627        assert get_ipv6()['status'] == 404, '0 ipv6'
1628
1629    def test_routes_source_cidr_ipv6(self):
1630        assert 'success' in self.conf(
1631            {
1632                "[::1]:7080": {"pass": "routes"},
1633                "127.0.0.1:7081": {"pass": "routes"},
1634            },
1635            'listeners',
1636        ), 'source listeners configure'
1637
1638        self.route_match({"source": "::1/128"})
1639        assert self.get(sock_type='ipv6')['status'] == 200, '128'
1640        assert self.get(port=7081)['status'] == 404, '128 ipv4'
1641
1642        self.route_match({"source": "::0/128"})
1643        assert self.get(sock_type='ipv6')['status'] == 404, '128 2'
1644        assert self.get(port=7081)['status'] == 404, '128 ipv4'
1645
1646        self.route_match({"source": "::0/127"})
1647        assert self.get(sock_type='ipv6')['status'] == 200, '127'
1648        assert self.get(port=7081)['status'] == 404, '127 ipv4'
1649
1650        self.route_match({"source": "::0/32"})
1651        assert self.get(sock_type='ipv6')['status'] == 200, '32'
1652        assert self.get(port=7081)['status'] == 404, '32 ipv4'
1653
1654        self.route_match({"source": "::0/1"})
1655        assert self.get(sock_type='ipv6')['status'] == 200, '1'
1656        assert self.get(port=7081)['status'] == 404, '1 ipv4'
1657
1658        self.route_match({"source": "::/0"})
1659        assert self.get(sock_type='ipv6')['status'] == 200, '0'
1660        assert self.get(port=7081)['status'] == 404, '0 ipv4'
1661
1662    def test_routes_source_unix(self, temp_dir):
1663        addr = temp_dir + '/sock'
1664
1665        assert 'success' in self.conf(
1666            {"unix:" + addr: {"pass": "routes"}}, 'listeners'
1667        ), 'source listeners configure'
1668
1669        self.route_match({"source": "!0.0.0.0/0"})
1670        assert (
1671            self.get(sock_type='unix', addr=addr)['status'] == 200
1672        ), 'unix ipv4'
1673
1674        self.route_match({"source": "!::/0"})
1675        assert (
1676            self.get(sock_type='unix', addr=addr)['status'] == 200
1677        ), 'unix ipv6'
1678
1679    def test_routes_match_source(self):
1680        self.route_match({"source": "::"})
1681        self.route_match(
1682            {
1683                "source": [
1684                    "127.0.0.1",
1685                    "192.168.0.10:8080",
1686                    "192.168.0.11:8080-8090",
1687                ]
1688            }
1689        )
1690        self.route_match(
1691            {
1692                "source": [
1693                    "10.0.0.0/8",
1694                    "10.0.0.0/7:1000",
1695                    "10.0.0.0/32:8080-8090",
1696                ]
1697            }
1698        )
1699        self.route_match(
1700            {
1701                "source": [
1702                    "10.0.0.0-10.0.0.1",
1703                    "10.0.0.0-11.0.0.0:1000",
1704                    "127.0.0.0-127.0.0.255:8080-8090",
1705                ]
1706            }
1707        )
1708        self.route_match(
1709            {"source": ["2001::", "[2002::]:8000", "[2003::]:8080-8090"]}
1710        )
1711        self.route_match(
1712            {
1713                "source": [
1714                    "2001::-200f:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
1715                    "[fe08::-feff::]:8000",
1716                    "[fff0::-fff0::10]:8080-8090",
1717                ]
1718            }
1719        )
1720        self.route_match(
1721            {
1722                "source": [
1723                    "2001::/16",
1724                    "[0ff::/64]:8000",
1725                    "[fff0:abcd:ffff:ffff:ffff::/128]:8080-8090",
1726                ]
1727            }
1728        )
1729        self.route_match({"source": "*:0-65535"})
1730        assert self.get()['status'] == 200, 'source any'
1731
1732    def test_routes_match_source_invalid(self):
1733        self.route_match_invalid({"source": "127"})
1734        self.route_match_invalid({"source": "256.0.0.1"})
1735        self.route_match_invalid({"source": "127.0.0."})
1736        self.route_match_invalid({"source": " 127.0.0.1"})
1737        self.route_match_invalid({"source": "127.0.0.1:"})
1738        self.route_match_invalid({"source": "127.0.0.1/"})
1739        self.route_match_invalid({"source": "11.0.0.0/33"})
1740        self.route_match_invalid({"source": "11.0.0.0/65536"})
1741        self.route_match_invalid({"source": "11.0.0.0-10.0.0.0"})
1742        self.route_match_invalid({"source": "11.0.0.0:3000-2000"})
1743        self.route_match_invalid({"source": ["11.0.0.0:3000-2000"]})
1744        self.route_match_invalid({"source": "[2001::]:3000-2000"})
1745        self.route_match_invalid({"source": "2001::-2000::"})
1746        self.route_match_invalid({"source": "2001::/129"})
1747        self.route_match_invalid({"source": "::FFFFF"})
1748        self.route_match_invalid({"source": "[::1]:"})
1749        self.route_match_invalid({"source": "[:::]:7080"})
1750        self.route_match_invalid({"source": "*:"})
1751        self.route_match_invalid({"source": "*:1-a"})
1752        self.route_match_invalid({"source": "*:65536"})
1753
1754    def test_routes_match_source_none(self):
1755        self.route_match({"source": []})
1756        assert self.get()['status'] == 404, 'source none'
1757
1758    def test_routes_match_destination(self):
1759        assert 'success' in self.conf(
1760            {"*:7080": {"pass": "routes"}, "*:7081": {"pass": "routes"}},
1761            'listeners',
1762        ), 'listeners configure'
1763
1764        self.route_match({"destination": "*:7080"})
1765        assert self.get()['status'] == 200, 'dest'
1766        assert self.get(port=7081)['status'] == 404, 'dest 2'
1767
1768        self.route_match({"destination": ["127.0.0.1:7080"]})
1769        assert self.get()['status'] == 200, 'dest 3'
1770        assert self.get(port=7081)['status'] == 404, 'dest 4'
1771
1772        self.route_match({"destination": "!*:7080"})
1773        assert self.get()['status'] == 404, 'dest neg'
1774        assert self.get(port=7081)['status'] == 200, 'dest neg 2'
1775
1776        self.route_match({"destination": ['!*:7080', '!*:7081']})
1777        assert self.get()['status'] == 404, 'dest neg 3'
1778        assert self.get(port=7081)['status'] == 404, 'dest neg 4'
1779
1780        self.route_match({"destination": ['!*:7081', '!*:7082']})
1781        assert self.get()['status'] == 200, 'dest neg 5'
1782
1783        self.route_match({"destination": ['*:7080', '!*:7080']})
1784        assert self.get()['status'] == 404, 'dest neg 6'
1785
1786        self.route_match(
1787            {"destination": ['127.0.0.1:7080', '*:7081', '!*:7080']}
1788        )
1789        assert self.get()['status'] == 404, 'dest neg 7'
1790        assert self.get(port=7081)['status'] == 200, 'dest neg 8'
1791
1792        self.route_match({"destination": ['!*:7081', '!*:7082', '*:7083']})
1793        assert self.get()['status'] == 404, 'dest neg 9'
1794
1795        self.route_match(
1796            {"destination": ['*:7081', '!127.0.0.1:7080', '*:7080']}
1797        )
1798        assert self.get()['status'] == 404, 'dest neg 10'
1799        assert self.get(port=7081)['status'] == 200, 'dest neg 11'
1800
1801        assert 'success' in self.conf_delete(
1802            'routes/0/match/destination/0'
1803        ), 'remove destination rule'
1804        assert self.get()['status'] == 404, 'dest neg 12'
1805        assert self.get(port=7081)['status'] == 404, 'dest neg 13'
1806
1807        assert 'success' in self.conf_delete(
1808            'routes/0/match/destination/0'
1809        ), 'remove destination rule 2'
1810        assert self.get()['status'] == 200, 'dest neg 14'
1811        assert self.get(port=7081)['status'] == 404, 'dest neg 15'
1812
1813        assert 'success' in self.conf_post(
1814            "\"!127.0.0.1\"", 'routes/0/match/destination'
1815        ), 'add destination rule'
1816        assert self.get()['status'] == 404, 'dest neg 16'
1817        assert self.get(port=7081)['status'] == 404, 'dest neg 17'
1818
1819    def test_routes_match_destination_proxy(self):
1820        assert 'success' in self.conf(
1821            {
1822                "listeners": {
1823                    "*:7080": {"pass": "routes/first"},
1824                    "*:7081": {"pass": "routes/second"},
1825                },
1826                "routes": {
1827                    "first": [{"action": {"proxy": "http://127.0.0.1:7081"}}],
1828                    "second": [
1829                        {
1830                            "match": {"destination": ["127.0.0.1:7081"]},
1831                            "action": {"return": 200},
1832                        }
1833                    ],
1834                },
1835                "applications": {},
1836            }
1837        ), 'proxy configure'
1838
1839        assert self.get()['status'] == 200, 'proxy'
1840