# -*- Mode: Python; tab-width: 4 -*-

#			
# http proxy extension
#

# ================
# WORK IN PROGRESS
# ================

import socket
import string
import time

import asyncore
import asynchat

import http_server
import regex

import resolver

numeric_host = regex.compile ('[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')

class http_proxy_handler:
	
	url_regex = regex.compile (
		# groups:  1=host 2=port 3=uri
		'http://\([^/:]+\)\(:[0-9]+\)?\([^ ]*\).*',
		regex.casefold
		)
	
	def match (self, request):
		uri = request.uri
		if self.url_regex.match (uri) == len(uri):
			return 1
		else:
			return 0

	def handle_request (self, request):
		# install the proxy version of the writable predicate
		request.channel.writable = request.channel.writable_for_proxy
		# our task: feed the request object a producer that will either
		# answer it correctly, or produce an error message.
		http_proxy_producer (
			self.url_regex.group(1),
			self.url_regex.group(2),
			self.url_regex.group(3),
			request
			)
		# for now, be sure and close the connection
		#request['Connection'] = 'close'
		#request.channel.producer_fifo.push (None)

# this producer implements two interfaces: the async_chat interface,
# and the 'stallable producer' interface.  when this object is at the
# front of an http channel's producer fifo, it will affect the channel's
# response to the 'writable' predicate, depending on whether or not
# any proxy data has accumulated.

class http_proxy_producer (asynchat.async_chat):

	#resolver = resolver ()
	resolver = resolver.caching_resolver ('127.0.0.1')

	def __init__ (self, host, port, uri, request):
		if numeric_host.match (host) == len(host):
			resolved = 1
			self.ip = host
		else:
			resolved = 0
			self.ip = None

		if not port:
			self.port = 80
		else:
			self.port = string.atoi (port)

		self.uri = uri
		self.request = request

		if not resolved:
			if host in ['ad.doubleclick.net', 'ad.linkexchange.com', 'ads.imdb.com']:
				self.send_blank_gif()
			else:
				print 'sending DNS request'
				self.resolver.resolve (host, self.resolver_callback)
		else:
			self.resolver_callback (host, self.ip)

		self.outgoing_buffer = ''

	def resolver_callback (self, host, ttl, ip):
		asynchat.async_chat.__init__ (self)
		self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
		# handle errors!
		self.ip = ip
		self.set_terminator (None)
		self.connect ((self.ip, self.port))

	def collect_incoming_data (self, data):
		print 'pushing %d bytes' % len(data)
		self.request.channel.push (data)

	def found_terminator (self):
		print 'found_terminator should not have been called'

	def handle_connect (self):
		print 'connected'
		# for now, just send all the headers, and use HTTP/1.0
		self.push (
			string.join (
				['%s %s HTTP/1.0' % (
					string.upper (self.request.command),
					self.uri
					)] + self.request.header,
				'\r\n'
				) + '\r\n\r\n'
			)

	done = 0
	def handle_close (self):
		self.done = 1
		self.close()
		self.request.channel.close_when_done()

	# this method is called by http_channel.writable_for_proxy(),
	# to let it know whether or not any data is actually available.
	def stalled (self):
		return len(self.outgoing_buffer) == 0 and not self.done

	def more (self):
		# just return the whole buffer.
		r = self.outgoing_buffer
		self.outgoing_buffer = ''
		return r

	def send_blank_gif (self):
		import blank_gif
		self.request['Content-Type'] = 'image/gif'
		self.request['Content-Length'] = len(blank_gif.data)
		self.request.push (blank_gif.data)
		self.request.done()

if __name__ == '__main__':
	import sys
	if len(sys.argv) < 2:
		print 'usage: %s <root> <port>' % (sys.argv[0])
	else:
		import monitor
		import filesys
		import default_handler
		fs = filesys.os_filesystem (sys.argv[1])
		dh = default_handler.default_handler (fs)
		ph = http_proxy_handler()
		hs = http_server.http_server ('', string.atoi (sys.argv[2]))
		hs.install_handler (dh)
		hs.install_handler (ph)
		ms = monitor.monitor_server ('', 9998)
		if ('-p' in sys.argv):
			def profile_loop ():
				try:
					asyncore.loop()
				except KeyboardInterrupt:
					pass
			import profile
			profile.run ('profile_loop()', 'profile.out')
		else:
			asyncore.loop()