Created
April 6, 2012 04:46
-
-
Save sandro/2316950 to your computer and use it in GitHub Desktop.
http proxy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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