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