blob: a9e2e1b231c68c30aaf35a7cb7974a362c6e4272 [file] [log] [blame]
import unittest
from appurify.tunnel import HttpParser
from appurify.tunnel import (CRLF, HTTP_PARSER_STATE_COMPLETE, HTTP_PARSER_STATE_LINE_RCVD,
HTTP_PARSER_STATE_RCVING_HEADERS, HTTP_PARSER_STATE_INITIALIZED,
HTTP_PARSER_STATE_HEADERS_COMPLETE, HTTP_PARSER_STATE_RCVING_BODY,
HTTP_RESPONSE_PARSER)
class TestHttpParser(unittest.TestCase):
def setUp(self):
self.parser = HttpParser()
def test_get_full_parse(self):
raw = CRLF.join([
"GET %s HTTP/1.1",
"Host: %s",
CRLF
])
self.parser.parse(raw % ('https://example.com/path/dir/?a=b&c=d#p=q', 'example.com'))
self.assertEqual(self.parser.build_url(), '/path/dir/?a=b&c=d#p=q')
self.assertEqual(self.parser.method, "GET")
self.assertEqual(self.parser.url.hostname, "example.com")
self.assertEqual(self.parser.url.port, None)
self.assertEqual(self.parser.version, "HTTP/1.1")
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_COMPLETE)
self.assertDictContainsSubset({'host':('Host', 'example.com')}, self.parser.headers)
self.assertEqual(raw % ('/path/dir/?a=b&c=d#p=q', 'example.com'), self.parser.build(del_headers=['host'], add_headers=[('Host', 'example.com')]))
def test_build_url_none(self):
self.assertEqual(self.parser.build_url(), '/None')
def test_line_rcvd_to_rcving_headers_state_change(self):
self.parser.parse("GET http://localhost HTTP/1.1")
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_INITIALIZED)
self.parser.parse(CRLF)
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_LINE_RCVD)
self.parser.parse(CRLF)
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_RCVING_HEADERS)
def test_get_partial_parse1(self):
self.parser.parse(CRLF.join([
"GET http://localhost:8080 HTTP/1.1"
]))
self.assertEqual(self.parser.method, None)
self.assertEqual(self.parser.url, None)
self.assertEqual(self.parser.version, None)
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_INITIALIZED)
self.parser.parse(CRLF)
self.assertEqual(self.parser.method, "GET")
self.assertEqual(self.parser.url.hostname, "localhost")
self.assertEqual(self.parser.url.port, 8080)
self.assertEqual(self.parser.version, "HTTP/1.1")
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_LINE_RCVD)
self.parser.parse("Host: localhost:8080")
self.assertDictEqual(self.parser.headers, dict())
self.assertEqual(self.parser.buffer, "Host: localhost:8080")
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_LINE_RCVD)
self.parser.parse(CRLF*2)
self.assertDictContainsSubset({'host':('Host', 'localhost:8080')}, self.parser.headers)
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_COMPLETE)
def test_get_partial_parse2(self):
self.parser.parse(CRLF.join([
"GET http://localhost:8080 HTTP/1.1",
"Host: "
]))
self.assertEqual(self.parser.method, "GET")
self.assertEqual(self.parser.url.hostname, "localhost")
self.assertEqual(self.parser.url.port, 8080)
self.assertEqual(self.parser.version, "HTTP/1.1")
self.assertEqual(self.parser.buffer, "Host: ")
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_LINE_RCVD)
self.parser.parse("localhost:8080%s" % CRLF)
self.assertDictContainsSubset({'host': ('Host', 'localhost:8080')}, self.parser.headers)
self.assertEqual(self.parser.buffer, "")
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_RCVING_HEADERS)
self.parser.parse("Content-Type: text/plain%s" % CRLF)
self.assertEqual(self.parser.buffer, "")
self.assertDictContainsSubset({'content-type': ('Content-Type', 'text/plain')}, self.parser.headers)
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_RCVING_HEADERS)
self.parser.parse(CRLF)
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_COMPLETE)
def test_post_full_parse(self):
raw = CRLF.join([
"POST %s HTTP/1.1",
"Host: localhost",
"Content-Length: 7",
"Content-Type: application/x-www-form-urlencoded%s" % CRLF,
"a=b&c=d"
])
self.parser.parse(raw % 'http://localhost')
self.assertEqual(self.parser.method, "POST")
self.assertEqual(self.parser.url.hostname, "localhost")
self.assertEqual(self.parser.url.port, None)
self.assertEqual(self.parser.version, "HTTP/1.1")
self.assertDictContainsSubset({'content-type': ('Content-Type', 'application/x-www-form-urlencoded')}, self.parser.headers)
self.assertDictContainsSubset({'content-length': ('Content-Length', '7')}, self.parser.headers)
self.assertEqual(self.parser.body, "a=b&c=d")
self.assertEqual(self.parser.buffer, "")
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_COMPLETE)
self.assertEqual(len(self.parser.build()), len(raw % '/'))
def test_post_partial_parse(self):
self.parser.parse(CRLF.join([
"POST http://localhost HTTP/1.1",
"Host: localhost",
"Content-Length: 7",
"Content-Type: application/x-www-form-urlencoded"
]))
self.assertEqual(self.parser.method, "POST")
self.assertEqual(self.parser.url.hostname, "localhost")
self.assertEqual(self.parser.url.port, None)
self.assertEqual(self.parser.version, "HTTP/1.1")
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_RCVING_HEADERS)
self.parser.parse(CRLF)
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_RCVING_HEADERS)
self.parser.parse(CRLF)
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_HEADERS_COMPLETE)
self.parser.parse("a=b")
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_RCVING_BODY)
self.assertEqual(self.parser.body, "a=b")
self.assertEqual(self.parser.buffer, "")
self.parser.parse("&c=d")
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_COMPLETE)
self.assertEqual(self.parser.body, "a=b&c=d")
self.assertEqual(self.parser.buffer, "")
def test_response_parse(self):
self.parser.type = HTTP_RESPONSE_PARSER
self.parser.parse(''.join([
'HTTP/1.1 301 Moved Permanently\r\n',
'Location: http://www.google.com/\r\n',
'Content-Type: text/html; charset=UTF-8\r\n',
'Date: Wed, 22 May 2013 14:07:29 GMT\r\n',
'Expires: Fri, 21 Jun 2013 14:07:29 GMT\r\n',
'Cache-Control: public, max-age=2592000\r\n',
'Server: gws\r\n',
'Content-Length: 219\r\n',
'X-XSS-Protection: 1; mode=block\r\n',
'X-Frame-Options: SAMEORIGIN\r\n\r\n',
'<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">\n<TITLE>301 Moved</TITLE></HEAD>',
'<BODY>\n<H1>301 Moved</H1>\nThe document has moved\n<A HREF="http://www.google.com/">here</A>.\r\n</BODY></HTML>\r\n'
]))
self.assertEqual(self.parser.code, '301')
self.assertEqual(self.parser.reason, 'Moved Permanently')
self.assertEqual(self.parser.version, 'HTTP/1.1')
self.assertEqual(self.parser.body, '<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">\n<TITLE>301 Moved</TITLE></HEAD><BODY>\n<H1>301 Moved</H1>\nThe document has moved\n<A HREF="http://www.google.com/">here</A>.\r\n</BODY></HTML>\r\n')
self.assertDictContainsSubset({'content-length': ('Content-Length', '219')}, self.parser.headers)
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_COMPLETE)
def test_response_partial_parse(self):
self.parser.type = HTTP_RESPONSE_PARSER
self.parser.parse(''.join([
'HTTP/1.1 301 Moved Permanently\r\n',
'Location: http://www.google.com/\r\n',
'Content-Type: text/html; charset=UTF-8\r\n',
'Date: Wed, 22 May 2013 14:07:29 GMT\r\n',
'Expires: Fri, 21 Jun 2013 14:07:29 GMT\r\n',
'Cache-Control: public, max-age=2592000\r\n',
'Server: gws\r\n',
'Content-Length: 219\r\n',
'X-XSS-Protection: 1; mode=block\r\n',
'X-Frame-Options: SAMEORIGIN\r\n'
]))
self.assertDictContainsSubset({'x-frame-options': ('X-Frame-Options', 'SAMEORIGIN')}, self.parser.headers)
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_RCVING_HEADERS)
self.parser.parse('\r\n')
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_HEADERS_COMPLETE)
self.parser.parse('<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">\n<TITLE>301 Moved</TITLE></HEAD>')
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_RCVING_BODY)
self.parser.parse('<BODY>\n<H1>301 Moved</H1>\nThe document has moved\n<A HREF="http://www.google.com/">here</A>.\r\n</BODY></HTML>\r\n')
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_COMPLETE)
def test_chunked_response_parse(self):
self.parser.type = HTTP_RESPONSE_PARSER
self.parser.parse(''.join([
'HTTP/1.1 200 OK\r\n',
'Content-Type: application/json\r\n',
'Date: Wed, 22 May 2013 15:08:15 GMT\r\n',
'Server: gunicorn/0.16.1\r\n',
'transfer-encoding: chunked\r\n',
'Connection: keep-alive\r\n\r\n',
'4\r\n',
'Wiki\r\n',
'5\r\n',
'pedia\r\n',
'E\r\n',
' in\r\n\r\nchunks.\r\n',
'0\r\n',
'\r\n'
]))
self.assertEqual(self.parser.body, 'Wikipedia in\r\n\r\nchunks.')
self.assertEqual(self.parser.state, HTTP_PARSER_STATE_COMPLETE)