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