Skip to content

Instantly share code, notes, and snippets.

@sandro
Created April 6, 2012 04:46
Show Gist options
  • Save sandro/2316950 to your computer and use it in GitHub Desktop.
Save sandro/2316950 to your computer and use it in GitHub Desktop.
http proxy
require 'socket'
require 'uri'
require 'net/http'
require 'openssl'
# module Net
# OHTTP = remove_const(:HTTP)
# class HTTP < OHTTP
# include OHTTP::ProxyDelta
# @is_proxy_class = true
# @proxy_address = 'localhost'
# @proxy_port = 44567
# def initialize(*args)
# super
# self.verify_mode = nil
# end
# def verify_mode=(*args)
# @verify_mode = OpenSSL::SSL::VERIFY_NONE
# end
# alias verify_mode verify_mode=
# end
# end
class ProxyReader
attr_reader :socket
def initialize(socket)
@socket = socket
end
def read
request = catch(:request_done) do
buf = ProxyRequest.new
buf.tunnel_host = socket.tunnel_host if socket.respond_to?(:tunnel_host)
while line = socket.gets
buf << line
# puts "Line: #{line}"
if buf.complete?
if buf.content_length?
buf << socket.read(buf.content_length)
end
throw :request_done, buf
end
end
end
end
end
class ProxyRequest
attr_accessor :tunnel_host
def initialize
@buffer = ""
end
def <<(str)
@buffer << str
end
def complete?
@buffer =~ /\r?\n\r?\n$/
end
def host
uri.host
end
def port
uri.port
end
def http_method
@http_method ||= first_line[0]
end
def uri
@uri ||= parse_uri
end
def parse_uri
h = first_line[1]
if tunnel_host
h = "https://#{tunnel_host}#{h}"
elsif h !~ /^https?:\/\//
h = "http://#{h}"
end
begin
URI.parse(h)
rescue URI::InvalidURIError
URI.parse(URI.escape(h))
end
end
def http_version
first_line[2]
end
def http_method_class
Net::HTTP.const_get(http_method.downcase.capitalize)
end
def http_request
req = http_method_class.new(uri.request_uri)
req.set_form_data(form_data) unless form_data.empty?
headers.each {|k,v| req[k] = v}
req
end
def ssl_tunnel?
http_method.upcase == "CONNECT"
end
def ssl?
uri.scheme == "https" || uri.port == URI::HTTPS::DEFAULT_PORT
end
def to_s
@buffer
end
def first_line
@first_line ||= lines[0].split(" ", 3)
end
def lines
@lines ||= @buffer.split(/\r?\n/)
end
def headers
@headers ||= parse_headers
end
def parse_headers
h = {}
lines[1..-1].each do |header|
k,v = header.split(": ", 2)
h[k] = v
end
h
end
def content_length
headers['Content-Length'].to_i
end
def content_length?
content_length > 0
end
def form_data
@form_data ||= parse_form_data
end
def parse_form_data
h = {}
if content_length?
data = @buffer.split(/\r?\n\r?\n/, 2).last
data.split("&").each do |set|
k,v = set.split('=', 2)
h[k] = v
end
end
h
end
end
class ProxyForwarder
attr_reader :proxy_req, :response
def initialize(proxy_req)
@proxy_req = proxy_req
@raw = ""
end
def start
if proxy_req.ssl_tunnel?
@raw = "#{proxy_req.http_version} 200 Connection established\r\n"
yield @raw
else
http = Net::HTTP.new(proxy_req.host, proxy_req.port)
http.use_ssl = proxy_req.ssl?
http.start do |http|
http.request(proxy_req.http_request) do |response|
@response = response
@raw << response_headers
yield @raw
response.read_body do |data|
@raw << data
yield data
data = nil
end
end
end
end
yield "\r\n"
end
def response_headers
h = ["HTTP/#{response.http_version} #{response.code} #{response.message}"]
response.each_capitalized do |k,v|
unless ['Transfer-Encoding'].include?(k)
h << "#{k}: #{v}"
end
end
h.join("\r\n") << "\r\n\r\n"
end
end
server = TCPServer.new(44567)
module SSLTunnel
attr_accessor :tunnel_host
end
def ssl_sock(sock)
sslContext = OpenSSL::SSL::SSLContext.new
sslContext.cert = OpenSSL::X509::Certificate.new(File.open("certificate.pem"))
sslContext.key = OpenSSL::PKey::RSA.new(File.open("key.pem"))
ssl = OpenSSL::SSL::SSLSocket.new(sock, sslContext)
ssl.sync_close = false
ssl
end
@servers = [server]
while true
selection = select(@servers, [], [], 1)
if selection
selection.first.each do |socket_ready|
unless socket_ready.closed?
s = socket_ready.accept
reader = ProxyReader.new(s)
request = reader.read
if request
forwarder = ProxyForwarder.new(request)
forwarder.start do |str|
# print str
s.print(str) unless s.closed?
end
if request.ssl_tunnel?
ssl = ssl_sock(s)
ssl.extend SSLTunnel
ssl.tunnel_host = request.host
@servers << ssl
else
s.close
@servers.delete(s)
end
else
puts "No request #{request.inspect}"
s.close
end
else
puts "Socket closed: #{socket_ready.inspect}"
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment