Member of The Internet Defense League Últimos cambios
Últimos Cambios
Blog personal: El hilo del laberinto Geocaching

Bug solucionado en BitTornado: El "announce" contiene un carácter ilegal

Última Actualización: 20 de febrero de 2008 - Miércoles

Hace unos días me bajé un torrent pero, al activarlo, el tracker me decía que no existía. Haciendo un "scrap" del tracker compruebo que hay "peers" a los que el torrent funciona sin problemas. Aparentemente se trata, pues, de un problema con BitTornado.

Tras investigar el asunto, llego a la conclusión de que BitTornado está codificando el "announce" al tracker de forma incorrecta. En particular, no está escapando el carácter "/".

BitTornado llama a la rutina "urllib.quote()", pero la emplea mal. La invoca sin darse cuenta de que, por defecto, "urllib.quote()" no escapa el carácter "/", como está claramente documentado en el manual.

El parche es largo pero trivial:

Index: BitTornado/BT1/StreamCheck.py
===================================================================
--- BitTornado/BT1/StreamCheck.py       (revision 5)
+++ BitTornado/BT1/StreamCheck.py       (working copy)
@@ -40,7 +40,7 @@
 def make_readable(s):
     if not s:
         return ''
-    if quote(s).find('%') >= 0:
+    if quote(s,safe="").find('%') >= 0:
         return tohex(s)
     return '"'+s+'"'

Index: BitTornado/BT1/HTTPDownloader.py
===================================================================
--- BitTornado/BT1/HTTPDownloader.py    (revision 5)
+++ BitTornado/BT1/HTTPDownloader.py    (working copy)
@@ -48,7 +48,7 @@
         self.seedurl += '?'
         if query:
             self.seedurl += query+'&'
-        self.seedurl += 'info_hash='+quote(self.downloader.infohash)
+        self.seedurl += 'info_hash='+quote(self.downloader.infohash,safe="")

         self.measure = Measure(downloader.max_rate_period)
         self.index = None
Index: BitTornado/BT1/track.py
===================================================================
--- BitTornado/BT1/track.py     (revision 5)
+++ BitTornado/BT1/track.py     (working copy)
@@ -451,7 +451,7 @@
                             szt = sz * n   # Transferred for this torrent
                             tt = tt + szt
                             if self.allow_get == 1:
-                                linkname = '<a href="/file?info_hash=' + quote(hash) + '">' + name + '</a>'
+                                linkname = '<a href="/file?info_hash=' + quote(hash,safe="") + '">' + name + '</a>'
                             else:
                                 linkname = name
                             s.write('<tr><td><code>%s</code></td><td>%s</td><td align="right">%s</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i</td><td align="right">%s</td></tr>\n' \
@@ -906,7 +906,7 @@
     def natchecklog(self, peerid, ip, port, result):
         year, month, day, hour, minute, second, a, b, c = localtime(time())
         print '%s - %s [%02d/%3s/%04d:%02d:%02d:%02d] "!natcheck-%s:%i" %i 0 - -' % (
-            ip, quote(peerid), day, months[month], year, hour, minute, second,
+            ip, quote(peerid,safe=""), day, months[month], year, hour, minute, second,
             ip, port, result)

     def connectback_result(self, result, downloadid, peerid, ip, port):
Index: BitTornado/BT1/Rerequester.py
===================================================================
--- BitTornado/BT1/Rerequester.py       (revision 5)
+++ BitTornado/BT1/Rerequester.py       (working copy)
@@ -70,7 +70,7 @@
         self.rejectedmessage = 'rejected by tracker - '

         self.url = ('?info_hash=%s&peer_id=%s&port=%s' %
-            (quote(infohash), quote(myid), str(port)))
+            (quote(infohash,safe=""), quote(myid,safe=""), str(port)))
         self.ip = ip
         self.interval = interval
         self.last = None
@@ -92,7 +92,7 @@
         self.downratefunc = downratefunc
         self.unpauseflag = unpauseflag
         if seed_id:
-            self.url += '&seed_id='+quote(seed_id)
+            self.url += '&seed_id='+quote(seed_id,safe="")
         self.seededfunc = seededfunc
         if seededfunc:
             self.url += '&check_seeded=1'
@@ -160,9 +160,9 @@
                 (self.url, str(self.up()), str(self.down()),
                 str(self.amount_left())))
         if self.last is not None:
-            s += '&last=' + quote(str(self.last))
+            s += '&last=' + quote(str(self.last,safe=""))
         if self.trackerid is not None:
-            s += '&trackerid=' + quote(str(self.trackerid))
+            s += '&trackerid=' + quote(str(self.trackerid),safe="")
         if self.howmany() >= self.maxpeers:
             s += '&numwant=0'
         else:
Index: BitTornado/BT1/T2T.py
===================================================================
--- BitTornado/BT1/T2T.py       (revision 5)
+++ BitTornado/BT1/T2T.py       (working copy)
@@ -62,7 +62,7 @@
         self.lastsuccessful = True
         self.newpeerdata = []
         if DEBUG:
-            print 'contacting %s for info_hash=%s' % (self.tracker, quote(self.hash))
+            print 'contacting %s for info_hash=%s' % (self.tracker, quote(self.hash,safe=""))
         self.rerequester.snoop(self.peers, self.callback)

     def callback(self):
@@ -76,7 +76,7 @@
             self.operatinginterval = self.rerequester.announce_interval
             if DEBUG:
                 print ("%s with info_hash=%s returned %d peers" %
-                        (self.tracker, quote(self.hash), len(self.newpeerdata)))
+                        (self.tracker, quote(self.hash,safe=""), len(self.newpeerdata)))
             self.peerlists.append(self.newpeerdata)
             self.peerlists = self.peerlists[-10:]  # keep up to the last 10 announces
         if self.isactive():
@@ -89,7 +89,7 @@
     def errorfunc(self, r):
         self.lastsuccessful = False
         if DEBUG:
-            print "%s with info_hash=%s gives error: '%s'" % (self.tracker, quote(self.hash), r)
+            print "%s with info_hash=%s gives error: '%s'" % (self.tracker, quote(self.hash,safe=""), r)
         if r == self.rerequester.rejectedmessage + 'disallowed':   # whoops!
             if DEBUG:
                 print ' -- disallowed - deactivating'
Index: BitTornado/BT1/Encrypter.py
===================================================================
--- BitTornado/BT1/Encrypter.py (revision 5)
+++ BitTornado/BT1/Encrypter.py (working copy)
@@ -38,7 +38,7 @@
 def make_readable(s):
     if not s:
         return ''
-    if quote(s).find('%') >= 0:
+    if quote(s,safe="").find('%') >= 0:
         return tohex(s)
     return '"'+s+'"'

El parche se limita a cambiar las llamadas a "urllib.quote()" para que se escape también el carácter "/". Se podría haber hecho más simple utilizando la técnica que ya expliqué hace unos meses, pero -en este caso concreto- prefiero hacerlo explícito.

Mi agradecimiento a Adriá por ayudarme a diagnosticar el problema.


Historia

  • 20/feb/08: Primera versión de esta página.



Python Zope ©2008 jcea@jcea.es

Más información sobre los OpenBadges

Donación BitCoin: 19niBN42ac2pqDQFx6GJZxry2JQSFvwAfS