3 """A basic HTTP proxy server that intercepts communication with
4 BitTorrent trackers and optionally spoofs the amount of data
5 uploaded. Free from artificial colours and preservatives. Web 2.0
20 class CheatHandler(BaseHTTPServer
.BaseHTTPRequestHandler
):
21 """Used by HTTPServer to handle HTTP requests"""
26 """Called by BaseHTTPRequestHandler when a GET request is
27 received from a client."""
29 cheatpath
= cheatbt
.cheat(self
.path
, CheatHandler
.mappings
)
31 logger
= logging
.getLogger("cheatproxy")
32 logger
.info(cheatpath
)
34 (scheme
, netloc
, path
, params
, query
, fragment
) = \
35 urlparse
.urlparse(cheatpath
, 'http')
37 # TODO: https support.
38 if scheme
!= 'http' or fragment
or not netloc
:
42 soc
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
45 if self
._connect
_to
(netloc
, soc
):
46 request
= urlparse
.urlunparse(('', '', path
, params
, query
, ''))
47 soc
.send("%s %s %s\r\n" % (self
.command
, request
,
48 self
.request_version
))
49 self
.headers
['Connection'] = 'close'
50 del self
.headers
['Proxy-Connection']
51 # This is naughty. But rfc822.Message, which self.headers is a
52 # subclass of, insists on converting headers to lowercase when
53 # accessed conventionally (i.e. as a dict).
54 for header
in self
.headers
.headers
:
55 header
= header
.strip() + '\r\n'
57 logger
.debug(repr(header
))
62 self
.connection
.close()
64 def _connect_to(self
, netloc
, soc
):
65 """Attempt to establish a connection to the tracker."""
69 host_port
= (netloc
[:i
], int(netloc
[i
+1:]))
71 host_port
= (netloc
, 80)
74 soc
.connect(host_port
)
80 def _read_write(self
, soc
, max_idling
=20):
81 """Pass data between the remote server and the client. I
84 logger
= logging
.getLogger("cheatproxy")
86 rlist
= [self
.connection
, soc
]
91 (ins
, _
, exs
) = select
.select(rlist
, wlist
, rlist
, 3)
105 except socket
.error
, msg
:
109 if count
== max_idling
:
112 class CheatProxy(SocketServer
.ThreadingMixIn
, BaseHTTPServer
.HTTPServer
):
113 def __init__(self
, host
, port
):
114 BaseHTTPServer
.HTTPServer
.__init
__(self
, (host
, port
),
118 """Prints usage information and exits."""
121 usage: %s [-b host] [-p port] [-f file] [-v] [-d] [-h]
123 -b host IP or hostname to bind to. Default is localhost.
124 -p port Port to listen on. Default is 8000.
125 -f file Mappings file.
128 -h What you're reading.
136 rootlogger
= logging
.getLogger("")
137 ch
= logging
.StreamHandler()
139 logging
.Formatter("%(asctime)s:%(name)s:%(levelname)s %(message)s"))
140 rootlogger
.addHandler(ch
)
143 opts
, _
= getopt
.getopt(sys
.argv
[1:], "b:f:p:hvd")
144 except getopt
.GetoptError
:
147 for opt
, val
in opts
:
153 CheatHandler
.mappings
= cheatbt
.load_mappings(val
)
155 rootlogger
.setLevel(logging
.INFO
)
157 rootlogger
.setLevel(logging
.DEBUG
)
161 httpd
= CheatProxy(host
, port
)
163 logger
= logging
.getLogger("cheatproxy")
164 logger
.info("listening on %s:%d" % (host
, port
))
166 httpd
.serve_forever()
168 if __name__
== "__main__":
171 except KeyboardInterrupt: