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