The little things give you away... A collection of various small helper stuff
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

151 lignes
3.3 KiB

  1. #!/usr/bin/env python3
  2. import enum
  3. import hashlib
  4. import sys
  5. class ParserState(enum.Enum):
  6. NONE = 0
  7. DICTIONARY = 1
  8. LIST = 2
  9. class Placeholder:
  10. def __init__(self, s):
  11. self._s = s
  12. def __repr__(self):
  13. return self._s
  14. dictEntry = Placeholder('dict')
  15. listEntry = Placeholder('list')
  16. class CopyingFileReader:
  17. '''All reads to the underlying file-like object are copied to write, which must be a callable accepting the data.'''
  18. def __init__(self, fp, write):
  19. self.fp = fp
  20. self.write = write
  21. def read(self, *args, **kwargs):
  22. data = self.fp.read(*args, **kwargs)
  23. self.write(data)
  24. return data
  25. def read_str(fp, c):
  26. '''Reads a string from the current position with c being the first character of the length (having been read already)'''
  27. length = read_int(fp, c, end = b':')
  28. s = fp.read(length)
  29. if len(s) != length:
  30. raise ValueError
  31. try:
  32. s = s.decode('utf-8')
  33. except UnicodeDecodeError:
  34. pass
  35. return s
  36. def read_int(fp, c = b'', end = b'e'):
  37. '''Reads an int from the current position with c optionally being the first digit'''
  38. i = c
  39. while True:
  40. c = fp.read(1)
  41. if c == end:
  42. break
  43. elif c in b'0123456789':
  44. i += c
  45. else:
  46. raise ValueError
  47. i = int(i.decode('ascii'))
  48. return i
  49. def read_or_stack_value(fp, stateStack, c = b''):
  50. if not c:
  51. c = fp.read(1)
  52. if c == b'l':
  53. stateStack.append(ParserState.LIST)
  54. return listEntry
  55. elif c == b'd':
  56. stateStack.append(ParserState.DICTIONARY)
  57. return dictEntry
  58. elif c == b'i':
  59. return read_int(fp)
  60. elif c in b'0123456789': # String value
  61. return read_str(fp, c)
  62. else:
  63. raise ValueError
  64. def bdecode(fp, display = False, infoWrite = None):
  65. c = fp.read(1)
  66. if c != b'd':
  67. raise ValueError
  68. stateStack = [ParserState.NONE, ParserState.DICTIONARY]
  69. inInfo = False
  70. print_ = print if display else (lambda *args, **kwargs: None)
  71. print_(f'(global): {dictEntry}')
  72. while stateStack:
  73. state = stateStack[-1]
  74. indent = ' ' * (len(stateStack) - 1)
  75. if state == ParserState.DICTIONARY:
  76. c = fp.read(1)
  77. if c == b'e': # End of dict
  78. stateStack.pop(-1)
  79. if len(stateStack) == 2 and inInfo and infoWrite:
  80. inInfo = False
  81. fp = fp.fp
  82. continue
  83. elif c in b'0123456789': # Key
  84. key = read_str(fp, c)
  85. if len(stateStack) == 2 and key == b'info' and infoWrite: # If in global dict and this is the 'info' value and a copy is desired...
  86. inInfo = True
  87. fp = CopyingFileReader(fp, infoWrite)
  88. v = read_or_stack_value(fp, stateStack)
  89. print_(f'{indent}{key!r}: {v!r}')
  90. else:
  91. raise ValueError
  92. elif state == ParserState.LIST:
  93. c = fp.read(1)
  94. if c == b'e':
  95. stateStack.pop(-1)
  96. continue
  97. else:
  98. v = read_or_stack_value(fp, stateStack, c)
  99. print_(f'{indent}- {v!r}')
  100. elif state == ParserState.NONE:
  101. assert len(stateStack) == 1
  102. return
  103. def print_torrent(fp):
  104. bdecode(fp, display = True)
  105. def get_info_hash(fp):
  106. hasher = hashlib.sha1()
  107. bdecode(fp, infoWrite = hasher.update)
  108. return hasher.hexdigest()
  109. def main():
  110. if len(sys.argv) < 3 or sys.argv[1] not in ('print', 'infohash'):
  111. print('Usage: torrent-tiny MODE FILE [FILE...]', file = sys.stderr)
  112. print('MODEs: print, infohash', file = sys.stderr)
  113. sys.exit(1)
  114. mode = sys.argv[1]
  115. for fn in sys.argv[2:]:
  116. with open(fn, 'rb') as fp:
  117. if mode == 'print':
  118. print_torrent(fp)
  119. elif mode == 'infohash':
  120. print(get_info_hash(fp))
  121. if __name__ == '__main__':
  122. main()