|
8 | 8 | SimpleHTTPRequestHandler
|
9 | 9 | fromhttpimportserver,HTTPStatus
|
10 | 10 |
|
| 11 | +importcontextlib |
11 | 12 | importos
|
12 | 13 | importsocket
|
13 | 14 | importsys
|
|
20 | 21 | importhtml
|
21 | 22 | importhttp,http.client
|
22 | 23 | importurllib.parse
|
| 24 | +importurllib.request |
23 | 25 | importtempfile
|
24 | 26 | importtime
|
25 | 27 | importdatetime
|
|
32 | 34 | fromtest.supportimport (
|
33 | 35 | is_apple,import_helper,os_helper,threading_helper
|
34 | 36 | )
|
| 37 | +fromtest.support.script_helperimportkill_python,spawn_python |
| 38 | +fromtest.support.socket_helperimportfind_unused_port |
35 | 39 |
|
36 | 40 | try:
|
37 | 41 | importssl
|
@@ -1281,6 +1285,256 @@ def test_server_test_ipv4(self, _):
|
1281 | 1285 | self.assertEqual(mock_server.address_family,socket.AF_INET)
|
1282 | 1286 |
|
1283 | 1287 |
|
| 1288 | +classCommandLineTestCase(unittest.TestCase): |
| 1289 | +default_port=8000 |
| 1290 | +default_bind=None |
| 1291 | +default_protocol='HTTP/1.0' |
| 1292 | +default_handler=SimpleHTTPRequestHandler |
| 1293 | +default_server=unittest.mock.ANY |
| 1294 | +tls_cert=certdata_file('ssl_cert.pem') |
| 1295 | +tls_key=certdata_file('ssl_key.pem') |
| 1296 | +tls_password='somepass' |
| 1297 | +tls_cert_options= ['--tls-cert'] |
| 1298 | +tls_key_options= ['--tls-key'] |
| 1299 | +tls_password_options= ['--tls-password-file'] |
| 1300 | +args= { |
| 1301 | +'HandlerClass':default_handler, |
| 1302 | +'ServerClass':default_server, |
| 1303 | +'protocol':default_protocol, |
| 1304 | +'port':default_port, |
| 1305 | +'bind':default_bind, |
| 1306 | +'tls_cert':None, |
| 1307 | +'tls_key':None, |
| 1308 | +'tls_password':None, |
| 1309 | + } |
| 1310 | + |
| 1311 | +defsetUp(self): |
| 1312 | +super().setUp() |
| 1313 | +self.tls_password_file=tempfile.mktemp() |
| 1314 | +withopen(self.tls_password_file,'wb')asf: |
| 1315 | +f.write(self.tls_password.encode()) |
| 1316 | +self.addCleanup(os_helper.unlink,self.tls_password_file) |
| 1317 | + |
| 1318 | +definvoke_httpd(self,*args,stdout=None,stderr=None): |
| 1319 | +stdout=StringIO()ifstdoutisNoneelsestdout |
| 1320 | +stderr=StringIO()ifstderrisNoneelsestderr |
| 1321 | +withcontextlib.redirect_stdout(stdout), \ |
| 1322 | +contextlib.redirect_stderr(stderr): |
| 1323 | +server._main(args) |
| 1324 | +returnstdout.getvalue(),stderr.getvalue() |
| 1325 | + |
| 1326 | +@mock.patch('http.server.test') |
| 1327 | +deftest_port_flag(self,mock_func): |
| 1328 | +ports= [8000,65535] |
| 1329 | +forportinports: |
| 1330 | +withself.subTest(port=port): |
| 1331 | +self.invoke_httpd(str(port)) |
| 1332 | +call_args=self.args|dict(port=port) |
| 1333 | +mock_func.assert_called_once_with(**call_args) |
| 1334 | +mock_func.reset_mock() |
| 1335 | + |
| 1336 | +@mock.patch('http.server.test') |
| 1337 | +deftest_directory_flag(self,mock_func): |
| 1338 | +options= ['-d','--directory'] |
| 1339 | +directories= ['.','/foo','\\bar','/', |
| 1340 | +'C:\\','C:\\foo','C:\\bar', |
| 1341 | +'/home/user','./foo/foo2','D:\\foo\\bar'] |
| 1342 | +forflaginoptions: |
| 1343 | +fordirectoryindirectories: |
| 1344 | +withself.subTest(flag=flag,directory=directory): |
| 1345 | +self.invoke_httpd(flag,directory) |
| 1346 | +mock_func.assert_called_once_with(**self.args) |
| 1347 | +mock_func.reset_mock() |
| 1348 | + |
| 1349 | +@mock.patch('http.server.test') |
| 1350 | +deftest_bind_flag(self,mock_func): |
| 1351 | +options= ['-b','--bind'] |
| 1352 | +bind_addresses= ['localhost','127.0.0.1','::1', |
| 1353 | +'0.0.0.0','8.8.8.8'] |
| 1354 | +forflaginoptions: |
| 1355 | +forbind_addressinbind_addresses: |
| 1356 | +withself.subTest(flag=flag,bind_address=bind_address): |
| 1357 | +self.invoke_httpd(flag,bind_address) |
| 1358 | +call_args=self.args|dict(bind=bind_address) |
| 1359 | +mock_func.assert_called_once_with(**call_args) |
| 1360 | +mock_func.reset_mock() |
| 1361 | + |
| 1362 | +@mock.patch('http.server.test') |
| 1363 | +deftest_protocol_flag(self,mock_func): |
| 1364 | +options= ['-p','--protocol'] |
| 1365 | +protocols= ['HTTP/1.0','HTTP/1.1','HTTP/2.0','HTTP/3.0'] |
| 1366 | +forflaginoptions: |
| 1367 | +forprotocolinprotocols: |
| 1368 | +withself.subTest(flag=flag,protocol=protocol): |
| 1369 | +self.invoke_httpd(flag,protocol) |
| 1370 | +call_args=self.args|dict(protocol=protocol) |
| 1371 | +mock_func.assert_called_once_with(**call_args) |
| 1372 | +mock_func.reset_mock() |
| 1373 | + |
| 1374 | +@unittest.skipIf(sslisNone,"requires ssl") |
| 1375 | +@mock.patch('http.server.test') |
| 1376 | +deftest_tls_cert_and_key_flags(self,mock_func): |
| 1377 | +fortls_cert_optioninself.tls_cert_options: |
| 1378 | +fortls_key_optioninself.tls_key_options: |
| 1379 | +self.invoke_httpd(tls_cert_option,self.tls_cert, |
| 1380 | +tls_key_option,self.tls_key) |
| 1381 | +call_args=self.args| { |
| 1382 | +'tls_cert':self.tls_cert, |
| 1383 | +'tls_key':self.tls_key, |
| 1384 | + } |
| 1385 | +mock_func.assert_called_once_with(**call_args) |
| 1386 | +mock_func.reset_mock() |
| 1387 | + |
| 1388 | +@unittest.skipIf(sslisNone,"requires ssl") |
| 1389 | +@mock.patch('http.server.test') |
| 1390 | +deftest_tls_cert_and_key_and_password_flags(self,mock_func): |
| 1391 | +fortls_cert_optioninself.tls_cert_options: |
| 1392 | +fortls_key_optioninself.tls_key_options: |
| 1393 | +fortls_password_optioninself.tls_password_options: |
| 1394 | +self.invoke_httpd(tls_cert_option, |
| 1395 | +self.tls_cert, |
| 1396 | +tls_key_option, |
| 1397 | +self.tls_key, |
| 1398 | +tls_password_option, |
| 1399 | +self.tls_password_file) |
| 1400 | +call_args=self.args| { |
| 1401 | +'tls_cert':self.tls_cert, |
| 1402 | +'tls_key':self.tls_key, |
| 1403 | +'tls_password':self.tls_password, |
| 1404 | + } |
| 1405 | +mock_func.assert_called_once_with(**call_args) |
| 1406 | +mock_func.reset_mock() |
| 1407 | + |
| 1408 | +@unittest.skipIf(sslisNone,"requires ssl") |
| 1409 | +@mock.patch('http.server.test') |
| 1410 | +deftest_missing_tls_cert_flag(self,mock_func): |
| 1411 | +fortls_key_optioninself.tls_key_options: |
| 1412 | +withself.assertRaises(SystemExit): |
| 1413 | +self.invoke_httpd(tls_key_option,self.tls_key) |
| 1414 | +mock_func.reset_mock() |
| 1415 | + |
| 1416 | +fortls_password_optioninself.tls_password_options: |
| 1417 | +withself.assertRaises(SystemExit): |
| 1418 | +self.invoke_httpd(tls_password_option,self.tls_password) |
| 1419 | +mock_func.reset_mock() |
| 1420 | + |
| 1421 | +@unittest.skipIf(sslisNone,"requires ssl") |
| 1422 | +@mock.patch('http.server.test') |
| 1423 | +deftest_invalid_password_file(self,mock_func): |
| 1424 | +non_existent_file='non_existent_file' |
| 1425 | +fortls_password_optioninself.tls_password_options: |
| 1426 | +fortls_cert_optioninself.tls_cert_options: |
| 1427 | +withself.assertRaises(SystemExit): |
| 1428 | +self.invoke_httpd(tls_cert_option, |
| 1429 | +self.tls_cert, |
| 1430 | +tls_password_option, |
| 1431 | +non_existent_file) |
| 1432 | + |
| 1433 | +@mock.patch('http.server.test') |
| 1434 | +deftest_no_arguments(self,mock_func): |
| 1435 | +self.invoke_httpd() |
| 1436 | +mock_func.assert_called_once_with(**self.args) |
| 1437 | +mock_func.reset_mock() |
| 1438 | + |
| 1439 | +@mock.patch('http.server.test') |
| 1440 | +deftest_help_flag(self,_): |
| 1441 | +options= ['-h','--help'] |
| 1442 | +foroptioninoptions: |
| 1443 | +stdout,stderr=StringIO(),StringIO() |
| 1444 | +withself.assertRaises(SystemExit): |
| 1445 | +self.invoke_httpd(option,stdout=stdout,stderr=stderr) |
| 1446 | +self.assertIn('usage',stdout.getvalue()) |
| 1447 | +self.assertEqual(stderr.getvalue(),'') |
| 1448 | + |
| 1449 | +@mock.patch('http.server.test') |
| 1450 | +deftest_unknown_flag(self,_): |
| 1451 | +stdout,stderr=StringIO(),StringIO() |
| 1452 | +withself.assertRaises(SystemExit): |
| 1453 | +self.invoke_httpd('--unknown-flag',stdout=stdout,stderr=stderr) |
| 1454 | +self.assertEqual(stdout.getvalue(),'') |
| 1455 | +self.assertIn('error',stderr.getvalue()) |
| 1456 | + |
| 1457 | + |
| 1458 | +classCommandLineRunTimeTestCase(unittest.TestCase): |
| 1459 | +served_data=os.urandom(32) |
| 1460 | +served_file_name='served_filename' |
| 1461 | +tls_cert=certdata_file('ssl_cert.pem') |
| 1462 | +tls_key=certdata_file('ssl_key.pem') |
| 1463 | +tls_password='somepass' |
| 1464 | + |
| 1465 | +defsetUp(self): |
| 1466 | +super().setUp() |
| 1467 | +withopen(self.served_file_name,'wb')asf: |
| 1468 | +f.write(self.served_data) |
| 1469 | +self.addCleanup(os_helper.unlink,self.served_file_name) |
| 1470 | +self.tls_password_file=tempfile.mktemp() |
| 1471 | +withopen(self.tls_password_file,'wb')asf: |
| 1472 | +f.write(self.tls_password.encode()) |
| 1473 | +self.addCleanup(os_helper.unlink,self.tls_password_file) |
| 1474 | + |
| 1475 | +deffetch_file(self,path): |
| 1476 | +context=ssl.create_default_context() |
| 1477 | +# allow self-signed certificates |
| 1478 | +context.check_hostname=False |
| 1479 | +context.verify_mode=ssl.CERT_NONE |
| 1480 | +req=urllib.request.Request(path,method='GET') |
| 1481 | +withurllib.request.urlopen(req,context=context)asres: |
| 1482 | +returnres.read() |
| 1483 | + |
| 1484 | +defparse_cli_output(self,output): |
| 1485 | +matches=re.search(r'\((https?)://([^/:]+):(\d+)/?\)',output) |
| 1486 | +ifmatchesisNone: |
| 1487 | +returnNone,None,None |
| 1488 | +returnmatches.group(1),matches.group(2),int(matches.group(3)) |
| 1489 | + |
| 1490 | +defwait_for_server(self,proc,protocol,port,bind,timeout=50): |
| 1491 | +"""Check the server process output. |
| 1492 | +
|
| 1493 | + Return True if the server was successfully started |
| 1494 | + and is listening on the given port and bind address. |
| 1495 | + """ |
| 1496 | +whiletimeout>0: |
| 1497 | +line=proc.stdout.readline() |
| 1498 | +ifnotline: |
| 1499 | +time.sleep(0.1) |
| 1500 | +timeout-=1 |
| 1501 | +continue |
| 1502 | +protocol_,host_,port_=self.parse_cli_output(line) |
| 1503 | +ifnotprotocol_ornothost_ornotport_: |
| 1504 | +time.sleep(0.1) |
| 1505 | +timeout-=1 |
| 1506 | +continue |
| 1507 | +ifprotocol_==protocolandhost_==bindandport_==port: |
| 1508 | +returnTrue |
| 1509 | +break |
| 1510 | +returnFalse |
| 1511 | + |
| 1512 | +deftest_http_client(self): |
| 1513 | +port=find_unused_port() |
| 1514 | +bind='127.0.0.1' |
| 1515 | +proc=spawn_python('-u','-m','http.server',str(port),'-b',bind, |
| 1516 | +bufsize=1,text=True) |
| 1517 | +self.addCleanup(kill_python,proc) |
| 1518 | +self.addCleanup(proc.terminate) |
| 1519 | +self.assertTrue(self.wait_for_server(proc,'http',port,bind)) |
| 1520 | +res=self.fetch_file(f'http://{bind}:{port}/{self.served_file_name}') |
| 1521 | +self.assertEqual(res,self.served_data) |
| 1522 | + |
| 1523 | +deftest_https_client(self): |
| 1524 | +port=find_unused_port() |
| 1525 | +bind='127.0.0.1' |
| 1526 | +proc=spawn_python('-u','-m','http.server',str(port),'-b',bind, |
| 1527 | +'--tls-cert',self.tls_cert, |
| 1528 | +'--tls-key',self.tls_key, |
| 1529 | +'--tls-password-file',self.tls_password_file, |
| 1530 | +bufsize=1,text=True) |
| 1531 | +self.addCleanup(kill_python,proc) |
| 1532 | +self.addCleanup(proc.terminate) |
| 1533 | +self.assertTrue(self.wait_for_server(proc,'https',port,bind)) |
| 1534 | +res=self.fetch_file(f'https://{bind}:{port}/{self.served_file_name}') |
| 1535 | +self.assertEqual(res,self.served_data) |
| 1536 | + |
| 1537 | + |
1284 | 1538 | defsetUpModule():
|
1285 | 1539 | unittest.addModuleCleanup(os.chdir,os.getcwd())
|
1286 | 1540 |
|
|