#!/usr/bin/env python3

# Requires Python 3+

'''
This script reads a raw HTTP request and writes to stdout a Python
script.  The generated script sends the same (or a very similar) 
request using the standard httplib/http.client library, or optionally
using the more user friendly python-requests library.

Certainly if you have a raw request, you could simply send it via TCP
sockets, but if for some reason the server behaves oddly with flow control,
insists on using gzip/deflate encoding, insists on using chunked encoding,
or any number of other annoying things, then using an HTTP library is a 
lot more convenient.  This script attempts to make that conversion easy.


Copyright (C) 2011-2013 Virtual Security Research, LLC
Author: Timothy D. Morgan

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License, version 3,
 as published by the Free Software Foundation.

 This program 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 sys
import argparse

bopen = lambda f: open(f, 'rb')

parser = argparse.ArgumentParser(
    description='A script which accepts an HTTP request and prints out a'
    ' generated Python script which sends a similar request.  This is useful'
    ' when one wants to automate sending a large number of requests to a'
    ' particular page or application.'
    ' For more information, see: http://code.google.com/p/bletchley/wiki/Overview')
parser.add_argument(
    'requestfile', type=bopen, nargs='?', default=sys.stdin.buffer, 
    help='A file containing an HTTP request.  Defaults to stdin if omitted.')
parser.add_argument(
    '--requests', action='store_true', help='Generate a script that uses the'
    ' python-requests module rather than httplib/http.client (experimental).')

args = parser.parse_args()
input_req = args.requestfile.read()


if b'\r\n\r\n' in input_req:
    raw_headers,body = input_req.split(b'\r\n\r\n', 1)
elif b'\n\n' in input_req:
    raw_headers,body = input_req.split(b'\n\n', 1)
else:
    raw_headers = input_req
    body = b''

raw_headers = raw_headers.decode('utf-8')

header_lines = raw_headers.split('\n')
method,path,version = header_lines[0].split(' ', 2)

host = 'TODO'
port = 80
use_ssl = False
protocol = 'http'

headers = []
for l in header_lines[1:]:
    if len(l) < 1: 
        break
    # Handle header line continuations
    if l[0] in ' \t':
        if len(headers) == 0:
            continue
        name,values = headers[-1]
        values.append(l.lstrip('\t'))
        headers[-1] = (name,values)
        continue

    name,value = l.split(':',1)
    value = value.lstrip(' ').rstrip('\r')

    # Skip headers that have to do with transfer encodings and connection longevity
    if name.lower() not in ['accept','accept-language',
                            'accept-encoding','accept-charset',
                            'connection', 'keep-alive', 'host', 
                            'content-length', 'proxy-connection']:
        headers.append((name,[value]))

    if name.lower() == 'host':
        if ':' in value:
            host,port = value.split(':',1)
            port = int(port, 10)
            if port == 443:
                use_ssl = True
                protocol = 'https'
        else:
            host = value


formatted_body = '\n            '.join([repr(body[i:i+40]) for i in range(0,len(body),40)])
if formatted_body == '':
    formatted_body = "b''"


if args.requests:
    print('''#!/usr/bin/env python3

import sys
try:
    import requests
except:
    sys.stderr.write('ERROR: Could not import requests module.  Ensure it is installed.\\n')
    sys.stderr.write('       Under Debian, the package name is "python3-requests"\\n.')
    sys.exit(1)


# TODO: ensure the host, port, and SSL settings are correct.
host = %s
port = %s
protocol = %s
''' % (repr(host),repr(port),repr(protocol)))

    headers = dict(headers)
    # XXX: We don't currently support exactly formatted header
    #      continuations with python requests, but this should be
    #      semantically equivalent.
    for h in headers.keys():
        headers[h] = ' '.join(headers[h])

    print('''
session = requests.Session()
# TODO: use "data" to supply any parameters to be included in the request
def sendRequest(session, data=None):
    method = %s
    path = %s
    headers = %s
    url = "%%s://%%s:%%d%%s" %% (protocol,host,port,path)
    body = (%s)

    return session.request(method, url, headers=headers, data=body)
    ''' % (repr(method), repr(path), repr(headers), formatted_body))

    print('''    

def fetch(data):
    global session
    ret_val = None

    # TODO: customize code here to retrieve what you need from the response(s)
    # For information on the response object's interface, see:
    #   http://docs.python-requests.org/en/latest/api/#requests.Response
    response = sendRequest(session, data)
    print(response.headers)
    print(repr(response.content))

    return ret_val

data = ''
fetch(data)
''')



else:
    print('''#!/usr/bin/env python3

import sys
import http.client as httpc


# TODO: ensure the host, port, and SSL settings are correct.
host = %s
port = %s
use_ssl = %s
''' % (repr(host),repr(port),repr(use_ssl)))

    print('''
# TODO: use "data" to supply any parameters to be included in the request
def sendRequest(connection, data=None):
    method = %s
    path = %s
    body = (%s)
    
    connection.putrequest(method, path)
    ''' % (repr(method), repr(path), formatted_body))

    for name,values in headers:
        if len(values) > 1:
            continuations = ','.join([repr(v) for v in values[1:]])
            print('''    connection.putheader(%s, %s, %s)''' % (repr(name),repr(values[0]),continuations))
        else:
            print('''    connection.putheader(%s, %s)''' % (repr(name),repr(values[0])))

    print('''    
    if len(body) > 0:
        connection.putheader('Content-Length', len(body))
    connection.endheaders()
    connection.send(body)
    
    return connection.getresponse()


def newConnection():
    if use_ssl:
        return httpc.HTTPSConnection(host, port)
    else:
        return httpc.HTTPConnection(host, port)


def fetch(data):
    ret_val = None
    connection = newConnection()

    # TODO: customize code here to retrieve what you need from the response(s)
    # For information on the response object's interface, see:
    #   http://docs.python.org/library/httplib.html#httpresponse-objects
    response = sendRequest(connection, data)
    print(response.getheaders())
    print(repr(response.read()))

    connection.close()
    return ret_val

data = ''
fetch(data)
''')
