Package dpkt :: Module http
[hide private]
[frames] | no frames]

Source Code for Module dpkt.http

  1  # $Id: http.py 367 2006-05-08 19:34:50Z dugsong $ 
  2   
  3  """Hypertext Transfer Protocol.""" 
  4   
  5  import cStringIO 
  6  import dpkt 
  7   
8 -def parse_headers(f):
9 """Return dict of HTTP headers parsed from a file object.""" 10 d = {} 11 while 1: 12 line = f.readline() 13 if not line: 14 raise dpkt.NeedData('premature end of headers') 15 line = line.strip() 16 if not line: 17 break 18 l = line.split(None, 1) 19 if not l[0].endswith(':'): 20 raise dpkt.UnpackError('invalid header: %r' % line) 21 k = l[0][:-1].lower() 22 d[k] = len(l) != 1 and l[1] or '' 23 return d
24
25 -def parse_body(f, headers):
26 """Return HTTP body parsed from a file object, given HTTP header dict.""" 27 if headers.get('transfer-encoding', '').lower() == 'chunked': 28 l = [] 29 found_end = False 30 while 1: 31 try: 32 sz = f.readline().split(None, 1)[0] 33 except IndexError: 34 raise dpkt.UnpackError('missing chunk size') 35 n = int(sz, 16) 36 if n == 0: 37 found_end = True 38 buf = f.read(n) 39 if f.readline().strip(): 40 break 41 if n and len(buf) == n: 42 l.append(buf) 43 else: 44 break 45 if not found_end: 46 raise dpkt.NeedData('premature end of chunked body') 47 body = ''.join(l) 48 elif 'content-length' in headers: 49 n = int(headers['content-length']) 50 body = f.read(n) 51 if len(body) != n: 52 raise dpkt.NeedData('short body (missing %d bytes)' % (n - len(body))) 53 elif 'content-type' in headers: 54 body = f.read() 55 else: 56 # XXX - need to handle HTTP/0.9 57 body = '' 58 return body
59
60 -class Message(dpkt.Packet):
61 """Hypertext Transfer Protocol headers + body.""" 62 __metaclass__ = type 63 __hdr_defaults__ = {} 64 headers = None 65 body = None 66
67 - def __init__(self, *args, **kwargs):
68 if args: 69 self.unpack(args[0]) 70 else: 71 self.headers = {} 72 self.body = '' 73 for k, v in self.__hdr_defaults__.iteritems(): 74 setattr(self, k, v) 75 for k, v in kwargs.iteritems(): 76 setattr(self, k, v)
77
78 - def unpack(self, buf):
79 f = cStringIO.StringIO(buf) 80 # Parse headers 81 self.headers = parse_headers(f) 82 # Parse body 83 self.body = parse_body(f, self.headers) 84 # Save the rest 85 self.data = f.read()
86
87 - def pack_hdr(self):
88 return ''.join([ '%s: %s\r\n' % t for t in self.headers.iteritems() ])
89
90 - def __len__(self):
91 return len(str(self))
92
93 - def __str__(self):
94 return '%s\r\n%s' % (self.pack_hdr(), self.body)
95
96 -class Request(Message):
97 """Hypertext Transfer Protocol Request.""" 98 __hdr_defaults__ = { 99 'method':'GET', 100 'uri':'/', 101 'version':'1.0', 102 } 103 __methods = dict.fromkeys(( 104 'GET', 'PUT', 'ICY', 105 'COPY', 'HEAD', 'LOCK', 'MOVE', 'POLL', 'POST', 106 'BCOPY', 'BMOVE', 'MKCOL', 'TRACE', 'LABEL', 'MERGE', 107 'DELETE', 'SEARCH', 'UNLOCK', 'REPORT', 'UPDATE', 'NOTIFY', 108 'BDELETE', 'CONNECT', 'OPTIONS', 'CHECKIN', 109 'PROPFIND', 'CHECKOUT', 'CCM_POST', 110 'SUBSCRIBE', 'PROPPATCH', 'BPROPFIND', 111 'BPROPPATCH', 'UNCHECKOUT', 'MKACTIVITY', 112 'MKWORKSPACE', 'UNSUBSCRIBE', 'RPC_CONNECT', 113 'VERSION-CONTROL', 114 'BASELINE-CONTROL' 115 )) 116 __proto = 'HTTP' 117
118 - def unpack(self, buf):
119 f = cStringIO.StringIO(buf) 120 line = f.readline() 121 l = line.strip().split() 122 if len(l) != 3 or l[0] not in self.__methods or \ 123 not l[2].startswith(self.__proto): 124 raise dpkt.UnpackError('invalid request: %r' % line) 125 self.method = l[0] 126 self.uri = l[1] 127 self.version = l[2][len(self.__proto)+1:] 128 Message.unpack(self, f.read())
129
130 - def __str__(self):
131 return '%s %s %s/%s\r\n' % (self.method, self.uri, self.__proto, 132 self.version) + Message.__str__(self)
133
134 -class Response(Message):
135 """Hypertext Transfer Protocol Response.""" 136 __hdr_defaults__ = { 137 'version':'1.0', 138 'status':'200', 139 'reason':'OK' 140 } 141 __proto = 'HTTP' 142
143 - def unpack(self, buf):
144 f = cStringIO.StringIO(buf) 145 line = f.readline() 146 l = line.strip().split(None, 2) 147 if len(l) < 2 or not l[0].startswith(self.__proto) or not l[1].isdigit(): 148 raise dpkt.UnpackError('invalid response: %r' % line) 149 self.version = l[0][len(self.__proto)+1:] 150 self.status = l[1] 151 self.reason = l[2] 152 Message.unpack(self, f.read())
153
154 - def __str__(self):
155 return '%s/%s %s %s\r\n' % (self.__proto, self.version, self.status, 156 self.reason) + Message.__str__(self)
157 158 if __name__ == '__main__': 159 import unittest 160
161 - class HTTPTest(unittest.TestCase):
162 - def test_parse_request(self):
163 s = """POST /main/redirect/ab/1,295,,00.html HTTP/1.0\r\nReferer: http://www.email.com/login/snap/login.jhtml\r\nConnection: Keep-Alive\r\nUser-Agent: Mozilla/4.75 [en] (X11; U; OpenBSD 2.8 i386; Nav)\r\nHost: ltd.snap.com\r\nAccept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*\r\nAccept-Encoding: gzip\r\nAccept-Language: en\r\nAccept-Charset: iso-8859-1,*,utf-8\r\nContent-type: application/x-www-form-urlencoded\r\nContent-length: 61\r\n\r\nsn=em&mn=dtest4&pw=this+is+atest&fr=true&login=Sign+in&od=www""" 164 r = Request(s) 165 assert r.method == 'POST' 166 assert r.uri == '/main/redirect/ab/1,295,,00.html' 167 assert r.body == 'sn=em&mn=dtest4&pw=this+is+atest&fr=true&login=Sign+in&od=www' 168 assert r.headers['content-type'] == 'application/x-www-form-urlencoded' 169 try: 170 r = Request(s[:60]) 171 assert 'invalid headers parsed!' 172 except dpkt.UnpackError: 173 pass
174
175 - def test_format_request(self):
176 r = Request() 177 assert str(r) == 'GET / HTTP/1.0\r\n\r\n' 178 r.method = 'POST' 179 r.uri = '/foo/bar/baz.html' 180 r.headers['content-type'] = 'text/plain' 181 r.headers['content-length'] = '5' 182 r.body = 'hello' 183 assert str(r) == 'POST /foo/bar/baz.html HTTP/1.0\r\ncontent-length: 5\r\ncontent-type: text/plain\r\n\r\nhello' 184 r = Request(str(r)) 185 assert str(r) == 'POST /foo/bar/baz.html HTTP/1.0\r\ncontent-length: 5\r\ncontent-type: text/plain\r\n\r\nhello'
186
187 - def test_chunked_response(self):
188 s = """HTTP/1.1 200 OK\r\nCache-control: no-cache\r\nPragma: no-cache\r\nContent-Type: text/javascript; charset=utf-8\r\nContent-Encoding: gzip\r\nTransfer-Encoding: chunked\r\nSet-Cookie: S=gmail=agg:gmail_yj=v2s:gmproxy=JkU; Domain=.google.com; Path=/\r\nServer: GFE/1.3\r\nDate: Mon, 12 Dec 2005 22:33:23 GMT\r\n\r\na\r\n\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00\r\n152\r\nm\x91MO\xc4 \x10\x86\xef\xfe\n\x82\xc9\x9eXJK\xe9\xb6\xee\xc1\xe8\x1e6\x9e4\xf1\xe0a5\x86R\xda\x12Yh\x80\xba\xfa\xef\x85\xee\x1a/\xf21\x99\x0c\xef0<\xc3\x81\xa0\xc3\x01\xe6\x10\xc1<\xa7eYT5\xa1\xa4\xac\xe1\xdb\x15:\xa4\x9d\x0c\xfa5K\x00\xf6.\xaa\xeb\x86\xd5y\xcdHY\x954\x8e\xbc*h\x8c\x8e!L7Y\xe6\'\xeb\x82WZ\xcf>8\x1ed\x87\x851X\xd8c\xe6\xbc\x17Z\x89\x8f\xac \x84e\xde\n!]\x96\x17i\xb5\x02{{\xc2z0\x1e\x0f#7\x9cw3v\x992\x9d\xfc\xc2c8\xea[/EP\xd6\xbc\xce\x84\xd0\xce\xab\xf7`\'\x1f\xacS\xd2\xc7\xd2\xfb\x94\x02N\xdc\x04\x0f\xee\xba\x19X\x03TtW\xd7\xb4\xd9\x92\n\xbcX\xa7;\xb0\x9b\'\x10$?F\xfd\xf3CzPt\x8aU\xef\xb8\xc8\x8b-\x18\xed\xec<\xe0\x83\x85\x08!\xf8"[\xb0\xd3j\x82h\x93\xb8\xcf\xd8\x9b\xba\xda\xd0\x92\x14\xa4a\rc\reM\xfd\x87=X;h\xd9j;\xe0db\x17\xc2\x02\xbd\xb0F\xc2in#\xfb:\xb6\xc4x\x15\xd6\x9f\x8a\xaf\xcf)\x0b^\xbc\xe7i\x11\x80\x8b\x00D\x01\xd8/\x82x\xf6\xd8\xf7J(\xae/\x11p\x1f+\xc4p\t:\xfe\xfd\xdf\xa3Y\xfa\xae4\x7f\x00\xc5\xa5\x95\xa1\xe2\x01\x00\x00\r\n0\r\n\r\n""" 189 r = Response(s) 190 assert r.version == '1.1' 191 assert r.status == '200' 192 assert r.reason == 'OK'
193 194 unittest.main() 195