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