├── README.md ├── config.py ├── lib ├── __pycache__ │ └── send_request.cpython-39.pyc └── send_request.py ├── main.py └── testcase ├── case.py ├── content_length.py ├── header_field.py ├── host.py ├── http_version_not_supported.py └── request_line.py /README.md: -------------------------------------------------------------------------------- 1 | # webserv_tester 2 | 3 | This is a tester for 42 subject webserv 4 | it tests: 5 | 6 | 1. check if the webserver follows RFC 7 | 2. check if it correctly validate input values (security) 8 | 9 | this tester isn't perfect. you can justify your choice if the tests fails 10 | 11 | # TODO 12 | - add headers in subjects 13 | - Host 14 | - Accept-Charsets 15 | - Accept-Language 16 | - Allow 17 | - Authorization 18 | - Content-Language 19 | - Content-Length 20 | - Content-Location 21 | - Content-Type 22 | - Date 23 | - Last-Modified 24 | - Location 25 | - Referer 26 | - Retry-After 27 | - Server 28 | - Transfer-Encoding 29 | - User-Agent 30 | - WWW-Authenticate 31 | - add security attacks 32 | - response splitting 33 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | SERVER_ADDR = '127.0.0.1' 2 | SERVER_PORT = 80 3 | 4 | # if server receive uri longer than max uri length, it should reponse with 414 5 | MAX_URI_LENGTH = 100000000 6 | -------------------------------------------------------------------------------- /lib/__pycache__/send_request.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hygoni/webserv_tester/950026bd4e3de32e757af5f7d2061a3d03ae2568/lib/__pycache__/send_request.cpython-39.pyc -------------------------------------------------------------------------------- /lib/send_request.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../') 3 | import config 4 | import socket 5 | from http.client import HTTPResponse 6 | 7 | def send_request(request_header): 8 | client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 9 | client.connect((config.SERVER_ADDR, config.SERVER_PORT)) 10 | client.send(request_header.encode()) 11 | # read and parse http response 12 | http_response = HTTPResponse(client) 13 | http_response.begin() 14 | return http_response 15 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('./testcase') 3 | sys.path.append('./lib') 4 | from case import case 5 | 6 | # run all testcases 7 | for run_case in case: 8 | run_case() 9 | -------------------------------------------------------------------------------- /testcase/case.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('.') 3 | import http_version_not_supported 4 | import request_line 5 | import header_field 6 | import content_length 7 | 8 | # list of test cases 9 | case = [] 10 | 11 | case.append(http_version_not_supported.run) 12 | case.append(request_line.run) 13 | case.append(header_field.run) 14 | case.append(content_length.run) 15 | -------------------------------------------------------------------------------- /testcase/content_length.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../') 3 | sys.path.append('../lib') 4 | from send_request import send_request 5 | import config 6 | import socket 7 | from http.client import HTTPResponse 8 | 9 | # test rfc7230 section 3.3.2: Content Length 10 | 11 | def run(): 12 | print('testing {}...'.format(__file__)) 13 | 14 | # invalid content length 15 | length = '-1' 16 | request_header = 'GET / HTTP/1.1\r\nHost:{}\r\nContent-Length: {}\r\n\r\n'.format(config.SERVER_ADDR, length) 17 | http_response = send_request(request_header) 18 | if http_response.status != 400: 19 | print('error: {}'.format(__file__)) 20 | print('expected status: {}, actual status: {}'.format('400', str(http_response.status))) 21 | 22 | length = '100000000000000000000000' 23 | request_header = 'GET / HTTP/1.1\r\nHost:{}\r\nContent-Length: {}\r\n\r\n'.format(config.SERVER_ADDR, length) 24 | http_response = send_request(request_header) 25 | if http_response.status != 400: 26 | print('error: {}'.format(__file__)) 27 | print('expected status: {}, actual status: {}'.format('400', str(http_response.status))) 28 | 29 | length = 'NOTDIGIT' 30 | request_header = 'GET / HTTP/1.1\r\nHost:{}\r\nContent-Length: {}\r\n\r\n'.format(config.SERVER_ADDR, length) 31 | http_response = send_request(request_header) 32 | if http_response.status != 400: 33 | print('error: {}'.format(__file__)) 34 | print('expected status: {}, actual status: {}'.format('400', str(http_response.status))) 35 | 36 | # Content-Length with Transfer-Encoding 37 | # Transfer-Encoding overrides Content-Length 38 | request_header = 'GET / HTTP/1.1\r\nHost:{}\r\nContent-Length: 10000\r\nTransfer-Encoding: chunked\r\n\r\n0'.format(config.SERVER_ADDR) 39 | http_response = send_request(request_header) 40 | if http_response.status != 200: 41 | print('error: {}'.format(__file__)) 42 | print('expected status: {}, actual status: {}'.format('200', str(http_response.status))) 43 | 44 | # multiple Content-Length differing size 45 | request_header = 'GET / HTTP/1.1\r\nHost:{}\r\nContent-Length: 1\r\nContent-Length: 0\r\n\r\n'.format(config.SERVER_ADDR) 46 | http_response = send_request(request_header) 47 | if http_response.status != 400: 48 | print('error: {}'.format(__file__)) 49 | print('expected status: {}, actual status: {}'.format('200', str(http_response.status))) 50 | 51 | if __name__ == '__main__': 52 | run() 53 | -------------------------------------------------------------------------------- /testcase/header_field.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../') 3 | sys.path.append('../lib') 4 | from send_request import send_request 5 | import config 6 | import socket 7 | from http.client import HTTPResponse 8 | 9 | # test rfc7230 section 3.2.4: Field Parsing 10 | 11 | def run(): 12 | print('testing {}...'.format(__file__)) 13 | 14 | # space between header-name and colon 15 | request_header = 'GET / HTTP/1.1\r\nHost :{}\r\n\r\n'.format(config.SERVER_ADDR) 16 | http_response = send_request(request_header) 17 | if http_response.status != 400: 18 | print('error : {}'.format(__file__)) 19 | print('reason: space between header-name and colon in Host') 20 | print('expected status: {}, actual status: {}'.format('400', str(http_response.status))) 21 | 22 | request_header = 'GET / HTTP/1.1\r\nHost:{}\r\nAccept-Language :hyeyoo\r\n\r\n'.format(config.SERVER_ADDR) 23 | http_response = send_request(request_header) 24 | if http_response.status != 400: 25 | print('error : {}'.format(__file__)) 26 | print('reason: space between header-name and colon in Accept-Language') 27 | print('expected status: {}, actual status: {}'.format('400', str(http_response.status))) 28 | 29 | # too long header 30 | # not necessarily 400, it can be 4XX 31 | long_text = 'A' * 1000000000 32 | request_header = 'GET / HTTP/1.1\r\nHost:{}\r\nUser-Agent:{}\r\n\r\n'.format(config.SERVER_ADDR, long_text) 33 | http_response = send_request(request_header) 34 | if http_response.status // 100 != 4: 35 | print('error : {}'.format(__file__)) 36 | print('reason: too long header') 37 | print('expected status: {}, actual status: {}'.format('4XX', str(http_response.status))) 38 | 39 | # empty header name 40 | request_header = 'GET / HTTP/1.1\r\nHost:{}\r\n:empty_name\r\n\r\n'.format(config.SERVER_ADDR) 41 | http_response = send_request(request_header) 42 | #if True: 43 | if http_response.status // 100 != 4: 44 | print('error : {}'.format(__file__)) 45 | print('reason: empty header name') 46 | print('expected status: {}, actual status: {}'.format('4XX', str(http_response.status))) 47 | 48 | if __name__ == '__main__': 49 | run() 50 | -------------------------------------------------------------------------------- /testcase/host.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../') 3 | sys.path.append('../lib') 4 | from send_request import send_request 5 | import config 6 | import socket 7 | from http.client import HTTPResponse 8 | 9 | # test rfc7230 section 5.4 Host 10 | 11 | def run(): 12 | print('testing {}...'.format(__file__)) 13 | 14 | # no host 15 | request_header = 'GET / HTTP/1.1\r\n\r\n' 16 | http_response = send_request(request_header) 17 | if http_response.status != 400: 18 | print('error: {}'.format(__file__)) 19 | print('expected status: {}, actual status: {}'.format('400', str(http_response.status))) 20 | 21 | # multiple host 22 | request_header = 'GET / HTTP/1.1\r\nHost: naver.com\r\nHost: hyeyoo.com\r\n\r\n' 23 | http_response = send_request(request_header) 24 | if http_response.status != 400: 25 | print('error: {}'.format(__file__)) 26 | print('expected status: {}, actual status: {}'.format('400', str(http_response.status))) 27 | 28 | # multiple host 2 29 | request_header = 'GET / HTTP/1.1\r\nHost: {}\r\nHost: {}\r\n\r\n'.format(config.SERVER_ADDR, config.SERVER_ADDR) 30 | http_response = send_request(request_header) 31 | if http_response.status != 400: 32 | print('error: {}'.format(__file__)) 33 | print('expected status: {}, actual status: {}'.format('400', str(http_response.status))) 34 | 35 | 36 | # invalid field value in host 37 | request_header = 'GET / HTTP/1.1\r\nHost: hyeyoo@hyeyoo.com\r\n\r\n' 38 | http_response = send_request(request_header) 39 | if http_response.status != 400: 40 | print('error: {}'.format(__file__)) 41 | print('expected status: {}, actual status: {}'.format('400', str(http_response.status))) 42 | 43 | if __name__ == '__main__': 44 | run() 45 | -------------------------------------------------------------------------------- /testcase/http_version_not_supported.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../') 3 | sys.path.append('../lib') 4 | from send_request import send_request 5 | import config 6 | import socket 7 | from http.client import HTTPResponse 8 | 9 | # test rfc7230 section 2.6: Protocol Versioning 10 | 11 | def run(): 12 | print('testing {}...'.format(__file__)) 13 | 14 | # send http header 15 | request_header = 'GET / HTTP/0.1\r\nHost:{}\r\n\r\n'.format(config.SERVER_ADDR) 16 | 17 | http_response = send_request(request_header) 18 | # 505 error is expected for invalid http version 19 | if http_response.status != 505 and http_response.status // 100 != 4: 20 | print('error: {}'.format(__file__)) 21 | print('expected status: {}, actual status: {}'.format('505 or 4XX', str(http_response.status))) 22 | 23 | if __name__ == '__main__': 24 | run() 25 | -------------------------------------------------------------------------------- /testcase/request_line.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('../') 3 | sys.path.append('../lib') 4 | from send_request import send_request 5 | import config 6 | import socket 7 | from http.client import HTTPResponse 8 | 9 | # test rfc7230 section 3.3.1: Request Line 10 | 11 | def run(): 12 | print('testing {}...'.format(__file__)) 13 | 14 | # multiple spaces 15 | request_header = 'GET / HTTP/1.1\r\nHost:{}\r\n\r\n'.format(config.SERVER_ADDR) 16 | http_response = send_request(request_header) 17 | if http_response.status != 400: 18 | print('error: {}'.format(__file__)) 19 | print('expected status: {}, actual status: {}'.format('400', str(http_response.status))) 20 | 21 | # too long URI 22 | target = '/' + 'A' * (config.MAX_URI_LENGTH - 1) 23 | request_header = 'GET {} HTTP/1.1\r\nHost:{}\r\n\r\n'.format(target, config.SERVER_ADDR) 24 | http_response = send_request(request_header) 25 | if http_response.status != 414: 26 | print('error: {}'.format(__file__)) 27 | print('expected status: {}, actual status: {}'.format('414', str(http_response.status))) 28 | 29 | if __name__ == '__main__': 30 | run() 31 | --------------------------------------------------------------------------------