xref: /unit/test/test_php_application.py (revision 1381:6c76371a7a75)
1import os
2import re
3import shutil
4import unittest
5from unit.applications.lang.php import TestApplicationPHP
6
7class TestPHPApplication(TestApplicationPHP):
8    prerequisites = {'modules': ['php']}
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        (resp, sock) = self.post(
187            headers={
188                'Host': 'localhost',
189                'Connection': 'keep-alive',
190                'Content-Type': 'text/html',
191            },
192            start=True,
193            body='0123456789' * 500,
194            read_timeout=1,
195        )
196
197        self.assertEqual(resp['body'], '0123456789' * 500, 'keep-alive 1')
198
199        resp = self.post(
200            headers={
201                'Host': 'localhost',
202                'Connection': 'close',
203                'Content-Type': 'text/html',
204            },
205            sock=sock,
206            body='0123456789',
207        )
208
209        self.assertEqual(resp['body'], '0123456789', 'keep-alive 2')
210
211    def test_php_application_conditional(self):
212        self.load('conditional')
213
214        self.assertRegex(self.get()['body'], r'True', 'conditional true')
215        self.assertRegex(self.post()['body'], r'False', 'conditional false')
216
217    def test_php_application_get_variables(self):
218        self.load('get_variables')
219
220        resp = self.get(url='/?var1=val1&var2=&var3')
221        self.assertEqual(resp['headers']['X-Var-1'], 'val1', 'GET variables')
222        self.assertEqual(resp['headers']['X-Var-2'], '1', 'GET variables 2')
223        self.assertEqual(resp['headers']['X-Var-3'], '1', 'GET variables 3')
224        self.assertEqual(resp['headers']['X-Var-4'], '', 'GET variables 4')
225
226    def test_php_application_post_variables(self):
227        self.load('post_variables')
228
229        resp = self.post(
230            headers={
231                'Content-Type': 'application/x-www-form-urlencoded',
232                'Host': 'localhost',
233                'Connection': 'close',
234            },
235            body='var1=val1&var2=',
236        )
237        self.assertEqual(resp['headers']['X-Var-1'], 'val1', 'POST variables')
238        self.assertEqual(resp['headers']['X-Var-2'], '1', 'POST variables 2')
239        self.assertEqual(resp['headers']['X-Var-3'], '', 'POST variables 3')
240
241    def test_php_application_cookies(self):
242        self.load('cookies')
243
244        resp = self.get(
245            headers={
246                'Cookie': 'var=val; var2=val2',
247                'Host': 'localhost',
248                'Connection': 'close',
249            }
250        )
251
252        self.assertEqual(resp['headers']['X-Cookie-1'], 'val', 'cookie')
253        self.assertEqual(resp['headers']['X-Cookie-2'], 'val2', 'cookie')
254
255    def test_php_application_ini_precision(self):
256        self.load('ini_precision')
257
258        self.assertNotEqual(
259            self.get()['headers']['X-Precision'], '4', 'ini value default'
260        )
261
262        self.conf(
263            {"file": "ini/php.ini"}, 'applications/ini_precision/options'
264        )
265
266        self.assertEqual(
267            self.get()['headers']['X-File'],
268            self.current_dir + '/php/ini_precision/ini/php.ini',
269            'ini file',
270        )
271        self.assertEqual(
272            self.get()['headers']['X-Precision'], '4', 'ini value'
273        )
274
275    @unittest.skip('not yet')
276    def test_php_application_ini_admin_user(self):
277        self.load('ini_precision')
278
279        self.assertIn(
280            'error',
281            self.conf(
282                {"user": {"precision": "4"}, "admin": {"precision": "5"}},
283                'applications/ini_precision/options',
284            ),
285            'ini admin user',
286        )
287
288    def test_php_application_ini_admin(self):
289        self.load('ini_precision')
290
291        self.conf(
292            {"file": "php.ini", "admin": {"precision": "5"}},
293            'applications/ini_precision/options',
294        )
295
296        self.assertEqual(
297            self.get()['headers']['X-Precision'], '5', 'ini value admin'
298        )
299
300    def test_php_application_ini_user(self):
301        self.load('ini_precision')
302
303        self.conf(
304            {"file": "php.ini", "user": {"precision": "5"}},
305            'applications/ini_precision/options',
306        )
307
308        self.assertEqual(
309            self.get()['headers']['X-Precision'], '5', 'ini value user'
310        )
311
312    def test_php_application_ini_user_2(self):
313        self.load('ini_precision')
314
315        self.conf(
316            {"file": "ini/php.ini"}, 'applications/ini_precision/options'
317        )
318
319        self.assertEqual(
320            self.get()['headers']['X-Precision'], '4', 'ini user file'
321        )
322
323        self.conf(
324            {"precision": "5"}, 'applications/ini_precision/options/user'
325        )
326
327        self.assertEqual(
328            self.get()['headers']['X-Precision'], '5', 'ini value user'
329        )
330
331    def test_php_application_ini_set_admin(self):
332        self.load('ini_precision')
333
334        self.conf(
335            {"admin": {"precision": "5"}}, 'applications/ini_precision/options'
336        )
337
338        self.assertEqual(
339            self.get(url='/?precision=6')['headers']['X-Precision'],
340            '5',
341            'ini set admin',
342        )
343
344    def test_php_application_ini_set_user(self):
345        self.load('ini_precision')
346
347        self.conf(
348            {"user": {"precision": "5"}}, 'applications/ini_precision/options'
349        )
350
351        self.assertEqual(
352            self.get(url='/?precision=6')['headers']['X-Precision'],
353            '6',
354            'ini set user',
355        )
356
357    def test_php_application_ini_repeat(self):
358        self.load('ini_precision')
359
360        self.conf(
361            {"user": {"precision": "5"}}, 'applications/ini_precision/options'
362        )
363
364        self.assertEqual(
365            self.get()['headers']['X-Precision'], '5', 'ini value'
366        )
367
368        self.assertEqual(
369            self.get()['headers']['X-Precision'], '5', 'ini value repeat'
370        )
371
372    def test_php_application_disable_functions_exec(self):
373        self.load('time_exec')
374
375        self.before_disable_functions()
376
377        self.conf(
378            {"admin": {"disable_functions": "exec"}},
379            'applications/time_exec/options',
380        )
381
382        body = self.get()['body']
383
384        self.assertRegex(body, r'time: \d+', 'disable_functions time')
385        self.assertNotRegex(body, r'exec: \/\w+', 'disable_functions exec')
386
387    def test_php_application_disable_functions_comma(self):
388        self.load('time_exec')
389
390        self.before_disable_functions()
391
392        self.conf(
393            {"admin": {"disable_functions": "exec,time"}},
394            'applications/time_exec/options',
395        )
396
397        body = self.get()['body']
398
399        self.assertNotRegex(body, r'time: \d+', 'disable_functions comma time')
400        self.assertNotRegex(
401            body, r'exec: \/\w+', 'disable_functions comma exec'
402        )
403
404    def test_php_application_disable_functions_space(self):
405        self.load('time_exec')
406
407        self.before_disable_functions()
408
409        self.conf(
410            {"admin": {"disable_functions": "exec time"}},
411            'applications/time_exec/options',
412        )
413
414        body = self.get()['body']
415
416        self.assertNotRegex(body, r'time: \d+', 'disable_functions space time')
417        self.assertNotRegex(
418            body, r'exec: \/\w+', 'disable_functions space exec'
419        )
420
421    def test_php_application_disable_functions_user(self):
422        self.load('time_exec')
423
424        self.before_disable_functions()
425
426        self.conf(
427            {"user": {"disable_functions": "exec"}},
428            'applications/time_exec/options',
429        )
430
431        body = self.get()['body']
432
433        self.assertRegex(body, r'time: \d+', 'disable_functions user time')
434        self.assertNotRegex(
435            body, r'exec: \/\w+', 'disable_functions user exec'
436        )
437
438    def test_php_application_disable_functions_nonexistent(self):
439        self.load('time_exec')
440
441        self.before_disable_functions()
442
443        self.conf(
444            {"admin": {"disable_functions": "blah"}},
445            'applications/time_exec/options',
446        )
447
448        body = self.get()['body']
449
450        self.assertRegex(
451            body, r'time: \d+', 'disable_functions nonexistent time'
452        )
453        self.assertRegex(
454            body, r'exec: \/\w+', 'disable_functions nonexistent exec'
455        )
456
457    def test_php_application_disable_classes(self):
458        self.load('date_time')
459
460        self.assertRegex(
461            self.get()['body'], r'012345', 'disable_classes before'
462        )
463
464        self.conf(
465            {"admin": {"disable_classes": "DateTime"}},
466            'applications/date_time/options',
467        )
468
469        self.assertNotRegex(
470            self.get()['body'], r'012345', 'disable_classes before'
471        )
472
473    def test_php_application_disable_classes_user(self):
474        self.load('date_time')
475
476        self.assertRegex(
477            self.get()['body'], r'012345', 'disable_classes before'
478        )
479
480        self.conf(
481            {"user": {"disable_classes": "DateTime"}},
482            'applications/date_time/options',
483        )
484
485        self.assertNotRegex(
486            self.get()['body'], r'012345', 'disable_classes before'
487        )
488
489    def test_php_application_script(self):
490        self.assertIn(
491            'success',
492            self.conf(
493                {
494                    "listeners": {"*:7080": {"pass": "applications/script"}},
495                    "applications": {
496                        "script": {
497                            "type": "php",
498                            "processes": {"spare": 0},
499                            "root": self.current_dir + "/php/script",
500                            "script": "phpinfo.php",
501                        }
502                    },
503                }
504            ),
505            'configure script',
506        )
507
508        resp = self.get()
509
510        self.assertEqual(resp['status'], 200, 'status')
511        self.assertNotEqual(resp['body'], '', 'body not empty')
512
513    def test_php_application_index_default(self):
514        self.assertIn(
515            'success',
516            self.conf(
517                {
518                    "listeners": {"*:7080": {"pass": "applications/phpinfo"}},
519                    "applications": {
520                        "phpinfo": {
521                            "type": "php",
522                            "processes": {"spare": 0},
523                            "root": self.current_dir + "/php/phpinfo",
524                        }
525                    },
526                }
527            ),
528            'configure index default',
529        )
530
531        resp = self.get()
532
533        self.assertEqual(resp['status'], 200, 'status')
534        self.assertNotEqual(resp['body'], '', 'body not empty')
535
536    def test_php_application_extension_check(self):
537        self.load('phpinfo')
538
539        self.assertNotEqual(
540            self.get(url='/index.wrong')['status'], 200, 'status'
541        )
542
543        new_root = self.testdir + "/php"
544        os.mkdir(new_root)
545        shutil.copy(self.current_dir + '/php/phpinfo/index.wrong', new_root)
546
547        self.assertIn(
548            'success',
549            self.conf(
550                {
551                    "listeners": {"*:7080": {"pass": "applications/phpinfo"}},
552                    "applications": {
553                        "phpinfo": {
554                            "type": "php",
555                            "processes": {"spare": 0},
556                            "root": new_root,
557                            "working_directory": new_root,
558                        }
559                    },
560                }
561            ),
562            'configure new root',
563        )
564
565        resp = self.get()
566        self.assertNotEqual(
567            str(resp['status']) + resp['body'], '200', 'status new root'
568        )
569
570    def run_php_application_cwd_root_tests(self):
571        self.assertIn(
572            'success', self.conf_delete('applications/cwd/working_directory')
573        )
574
575        script_cwd = self.current_dir + '/php/cwd'
576
577        resp = self.get()
578        self.assertEqual(resp['status'], 200, 'status ok')
579        self.assertEqual(resp['body'], script_cwd, 'default cwd')
580
581        self.assertIn(
582            'success',
583            self.conf(
584                '"' + self.current_dir + '"',
585                'applications/cwd/working_directory',
586            ),
587        )
588
589        resp = self.get()
590        self.assertEqual(resp['status'], 200, 'status ok')
591        self.assertEqual(resp['body'], script_cwd, 'wdir cwd')
592
593        resp = self.get(url='/?chdir=/')
594        self.assertEqual(resp['status'], 200, 'status ok')
595        self.assertEqual(resp['body'], '/', 'cwd after chdir')
596
597        # cwd must be restored
598
599        resp = self.get()
600        self.assertEqual(resp['status'], 200, 'status ok')
601        self.assertEqual(resp['body'], script_cwd, 'cwd restored')
602
603        resp = self.get(url='/subdir/')
604        self.assertEqual(
605            resp['body'], script_cwd + '/subdir', 'cwd subdir',
606        )
607
608    def test_php_application_cwd_root(self):
609        self.load('cwd')
610        self.run_php_application_cwd_root_tests()
611
612    def test_php_application_cwd_opcache_disabled(self):
613        self.load('cwd')
614        self.set_opcache('cwd', '0')
615        self.run_php_application_cwd_root_tests()
616
617    def test_php_application_cwd_opcache_enabled(self):
618        self.load('cwd')
619        self.set_opcache('cwd', '1')
620        self.run_php_application_cwd_root_tests()
621
622    def run_php_application_cwd_script_tests(self):
623        self.load('cwd')
624
625        script_cwd = self.current_dir + '/php/cwd'
626
627        self.assertIn(
628            'success', self.conf_delete('applications/cwd/working_directory')
629        )
630
631        self.assertIn(
632            'success', self.conf('"index.php"', 'applications/cwd/script')
633        )
634
635        self.assertEqual(
636            self.get()['body'], script_cwd, 'default cwd',
637        )
638
639        self.assertEqual(
640            self.get(url='/?chdir=/')['body'], '/', 'cwd after chdir',
641        )
642
643        # cwd must be restored
644        self.assertEqual(self.get()['body'], script_cwd, 'cwd restored')
645
646    def test_php_application_cwd_script(self):
647        self.load('cwd')
648        self.run_php_application_cwd_script_tests()
649
650    def test_php_application_cwd_script_opcache_disabled(self):
651        self.load('cwd')
652        self.set_opcache('cwd', '0')
653        self.run_php_application_cwd_script_tests()
654
655    def test_php_application_cwd_script_opcache_enabled(self):
656        self.load('cwd')
657        self.set_opcache('cwd', '1')
658        self.run_php_application_cwd_script_tests()
659
660    def test_php_application_path_relative(self):
661        self.load('open')
662
663        self.assertEqual(self.get()['body'], 'test', 'relative path')
664
665        self.assertNotEqual(
666            self.get(url='/?chdir=/')['body'], 'test', 'relative path w/ chdir'
667        )
668
669        self.assertEqual(self.get()['body'], 'test', 'relative path 2')
670
671
672if __name__ == '__main__':
673    TestPHPApplication.main()
674