lsar.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. import errno
  2. from subprocess import Popen, PIPE, STDOUT
  3. import re
  4. import json
  5. class Error(Exception):
  6. """Something did go wrong"""
  7. class UnknownOption(Error):
  8. """Unknown option to unar given."""
  9. class ArchiveNotFound(Error):
  10. """Archive not found."""
  11. class PasswordRequired(Error):
  12. """Password required."""
  13. class ParseError(Error):
  14. """ParseError."""
  15. class UnarchiveError(Error):
  16. """UnarchiveError."""
  17. class UnknownFormatError(Error):
  18. """UnknownFormatError."""
  19. class JsonResultItem:
  20. def __init__(self, itemData):
  21. self._data = itemData
  22. """
  23. XADSolidOffset
  24. XADCompressionName
  25. XADSolidObject
  26. XADFileName
  27. XADIndex
  28. XADPosixPermissions
  29. XADLastModificationDate
  30. XADFileSize
  31. XADCompressedSize
  32. XADSolidLength
  33. """
  34. def index(self): return self._data.get("XADIndex", None)
  35. def name(self): return self._data.get("XADFileName", "")
  36. class JsonResultItemRar(JsonResultItem):
  37. def __init__(self, itemData):
  38. JsonResultItem.__init__(self, itemData)
  39. def attributes(self): return self._data.get('RARAttributes', None)
  40. def solidIndex(self): return self._data.get('RARSolidIndex', None)
  41. def compressionVersion(self): return self._data.get('RARCompressionVersion', None)
  42. def compressionMethod(self): return self._data.get('RARCompressionMethod', None)
  43. def OS(self): return self._data.get('RAROS', None)
  44. def CRC32(self): return self._data.get('RARCRC32', None)
  45. def flags(self): return self._data.get('RARFlags', None)
  46. def OSName(self): return self._data.get('RAROSName', "")
  47. class JsonResultItemTar(JsonResultItem):
  48. def __init__(self, itemData):
  49. JsonResultItem.__init__(self, itemData)
  50. def isSparseFile(self): return self._data.get('TARIsSparseFile', None)
  51. class JsonResultItemZip(JsonResultItem):
  52. def __init__(self, itemData):
  53. JsonResultItem.__init__(self, itemData)
  54. def localDate(self): return self._data.get('ZipLocalDate', 0)
  55. def compressionMethod(self): return self._data.get('ZipCompressionMethod', None)
  56. def extractVersion(self): return self._data.get('ZipExtractVersion', None)
  57. def OS(self): return self._data.get('ZipOS', None)
  58. def CRC32(self): return self._data.get('ZipCRC32', None)
  59. def fileAttributes(self): return self._data.get('ZipFileAttributes', None)
  60. def flags(self): return self._data.get('ZipFlags', None)
  61. def OSName(self): return self._data.get('ZipOSName', "")
  62. class JsonResult:
  63. def __init__(self, data):
  64. self._data = json.loads(data)
  65. @staticmethod
  66. def getFormatName(data):
  67. """
  68. getFormatName static method to get archive format name
  69. @type data: dict
  70. @param data: json data from the lsar command
  71. @rtype: string
  72. @return: Returns the archive it's format (tar, zip, rar, etc..)
  73. """
  74. return json.loads(data).get('lsarFormatName', "")
  75. def formatName(self):
  76. """
  77. formatName get archive format name
  78. @rtype: string
  79. @return: Returns the archive it's format (tar, zip, rar, etc..)
  80. """
  81. return self._data.get('lsarFormatName', "")
  82. def formatVersion(self):
  83. """
  84. formatVersion
  85. @rtype: string
  86. @return: Returns the archive it's format (tar, zip, rar, etc..)
  87. """
  88. return self._data.get('lsarFormatVersion', None)
  89. def contents(self):
  90. """
  91. contents raw archive contents
  92. @rtype: list
  93. @return: Returns a list with dicts (every dict is a file)
  94. """
  95. return self._data.get('lsarContents', [])
  96. def encoding(self):
  97. """
  98. encoding archive it's encoding (guess)
  99. @rtype: string
  100. @return: Returns a string with the archive it's guessed encoding.
  101. """
  102. return self._data.get('lsarEncoding', "")
  103. def confidence(self): return self._data.get('lsarConfidence', None)
  104. def properties(self): return self._data.get('lsarProperties', {})
  105. def isEncrypted(self):
  106. """
  107. isEncrypted
  108. @rtype: bool
  109. @return: Returns True if the archive is encrypted, False if not.
  110. """
  111. return bool(self._data.get('XADIsEncrypted', 0))
  112. def archiveName(self):
  113. """
  114. archiveName
  115. @rtype: string
  116. @return: Returns the archive file name
  117. """
  118. return self.properties().get('XADArchiveName', "")
  119. def volumes(self):
  120. """
  121. volumes
  122. @rtype: list
  123. @return: Returns a list with strings (archive name(s))
  124. """
  125. return self.properties().get('XADVolumes', [])
  126. def contentsObjects(self):
  127. """
  128. contentsObjects
  129. @rtype: list
  130. @return: Returns a list with JsonResultItem|JsonResultItemZip
  131. |JsonResultItemRar|JsonResultItemTar objects depending
  132. on the archive it's format.
  133. """
  134. objType = JsonResultItem
  135. if self.formatName() == 'Zip': objType = JsonResultItemZip
  136. elif self.formatName() == 'RAR': objType = JsonResultItemRar
  137. elif self.formatName() == 'Tar': objType = JsonResultItemTar
  138. l = []
  139. for item in self.contents(): l.append(objType(item))
  140. return l
  141. def isMultiArchive(self):
  142. """
  143. isMultiArchive
  144. @rtype: bool
  145. @return: Returns True if the archive has multiple volumes,
  146. else it will return False.
  147. """
  148. return True if len(self.volumes()) > 1 else False
  149. class Lsar:
  150. # grep -rnw -e "XADException describeXADError" ./unarchiver
  151. EXCEPTION_PATTERNS = {
  152. "Unknown option" : UnknownOption,
  153. "Couldn't open archive." : ArchiveNotFound,
  154. "This archive requires a password to unpack" : PasswordRequired,
  155. "Couldn't recognize the archive format." : UnknownFormatError,
  156. "Archive parsing failed!" : ParseError,
  157. "Listing failed!" : UnarchiveError
  158. }
  159. def __init__(self):
  160. self._path = '/usr/bin'
  161. self._command = 'lsar'
  162. self._args = [] # unar command arguments
  163. self._files = [] # list of files to extract (alternative is all or use indexes with -i)
  164. self._file = ''
  165. # Options
  166. self._output = ""
  167. self._forceOverwrite = False
  168. self._forceRename = False
  169. self._forceSkip = False
  170. self._forceDirectory = False
  171. self._noDirectory = False
  172. self._password = ""
  173. self._encoding = None # TODO
  174. self._passwordEncoding = None # TODO
  175. self._indexes = []
  176. self._noRecursion = False
  177. self._copyTime = False
  178. self._forks = False
  179. self._quiet = False
  180. def run(self, cmd=None, cb=None):
  181. if cmd == None: cmd = self._compileCmd()
  182. p = Popen(cmd, stdout=PIPE, stderr=PIPE)
  183. log = ""
  184. errLog = ""
  185. while p.poll() is None:
  186. o = p.communicate()
  187. m = o[0].decode('UTF-8')
  188. log += m
  189. if o[1]: errLog += o[1].decode('UTF-8')
  190. exitCode = p.returncode
  191. # Check for exceptions.
  192. if exitCode != 0:
  193. for pattern, exception in self.EXCEPTION_PATTERNS.items():
  194. for line in log.split('\n'):
  195. if pattern in line:
  196. raise exception(line)
  197. for line in errLog.split('\n'):
  198. if pattern in line:
  199. raise exception(line)
  200. if cb: cb(exitCode)
  201. return log
  202. def _compileCmd(self):
  203. # full command path + command
  204. args = [self.fullCommandPath]
  205. # archive path
  206. if self.file: args.append(self.file)
  207. # optional files to extract TODO
  208. #if self._files: args.append(",".join())
  209. return args
  210. ########################
  211. @property
  212. def path(self): return self._path
  213. @path.setter
  214. def path(self, path): self._path = path
  215. @property
  216. def command(self): return self._command
  217. @command.setter
  218. def command(self, cmd): self._command = cmd
  219. @property
  220. def fullCommandPath(self):
  221. return "{0}/{1}".format(self._path, self._command)
  222. @fullCommandPath.setter
  223. def fullCommandPath(self, tupleValue):
  224. self._path = tupleValue[0]
  225. self._command = tupleValue[1]
  226. @property
  227. def file(self): return self._file
  228. @file.setter
  229. def file(self, path):
  230. self._file = path
  231. self._data = None
  232. """ Options
  233. """
  234. # -test (-t)
  235. def test(self): # TODO
  236. result = self.run(cmd=[self.fullCommandPath, '-t', self.file])
  237. matches = re.findall("(.*)(\.\.\. ([a-zA-Z]+)(\.|!))", result, re.M)
  238. tests = {}
  239. if matches:
  240. for r in matches: print(r)
  241. def fetch(self):
  242. result = self.run(cmd=[self.fullCommandPath, '--json', self.file])
  243. if result: return JsonResult(result)
  244. return None