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