1
2
3 """Hypertext Transfer Protocol."""
4
5 import cStringIO
6 import dpkt
7
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
57 body = ''
58 return body
59
61 """Hypertext Transfer Protocol headers + body."""
62 __metaclass__ = type
63 __hdr_defaults__ = {}
64 headers = None
65 body = None
66
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
86
88 return ''.join([ '%s: %s\r\n' % t for t in self.headers.iteritems() ])
89
92
95
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
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
133
135 """Hypertext Transfer Protocol Response."""
136 __hdr_defaults__ = {
137 'version':'1.0',
138 'status':'200',
139 'reason':'OK'
140 }
141 __proto = 'HTTP'
142
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
157
158 if __name__ == '__main__':
159 import unittest
160
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
186
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