source: trunk/bin/bletchley-http2py @ 58

Last change on this file since 58 was 51, checked in by tmorgan, 11 years ago

script generation fixes

  • 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            port = int(port, 10)
102            if port == 443:
103                use_ssl = True
104                protocol = 'https'
105        else:
106            host = value
107
108
109formatted_body = '\n            '.join([repr(body[i:i+40]) for i in range(0,len(body),40)])
110if formatted_body == '':
111    formatted_body = "b''"
112
113
114if args.requests:
115    print('''#!/usr/bin/env python3
116
117import sys
118try:
119    import requests
120except:
121    sys.stderr.write('ERROR: Could not import requests module.  Ensure it is installed.\\n')
122    sys.stderr.write('       Under Debian, the package name is "python3-requests"\\n.')
123    sys.exit(1)
124
125
126# TODO: ensure the host, port, and SSL settings are correct.
127host = %s
128port = %s
129protocol = %s
130''' % (repr(host),repr(port),repr(protocol)))
131
132    headers = dict(headers)
133    # XXX: We don't currently support exactly formatted header
134    #      continuations with python requests, but this should be
135    #      semantically equivalent.
136    for h in headers.keys():
137        headers[h] = ' '.join(headers[h])
138
139    print('''
140session = requests.Session()
141# TODO: use "data" to supply any parameters to be included in the request
142def sendRequest(session, data=None):
143    method = %s
144    path = %s
145    headers = %s
146    url = "%%s://%%s:%%d%%s" %% (protocol,host,port,path)
147    body = (%s)
148
149    return session.request(method, url, headers=headers, data=body)
150    ''' % (repr(method), repr(path), repr(headers), formatted_body))
151
152    print('''   
153
154def fetch(data):
155    global session
156    ret_val = None
157
158    # TODO: customize code here to retrieve what you need from the response(s)
159    # For information on the response object's interface, see:
160    #   http://docs.python-requests.org/en/latest/api/#requests.Response
161    response = sendRequest(session, data)
162    print(response.headers)
163    print(repr(response.content))
164
165    return ret_val
166
167data = ''
168fetch(data)
169''')
170
171
172
173else:
174    print('''#!/usr/bin/env python3
175
176import sys
177import http.client as httpc
178
179
180# TODO: ensure the host, port, and SSL settings are correct.
181host = %s
182port = %s
183use_ssl = %s
184''' % (repr(host),repr(port),repr(use_ssl)))
185
186    print('''
187# TODO: use "data" to supply any parameters to be included in the request
188def sendRequest(connection, data=None):
189    method = %s
190    path = %s
191    body = (%s)
192   
193    connection.putrequest(method, path)
194    ''' % (repr(method), repr(path), formatted_body))
195
196    for name,values in headers:
197        if len(values) > 1:
198            continuations = ','.join([repr(v) for v in values[1:]])
199            print('''    connection.putheader(%s, %s, %s)''' % (repr(name),repr(values[0]),continuations))
200        else:
201            print('''    connection.putheader(%s, %s)''' % (repr(name),repr(values[0])))
202
203    print('''   
204    if len(body) > 0:
205        connection.putheader('Content-Length', len(body))
206    connection.endheaders()
207    connection.send(body)
208   
209    return connection.getresponse()
210
211
212def newConnection():
213    if use_ssl:
214        return httpc.HTTPSConnection(host, port)
215    else:
216        return httpc.HTTPConnection(host, port)
217
218
219def fetch(data):
220    ret_val = None
221    connection = newConnection()
222
223    # TODO: customize code here to retrieve what you need from the response(s)
224    # For information on the response object's interface, see:
225    #   http://docs.python.org/library/httplib.html#httpresponse-objects
226    response = sendRequest(connection, data)
227    print(response.getheaders())
228    print(repr(response.read()))
229
230    connection.close()
231    return ret_val
232
233data = ''
234fetch(data)
235''')
Note: See TracBrowser for help on using the repository browser.