def _setup_transport(self): if self._transport is None: if self.__proxy is not None: self._transport = TCPClient(self.__proxy.host, self.__proxy.port) else: self._transport = TCPClient(self._host, self._port) self._http_parser = HTTPParser(self._transport) self._http_parser.connect("received", self._on_response_received) self._transport.connect("notify::status", self._on_status_change) self._transport.connect("error", self._on_error) self._transport.connect("sent", self._on_request_sent) if self._transport.get_property("status") != IoStatus.OPEN: self._transport.open()
def _setup_transport(self): if self._transport is None: if self._http_proxy: self._transport = TCPClient(self._http_proxy.host, self._http_proxy.port) else: self._transport = TCPClient(self._host, self._port) if self._proxies: self._transport = ProxyFactory(self._transport, self._proxies, 'http') self._setup_parser() if self._transport.get_property("status") != IoStatus.OPEN: self._transport.open()
def __init__(self, client, proxy_infos): assert (proxy_infos.type in ( 'http', 'https')), "HTTPConnectProxy expects an http(s) proxy description" assert (client.domain == AF_INET ), "HTTP CONNECT only handles INET address family" assert (client.type == SOCK_STREAM ), "HTTP CONNECT only handles SOCK_STREAM" assert (client.status == IoStatus.CLOSED ), "HTTPConnectProxy expects a closed client" AbstractProxy.__init__(self, client, proxy_infos) self._transport = TCPClient(self._proxy.host, self._proxy.port) self._transport.connect("notify::status", self._on_transport_status) self._transport.connect("error", self._on_transport_error) self._http_parser = HTTPParser(self._transport) self._http_parser.connect("received", self._on_proxy_response)
def __init__(self, client, proxy_infos): assert(proxy_infos.type == 'socks4'), \ "SOCKS4Proxy expects a socks4 proxy description" # TODO : implement version 4a of the protocol to allow proxy-side name resolution assert(client.domain == AF_INET), \ "SOCKS4 CONNECT only handles INET address family" assert(client.type == SOCK_STREAM), \ "SOCKS4 CONNECT only handles SOCK_STREAM" assert(client.status == IoStatus.CLOSED), \ "SOCKS4Proxy expects a closed client" AbstractProxy.__init__(self, client, proxy_infos) self._transport = TCPClient(self._proxy.host, self._proxy.port) self._transport.connect("notify::status", self._on_transport_status) self._transport.connect("error", self._on_transport_error) self._delimiter_parser = DelimiterParser(self._transport) self._delimiter_parser.delimiter = 8 self._delimiter_parser.connect("received", self._on_proxy_response)
def __init__(self, client, proxy_infos): assert(proxy_infos.type in ('http', 'https')), "HTTPConnectProxy expects an http(s) proxy description" assert(client.domain == AF_INET), "HTTP CONNECT only handles INET address family" assert(client.type == SOCK_STREAM), "HTTP CONNECT only handles SOCK_STREAM" assert(client.status == IoStatus.CLOSED), "HTTPConnectProxy expects a closed client" AbstractProxy.__init__(self, client, proxy_infos) self._transport = TCPClient(self._proxy.host, self._proxy.port) self._transport.connect("notify::status", self._on_transport_status) self._transport.connect("error", self._on_transport_error) self._http_parser = HTTPParser(self._transport) self._http_parser.connect("received", self._on_proxy_response)
def __init__(self, client, proxy): assert(proxy.type in ('socks', 'socks5')), \ "SOCKS5Proxy expects a socks5 proxy description" assert(client.domain == AF_INET), \ "SOCKS5 CONNECT only handles INET address family" assert(client.type == SOCK_STREAM), \ "SOCKS5 CONNECT only handles SOCK_STREAM" assert(client.status == IoStatus.CLOSED), \ "SOCKS5Proxy expects a closed client" AbstractProxy.__init__(self, client, proxy) self._transport = TCPClient(self._proxy.host, self._proxy.port) self._transport.connect("notify::status", self._on_transport_status) self._transport.connect("error", self._on_transport_error) self._delimiter_parser = DelimiterParser(self._transport) self._delimiter_parser.connect("received", self._on_proxy_response) self._state = None self._must_auth = False
class HTTPConnectProxy(AbstractProxy): def __init__(self, client, proxy_infos): assert (proxy_infos.type in ( 'http', 'https')), "HTTPConnectProxy expects an http(s) proxy description" assert (client.domain == AF_INET ), "HTTP CONNECT only handles INET address family" assert (client.type == SOCK_STREAM ), "HTTP CONNECT only handles SOCK_STREAM" assert (client.status == IoStatus.CLOSED ), "HTTPConnectProxy expects a closed client" AbstractProxy.__init__(self, client, proxy_infos) self._transport = TCPClient(self._proxy.host, self._proxy.port) self._transport.connect("notify::status", self._on_transport_status) self._transport.connect("error", self._on_transport_error) self._http_parser = HTTPParser(self._transport) self._http_parser.connect("received", self._on_proxy_response) # opening state methods def _pre_open(self, io_object=None): AbstractProxy._pre_open(self) def _post_open(self): AbstractProxy._post_open(self) host = self._client.get_property("host") port = self._client.get_property("port") proxy_protocol = 'CONNECT %s:%s HTTP/1.1\r\n' % (host, port) proxy_protocol += 'Proxy-Connection: Keep-Alive\r\n' proxy_protocol += 'Pragma: no-cache\r\n' proxy_protocol += 'User-Agent: %s/%s\r\n' % (GNet.NAME, GNet.VERSION) if self._proxy.user: auth = base64.encodestring(self._proxy.user + ':' + self._proxy.password).strip() proxy_protocol += 'Proxy-authorization: Basic ' + auth + '\r\n' proxy_protocol += '\r\n' self._transport.send(proxy_protocol) # public API def open(self): """Open the connection.""" if not self._configure(): return self._pre_open() try: self._transport.open() except: pass def close(self): """Close the connection.""" self._client._proxy_closed() def send(self, buffer, callback=None, *args): self._client.send(buffer, callback, *args) # callbacks and signal handlers def _on_transport_status(self, transport, param): if transport.status == IoStatus.OPEN: self._post_open() elif transport.status == IoStatus.OPENING: self._client._proxy_opening(self._transport._transport) self._status = transport.status else: self._status = transport.status def _on_transport_error(self, transport, error_code): if error_code == IoError.CONNECTION_FAILED: error_code = IoError.PROXY_CONNECTION_FAILED self.close() self.emit("error", error_code) def _on_proxy_response(self, parser, response): if self.status == IoStatus.OPENING: if response.status == 200: del self._http_parser self._transport._watch_remove() # HACK: ok this is ugly ! self._client._proxy_open() elif response.status == 100: return True elif response.status == 407: self.close() self.emit("error", IoError.PROXY_AUTHENTICATION_REQUIRED) else: raise NotImplementedError("Unknown Proxy response code") return False
class SOCKS4Proxy(AbstractProxy): PROTOCOL_VERSION = 4 CONNECT_COMMAND = 1 """Proxy class used to communicate with SOCKS4 proxies.""" def __init__(self, client, proxy_infos): assert(proxy_infos.type == 'socks4'), \ "SOCKS4Proxy expects a socks4 proxy description" # TODO : implement version 4a of the protocol to allow proxy-side name resolution assert(client.domain == AF_INET), \ "SOCKS4 CONNECT only handles INET address family" assert(client.type == SOCK_STREAM), \ "SOCKS4 CONNECT only handles SOCK_STREAM" assert(client.status == IoStatus.CLOSED), \ "SOCKS4Proxy expects a closed client" AbstractProxy.__init__(self, client, proxy_infos) self._transport = TCPClient(self._proxy.host, self._proxy.port) self._transport.connect("notify::status", self._on_transport_status) self._transport.connect("error", self._on_transport_error) self._delimiter_parser = DelimiterParser(self._transport) self._delimiter_parser.delimiter = 8 self._delimiter_parser.connect("received", self._on_proxy_response) # Opening state methods def _pre_open(self, io_object=None): AbstractProxy._pre_open(self) def _post_open(self): AbstractProxy._post_open(self) user = self._proxy.user proxy_protocol = struct.pack('!BBH', SOCKS4Proxy.PROTOCOL_VERSION, SOCKS4Proxy.CONNECT_COMMAND, self.port) for part in self.host.split('.'): proxy_protocol += struct.pack('B', int(part)) proxy_protocol += user proxy_protocol += struct.pack('B', 0) self._delimiter_parser.enable() self._transport.send(proxy_protocol) # Public API @property def protocol(self): return "SOCKS4" def open(self): """Open the connection.""" if not self._configure(): return self._pre_open() try: self._transport.open() except: pass def close(self): """Close the connection.""" self._delimiter_parser.disable() self._client._proxy_closed() self._transport.close() def send(self, buffer, callback=None, errback=None): self._client.send(buffer, callback, errback=None) # Callbacks def _on_transport_status(self, transport, param): if transport.status == IoStatus.OPEN: self._post_open() elif transport.status == IoStatus.OPENING: self._client._proxy_opening(self._transport._transport) self._status = transport.status else: self._status = transport.status def _on_transport_error(self, transport, error): self.close() self.emit("error", error) def _on_proxy_response(self, parser, response): version, response_code = struct.unpack('BB', response[0:2]) assert(version == 0) if self.status == IoStatus.OPENING: if response_code == 90: self._delimiter_parser.disable() self._transport.disable() self._client._proxy_open() else: logger.error("Connection failed (%s)" % response_code) self.close() self.emit("error", SOCKS4Error(self, response_code)) return False
class HTTPConnectProxy(AbstractProxy): def __init__(self, client, proxy_infos): assert(proxy_infos.type in ('http', 'https')), "HTTPConnectProxy expects an http(s) proxy description" assert(client.domain == AF_INET), "HTTP CONNECT only handles INET address family" assert(client.type == SOCK_STREAM), "HTTP CONNECT only handles SOCK_STREAM" assert(client.status == IoStatus.CLOSED), "HTTPConnectProxy expects a closed client" AbstractProxy.__init__(self, client, proxy_infos) self._transport = TCPClient(self._proxy.host, self._proxy.port) self._transport.connect("notify::status", self._on_transport_status) self._transport.connect("error", self._on_transport_error) self._http_parser = HTTPParser(self._transport) self._http_parser.connect("received", self._on_proxy_response) # opening state methods def _pre_open(self, io_object=None): AbstractProxy._pre_open(self) def _post_open(self): AbstractProxy._post_open(self) proxy_protocol = 'CONNECT %s:%s HTTP/1.1\r\n' % (self.host, self.port) proxy_protocol += 'Proxy-Connection: Keep-Alive\r\n' proxy_protocol += 'Pragma: no-cache\r\n' proxy_protocol += 'User-Agent: %s/%s\r\n' % (GNet.NAME, GNet.VERSION) if self._proxy.user: auth = base64.encodestring(self._proxy.user + ':' + self._proxy.password).strip() proxy_protocol += 'Proxy-authorization: Basic ' + auth + '\r\n' proxy_protocol += '\r\n' self._http_parser.enable() self._transport.send(proxy_protocol) # public API @property def protocol(self): return "HTTPConnect" def open(self): """Open the connection.""" if not self._configure(): return self._pre_open() try: self._transport.open() except: pass def close(self): """Close the connection.""" self._http_parser.disable() self._client._proxy_closed() self._transport.close() def send(self, buffer, callback=None, errback=None): self._client.send(buffer, callback, errback=None) # callbacks and signal handlers def _on_transport_status(self, transport, param): if transport.status == IoStatus.OPEN: self._post_open() elif transport.status == IoStatus.OPENING: self._client._proxy_opening(self._transport._transport) self._status = transport.status else: self._status = transport.status def _on_transport_error(self, transport, error): self.close() self.emit("error", error) def _on_proxy_response(self, parser, response): if self.status == IoStatus.OPENING: if response.status == 200: self._http_parser.disable() self._transport.disable() self._client._proxy_open() elif response.status == 100: return True else: logger.error("Connection failed (%s)" % response.status) self.close() self.emit("error", HTTPConnectError(self, response.status)) return False
class HTTP(gobject.GObject): """HTTP protocol client class.""" __gsignals__ = { "error": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_ULONG, )), "response-received": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object, )), # HTTPResponse "request-sent": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object, )), # HTTPRequest } def __init__(self, host, port=80, proxy=None): """Connection initialization @param host: the host to connect to. @type host: string @param port: the port number to connect to @type port: integer @param proxy: proxy that we can use to connect @type proxy: L{gnet.proxy.ProxyInfos}""" gobject.GObject.__init__(self) assert (proxy is None or proxy.type == 'http' ) # TODO: add support for other proxies (socks4 and 5) self._host = host self._port = port self.__proxy = proxy self._transport = None self._http_parser = None self._outgoing_queue = [] self._waiting_response = False def _setup_transport(self): if self._transport is None: if self.__proxy is not None: self._transport = TCPClient(self.__proxy.host, self.__proxy.port) else: self._transport = TCPClient(self._host, self._port) self._http_parser = HTTPParser(self._transport) self._http_parser.connect("received", self._on_response_received) self._transport.connect("notify::status", self._on_status_change) self._transport.connect("error", self._on_error) self._transport.connect("sent", self._on_request_sent) if self._transport.get_property("status") != IoStatus.OPEN: self._transport.open() def _on_status_change(self, transport, param): if transport.get_property("status") == IoStatus.OPEN: self._process_queue() elif transport.get_property("status") == IoStatus.CLOSED and\ (self._waiting_response or len(self._outgoing_queue) > 0): self._waiting_response = False self._setup_transport() def _on_request_sent(self, transport, request, length): assert (str(self._outgoing_queue[0]) == request) self._waiting_response = True self.emit("request-sent", self._outgoing_queue[0]) def _on_response_received(self, parser, response): if response.status >= 100 and response.status < 200: return #if response.status in (301, 302): # UNTESTED: please test # location = response.headers['Location'] # location = location.rsplit("://", 1) # if len(location) == 2: # scheme = location[0] # location = location[1] # if scheme == "http": # location = location.rsplit(":", 1) # self._host = location[0] # if len(location) == 2: # self._port = int(location[1]) # self._outgoing_queue[0].headers['Host'] = response.headers['Location'] # self._setup_transport() # return self._outgoing_queue.pop(0) # pop the request from the queue self.emit("response-received", response) self._waiting_response = False self._process_queue() # next request ? def _on_error(self, transport, error): self.emit("error", error) def _process_queue(self): if len(self._outgoing_queue) == 0 or \ self._waiting_response: # no pipelining return if self._transport is None or \ self._transport.get_property("status") != IoStatus.OPEN: self._setup_transport() return self._transport.send(str(self._outgoing_queue[0])) def request(self, resource='/', headers=None, data='', method='GET'): if headers is None: headers = {} headers['Host'] = self._host + ':' + str(self._port) headers['Content-Length'] = str(len(data)) if 'User-Agent' not in headers: user_agent = GNet.NAME, GNet.VERSION, platform.system( ), platform.machine() headers['User-Agent'] = "%s/%s (%s %s)" % user_agent if self.__proxy is not None: url = 'http://%s:%d%s' % (self._host, self._port, resource) if self.__proxy.user: auth = self.__proxy.user + ':' + self.__proxy.password credentials = base64.encodestring(auth).strip() headers['Proxy-Authorization'] = 'Basic ' + credentials else: url = resource request = HTTPRequest(headers, data, method, url) self._outgoing_queue.append(request) self._process_queue()
class SOCKS5Proxy(AbstractProxy): VERSION = 5 CMD_CONNECT = 0x01 AUTH_VERSION = 1 MAX_LEN = 255 RESERVED = 0x00 AUTH_NONE = 0x00 AUTH_GSSAPI = 0x01 AUTH_USR_PASS = 0x02 AUTH_NO_ACCEPT = 0xff ATYP_IPV4 = 0x01 ATYP_DOMAINNAME = 0x03 ATYP_IPV6 = 0x04 STATE_NEGO = 0x01 STATE_AUTH = 0x02 STATE_CONN = 0x03 STATE_RECV_IPV4_ADDR = 0x04 STATE_RECV_ADDR_LEN = 0x05 STATE_RECV_DOMAINNAME = 0x06 CODE_SUCCEEDED = 0x00 CODE_SRV_FAILURE = 0x01 CODE_NOT_ALLOWED = 0x02 CODE_NET_UNREACH = 0x03 CODE_HOST_UNREACH = 0x04 CODE_REFUSED = 0x05 CODE_TTL_EXPIRED = 0x06 CODE_CMD_NOT_SUP = 0x07 CODE_ATYPE_NOT_SUP = 0x08 NEGO_REPLY_LEN = 2 AUTH_REPLY_LEN = 2 CONN_REPLY_LEN = 4 IPV4_ADDR_LEN = 4 """Proxy class used to communicate with SOCKS5 proxies.""" def __init__(self, client, proxy): assert(proxy.type in ('socks', 'socks5')), \ "SOCKS5Proxy expects a socks5 proxy description" assert(client.domain == AF_INET), \ "SOCKS5 CONNECT only handles INET address family" assert(client.type == SOCK_STREAM), \ "SOCKS5 CONNECT only handles SOCK_STREAM" assert(client.status == IoStatus.CLOSED), \ "SOCKS5Proxy expects a closed client" AbstractProxy.__init__(self, client, proxy) self._transport = TCPClient(self._proxy.host, self._proxy.port) self._transport.connect("notify::status", self._on_transport_status) self._transport.connect("error", self._on_transport_error) self._delimiter_parser = DelimiterParser(self._transport) self._delimiter_parser.connect("received", self._on_proxy_response) self._state = None self._must_auth = False # Opening state methods def _pre_open(self, io_object=None): AbstractProxy._pre_open(self) def _post_open(self): AbstractProxy._post_open(self) self._delimiter_parser.enable() self._send_nego_msg() # Public API @property def protocol(self): return "SOCKS5" def open(self): """Open the connection.""" if not self._configure(): return self._pre_open() try: self._transport.open() except: pass def close(self): """Close the connection.""" self._delimiter_parser.disable() self._client._proxy_closed() self._transport.close() def send(self, buffer, callback=None, errback=None): self._client.send(buffer, callback, errback) # Handshake def _send_nego_msg(self): user = self._proxy.user password = self._proxy.password methods = [self.AUTH_NONE] if user or password: methods.append(self.AUTH_USR_PASS) msg = struct.pack('!BB', SOCKS5Proxy.VERSION, len(methods)) for method in methods: msg += struct.pack('B', method) logger.info("Sending negotiation request (%i methods)" % len(methods)) self._state = SOCKS5Proxy.STATE_NEGO self._delimiter_parser.delimiter = SOCKS5Proxy.NEGO_REPLY_LEN self._transport.send(msg) return True def _parse_nego_reply(self, response): version, method = struct.unpack('!BB', response[0:2]) if version != SOCKS5Proxy.VERSION: raise Exception("Server is not SOCKS5 compatible") if method == SOCKS5Proxy.AUTH_NO_ACCEPT: raise Exception("Server doesn't support any of the proposed \ authentication methods") logger.info("Server chose authentication method %i" % method) self._must_auth = (method == SOCKS5Proxy.AUTH_USR_PASS) return True def _send_auth_msg(self): user = self._proxy.user password = self._proxy.password if len(user) > self.MAX_LEN or len(password) > self.MAX_LEN: raise Exception("User and password need to be less than %i \ characters long" % self.MAX_LEN) msg = struct.pack('B', SOCKS5Proxy.AUTH_VERSION) msg += struct.pack('B', len(user)) if user: msg += user msg += struct.pack('B', len(password)) if password: msg += password logger.info("Sending authentication request") self._state = SOCKS5Proxy.STATE_AUTH self._delimiter_parser.delimiter = SOCKS5Proxy.AUTH_REPLY_LEN self._transport.send(msg) def _check_auth_status(self, response): version, code = struct.unpack('!BB', response[0:2]) if (version != SOCKS5Proxy.VERSION or code != SOCKS5Proxy.CODE_SUCCEEDED): raise Exception("Authentication didn't succeed (%s)" % code) logger.info("Authentication succeeded") return True def _send_connect_msg(self): msg = struct.pack('!BBB', SOCKS5Proxy.VERSION, SOCKS5Proxy.CMD_CONNECT, SOCKS5Proxy.RESERVED) try: addr = socket.inet_aton(self.host) msg += struct.pack('!BI', SOCKS5Proxy.ATYP_IPV4, addr) except: if len(self.host) > SOCKS5Proxy.MAX_LEN: raise Exception("Hostname is longer than max allowed length") msg += struct.pack('!BB', SOCKS5Proxy.ATYP_DOMAINNAME, len(self.host)) msg += self.host msg += struct.pack('!H', self.port) logger.info("Connection request to %s:%u" % (self.host, self.port)) self._state = SOCKS5Proxy.STATE_CONN self._delimiter_parser.delimiter = SOCKS5Proxy.CONN_REPLY_LEN self._transport.send(msg) def _parse_connect_reply(self, response): version, code, reserved, atyp = struct.unpack('!BBBB', response[0:4]) if version != SOCKS5Proxy.VERSION or reserved != SOCKS5Proxy.RESERVED: raise Exception("Connection reply isn't SOCKS5 compatible") if code == SOCKS5Proxy.CODE_SUCCEEDED: logger.info("Connection request has been accepted") else: raise Exception("Connection request has been declined (%i)" % code) if atyp == SOCKS5Proxy.ATYP_IPV4: self._state = SOCKS5Proxy.STATE_RECV_IPV4_ADDR self._delimiter_parser.delimiter = SOCKS5Proxy.IPV4_ADDR_LEN elif atyp == SOCKS5Proxy.ATYP_DOMAINNAME: self._state = SOCKS5Proxy.STATE_RECV_ADDR_LEN self._delimiter_parser.delimiter = 1 else: raise Exception('Unsupported address type: %i' % atyp) def _parse_domain_name_length(self, response): length, = struct.unpack('!B', response) self._state = SOCKS5Proxy.STATE_RECV_DOMAINNAME self._delimiter_parser.delimiter = length # Callbacks def _on_transport_status(self, transport, param): if transport.status == IoStatus.OPEN: self._post_open() elif transport.status == IoStatus.OPENING: self._client._proxy_opening(self._transport._transport) self._status = transport.status else: self._status = transport.status def _on_transport_error(self, transport, error): self.close() self.emit("error", error) def _on_proxy_response(self, parser, response): try: if self._state == SOCKS5Proxy.STATE_NEGO: self._parse_nego_reply(response) if self._must_auth: self._send_auth_msg() else: self._send_connect_msg() elif self._state == SOCKS5Proxy.STATE_AUTH: self._check_auth_status(response) self._send_connect_msg() elif self._state == SOCKS5Proxy.STATE_CONN: self._parse_connect_reply(response) elif self._state == SOCKS5Proxy.STATE_RECV_ADDR_LEN: self._parse_domain_name_length(response) elif (self._state == SOCKS5Proxy.STATE_RECV_IPV4_ADDR or self._state == SOCKS5Proxy.STATE_RECV_DOMAINNAME): self._delimiter_parser.disable() self._transport.disable() self._client._proxy_open() except Exception, err: logger.error("Handshake failed") logger.exception(err) self.close() self.emit("error", SOCKS5Error(self, str(err))) return False
class SOCKS4Proxy(AbstractProxy): PROTOCOL_VERSION = 4 CONNECT_COMMAND = 1 """Proxy class used to communicate with SOCKS4 proxies.""" def __init__(self, client, proxy_infos): assert(proxy_infos.type == 'socks4'), \ "SOCKS4Proxy expects a socks4 proxy description" # TODO : implement version 4a of the protocol to allow proxy-side name resolution assert(client.domain == AF_INET), \ "SOCKS4 CONNECT only handles INET address family" assert(client.type == SOCK_STREAM), \ "SOCKS4 CONNECT only handles SOCK_STREAM" assert(client.status == IoStatus.CLOSED), \ "SOCKS4Proxy expects a closed client" AbstractProxy.__init__(self, client, proxy_infos) self._transport = TCPClient(self._proxy.host, self._proxy.port) self._transport.connect("notify::status", self._on_transport_status) self._transport.connect("error", self._on_transport_error) self._delimiter_parser = DelimiterParser(self._transport) self._delimiter_parser.delimiter = 8 self._delimiter_parser.connect("received", self._on_proxy_response) # Opening state methods def _pre_open(self, io_object=None): AbstractProxy._pre_open(self) def _post_open(self): AbstractProxy._post_open(self) host = self._client.get_property("host") port = self._client.get_property("port") user = self._proxy.user proxy_protocol = struct.pack('!BBH', SOCKS4Proxy.PROTOCOL_VERSION, SOCKS4Proxy.CONNECT_COMMAND, port) for part in host.split('.'): proxy_protocol += struct.pack('B', int(part)) proxy_protocol += user proxy_protocol += struct.pack('B', 0) self._transport.send(proxy_protocol) # Public API def open(self): """Open the connection.""" if not self._configure(): return self._pre_open() try: self._transport.open() except: pass def close(self): """Close the connection.""" self._client._proxy_closed() def send(self, buffer, callback=None, *args): self._client.send(buffer, callback, *args) # Callbacks def _on_transport_status(self, transport, param): if transport.status == IoStatus.OPEN: self._post_open() elif transport.status == IoStatus.OPENING: self._client._proxy_opening(self._transport._transport) self._status = transport.status else: self._status = transport.status def _on_transport_error(self, transport, error_code): if error_code == IoError.CONNECTION_FAILED: error_code = IoError.PROXY_CONNECTION_FAILED self.close() self.emit("error", error_code) def _on_proxy_response(self, parser, response): version, response_code = struct.unpack('BB', response[0:2]) assert (version == 0) if self.status == IoStatus.OPENING: if response_code == 90: del self._delimiter_parser self._transport._watch_remove() # HACK: ok this is ugly ! self._client._proxy_open() elif response_code == 91: self.close() self.emit("error", IoError.PROXY_CONNECTION_FAILED) elif response_code == 92: self.close() self.emit("error", IoError.PROXY_AUTHENTICATION_REQUIRED) elif response_code == 93: self.close() self.emit("error", IoError.PROXY_AUTHENTICATION_REQUIRED) else: raise NotImplementedError("Unknow Proxy response code") return False
class SOCKS4Proxy(AbstractProxy): PROTOCOL_VERSION = 4 CONNECT_COMMAND = 1 """Proxy class used to communicate with SOCKS4 proxies.""" def __init__(self, client, proxy_infos): assert(proxy_infos.type == 'socks4'), \ "SOCKS4Proxy expects a socks4 proxy description" # TODO : implement version 4a of the protocol to allow proxy-side name resolution assert(client.domain == AF_INET), \ "SOCKS4 CONNECT only handles INET address family" assert(client.type == SOCK_STREAM), \ "SOCKS4 CONNECT only handles SOCK_STREAM" assert(client.status == IoStatus.CLOSED), \ "SOCKS4Proxy expects a closed client" AbstractProxy.__init__(self, client, proxy_infos) self._transport = TCPClient(self._proxy.host, self._proxy.port) self._transport.connect("notify::status", self._on_transport_status) self._transport.connect("error", self._on_transport_error) self._delimiter_parser = DelimiterParser(self._transport) self._delimiter_parser.delimiter = 8 self._delimiter_parser.connect("received", self._on_proxy_response) # Opening state methods def _pre_open(self, io_object=None): AbstractProxy._pre_open(self) def _post_open(self): AbstractProxy._post_open(self) host = self._client.get_property("host") port = self._client.get_property("port") user = self._proxy.user proxy_protocol = struct.pack('!BBH', SOCKS4Proxy.PROTOCOL_VERSION, SOCKS4Proxy.CONNECT_COMMAND, port) for part in host.split('.'): proxy_protocol += struct.pack('B', int(part)) proxy_protocol += user proxy_protocol += struct.pack('B', 0) self._transport.send(proxy_protocol) # Public API def open(self): """Open the connection.""" if not self._configure(): return self._pre_open() try: self._transport.open() except: pass def close(self): """Close the connection.""" self._client._proxy_closed() def send(self, buffer, callback=None, *args): self._client.send(buffer, callback, *args) # Callbacks def _on_transport_status(self, transport, param): if transport.status == IoStatus.OPEN: self._post_open() elif transport.status == IoStatus.OPENING: self._client._proxy_opening(self._transport._transport) self._status = transport.status else: self._status = transport.status def _on_transport_error(self, transport, error_code): if error_code == IoError.CONNECTION_FAILED: error_code = IoError.PROXY_CONNECTION_FAILED self.close() self.emit("error", error_code) def _on_proxy_response(self, parser, response): version, response_code = struct.unpack('BB', response[0:2]) assert(version == 0) if self.status == IoStatus.OPENING: if response_code == 90: del self._delimiter_parser self._transport._watch_remove() # HACK: ok this is ugly ! self._client._proxy_open() elif response_code == 91: self.close() self.emit("error", IoError.PROXY_CONNECTION_FAILED) elif response_code == 92: self.close() self.emit("error", IoError.PROXY_AUTHENTICATION_REQUIRED) elif response_code == 93: self.close() self.emit("error", IoError.PROXY_AUTHENTICATION_REQUIRED) else: raise NotImplementedError("Unknow Proxy response code") return False
class HTTPConnectProxy(AbstractProxy): def __init__(self, client, proxy_infos): assert (proxy_infos.type in ( 'http', 'https')), "HTTPConnectProxy expects an http(s) proxy description" assert (client.domain == AF_INET ), "HTTP CONNECT only handles INET address family" assert (client.type == SOCK_STREAM ), "HTTP CONNECT only handles SOCK_STREAM" assert (client.status == IoStatus.CLOSED ), "HTTPConnectProxy expects a closed client" AbstractProxy.__init__(self, client, proxy_infos) self._transport = TCPClient(self._proxy.host, self._proxy.port) self._transport.connect("notify::status", self._on_transport_status) self._transport.connect("error", self._on_transport_error) self._http_parser = HTTPParser(self._transport) self._http_parser.connect("received", self._on_proxy_response) # opening state methods def _pre_open(self, io_object=None): AbstractProxy._pre_open(self) def _post_open(self): AbstractProxy._post_open(self) proxy_protocol = 'CONNECT %s:%s HTTP/1.1\r\n' % (self.host, self.port) proxy_protocol += 'Proxy-Connection: Keep-Alive\r\n' proxy_protocol += 'Pragma: no-cache\r\n' proxy_protocol += 'User-Agent: %s/%s\r\n' % (GNet.NAME, GNet.VERSION) if self._proxy.user: auth = base64.encodestring(self._proxy.user + ':' + self._proxy.password).strip() proxy_protocol += 'Proxy-authorization: Basic ' + auth + '\r\n' proxy_protocol += '\r\n' self._http_parser.enable() self._transport.send(proxy_protocol) # public API @property def protocol(self): return "HTTPConnect" def open(self): """Open the connection.""" if not self._configure(): return self._pre_open() try: self._transport.open() except: pass def close(self): """Close the connection.""" self._http_parser.disable() self._client._proxy_closed() self._transport.close() def send(self, buffer, callback=None, errback=None): self._client.send(buffer, callback, errback=None) # callbacks and signal handlers def _on_transport_status(self, transport, param): if transport.status == IoStatus.OPEN: self._post_open() elif transport.status == IoStatus.OPENING: self._client._proxy_opening(self._transport._transport) self._status = transport.status else: self._status = transport.status def _on_transport_error(self, transport, error): self.close() self.emit("error", error) def _on_proxy_response(self, parser, response): if self.status == IoStatus.OPENING: if response.status == 200: self._http_parser.disable() self._transport.disable() self._client._proxy_open() elif response.status == 100: return True else: logger.error("Connection failed (%s)" % response.status) self.close() self.emit("error", HTTPConnectError(self, response.status)) return False
class HTTPConnectProxy(AbstractProxy): def __init__(self, client, proxy_infos): assert(proxy_infos.type in ('http', 'https')), "HTTPConnectProxy expects an http(s) proxy description" assert(client.domain == AF_INET), "HTTP CONNECT only handles INET address family" assert(client.type == SOCK_STREAM), "HTTP CONNECT only handles SOCK_STREAM" assert(client.status == IoStatus.CLOSED), "HTTPConnectProxy expects a closed client" AbstractProxy.__init__(self, client, proxy_infos) self._transport = TCPClient(self._proxy.host, self._proxy.port) self._transport.connect("notify::status", self._on_transport_status) self._transport.connect("error", self._on_transport_error) self._http_parser = HTTPParser(self._transport) self._http_parser.connect("received", self._on_proxy_response) # opening state methods def _pre_open(self, io_object=None): AbstractProxy._pre_open(self) def _post_open(self): AbstractProxy._post_open(self) host = self._client.get_property("host") port = self._client.get_property("port") proxy_protocol = 'CONNECT %s:%s HTTP/1.1\r\n' % (host, port) proxy_protocol += 'Proxy-Connection: Keep-Alive\r\n' proxy_protocol += 'Pragma: no-cache\r\n' proxy_protocol += 'User-Agent: %s/%s\r\n' % (GNet.NAME, GNet.VERSION) if self._proxy.user: auth = base64.encodestring(self._proxy.user + ':' + self._proxy.password).strip() proxy_protocol += 'Proxy-authorization: Basic ' + auth + '\r\n' proxy_protocol += '\r\n' self._transport.send(proxy_protocol) # public API def open(self): """Open the connection.""" if not self._configure(): return self._pre_open() try: self._transport.open() except: pass def close(self): """Close the connection.""" self._client._proxy_closed() def send(self, buffer, callback=None, *args): self._client.send(buffer, callback, *args) # callbacks and signal handlers def _on_transport_status(self, transport, param): if transport.status == IoStatus.OPEN: self._post_open() elif transport.status == IoStatus.OPENING: self._client._proxy_opening(self._transport._transport) self._status = transport.status else: self._status = transport.status def _on_transport_error(self, transport, error_code): if error_code == IoError.CONNECTION_FAILED: error_code = IoError.PROXY_CONNECTION_FAILED self.close() self.emit("error", error_code) def _on_proxy_response(self, parser, response): if self.status == IoStatus.OPENING: if response.status == 200: del self._http_parser self._transport._watch_remove() # HACK: ok this is ugly ! self._client._proxy_open() elif response.status == 100: return True elif response.status == 407: self.close() self.emit("error", IoError.PROXY_AUTHENTICATION_REQUIRED) else: raise NotImplementedError("Unknown Proxy response code") return False
class HTTP(gobject.GObject): """HTTP protocol client class.""" __gsignals__ = { "error" : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_ULONG,)), "response-received": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object,)), # HTTPResponse "request-sent": (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (object,)), # HTTPRequest } def __init__(self, host, port=80, proxy=None): """Connection initialization @param host: the host to connect to. @type host: string @param port: the port number to connect to @type port: integer @param proxy: proxy that we can use to connect @type proxy: L{gnet.proxy.ProxyInfos}""" gobject.GObject.__init__(self) assert(proxy is None or proxy.type == 'http') # TODO: add support for other proxies (socks4 and 5) self._host = host self._port = port self.__proxy = proxy self._transport = None self._http_parser = None self._outgoing_queue = [] self._waiting_response = False def _setup_transport(self): if self._transport is None: if self.__proxy is not None: self._transport = TCPClient(self.__proxy.host, self.__proxy.port) else: self._transport = TCPClient(self._host, self._port) self._http_parser = HTTPParser(self._transport) self._http_parser.connect("received", self._on_response_received) self._transport.connect("notify::status", self._on_status_change) self._transport.connect("error", self._on_error) self._transport.connect("sent", self._on_request_sent) if self._transport.get_property("status") != IoStatus.OPEN: self._transport.open() def _on_status_change(self, transport, param): if transport.get_property("status") == IoStatus.OPEN: self._process_queue() elif transport.get_property("status") == IoStatus.CLOSED and\ (self._waiting_response or len(self._outgoing_queue) > 0): self._waiting_response = False self._setup_transport() def _on_request_sent(self, transport, request, length): assert(str(self._outgoing_queue[0]) == request) self._waiting_response = True self.emit("request-sent", self._outgoing_queue[0]) def _on_response_received(self, parser, response): if response.status >= 100 and response.status < 200: return #if response.status in (301, 302): # UNTESTED: please test # location = response.headers['Location'] # location = location.rsplit("://", 1) # if len(location) == 2: # scheme = location[0] # location = location[1] # if scheme == "http": # location = location.rsplit(":", 1) # self._host = location[0] # if len(location) == 2: # self._port = int(location[1]) # self._outgoing_queue[0].headers['Host'] = response.headers['Location'] # self._setup_transport() # return self._outgoing_queue.pop(0) # pop the request from the queue self.emit("response-received", response) self._waiting_response = False self._process_queue() # next request ? def _on_error(self, transport, error): self.emit("error", error) def _process_queue(self): if len(self._outgoing_queue) == 0 or \ self._waiting_response: # no pipelining return if self._transport is None or \ self._transport.get_property("status") != IoStatus.OPEN: self._setup_transport() return self._transport.send(str(self._outgoing_queue[0])) def request(self, resource='/', headers=None, data='', method='GET'): if headers is None: headers = {} headers['Host'] = self._host + ':' + str(self._port) headers['Content-Length'] = str(len(data)) if 'User-Agent' not in headers: user_agent = GNet.NAME, GNet.VERSION, platform.system(), platform.machine() headers['User-Agent'] = "%s/%s (%s %s)" % user_agent if self.__proxy is not None: url = 'http://%s:%d%s' % (self._host, self._port, resource) if self.__proxy.user: auth = self.__proxy.user + ':' + self.__proxy.password credentials = base64.encodestring(auth).strip() headers['Proxy-Authorization'] = 'Basic ' + credentials else: url = resource request = HTTPRequest(headers, data, method, url) self._outgoing_queue.append(request) self._process_queue()