public
Last active

super-hacky flask proxy

  • Download Gist
proxy_views.py
Python
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
# coding:utf-8
# Copyright 2011 litl, LLC. All Rights Reserved.
 
import httplib
import re
import urllib
import urlparse
 
from flask import Blueprint, request, Response, url_for
from werkzeug.datastructures import Headers
from werkzeug.exceptions import NotFound
 
from jiffy.admin.login import check_login
 
proxy = Blueprint('proxy', __name__)
 
proxy.before_request(check_login)
 
HTML_REGEX = re.compile(r'((?:src|action|href)=["\'])/')
JQUERY_REGEX = re.compile(r'(\$\.(?:get|post)\(["\'])/')
JS_LOCATION_REGEX = re.compile(r'((?:window|document)\.location.*=.*["\'])/')
CSS_REGEX = re.compile(r'(url\(["\']?)/')
 
REGEXES = [HTML_REGEX, JQUERY_REGEX, JS_LOCATION_REGEX, CSS_REGEX]
 
 
def iterform(multidict):
for key in multidict.keys():
for value in multidict.getlist(key):
yield (key.encode("utf8"), value.encode("utf8"))
 
def parse_host_port(h):
"""Parses strings in the form host[:port]"""
host_port = h.split(":", 1)
if len(host_port) == 1:
return (h, 80)
else:
host_port[1] = int(host_port[1])
return host_port
 
 
@proxy.route('/proxy/<host>/', methods=["GET", "POST"])
@proxy.route('/proxy/<host>/<path:file>', methods=["GET", "POST"])
def proxy_request(host, file=""):
hostname, port = parse_host_port(host)
 
# Whitelist a few headers to pass on
request_headers = {}
for h in ["Cookie", "Referer", "X-Csrf-Token"]:
if h in request.headers:
request_headers[h] = request.headers[h]
 
if request.query_string:
path = "/%s?%s" % (file, request.query_string)
else:
path = "/" + file
 
if request.method == "POST":
form_data = list(iterform(request.form))
form_data = urllib.urlencode(form_data)
request_headers["Content-Length"] = len(form_data)
else:
form_data = None
 
conn = httplib.HTTPConnection(hostname, port)
conn.request(request.method, path, body=form_data, headers=request_headers)
resp = conn.getresponse()
 
# Clean up response headers for forwarding
response_headers = Headers()
for key, value in resp.getheaders():
if key in ["content-length", "connection", "content-type"]:
continue
 
if key == "set-cookie":
cookies = value.split(",")
[response_headers.add(key, c) for c in cookies]
else:
response_headers.add(key, value)
 
# If this is a redirect, munge the Location URL
if "location" in response_headers:
redirect = response_headers["location"]
parsed = urlparse.urlparse(request.url)
redirect_parsed = urlparse.urlparse(redirect)
 
redirect_host = redirect_parsed.netloc
if not redirect_host:
redirect_host = "%s:%d" % (hostname, port)
 
redirect_path = redirect_parsed.path
if redirect_parsed.query:
redirect_path += "?" + redirect_parsed.query
 
munged_path = url_for(".proxy_request",
host=redirect_host,
file=redirect_path[1:])
 
url = "%s://%s%s" % (parsed.scheme, parsed.netloc, munged_path)
response_headers["location"] = url
 
# Rewrite URLs in the content to point to our URL scheme instead.
# Ugly, but seems to mostly work.
root = url_for(".proxy_request", host=host)
contents = resp.read()
for regex in REGEXES:
contents = regex.sub(r'\1%s' % root, contents)
 
flask_response = Response(response=contents,
status=resp.status,
headers=response_headers,
content_type=resp.getheader('content-type'))
return flask_response

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.