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