forked from dgallot/proxpy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhttp.py
348 lines (287 loc) · 10.3 KB
/
http.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
"""
Copyright notice
================
Copyright (C) 2011
Roberto Paleari <[email protected]>
Alessandro Reina <[email protected]>
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
HyperDbg is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
"""
import datetime
import copy
import urlparse
import select
class HTTPUtil():
@staticmethod
def wait_read(socket):
select.select([socket], [], [])
class HTTPMessage():
EOL = "\r\n"
HTTP_CODE_OK = 200
# Global message ID, incremented at each HTTP request/reponse
uid = 0
def __init__(self, headers = None):
self.peer = None
self.time = datetime.datetime.now()
# Set unique message ID
self.uid = HTTPMessage.uid
if headers is None:
self.headers = {}
elif isinstance(headers, list):
self.headers = HTTPMessage._readheaders(headers)
else:
assert isinstance(headers, dict)
self.headers = headers
HTTPMessage.uid += 1
@staticmethod
def _readheaders(data):
headers = {}
for line in data:
if line == HTTPMessage.EOL:
break
assert ":" in line
line = line.rstrip(HTTPMessage.EOL)
i = line.index(":")
n = line[:i]
v = line[i+1:]
if n not in headers:
headers[n] = []
headers[n].append(v.lstrip())
return headers
@staticmethod
def _readbody(data, headers):
bodylen = None
chunked = False
for n,v in headers.iteritems():
if n.lower() == "content-length":
assert bodylen is None, "[!] Duplicated content length"
bodylen = int(v[0])
elif n.lower() == "transfer-encoding" and v[0].lower() == "chunked":
chunked = True
break
# Read HTTP body (if present)
body = ""
if bodylen is not None:
body = data.read(bodylen)
elif chunked:
# Chunked encoding
while True:
# Determine chunk length
chunklen = data.readline()
chunklen = int(chunklen, 16)
# Read the whole chunk
chunk = ""
chunk = data.read(chunklen)
body += chunk
if chunklen == 0:
break
# Read trailing CRLF
eol = data.read(2)
assert eol == HTTPMessage.EOL
return body
def isChunked(self):
r = False
for n, v in self.headers.iteritems():
if n.lower() == "transfer-encoding" and v[0].lower() == "chunked":
r = True
break
return r
def isKeepAlive(self):
if 'Connection' in self.headers:
if self.headers['Connection'][0] == 'keep-alive':
return True
elif 'Proxy-Connection' in self.headers:
if self.headers['Proxy-Connection'][0] == 'keep-alive':
return True
return False
def setPeer(self, h, link = True):
self.peer = h
if link:
h.setPeer(self, link = False)
def clone(self):
return copy.deepcopy(self)
def fixup(self):
# Fix headers
for n in self.headers:
if n.lower() == "content-length":
self.headers[n][0] = len(self.body)
# Hack to fix HTTPS request
@staticmethod
def _fixURLMalformed(scheme, url, headers):
if ((url.find('http') != 0) and (url[0] == '/')):
assert 'Host' in headers
url = scheme + '://' + headers['Host'][0] + url
return url
def __findHeader(self, name, ignorecase = True):
r = None
for n in self.headers:
if (ignorecase and name.lower() == n.lower()) or ((not ignorecase) and name == n):
r = n
break
return r
def getHeader(self, name, ignorecase = True):
"""
Get the values of header(s) with name 'name'. If 'ignorecase' is True,
then the case of the header name is ignored.
"""
r = []
for n,v in self.headers.iteritems():
if (ignorecase and name.lower() == n.lower()) or ((not ignorecase) and name == n):
r.extend(v)
return r
def addHeader(self, name, value, ignorecase = True):
"""
Add a new 'name' header with value 'value' to this HTTPMessage. If
'ignorecase' is True, then the case of the header name is ignored.
"""
k = self.__findHeader(name, ignorecase)
if k not in self.headers:
self.headers[name] = []
self.headers[name].append(value)
def setHeader(self, name, value, ignorecase = True):
"""
Set header with name 'name' to 'value'. Any existing header with the
same name is replaced. If 'ignorecase' is True, then the case of the
header name is ignored.
"""
k = self.__findHeader(name, ignorecase)
self.headers[k] = [value, ]
class HTTPRequest(HTTPMessage):
METHOD_GET = 1
METHOD_POST = 2
METHOD_HEAD = 3
METHOD_OPTIONS = 4
METHOD_CONNECT = 5
def __init__(self, method, url, proto, headers = None, body = ""):
self.method = method
self.url = url
self.proto = proto
self.body = body
HTTPMessage.__init__(self, headers)
@staticmethod
def build(data):
# Read request line
reqline = data.readline().rstrip(HTTPMessage.EOL)
if reqline == '':
return None
method, url, proto = reqline.split()
# Read headers & body
headers = HTTPMessage._readheaders(data)
body = HTTPMessage._readbody(data, headers)
url = HTTPMessage._fixURLMalformed("https", url, headers)
return HTTPRequest(method, url, proto, headers, body)
def getHost(self):
if self.getMethod() == HTTPRequest.METHOD_CONNECT:
tmp = self.url.split(":")
host = tmp[0]
if len(tmp) > 0:
port = int(tmp[1])
else:
port = 80
else:
r = urlparse.urlparse(self.url)
port = r.port
if port is None:
if r.scheme != "https":
port = 80
else:
port = 443
host = r.hostname
assert host is not None and len(host) > 0, "[!] Cannot find target host in URL '%s'" % self.url
return host, port
def getPath(self):
r = urlparse.urlparse(self.url)
s = r.path
if len(r.params) > 0:
s += ";%s" % r.params
if len(r.query) > 0:
s += "?%s" % r.query
if len(r.fragment) > 0:
s += "#%s" % r.fragment
return s
def __str__(self):
s = "{REQ #%d} method: %s ; host: %s ; path: %s ; proto: %s ; len(body): %d\n" % \
(self.uid, self.method, self.getHost(), self.getPath(), self.proto, len(self.body))
for n,v in self.headers.iteritems():
for i in v:
s += " %s: %s\n" % (n, i)
return s
def isRequest(self):
return True
def getMethod(self):
m = self.method.lower()
if m == "get": r = HTTPRequest.METHOD_GET
elif m == "post": r = HTTPRequest.METHOD_POST
elif m == "head": r = HTTPRequest.METHOD_HEAD
elif m == "options": r = HTTPRequest.METHOD_OPTIONS
elif m == "connect": r = HTTPRequest.METHOD_CONNECT
elif m == "unknown": r = HTTPRequest.METHOD_UNKNOWN
return r
def getParams(self, typez = None):
params = {}
if typez is None or typez == HTTPRequest.METHOD_GET:
r = urlparse.urlparse(self.url).query
if len(r) > 0:
params.update(urlparse.parse_qs(r))
if typez is None or typez == HTTPRequest.METHOD_POST:
if len(self.body) > 0:
params.update(urlparse.parse_qs(self.body, keep_blank_values = True))
if params:
# FIXME: Do we lose v[1:] ?
tmp = {}
for k, v in params.iteritems():
tmp[k] = v[0]
params = tmp
return params
class HTTPResponse(HTTPMessage):
def __init__(self, proto, code, msg, headers = None, body = ""):
self.proto = proto
self.code = code
self.msg = msg
self.body = body
HTTPMessage.__init__(self, headers)
@staticmethod
def build(data):
# Read request line
reqline = data.readline().rstrip(HTTPMessage.EOL)
method, url, proto = reqline.split()
# Read headers & body
headers = HTTPMessage._readheaders(data)
body = HTTPMessage._readbody(data, headers)
return HTTPRequest(method, url, proto, headers, body)
def serialize(self):
# Response line
s = "%s %s %s" % (self.proto, self.code, self.msg)
s += HTTPMessage.EOL
# Headers
for n,v in self.headers.iteritems():
for i in v:
s += "%s: %s" % (n, i)
s += HTTPMessage.EOL
s += HTTPMessage.EOL
# Body
if not self.isChunked():
s += self.body
else:
# FIXME: Make a single-chunk body
s += "%x" % len(self.body) + HTTPMessage.EOL
s += self.body + HTTPMessage.EOL
s += HTTPMessage.EOL
s += "0" + HTTPMessage.EOL + HTTPMessage.EOL
return s
def __str__(self):
s = "{RES #%d} code: %d (%s) ; proto: %s ; len(body): %d\n" % \
(self.uid, self.code, self.msg, self.proto, len(self.body))
for n,v in self.headers.iteritems():
for i in v:
s += " %s: %s\n" % (n, i)
return s
def isResponse(self):
return True