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