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'] == '', 'GET variables 2' 202 assert resp['headers']['X-Var-3'] == '', 'GET variables 3' 203 assert resp['headers']['X-Var-4'] == 'not set', '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'] == '', 'POST variables 2' 218 assert resp['headers']['X-Var-3'] == 'not set', '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