xref: /unit/test/test_php_application.py (revision 1596:b7e2d4d92624)
1import os
2import pytest
3import re
4import shutil
5import time
6
7from unit.applications.lang.php import TestApplicationPHP
8from conftest import option
9
10class TestPHPApplication(TestApplicationPHP):
11    prerequisites = {'modules': {'php': 'all'}}
12
13    def before_disable_functions(self):
14        body = self.get()['body']
15
16        assert re.search(r'time: \d+', body), 'disable_functions before time'
17        assert re.search(r'exec: \/\w+', body), 'disable_functions before exec'
18
19    def set_opcache(self, app, val):
20        assert 'success' in self.conf(
21            {"admin": {"opcache.enable": val, "opcache.enable_cli": val,},},
22            'applications/' + app + '/options',
23        )
24
25        opcache = self.get()['headers']['X-OPcache']
26
27        if not opcache or opcache == '-1':
28            pytest.skip('opcache is not supported')
29
30        assert opcache == val, 'opcache value'
31
32    def test_php_application_variables(self):
33        self.load('variables')
34
35        body = 'Test body string.'
36
37        resp = self.post(
38            headers={
39                'Host': 'localhost',
40                'Content-Type': 'text/html',
41                'Custom-Header': 'blah',
42                'Connection': 'close',
43            },
44            body=body,
45            url='/index.php/blah?var=val',
46        )
47
48        assert resp['status'] == 200, 'status'
49        headers = resp['headers']
50        header_server = headers.pop('Server')
51        assert re.search(r'Unit/[\d\.]+', header_server), 'server header'
52        assert (
53            headers.pop('Server-Software') == header_server
54        ), 'server software header'
55
56        date = headers.pop('Date')
57        assert date[-4:] == ' GMT', 'date header timezone'
58        assert (
59            abs(self.date_to_sec_epoch(date) - self.sec_epoch()) < 5
60        ), 'date header'
61
62        if 'X-Powered-By' in headers:
63            headers.pop('X-Powered-By')
64
65        headers.pop('Content-type')
66        assert headers == {
67            'Connection': 'close',
68            'Content-Length': str(len(body)),
69            'Request-Method': 'POST',
70            'Path-Info': '/blah',
71            'Request-Uri': '/index.php/blah?var=val',
72            'Http-Host': 'localhost',
73            'Server-Protocol': 'HTTP/1.1',
74            'Custom-Header': 'blah',
75        }, 'headers'
76        assert resp['body'] == body, 'body'
77
78    def test_php_application_query_string(self):
79        self.load('query_string')
80
81        resp = self.get(url='/?var1=val1&var2=val2')
82
83        assert (
84            resp['headers']['Query-String'] == 'var1=val1&var2=val2'
85        ), 'query string'
86
87    def test_php_application_query_string_empty(self):
88        self.load('query_string')
89
90        resp = self.get(url='/?')
91
92        assert resp['status'] == 200, 'query string empty status'
93        assert resp['headers']['Query-String'] == '', 'query string empty'
94
95    def test_php_application_query_string_absent(self):
96        self.load('query_string')
97
98        resp = self.get()
99
100        assert resp['status'] == 200, 'query string absent status'
101        assert resp['headers']['Query-String'] == '', 'query string absent'
102
103    def test_php_application_phpinfo(self):
104        self.load('phpinfo')
105
106        resp = self.get()
107
108        assert resp['status'] == 200, 'status'
109        assert resp['body'] != '', 'body not empty'
110
111    def test_php_application_header_status(self):
112        self.load('header')
113
114        assert (
115            self.get(
116                headers={
117                    'Host': 'localhost',
118                    'Connection': 'close',
119                    'X-Header': 'HTTP/1.1 404 Not Found',
120                }
121            )['status']
122            == 404
123        ), 'status'
124
125        assert (
126            self.get(
127                headers={
128                    'Host': 'localhost',
129                    'Connection': 'close',
130                    'X-Header': 'http/1.1 404 Not Found',
131                }
132            )['status']
133            == 404
134        ), 'status case insensitive'
135
136        assert (
137            self.get(
138                headers={
139                    'Host': 'localhost',
140                    'Connection': 'close',
141                    'X-Header': 'HTTP/ 404 Not Found',
142                }
143            )['status']
144            == 404
145        ), 'status version empty'
146
147    def test_php_application_404(self):
148        self.load('404')
149
150        resp = self.get()
151
152        assert resp['status'] == 404, '404 status'
153        assert re.search(
154            r'<title>404 Not Found</title>', resp['body']
155        ), '404 body'
156
157    def test_php_application_keepalive_body(self):
158        self.load('mirror')
159
160        assert self.get()['status'] == 200, 'init'
161
162        body = '0123456789' * 500
163        (resp, sock) = self.post(
164            headers={
165                'Host': 'localhost',
166                'Connection': 'keep-alive',
167                'Content-Type': 'text/html',
168            },
169            start=True,
170            body=body,
171            read_timeout=1,
172        )
173
174        assert resp['body'] == body, 'keep-alive 1'
175
176        body = '0123456789'
177        resp = self.post(
178            headers={
179                'Host': 'localhost',
180                'Connection': 'close',
181                'Content-Type': 'text/html',
182            },
183            sock=sock,
184            body=body,
185        )
186
187        assert resp['body'] == body, 'keep-alive 2'
188
189    def test_php_application_conditional(self):
190        self.load('conditional')
191
192        assert re.search(r'True', self.get()['body']), 'conditional true'
193        assert re.search(r'False', self.post()['body']), 'conditional false'
194
195    def test_php_application_get_variables(self):
196        self.load('get_variables')
197
198        resp = self.get(url='/?var1=val1&var2=&var3')
199        assert resp['headers']['X-Var-1'] == 'val1', 'GET variables'
200        assert resp['headers']['X-Var-2'] == '1', 'GET variables 2'
201        assert resp['headers']['X-Var-3'] == '1', 'GET variables 3'
202        assert resp['headers']['X-Var-4'] == '', 'GET variables 4'
203
204    def test_php_application_post_variables(self):
205        self.load('post_variables')
206
207        resp = self.post(
208            headers={
209                'Content-Type': 'application/x-www-form-urlencoded',
210                'Host': 'localhost',
211                'Connection': 'close',
212            },
213            body='var1=val1&var2=',
214        )
215        assert resp['headers']['X-Var-1'] == 'val1', 'POST variables'
216        assert resp['headers']['X-Var-2'] == '1', 'POST variables 2'
217        assert resp['headers']['X-Var-3'] == '', 'POST variables 3'
218
219    def test_php_application_cookies(self):
220        self.load('cookies')
221
222        resp = self.get(
223            headers={
224                'Cookie': 'var=val; var2=val2',
225                'Host': 'localhost',
226                'Connection': 'close',
227            }
228        )
229
230        assert resp['headers']['X-Cookie-1'] == 'val', 'cookie'
231        assert resp['headers']['X-Cookie-2'] == 'val2', 'cookie'
232
233    def test_php_application_ini_precision(self):
234        self.load('ini_precision')
235
236        assert self.get()['headers']['X-Precision'] != '4', 'ini value default'
237
238        self.conf(
239            {"file": "ini/php.ini"}, 'applications/ini_precision/options'
240        )
241
242        assert (
243            self.get()['headers']['X-File']
244            == option.test_dir + '/php/ini_precision/ini/php.ini'
245        ), 'ini file'
246        assert self.get()['headers']['X-Precision'] == '4', 'ini value'
247
248    @pytest.mark.skip('not yet')
249    def test_php_application_ini_admin_user(self):
250        self.load('ini_precision')
251
252        assert 'error' in self.conf(
253            {"user": {"precision": "4"}, "admin": {"precision": "5"}},
254            'applications/ini_precision/options',
255        ), 'ini admin user'
256
257    def test_php_application_ini_admin(self):
258        self.load('ini_precision')
259
260        self.conf(
261            {"file": "php.ini", "admin": {"precision": "5"}},
262            'applications/ini_precision/options',
263        )
264
265        assert self.get()['headers']['X-Precision'] == '5', 'ini value admin'
266
267    def test_php_application_ini_user(self):
268        self.load('ini_precision')
269
270        self.conf(
271            {"file": "php.ini", "user": {"precision": "5"}},
272            'applications/ini_precision/options',
273        )
274
275        assert self.get()['headers']['X-Precision'] == '5', 'ini value user'
276
277    def test_php_application_ini_user_2(self):
278        self.load('ini_precision')
279
280        self.conf(
281            {"file": "ini/php.ini"}, 'applications/ini_precision/options'
282        )
283
284        assert self.get()['headers']['X-Precision'] == '4', 'ini user file'
285
286        self.conf(
287            {"precision": "5"}, 'applications/ini_precision/options/user'
288        )
289
290        assert self.get()['headers']['X-Precision'] == '5', 'ini value user'
291
292    def test_php_application_ini_set_admin(self):
293        self.load('ini_precision')
294
295        self.conf(
296            {"admin": {"precision": "5"}}, 'applications/ini_precision/options'
297        )
298
299        assert (
300            self.get(url='/?precision=6')['headers']['X-Precision'] == '5'
301        ), 'ini set admin'
302
303    def test_php_application_ini_set_user(self):
304        self.load('ini_precision')
305
306        self.conf(
307            {"user": {"precision": "5"}}, 'applications/ini_precision/options'
308        )
309
310        assert (
311            self.get(url='/?precision=6')['headers']['X-Precision'] == '6'
312        ), 'ini set user'
313
314    def test_php_application_ini_repeat(self):
315        self.load('ini_precision')
316
317        self.conf(
318            {"user": {"precision": "5"}}, 'applications/ini_precision/options'
319        )
320
321        assert self.get()['headers']['X-Precision'] == '5', 'ini value'
322
323        assert self.get()['headers']['X-Precision'] == '5', 'ini value repeat'
324
325    def test_php_application_disable_functions_exec(self):
326        self.load('time_exec')
327
328        self.before_disable_functions()
329
330        self.conf(
331            {"admin": {"disable_functions": "exec"}},
332            'applications/time_exec/options',
333        )
334
335        body = self.get()['body']
336
337        assert re.search(r'time: \d+', body), 'disable_functions time'
338        assert not re.search(r'exec: \/\w+', body), 'disable_functions exec'
339
340    def test_php_application_disable_functions_comma(self):
341        self.load('time_exec')
342
343        self.before_disable_functions()
344
345        self.conf(
346            {"admin": {"disable_functions": "exec,time"}},
347            'applications/time_exec/options',
348        )
349
350        body = self.get()['body']
351
352        assert not re.search(
353            r'time: \d+', body
354        ), 'disable_functions comma time'
355        assert not re.search(
356            r'exec: \/\w+', body
357        ), 'disable_functions comma exec'
358
359    def test_php_application_disable_functions_space(self):
360        self.load('time_exec')
361
362        self.before_disable_functions()
363
364        self.conf(
365            {"admin": {"disable_functions": "exec time"}},
366            'applications/time_exec/options',
367        )
368
369        body = self.get()['body']
370
371        assert not re.search(
372            r'time: \d+', body
373        ), 'disable_functions space time'
374        assert not re.search(
375            r'exec: \/\w+', body
376        ), 'disable_functions space exec'
377
378    def test_php_application_disable_functions_user(self):
379        self.load('time_exec')
380
381        self.before_disable_functions()
382
383        self.conf(
384            {"user": {"disable_functions": "exec"}},
385            'applications/time_exec/options',
386        )
387
388        body = self.get()['body']
389
390        assert re.search(r'time: \d+', body), 'disable_functions user time'
391        assert not re.search(
392            r'exec: \/\w+', body
393        ), 'disable_functions user exec'
394
395    def test_php_application_disable_functions_nonexistent(self):
396        self.load('time_exec')
397
398        self.before_disable_functions()
399
400        self.conf(
401            {"admin": {"disable_functions": "blah"}},
402            'applications/time_exec/options',
403        )
404
405        body = self.get()['body']
406
407        assert re.search(
408            r'time: \d+', body
409        ), 'disable_functions nonexistent time'
410        assert re.search(
411            r'exec: \/\w+', body
412        ), 'disable_functions nonexistent exec'
413
414    def test_php_application_disable_classes(self):
415        self.load('date_time')
416
417        assert re.search(
418            r'012345', self.get()['body']
419        ), 'disable_classes before'
420
421        self.conf(
422            {"admin": {"disable_classes": "DateTime"}},
423            'applications/date_time/options',
424        )
425
426        assert not re.search(
427            r'012345', self.get()['body']
428        ), 'disable_classes before'
429
430    def test_php_application_disable_classes_user(self):
431        self.load('date_time')
432
433        assert re.search(
434            r'012345', self.get()['body']
435        ), 'disable_classes before'
436
437        self.conf(
438            {"user": {"disable_classes": "DateTime"}},
439            'applications/date_time/options',
440        )
441
442        assert not re.search(
443            r'012345', self.get()['body']
444        ), 'disable_classes before'
445
446    def test_php_application_error_log(self):
447        self.load('error_log')
448
449        assert self.get()['status'] == 200, 'status'
450
451        time.sleep(1)
452
453        assert self.get()['status'] == 200, 'status 2'
454
455        self.stop()
456
457        pattern = r'\d{4}\/\d\d\/\d\d\s\d\d:.+\[notice\].+Error in application'
458
459        assert self.wait_for_record(pattern) is not None, 'errors print'
460
461        with open(self.temp_dir + '/unit.log', 'r', errors='ignore') as f:
462            errs = re.findall(pattern, f.read())
463
464            assert len(errs) == 2, 'error_log count'
465
466            date = errs[0].split('[')[0]
467            date2 = errs[1].split('[')[0]
468            assert date != date2, 'date diff'
469
470    def test_php_application_script(self):
471        assert 'success' in self.conf(
472            {
473                "listeners": {"*:7080": {"pass": "applications/script"}},
474                "applications": {
475                    "script": {
476                        "type": "php",
477                        "processes": {"spare": 0},
478                        "root": option.test_dir + "/php/script",
479                        "script": "phpinfo.php",
480                    }
481                },
482            }
483        ), 'configure script'
484
485        resp = self.get()
486
487        assert resp['status'] == 200, 'status'
488        assert resp['body'] != '', 'body not empty'
489
490    def test_php_application_index_default(self):
491        assert 'success' in self.conf(
492            {
493                "listeners": {"*:7080": {"pass": "applications/phpinfo"}},
494                "applications": {
495                    "phpinfo": {
496                        "type": "php",
497                        "processes": {"spare": 0},
498                        "root": option.test_dir + "/php/phpinfo",
499                    }
500                },
501            }
502        ), 'configure index default'
503
504        resp = self.get()
505
506        assert resp['status'] == 200, 'status'
507        assert resp['body'] != '', 'body not empty'
508
509    def test_php_application_extension_check(self):
510        self.load('phpinfo')
511
512        assert self.get(url='/index.wrong')['status'] != 200, 'status'
513
514        new_root = self.temp_dir + "/php"
515        os.mkdir(new_root)
516        shutil.copy(option.test_dir + '/php/phpinfo/index.wrong', new_root)
517
518        assert 'success' in self.conf(
519            {
520                "listeners": {"*:7080": {"pass": "applications/phpinfo"}},
521                "applications": {
522                    "phpinfo": {
523                        "type": "php",
524                        "processes": {"spare": 0},
525                        "root": new_root,
526                        "working_directory": new_root,
527                    }
528                },
529            }
530        ), 'configure new root'
531
532        resp = self.get()
533        assert str(resp['status']) + resp['body'] != '200', 'status new root'
534
535    def run_php_application_cwd_root_tests(self):
536        assert 'success' in self.conf_delete(
537            'applications/cwd/working_directory'
538        )
539
540        script_cwd = option.test_dir + '/php/cwd'
541
542        resp = self.get()
543        assert resp['status'] == 200, 'status ok'
544        assert resp['body'] == script_cwd, 'default cwd'
545
546        assert 'success' in self.conf(
547            '"' + option.test_dir + '"', 'applications/cwd/working_directory',
548        )
549
550        resp = self.get()
551        assert resp['status'] == 200, 'status ok'
552        assert resp['body'] == script_cwd, 'wdir cwd'
553
554        resp = self.get(url='/?chdir=/')
555        assert resp['status'] == 200, 'status ok'
556        assert resp['body'] == '/', 'cwd after chdir'
557
558        # cwd must be restored
559
560        resp = self.get()
561        assert resp['status'] == 200, 'status ok'
562        assert resp['body'] == script_cwd, 'cwd restored'
563
564        resp = self.get(url='/subdir/')
565        assert resp['body'] == script_cwd + '/subdir', 'cwd subdir'
566
567    def test_php_application_cwd_root(self):
568        self.load('cwd')
569        self.run_php_application_cwd_root_tests()
570
571    def test_php_application_cwd_opcache_disabled(self):
572        self.load('cwd')
573        self.set_opcache('cwd', '0')
574        self.run_php_application_cwd_root_tests()
575
576    def test_php_application_cwd_opcache_enabled(self):
577        self.load('cwd')
578        self.set_opcache('cwd', '1')
579        self.run_php_application_cwd_root_tests()
580
581    def run_php_application_cwd_script_tests(self):
582        self.load('cwd')
583
584        script_cwd = option.test_dir + '/php/cwd'
585
586        assert 'success' in self.conf_delete(
587            'applications/cwd/working_directory'
588        )
589
590        assert 'success' in self.conf('"index.php"', 'applications/cwd/script')
591
592        assert self.get()['body'] == script_cwd, 'default cwd'
593
594        assert self.get(url='/?chdir=/')['body'] == '/', 'cwd after chdir'
595
596        # cwd must be restored
597        assert self.get()['body'] == script_cwd, 'cwd restored'
598
599    def test_php_application_cwd_script(self):
600        self.load('cwd')
601        self.run_php_application_cwd_script_tests()
602
603    def test_php_application_cwd_script_opcache_disabled(self):
604        self.load('cwd')
605        self.set_opcache('cwd', '0')
606        self.run_php_application_cwd_script_tests()
607
608    def test_php_application_cwd_script_opcache_enabled(self):
609        self.load('cwd')
610        self.set_opcache('cwd', '1')
611        self.run_php_application_cwd_script_tests()
612
613    def test_php_application_path_relative(self):
614        self.load('open')
615
616        assert self.get()['body'] == 'test', 'relative path'
617
618        assert (
619            self.get(url='/?chdir=/')['body'] != 'test'
620        ), 'relative path w/ chdir'
621
622        assert self.get()['body'] == 'test', 'relative path 2'
623