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