murder 0.0.0.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/LICENSE +17 -0
- data/README +224 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/dist/BitTornado/BT1/Choker.py +128 -0
- data/dist/BitTornado/BT1/Connecter.py +288 -0
- data/dist/BitTornado/BT1/Downloader.py +594 -0
- data/dist/BitTornado/BT1/DownloaderFeedback.py +155 -0
- data/dist/BitTornado/BT1/Encrypter.py +333 -0
- data/dist/BitTornado/BT1/FileSelector.py +245 -0
- data/dist/BitTornado/BT1/Filter.py +12 -0
- data/dist/BitTornado/BT1/HTTPDownloader.py +251 -0
- data/dist/BitTornado/BT1/NatCheck.py +95 -0
- data/dist/BitTornado/BT1/PiecePicker.py +320 -0
- data/dist/BitTornado/BT1/Rerequester.py +426 -0
- data/dist/BitTornado/BT1/Statistics.py +177 -0
- data/dist/BitTornado/BT1/Storage.py +584 -0
- data/dist/BitTornado/BT1/StorageWrapper.py +1045 -0
- data/dist/BitTornado/BT1/StreamCheck.py +135 -0
- data/dist/BitTornado/BT1/T2T.py +193 -0
- data/dist/BitTornado/BT1/Uploader.py +145 -0
- data/dist/BitTornado/BT1/__init__.py +1 -0
- data/dist/BitTornado/BT1/btformats.py +100 -0
- data/dist/BitTornado/BT1/fakeopen.py +89 -0
- data/dist/BitTornado/BT1/makemetafile.py +263 -0
- data/dist/BitTornado/BT1/track.py +1067 -0
- data/dist/BitTornado/ConfigDir.py +401 -0
- data/dist/BitTornado/ConfigReader.py +1068 -0
- data/dist/BitTornado/ConnChoice.py +31 -0
- data/dist/BitTornado/CreateIcons.py +105 -0
- data/dist/BitTornado/CurrentRateMeasure.py +37 -0
- data/dist/BitTornado/HTTPHandler.py +167 -0
- data/dist/BitTornado/PSYCO.py +5 -0
- data/dist/BitTornado/RateLimiter.py +153 -0
- data/dist/BitTornado/RateMeasure.py +75 -0
- data/dist/BitTornado/RawServer.py +195 -0
- data/dist/BitTornado/ServerPortHandler.py +188 -0
- data/dist/BitTornado/SocketHandler.py +375 -0
- data/dist/BitTornado/__init__.py +63 -0
- data/dist/BitTornado/bencode.py +319 -0
- data/dist/BitTornado/bitfield.py +162 -0
- data/dist/BitTornado/clock.py +27 -0
- data/dist/BitTornado/download_bt1.py +882 -0
- data/dist/BitTornado/inifile.py +169 -0
- data/dist/BitTornado/iprangeparse.py +194 -0
- data/dist/BitTornado/launchmanycore.py +381 -0
- data/dist/BitTornado/natpunch.py +254 -0
- data/dist/BitTornado/parseargs.py +137 -0
- data/dist/BitTornado/parsedir.py +150 -0
- data/dist/BitTornado/piecebuffer.py +86 -0
- data/dist/BitTornado/selectpoll.py +109 -0
- data/dist/BitTornado/subnetparse.py +218 -0
- data/dist/BitTornado/torrentlistparse.py +38 -0
- data/dist/BitTornado/zurllib.py +100 -0
- data/dist/murder_client.py +291 -0
- data/dist/murder_make_torrent.py +46 -0
- data/dist/murder_tracker.py +28 -0
- data/doc/examples/Capfile +28 -0
- data/lib/capistrano/recipes/deploy/strategy/murder.rb +52 -0
- data/lib/murder.rb +43 -0
- data/lib/murder/admin.rb +47 -0
- data/lib/murder/murder.rb +121 -0
- data/murder.gemspec +101 -0
- metadata +129 -0
@@ -0,0 +1,375 @@
|
|
1
|
+
# Written by Bram Cohen
|
2
|
+
# see LICENSE.txt for license information
|
3
|
+
|
4
|
+
import socket
|
5
|
+
from errno import EWOULDBLOCK, ECONNREFUSED, EHOSTUNREACH
|
6
|
+
try:
|
7
|
+
from select import poll, error, POLLIN, POLLOUT, POLLERR, POLLHUP
|
8
|
+
timemult = 1000
|
9
|
+
except ImportError:
|
10
|
+
from selectpoll import poll, error, POLLIN, POLLOUT, POLLERR, POLLHUP
|
11
|
+
timemult = 1
|
12
|
+
from time import sleep
|
13
|
+
from clock import clock
|
14
|
+
import sys
|
15
|
+
from random import shuffle, randrange
|
16
|
+
from natpunch import UPnP_open_port, UPnP_close_port
|
17
|
+
# from BT1.StreamCheck import StreamCheck
|
18
|
+
# import inspect
|
19
|
+
try:
|
20
|
+
True
|
21
|
+
except:
|
22
|
+
True = 1
|
23
|
+
False = 0
|
24
|
+
|
25
|
+
all = POLLIN | POLLOUT
|
26
|
+
|
27
|
+
UPnP_ERROR = "unable to forward port via UPnP"
|
28
|
+
|
29
|
+
class SingleSocket:
|
30
|
+
def __init__(self, socket_handler, sock, handler, ip = None):
|
31
|
+
self.socket_handler = socket_handler
|
32
|
+
self.socket = sock
|
33
|
+
self.handler = handler
|
34
|
+
self.buffer = []
|
35
|
+
self.last_hit = clock()
|
36
|
+
self.fileno = sock.fileno()
|
37
|
+
self.connected = False
|
38
|
+
self.skipped = 0
|
39
|
+
# self.check = StreamCheck()
|
40
|
+
try:
|
41
|
+
self.ip = self.socket.getpeername()[0]
|
42
|
+
except:
|
43
|
+
if ip is None:
|
44
|
+
self.ip = 'unknown'
|
45
|
+
else:
|
46
|
+
self.ip = ip
|
47
|
+
|
48
|
+
def get_ip(self, real=False):
|
49
|
+
if real:
|
50
|
+
try:
|
51
|
+
self.ip = self.socket.getpeername()[0]
|
52
|
+
except:
|
53
|
+
pass
|
54
|
+
return self.ip
|
55
|
+
|
56
|
+
def close(self):
|
57
|
+
'''
|
58
|
+
for x in xrange(5,0,-1):
|
59
|
+
try:
|
60
|
+
f = inspect.currentframe(x).f_code
|
61
|
+
print (f.co_filename,f.co_firstlineno,f.co_name)
|
62
|
+
del f
|
63
|
+
except:
|
64
|
+
pass
|
65
|
+
print ''
|
66
|
+
'''
|
67
|
+
assert self.socket
|
68
|
+
self.connected = False
|
69
|
+
sock = self.socket
|
70
|
+
self.socket = None
|
71
|
+
self.buffer = []
|
72
|
+
del self.socket_handler.single_sockets[self.fileno]
|
73
|
+
self.socket_handler.poll.unregister(sock)
|
74
|
+
sock.close()
|
75
|
+
|
76
|
+
def shutdown(self, val):
|
77
|
+
self.socket.shutdown(val)
|
78
|
+
|
79
|
+
def is_flushed(self):
|
80
|
+
return not self.buffer
|
81
|
+
|
82
|
+
def write(self, s):
|
83
|
+
# self.check.write(s)
|
84
|
+
assert self.socket is not None
|
85
|
+
self.buffer.append(s)
|
86
|
+
if len(self.buffer) == 1:
|
87
|
+
self.try_write()
|
88
|
+
|
89
|
+
def try_write(self):
|
90
|
+
if self.connected:
|
91
|
+
dead = False
|
92
|
+
try:
|
93
|
+
while self.buffer:
|
94
|
+
buf = self.buffer[0]
|
95
|
+
amount = self.socket.send(buf)
|
96
|
+
if amount == 0:
|
97
|
+
self.skipped += 1
|
98
|
+
break
|
99
|
+
self.skipped = 0
|
100
|
+
if amount != len(buf):
|
101
|
+
self.buffer[0] = buf[amount:]
|
102
|
+
break
|
103
|
+
del self.buffer[0]
|
104
|
+
except socket.error, e:
|
105
|
+
try:
|
106
|
+
dead = e[0] != EWOULDBLOCK
|
107
|
+
except:
|
108
|
+
dead = True
|
109
|
+
self.skipped += 1
|
110
|
+
if self.skipped >= 3:
|
111
|
+
dead = True
|
112
|
+
if dead:
|
113
|
+
self.socket_handler.dead_from_write.append(self)
|
114
|
+
return
|
115
|
+
if self.buffer:
|
116
|
+
self.socket_handler.poll.register(self.socket, all)
|
117
|
+
else:
|
118
|
+
self.socket_handler.poll.register(self.socket, POLLIN)
|
119
|
+
|
120
|
+
def set_handler(self, handler):
|
121
|
+
self.handler = handler
|
122
|
+
|
123
|
+
class SocketHandler:
|
124
|
+
def __init__(self, timeout, ipv6_enable, readsize = 100000):
|
125
|
+
self.timeout = timeout
|
126
|
+
self.ipv6_enable = ipv6_enable
|
127
|
+
self.readsize = readsize
|
128
|
+
self.poll = poll()
|
129
|
+
# {socket: SingleSocket}
|
130
|
+
self.single_sockets = {}
|
131
|
+
self.dead_from_write = []
|
132
|
+
self.max_connects = 1000
|
133
|
+
self.port_forwarded = None
|
134
|
+
self.servers = {}
|
135
|
+
|
136
|
+
def scan_for_timeouts(self):
|
137
|
+
t = clock() - self.timeout
|
138
|
+
tokill = []
|
139
|
+
for s in self.single_sockets.values():
|
140
|
+
if s.last_hit < t:
|
141
|
+
tokill.append(s)
|
142
|
+
for k in tokill:
|
143
|
+
if k.socket is not None:
|
144
|
+
self._close_socket(k)
|
145
|
+
|
146
|
+
def bind(self, port, bind = '', reuse = False, ipv6_socket_style = 1, upnp = 0):
|
147
|
+
port = int(port)
|
148
|
+
addrinfos = []
|
149
|
+
self.servers = {}
|
150
|
+
self.interfaces = []
|
151
|
+
# if bind != "" thread it as a comma seperated list and bind to all
|
152
|
+
# addresses (can be ips or hostnames) else bind to default ipv6 and
|
153
|
+
# ipv4 address
|
154
|
+
if bind:
|
155
|
+
if self.ipv6_enable:
|
156
|
+
socktype = socket.AF_UNSPEC
|
157
|
+
else:
|
158
|
+
socktype = socket.AF_INET
|
159
|
+
bind = bind.split(',')
|
160
|
+
for addr in bind:
|
161
|
+
if sys.version_info < (2,2):
|
162
|
+
addrinfos.append((socket.AF_INET, None, None, None, (addr, port)))
|
163
|
+
else:
|
164
|
+
addrinfos.extend(socket.getaddrinfo(addr, port,
|
165
|
+
socktype, socket.SOCK_STREAM))
|
166
|
+
else:
|
167
|
+
if self.ipv6_enable:
|
168
|
+
addrinfos.append([socket.AF_INET6, None, None, None, ('', port)])
|
169
|
+
if not addrinfos or ipv6_socket_style != 0:
|
170
|
+
addrinfos.append([socket.AF_INET, None, None, None, ('', port)])
|
171
|
+
for addrinfo in addrinfos:
|
172
|
+
try:
|
173
|
+
server = socket.socket(addrinfo[0], socket.SOCK_STREAM)
|
174
|
+
if reuse:
|
175
|
+
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
176
|
+
server.setblocking(0)
|
177
|
+
server.bind(addrinfo[4])
|
178
|
+
self.servers[server.fileno()] = server
|
179
|
+
if bind:
|
180
|
+
self.interfaces.append(server.getsockname()[0])
|
181
|
+
server.listen(64)
|
182
|
+
self.poll.register(server, POLLIN)
|
183
|
+
except socket.error, e:
|
184
|
+
for server in self.servers.values():
|
185
|
+
try:
|
186
|
+
server.close()
|
187
|
+
except:
|
188
|
+
pass
|
189
|
+
if self.ipv6_enable and ipv6_socket_style == 0 and self.servers:
|
190
|
+
raise socket.error('blocked port (may require ipv6_binds_v4 to be set)')
|
191
|
+
raise socket.error(str(e))
|
192
|
+
if not self.servers:
|
193
|
+
raise socket.error('unable to open server port')
|
194
|
+
if upnp:
|
195
|
+
if not UPnP_open_port(port):
|
196
|
+
for server in self.servers.values():
|
197
|
+
try:
|
198
|
+
server.close()
|
199
|
+
except:
|
200
|
+
pass
|
201
|
+
self.servers = None
|
202
|
+
self.interfaces = None
|
203
|
+
raise socket.error(UPnP_ERROR)
|
204
|
+
self.port_forwarded = port
|
205
|
+
self.port = port
|
206
|
+
|
207
|
+
def find_and_bind(self, minport, maxport, bind = '', reuse = False,
|
208
|
+
ipv6_socket_style = 1, upnp = 0, randomizer = False):
|
209
|
+
e = 'maxport less than minport - no ports to check'
|
210
|
+
if maxport-minport < 50 or not randomizer:
|
211
|
+
portrange = range(minport, maxport+1)
|
212
|
+
if randomizer:
|
213
|
+
shuffle(portrange)
|
214
|
+
portrange = portrange[:20] # check a maximum of 20 ports
|
215
|
+
else:
|
216
|
+
portrange = []
|
217
|
+
while len(portrange) < 20:
|
218
|
+
listen_port = randrange(minport, maxport+1)
|
219
|
+
if not listen_port in portrange:
|
220
|
+
portrange.append(listen_port)
|
221
|
+
for listen_port in portrange:
|
222
|
+
try:
|
223
|
+
self.bind(listen_port, bind,
|
224
|
+
ipv6_socket_style = ipv6_socket_style, upnp = upnp)
|
225
|
+
return listen_port
|
226
|
+
except socket.error, e:
|
227
|
+
pass
|
228
|
+
raise socket.error(str(e))
|
229
|
+
|
230
|
+
|
231
|
+
def set_handler(self, handler):
|
232
|
+
self.handler = handler
|
233
|
+
|
234
|
+
|
235
|
+
def start_connection_raw(self, dns, socktype = socket.AF_INET, handler = None):
|
236
|
+
if handler is None:
|
237
|
+
handler = self.handler
|
238
|
+
sock = socket.socket(socktype, socket.SOCK_STREAM)
|
239
|
+
sock.setblocking(0)
|
240
|
+
try:
|
241
|
+
sock.connect_ex(dns)
|
242
|
+
except socket.error:
|
243
|
+
raise
|
244
|
+
except Exception, e:
|
245
|
+
raise socket.error(str(e))
|
246
|
+
self.poll.register(sock, POLLIN)
|
247
|
+
s = SingleSocket(self, sock, handler, dns[0])
|
248
|
+
self.single_sockets[sock.fileno()] = s
|
249
|
+
return s
|
250
|
+
|
251
|
+
|
252
|
+
def start_connection(self, dns, handler = None, randomize = False):
|
253
|
+
if handler is None:
|
254
|
+
handler = self.handler
|
255
|
+
if sys.version_info < (2,2):
|
256
|
+
s = self.start_connection_raw(dns,socket.AF_INET,handler)
|
257
|
+
else:
|
258
|
+
if self.ipv6_enable:
|
259
|
+
socktype = socket.AF_UNSPEC
|
260
|
+
else:
|
261
|
+
socktype = socket.AF_INET
|
262
|
+
try:
|
263
|
+
addrinfos = socket.getaddrinfo(dns[0], int(dns[1]),
|
264
|
+
socktype, socket.SOCK_STREAM)
|
265
|
+
except socket.error, e:
|
266
|
+
raise
|
267
|
+
except Exception, e:
|
268
|
+
raise socket.error(str(e))
|
269
|
+
if randomize:
|
270
|
+
shuffle(addrinfos)
|
271
|
+
for addrinfo in addrinfos:
|
272
|
+
try:
|
273
|
+
s = self.start_connection_raw(addrinfo[4],addrinfo[0],handler)
|
274
|
+
break
|
275
|
+
except:
|
276
|
+
pass
|
277
|
+
else:
|
278
|
+
raise socket.error('unable to connect')
|
279
|
+
return s
|
280
|
+
|
281
|
+
|
282
|
+
def _sleep(self):
|
283
|
+
sleep(1)
|
284
|
+
|
285
|
+
def handle_events(self, events):
|
286
|
+
for sock, event in events:
|
287
|
+
s = self.servers.get(sock)
|
288
|
+
if s:
|
289
|
+
if event & (POLLHUP | POLLERR) != 0:
|
290
|
+
self.poll.unregister(s)
|
291
|
+
s.close()
|
292
|
+
del self.servers[sock]
|
293
|
+
print "lost server socket"
|
294
|
+
elif len(self.single_sockets) < self.max_connects:
|
295
|
+
try:
|
296
|
+
newsock, addr = s.accept()
|
297
|
+
newsock.setblocking(0)
|
298
|
+
nss = SingleSocket(self, newsock, self.handler)
|
299
|
+
self.single_sockets[newsock.fileno()] = nss
|
300
|
+
self.poll.register(newsock, POLLIN)
|
301
|
+
self.handler.external_connection_made(nss)
|
302
|
+
except socket.error:
|
303
|
+
self._sleep()
|
304
|
+
else:
|
305
|
+
s = self.single_sockets.get(sock)
|
306
|
+
if not s:
|
307
|
+
continue
|
308
|
+
s.connected = True
|
309
|
+
if (event & (POLLHUP | POLLERR)):
|
310
|
+
self._close_socket(s)
|
311
|
+
continue
|
312
|
+
if (event & POLLIN):
|
313
|
+
try:
|
314
|
+
s.last_hit = clock()
|
315
|
+
data = s.socket.recv(100000)
|
316
|
+
if not data:
|
317
|
+
self._close_socket(s)
|
318
|
+
else:
|
319
|
+
s.handler.data_came_in(s, data)
|
320
|
+
except socket.error, e:
|
321
|
+
code, msg = e
|
322
|
+
if code != EWOULDBLOCK:
|
323
|
+
self._close_socket(s)
|
324
|
+
continue
|
325
|
+
if (event & POLLOUT) and s.socket and not s.is_flushed():
|
326
|
+
s.try_write()
|
327
|
+
if s.is_flushed():
|
328
|
+
s.handler.connection_flushed(s)
|
329
|
+
|
330
|
+
def close_dead(self):
|
331
|
+
while self.dead_from_write:
|
332
|
+
old = self.dead_from_write
|
333
|
+
self.dead_from_write = []
|
334
|
+
for s in old:
|
335
|
+
if s.socket:
|
336
|
+
self._close_socket(s)
|
337
|
+
|
338
|
+
def _close_socket(self, s):
|
339
|
+
s.close()
|
340
|
+
s.handler.connection_lost(s)
|
341
|
+
|
342
|
+
def do_poll(self, t):
|
343
|
+
r = self.poll.poll(t*timemult)
|
344
|
+
if r is None:
|
345
|
+
connects = len(self.single_sockets)
|
346
|
+
to_close = int(connects*0.05)+1 # close 5% of sockets
|
347
|
+
self.max_connects = connects-to_close
|
348
|
+
closelist = self.single_sockets.values()
|
349
|
+
shuffle(closelist)
|
350
|
+
closelist = closelist[:to_close]
|
351
|
+
for sock in closelist:
|
352
|
+
self._close_socket(sock)
|
353
|
+
return []
|
354
|
+
return r
|
355
|
+
|
356
|
+
def get_stats(self):
|
357
|
+
return { 'interfaces': self.interfaces,
|
358
|
+
'port': self.port,
|
359
|
+
'upnp': self.port_forwarded is not None }
|
360
|
+
|
361
|
+
|
362
|
+
def shutdown(self):
|
363
|
+
for ss in self.single_sockets.values():
|
364
|
+
try:
|
365
|
+
ss.close()
|
366
|
+
except:
|
367
|
+
pass
|
368
|
+
for server in self.servers.values():
|
369
|
+
try:
|
370
|
+
server.close()
|
371
|
+
except:
|
372
|
+
pass
|
373
|
+
if self.port_forwarded is not None:
|
374
|
+
UPnP_close_port(self.port_forwarded)
|
375
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
product_name = 'BitTornado'
|
2
|
+
version_short = 'T-0.3.17'
|
3
|
+
|
4
|
+
version = version_short+' ('+product_name+')'
|
5
|
+
report_email = version_short+'@degreez.net'
|
6
|
+
|
7
|
+
from types import StringType
|
8
|
+
from sha import sha
|
9
|
+
from time import time, clock
|
10
|
+
try:
|
11
|
+
from os import getpid
|
12
|
+
except ImportError:
|
13
|
+
def getpid():
|
14
|
+
return 1
|
15
|
+
|
16
|
+
mapbase64 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-'
|
17
|
+
|
18
|
+
_idprefix = version_short[0]
|
19
|
+
for subver in version_short[2:].split('.'):
|
20
|
+
try:
|
21
|
+
subver = int(subver)
|
22
|
+
except:
|
23
|
+
subver = 0
|
24
|
+
_idprefix += mapbase64[subver]
|
25
|
+
_idprefix += ('-' * (6-len(_idprefix)))
|
26
|
+
_idrandom = [None]
|
27
|
+
|
28
|
+
def resetPeerIDs():
|
29
|
+
try:
|
30
|
+
f = open('/dev/urandom','rb')
|
31
|
+
x = f.read(20)
|
32
|
+
f.close()
|
33
|
+
except:
|
34
|
+
x = ''
|
35
|
+
|
36
|
+
l1 = 0
|
37
|
+
t = clock()
|
38
|
+
while t == clock():
|
39
|
+
l1 += 1
|
40
|
+
l2 = 0
|
41
|
+
t = long(time()*100)
|
42
|
+
while t == long(time()*100):
|
43
|
+
l2 += 1
|
44
|
+
l3 = 0
|
45
|
+
if l2 < 1000:
|
46
|
+
t = long(time()*10)
|
47
|
+
while t == long(clock()*10):
|
48
|
+
l3 += 1
|
49
|
+
x += ( repr(time()) + '/' + str(time()) + '/'
|
50
|
+
+ str(l1) + '/' + str(l2) + '/' + str(l3) + '/'
|
51
|
+
+ str(getpid()) )
|
52
|
+
|
53
|
+
s = ''
|
54
|
+
for i in sha(x).digest()[-11:]:
|
55
|
+
s += mapbase64[ord(i) & 0x3F]
|
56
|
+
_idrandom[0] = s
|
57
|
+
|
58
|
+
resetPeerIDs()
|
59
|
+
|
60
|
+
def createPeerID(ins = '---'):
|
61
|
+
assert type(ins) is StringType
|
62
|
+
assert len(ins) == 3
|
63
|
+
return _idprefix + ins + _idrandom[0]
|
@@ -0,0 +1,319 @@
|
|
1
|
+
# Written by Petru Paler, Uoti Urpala, Ross Cohen and John Hoffman
|
2
|
+
# see LICENSE.txt for license information
|
3
|
+
|
4
|
+
from types import IntType, LongType, StringType, ListType, TupleType, DictType
|
5
|
+
try:
|
6
|
+
from types import BooleanType
|
7
|
+
except ImportError:
|
8
|
+
BooleanType = None
|
9
|
+
try:
|
10
|
+
from types import UnicodeType
|
11
|
+
except ImportError:
|
12
|
+
UnicodeType = None
|
13
|
+
from cStringIO import StringIO
|
14
|
+
|
15
|
+
def decode_int(x, f):
|
16
|
+
f += 1
|
17
|
+
newf = x.index('e', f)
|
18
|
+
try:
|
19
|
+
n = int(x[f:newf])
|
20
|
+
except:
|
21
|
+
n = long(x[f:newf])
|
22
|
+
if x[f] == '-':
|
23
|
+
if x[f + 1] == '0':
|
24
|
+
raise ValueError
|
25
|
+
elif x[f] == '0' and newf != f+1:
|
26
|
+
raise ValueError
|
27
|
+
return (n, newf+1)
|
28
|
+
|
29
|
+
def decode_string(x, f):
|
30
|
+
colon = x.index(':', f)
|
31
|
+
try:
|
32
|
+
n = int(x[f:colon])
|
33
|
+
except (OverflowError, ValueError):
|
34
|
+
n = long(x[f:colon])
|
35
|
+
if x[f] == '0' and colon != f+1:
|
36
|
+
raise ValueError
|
37
|
+
colon += 1
|
38
|
+
return (x[colon:colon+n], colon+n)
|
39
|
+
|
40
|
+
def decode_unicode(x, f):
|
41
|
+
s, f = decode_string(x, f+1)
|
42
|
+
return (s.decode('UTF-8'),f)
|
43
|
+
|
44
|
+
def decode_list(x, f):
|
45
|
+
r, f = [], f+1
|
46
|
+
while x[f] != 'e':
|
47
|
+
v, f = decode_func[x[f]](x, f)
|
48
|
+
r.append(v)
|
49
|
+
return (r, f + 1)
|
50
|
+
|
51
|
+
def decode_dict(x, f):
|
52
|
+
r, f = {}, f+1
|
53
|
+
lastkey = None
|
54
|
+
while x[f] != 'e':
|
55
|
+
k, f = decode_string(x, f)
|
56
|
+
if lastkey >= k:
|
57
|
+
raise ValueError
|
58
|
+
lastkey = k
|
59
|
+
r[k], f = decode_func[x[f]](x, f)
|
60
|
+
return (r, f + 1)
|
61
|
+
|
62
|
+
decode_func = {}
|
63
|
+
decode_func['l'] = decode_list
|
64
|
+
decode_func['d'] = decode_dict
|
65
|
+
decode_func['i'] = decode_int
|
66
|
+
decode_func['0'] = decode_string
|
67
|
+
decode_func['1'] = decode_string
|
68
|
+
decode_func['2'] = decode_string
|
69
|
+
decode_func['3'] = decode_string
|
70
|
+
decode_func['4'] = decode_string
|
71
|
+
decode_func['5'] = decode_string
|
72
|
+
decode_func['6'] = decode_string
|
73
|
+
decode_func['7'] = decode_string
|
74
|
+
decode_func['8'] = decode_string
|
75
|
+
decode_func['9'] = decode_string
|
76
|
+
#decode_func['u'] = decode_unicode
|
77
|
+
|
78
|
+
def bdecode(x, sloppy = 0):
|
79
|
+
try:
|
80
|
+
r, l = decode_func[x[0]](x, 0)
|
81
|
+
# except (IndexError, KeyError):
|
82
|
+
except (IndexError, KeyError, ValueError):
|
83
|
+
raise ValueError, "bad bencoded data"
|
84
|
+
if not sloppy and l != len(x):
|
85
|
+
raise ValueError, "bad bencoded data"
|
86
|
+
return r
|
87
|
+
|
88
|
+
def test_bdecode():
|
89
|
+
try:
|
90
|
+
bdecode('0:0:')
|
91
|
+
assert 0
|
92
|
+
except ValueError:
|
93
|
+
pass
|
94
|
+
try:
|
95
|
+
bdecode('ie')
|
96
|
+
assert 0
|
97
|
+
except ValueError:
|
98
|
+
pass
|
99
|
+
try:
|
100
|
+
bdecode('i341foo382e')
|
101
|
+
assert 0
|
102
|
+
except ValueError:
|
103
|
+
pass
|
104
|
+
assert bdecode('i4e') == 4L
|
105
|
+
assert bdecode('i0e') == 0L
|
106
|
+
assert bdecode('i123456789e') == 123456789L
|
107
|
+
assert bdecode('i-10e') == -10L
|
108
|
+
try:
|
109
|
+
bdecode('i-0e')
|
110
|
+
assert 0
|
111
|
+
except ValueError:
|
112
|
+
pass
|
113
|
+
try:
|
114
|
+
bdecode('i123')
|
115
|
+
assert 0
|
116
|
+
except ValueError:
|
117
|
+
pass
|
118
|
+
try:
|
119
|
+
bdecode('')
|
120
|
+
assert 0
|
121
|
+
except ValueError:
|
122
|
+
pass
|
123
|
+
try:
|
124
|
+
bdecode('i6easd')
|
125
|
+
assert 0
|
126
|
+
except ValueError:
|
127
|
+
pass
|
128
|
+
try:
|
129
|
+
bdecode('35208734823ljdahflajhdf')
|
130
|
+
assert 0
|
131
|
+
except ValueError:
|
132
|
+
pass
|
133
|
+
try:
|
134
|
+
bdecode('2:abfdjslhfld')
|
135
|
+
assert 0
|
136
|
+
except ValueError:
|
137
|
+
pass
|
138
|
+
assert bdecode('0:') == ''
|
139
|
+
assert bdecode('3:abc') == 'abc'
|
140
|
+
assert bdecode('10:1234567890') == '1234567890'
|
141
|
+
try:
|
142
|
+
bdecode('02:xy')
|
143
|
+
assert 0
|
144
|
+
except ValueError:
|
145
|
+
pass
|
146
|
+
try:
|
147
|
+
bdecode('l')
|
148
|
+
assert 0
|
149
|
+
except ValueError:
|
150
|
+
pass
|
151
|
+
assert bdecode('le') == []
|
152
|
+
try:
|
153
|
+
bdecode('leanfdldjfh')
|
154
|
+
assert 0
|
155
|
+
except ValueError:
|
156
|
+
pass
|
157
|
+
assert bdecode('l0:0:0:e') == ['', '', '']
|
158
|
+
try:
|
159
|
+
bdecode('relwjhrlewjh')
|
160
|
+
assert 0
|
161
|
+
except ValueError:
|
162
|
+
pass
|
163
|
+
assert bdecode('li1ei2ei3ee') == [1, 2, 3]
|
164
|
+
assert bdecode('l3:asd2:xye') == ['asd', 'xy']
|
165
|
+
assert bdecode('ll5:Alice3:Bobeli2ei3eee') == [['Alice', 'Bob'], [2, 3]]
|
166
|
+
try:
|
167
|
+
bdecode('d')
|
168
|
+
assert 0
|
169
|
+
except ValueError:
|
170
|
+
pass
|
171
|
+
try:
|
172
|
+
bdecode('defoobar')
|
173
|
+
assert 0
|
174
|
+
except ValueError:
|
175
|
+
pass
|
176
|
+
assert bdecode('de') == {}
|
177
|
+
assert bdecode('d3:agei25e4:eyes4:bluee') == {'age': 25, 'eyes': 'blue'}
|
178
|
+
assert bdecode('d8:spam.mp3d6:author5:Alice6:lengthi100000eee') == {'spam.mp3': {'author': 'Alice', 'length': 100000}}
|
179
|
+
try:
|
180
|
+
bdecode('d3:fooe')
|
181
|
+
assert 0
|
182
|
+
except ValueError:
|
183
|
+
pass
|
184
|
+
try:
|
185
|
+
bdecode('di1e0:e')
|
186
|
+
assert 0
|
187
|
+
except ValueError:
|
188
|
+
pass
|
189
|
+
try:
|
190
|
+
bdecode('d1:b0:1:a0:e')
|
191
|
+
assert 0
|
192
|
+
except ValueError:
|
193
|
+
pass
|
194
|
+
try:
|
195
|
+
bdecode('d1:a0:1:a0:e')
|
196
|
+
assert 0
|
197
|
+
except ValueError:
|
198
|
+
pass
|
199
|
+
try:
|
200
|
+
bdecode('i03e')
|
201
|
+
assert 0
|
202
|
+
except ValueError:
|
203
|
+
pass
|
204
|
+
try:
|
205
|
+
bdecode('l01:ae')
|
206
|
+
assert 0
|
207
|
+
except ValueError:
|
208
|
+
pass
|
209
|
+
try:
|
210
|
+
bdecode('9999:x')
|
211
|
+
assert 0
|
212
|
+
except ValueError:
|
213
|
+
pass
|
214
|
+
try:
|
215
|
+
bdecode('l0:')
|
216
|
+
assert 0
|
217
|
+
except ValueError:
|
218
|
+
pass
|
219
|
+
try:
|
220
|
+
bdecode('d0:0:')
|
221
|
+
assert 0
|
222
|
+
except ValueError:
|
223
|
+
pass
|
224
|
+
try:
|
225
|
+
bdecode('d0:')
|
226
|
+
assert 0
|
227
|
+
except ValueError:
|
228
|
+
pass
|
229
|
+
|
230
|
+
bencached_marker = []
|
231
|
+
|
232
|
+
class Bencached:
|
233
|
+
def __init__(self, s):
|
234
|
+
self.marker = bencached_marker
|
235
|
+
self.bencoded = s
|
236
|
+
|
237
|
+
BencachedType = type(Bencached('')) # insufficient, but good as a filter
|
238
|
+
|
239
|
+
def encode_bencached(x,r):
|
240
|
+
assert x.marker == bencached_marker
|
241
|
+
r.append(x.bencoded)
|
242
|
+
|
243
|
+
def encode_int(x,r):
|
244
|
+
r.extend(('i',str(x),'e'))
|
245
|
+
|
246
|
+
def encode_bool(x,r):
|
247
|
+
encode_int(int(x),r)
|
248
|
+
|
249
|
+
def encode_string(x,r):
|
250
|
+
r.extend((str(len(x)),':',x))
|
251
|
+
|
252
|
+
def encode_unicode(x,r):
|
253
|
+
#r.append('u')
|
254
|
+
encode_string(x.encode('UTF-8'),r)
|
255
|
+
|
256
|
+
def encode_list(x,r):
|
257
|
+
r.append('l')
|
258
|
+
for e in x:
|
259
|
+
encode_func[type(e)](e, r)
|
260
|
+
r.append('e')
|
261
|
+
|
262
|
+
def encode_dict(x,r):
|
263
|
+
r.append('d')
|
264
|
+
ilist = x.items()
|
265
|
+
ilist.sort()
|
266
|
+
for k,v in ilist:
|
267
|
+
r.extend((str(len(k)),':',k))
|
268
|
+
encode_func[type(v)](v, r)
|
269
|
+
r.append('e')
|
270
|
+
|
271
|
+
encode_func = {}
|
272
|
+
encode_func[BencachedType] = encode_bencached
|
273
|
+
encode_func[IntType] = encode_int
|
274
|
+
encode_func[LongType] = encode_int
|
275
|
+
encode_func[StringType] = encode_string
|
276
|
+
encode_func[ListType] = encode_list
|
277
|
+
encode_func[TupleType] = encode_list
|
278
|
+
encode_func[DictType] = encode_dict
|
279
|
+
if BooleanType:
|
280
|
+
encode_func[BooleanType] = encode_bool
|
281
|
+
if UnicodeType:
|
282
|
+
encode_func[UnicodeType] = encode_unicode
|
283
|
+
|
284
|
+
def bencode(x):
|
285
|
+
r = []
|
286
|
+
try:
|
287
|
+
encode_func[type(x)](x, r)
|
288
|
+
except:
|
289
|
+
print "*** error *** could not encode type %s (value: %s)" % (type(x), x)
|
290
|
+
assert 0
|
291
|
+
return ''.join(r)
|
292
|
+
|
293
|
+
def test_bencode():
|
294
|
+
assert bencode(4) == 'i4e'
|
295
|
+
assert bencode(0) == 'i0e'
|
296
|
+
assert bencode(-10) == 'i-10e'
|
297
|
+
assert bencode(12345678901234567890L) == 'i12345678901234567890e'
|
298
|
+
assert bencode('') == '0:'
|
299
|
+
assert bencode('abc') == '3:abc'
|
300
|
+
assert bencode('1234567890') == '10:1234567890'
|
301
|
+
assert bencode([]) == 'le'
|
302
|
+
assert bencode([1, 2, 3]) == 'li1ei2ei3ee'
|
303
|
+
assert bencode([['Alice', 'Bob'], [2, 3]]) == 'll5:Alice3:Bobeli2ei3eee'
|
304
|
+
assert bencode({}) == 'de'
|
305
|
+
assert bencode({'age': 25, 'eyes': 'blue'}) == 'd3:agei25e4:eyes4:bluee'
|
306
|
+
assert bencode({'spam.mp3': {'author': 'Alice', 'length': 100000}}) == 'd8:spam.mp3d6:author5:Alice6:lengthi100000eee'
|
307
|
+
try:
|
308
|
+
bencode({1: 'foo'})
|
309
|
+
assert 0
|
310
|
+
except AssertionError:
|
311
|
+
pass
|
312
|
+
|
313
|
+
|
314
|
+
try:
|
315
|
+
import psyco
|
316
|
+
psyco.bind(bdecode)
|
317
|
+
psyco.bind(bencode)
|
318
|
+
except ImportError:
|
319
|
+
pass
|