source: trunk/bin/bletchley-http2py @ 48

Last change on this file since 48 was 47, checked in by tmorgan, 11 years ago

added support for binary data in request body

  • Property svn:executable set to *
File size: 6.8 KB
Line 
1#!/usr/bin/env python3
2
3# Requires Python 3+
4
5'''
6This script reads a raw HTTP request and writes to stdout a Python
7script.  The generated script sends the same (or a very similar)
8request using the standard httplib/http.client library, or optionally
9using the more user friendly python-requests library.
10
11Certainly if you have a raw request, you could simply send it via TCP
12sockets, but if for some reason the server behaves oddly with flow control,
13insists on using gzip/deflate encoding, insists on using chunked encoding,
14or any number of other annoying things, then using an HTTP library is a
15lot more convenient.  This script attempts to make that conversion easy.
16
17
18Copyright (C) 2011-2013 Virtual Security Research, LLC
19Author: Timothy D. Morgan
20
21 This program is free software: you can redistribute it and/or modify
22 it under the terms of the GNU Lesser General Public License, version 3,
23 as published by the Free Software Foundation.
24
25 This program is distributed in the hope that it will be useful,
26 but WITHOUT ANY WARRANTY; without even the implied warranty of
27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28 GNU General Public License for more details.
29
30 You should have received a copy of the GNU General Public License
31 along with this program.  If not, see <http://www.gnu.org/licenses/>.
32'''
33
34
35import sys
36import argparse
37
38bopen = lambda f: open(f, 'rb')
39
40parser = argparse.ArgumentParser(
41    description='A script which accepts an HTTP request and prints out a'
42    ' generated Python script which sends a similar request.  This is useful'
43    ' when one wants to automate sending a large number of requests to a'
44    ' particular page or application.'
45    ' For more information, see: http://code.google.com/p/bletchley/wiki/Overview')
46parser.add_argument(
47    'requestfile', type=bopen, nargs='?', default=sys.stdin.buffer, 
48    help='A file containing an HTTP request.  Defaults to stdin if omitted.')
49parser.add_argument(
50    '--requests', action='store_true', help='Generate a script that uses the'
51    ' python-requests module rather than httplib/http.client (experimental).')
52
53args = parser.parse_args()
54input_req = args.requestfile.read()
55
56
57if b'\r\n\r\n' in input_req:
58    raw_headers,body = input_req.split(b'\r\n\r\n', 1)
59elif b'\n\n' in input_req:
60    raw_headers,body = input_req.split(b'\n\n', 1)
61else:
62    raw_headers = input_req
63    body = b''
64
65raw_headers = raw_headers.decode('utf-8')
66
67header_lines = raw_headers.split('\n')
68method,path,version = header_lines[0].split(' ', 2)
69
70host = 'TODO'
71port = 80
72use_ssl = False
73protocol = 'http'
74
75headers = []
76for l in header_lines[1:]:
77    if len(l) < 1: 
78        break
79    # Handle header line continuations
80    if l[0] in ' \t':
81        if len(headers) == 0:
82            continue
83        name,values = headers[-1]
84        values.append(l.lstrip('\t'))
85        headers[-1] = (name,values)
86        continue
87
88    name,value = l.split(':',1)
89    value = value.lstrip(' ').rstrip('\r')
90
91    # Skip headers that have to do with transfer encodings and connection longevity
92    if name.lower() not in ['accept','accept-language',
93                            'accept-encoding','accept-charset',
94                            'connection', 'keep-alive', 'host', 
95                            'content-length', 'proxy-connection']:
96        headers.append((name,[value]))
97
98    if name.lower() == 'host':
99        if ':' in value:
100            host,port = value.split(':',1)
101            if port == 443:
102                use_ssl = True
103                protocol = 'https'
104        else:
105            host = value
106
107
108formatted_body = '\n            '.join([repr(body[i:i+40]) for i in range(0,len(body),40)])
109if formatted_body == b'':
110    formatted_body = "b''"
111
112
113if args.requests:
114    print('''#!/usr/bin/env python3
115
116import sys
117try:
118    import requests
119except:
120    sys.stderr.write('ERROR: Could not import requests module.  Ensure it is installed.\\n')
121    sys.stderr.write('       Under Debian, the package name is "python3-requests"\\n.')
122    sys.exit(1)
123
124
125# TODO: ensure the host, port, and SSL settings are correct.
126host = %s
127port = %s
128protocol = %s
129''' % (repr(host),repr(port),repr(protocol)))
130
131    headers = dict(headers)
132    # XXX: We don't currently support exactly formatted header
133    #      continuations with python requests, but this should be
134    #      semantically equivalent.
135    for h in headers.keys():
136        headers[h] = ' '.join(headers[h])
137
138    print('''
139session = requests.Session()
140# TODO: use "data" to supply any parameters to be included in the request
141def sendRequest(session, data=None):
142    method = %s
143    path = %s
144    headers = %s
145    url = "%%s://%%s:%%d%%s" %% (protocol,host,port,path)
146    body = (%s)
147
148    return session.request(method, url, headers=headers, data=body)
149    ''' % (repr(method), repr(path), repr(headers), formatted_body))
150
151    print('''   
152
153def fetch(data):
154    global session
155    ret_val = None
156
157    # TODO: customize code here to retrieve what you need from the response(s)
158    # For information on the response object's interface, see:
159    #   http://docs.python-requests.org/en/latest/api/#requests.Response
160    response = sendRequest(session, data)
161    print(response.headers)
162    print(repr(response.content))
163
164    return ret_val
165
166data = ''
167fetch(data)
168''')
169
170
171
172else:
173    print('''#!/usr/bin/env python3
174
175import sys
176import http.client as httpc
177
178
179# TODO: ensure the host, port, and SSL settings are correct.
180host = %s
181port = %s
182use_ssl = %s
183''' % (repr(host),repr(port),repr(use_ssl)))
184
185    print('''
186# TODO: use "data" to supply any parameters to be included in the request
187def sendRequest(connection, data=None):
188    method = %s
189    path = %s
190    body = (%s)
191   
192    connection.putrequest(method, path)
193    ''' % (repr(method), repr(path), formatted_body))
194
195    for name,values in headers:
196        if len(values) > 1:
197            continuations = ','.join([repr(v) for v in values[1:]])
198            print('''    connection.putheader(%s, %s, %s)''' % (repr(name),repr(values[0]),continuations))
199        else:
200            print('''    connection.putheader(%s, %s)''' % (repr(name),repr(values[0])))
201
202    print('''   
203    if len(body) > 0:
204        connection.putheader('Content-Length', len(body))
205    connection.endheaders()
206    connection.send(body)
207   
208    return connection.getresponse()
209
210
211def newConnection():
212    if use_ssl:
213        return httpc.HTTPSConnection(host, port)
214    else:
215        return httpc.HTTPConnection(host, port)
216
217
218def fetch(data):
219    ret_val = None
220    connection = newConnection()
221
222    # TODO: customize code here to retrieve what you need from the response(s)
223    # For information on the response object's interface, see:
224    #   http://docs.python.org/library/httplib.html#httpresponse-objects
225    response = sendRequest(connection, data)
226    print(response.getheaders())
227    print(repr(response.read()))
228
229    connection.close()
230    return ret_val
231
232data = ''
233fetch(data)
234''')
Note: See TracBrowser for help on using the repository browser.