├── 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 |
--------------------------------------------------------------------------------