FileSpec.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import os
  2. from pandac.PandaModules import Filename, HashVal, VirtualFileSystem
  3. class FileSpec:
  4. """ This class represents a disk file whose hash and size
  5. etc. were read from an xml file. This class provides methods to
  6. verify whether the file on disk matches the version demanded by
  7. the xml. """
  8. def __init__(self):
  9. self.actualFile = None
  10. def fromFile(self, packageDir, filename, pathname = None, st = None):
  11. """ Reads the file information from the indicated file. If st
  12. is supplied, it is the result of os.stat on the filename. """
  13. vfs = VirtualFileSystem.getGlobalPtr()
  14. filename = Filename(filename)
  15. if pathname is None:
  16. pathname = Filename(packageDir, filename)
  17. self.filename = filename.cStr()
  18. self.basename = filename.getBasename()
  19. if st is None:
  20. st = os.stat(pathname.toOsSpecific())
  21. self.size = st.st_size
  22. self.timestamp = st.st_mtime
  23. hv = HashVal()
  24. hv.hashFile(pathname)
  25. self.hash = hv.asHex()
  26. def loadXml(self, xelement):
  27. """ Reads the file information from the indicated XML
  28. element. """
  29. self.filename = xelement.Attribute('filename')
  30. self.basename = None
  31. if self.filename:
  32. self.basename = Filename(self.filename).getBasename()
  33. size = xelement.Attribute('size')
  34. try:
  35. self.size = int(size)
  36. except:
  37. self.size = 0
  38. timestamp = xelement.Attribute('timestamp')
  39. try:
  40. self.timestamp = int(timestamp)
  41. except:
  42. self.timestamp = 0
  43. self.hash = xelement.Attribute('hash')
  44. def storeXml(self, xelement):
  45. """ Adds the file information to the indicated XML
  46. element. """
  47. if self.filename:
  48. xelement.SetAttribute('filename', self.filename)
  49. if self.size:
  50. xelement.SetAttribute('size', str(self.size))
  51. if self.timestamp:
  52. xelement.SetAttribute('timestamp', str(self.timestamp))
  53. if self.hash:
  54. xelement.SetAttribute('hash', self.hash)
  55. def storeMiniXml(self, xelement):
  56. """ Adds the just the "mini" file information--size and
  57. hash--to the indicated XML element. """
  58. if self.size:
  59. xelement.SetAttribute('size', str(self.size))
  60. if self.hash:
  61. xelement.SetAttribute('hash', self.hash)
  62. def quickVerify(self, packageDir = None, pathname = None):
  63. """ Performs a quick test to ensure the file has not been
  64. modified. This test is vulnerable to people maliciously
  65. attempting to fool the program (by setting datestamps etc.).
  66. Returns true if it is intact, false if it needs to be
  67. redownloaded. """
  68. if not pathname:
  69. pathname = Filename(packageDir, self.filename)
  70. try:
  71. st = os.stat(pathname.toOsSpecific())
  72. except OSError:
  73. # If the file is missing, the file fails.
  74. #print "file not found: %s" % (pathname)
  75. return False
  76. if st.st_size != self.size:
  77. # If the size is wrong, the file fails.
  78. #print "size wrong: %s" % (pathname)
  79. return False
  80. if st.st_mtime == self.timestamp:
  81. # If the size is right and the timestamp is right, the
  82. # file passes.
  83. #print "file ok: %s" % (pathname)
  84. return True
  85. #print "modification time wrong: %s" % (pathname)
  86. # If the size is right but the timestamp is wrong, the file
  87. # soft-fails. We follow this up with a hash check.
  88. if not self.checkHash(packageDir, pathname, st):
  89. # Hard fail, the hash is wrong.
  90. #print "hash check wrong: %s" % (pathname)
  91. return False
  92. #print "hash check ok: %s" % (pathname)
  93. # The hash is OK after all. Change the file's timestamp back
  94. # to what we expect it to be, so we can quick-verify it
  95. # successfully next time.
  96. self.__updateTimestamp(pathname, st)
  97. return True
  98. def fullVerify(self, packageDir = None, pathname = None):
  99. """ Performs a more thorough test to ensure the file has not
  100. been modified. This test is less vulnerable to malicious
  101. attacks, since it reads and verifies the entire file.
  102. Returns true if it is intact, false if it needs to be
  103. redownloaded. """
  104. if not pathname:
  105. pathname = Filename(packageDir, self.filename)
  106. try:
  107. st = os.stat(pathname.toOsSpecific())
  108. except OSError:
  109. # If the file is missing, the file fails.
  110. #print "file not found: %s" % (pathname)
  111. return False
  112. if st.st_size != self.size:
  113. # If the size is wrong, the file fails;
  114. #print "size wrong: %s" % (pathname)
  115. return False
  116. if not self.checkHash(packageDir, pathname, st):
  117. # Hard fail, the hash is wrong.
  118. #print "hash check wrong: %s" % (pathname)
  119. return False
  120. #print "hash check ok: %s" % (pathname)
  121. # The hash is OK. If the timestamp is wrong, change it back
  122. # to what we expect it to be, so we can quick-verify it
  123. # successfully next time.
  124. if st.st_mtime != self.timestamp:
  125. self.__updateTimestamp(pathname, st)
  126. return True
  127. def __updateTimestamp(self, pathname, st):
  128. # On Windows, we have to change the file to read-write before
  129. # we can successfully update its timestamp.
  130. os.chmod(pathname.toOsSpecific(), 0755)
  131. os.utime(pathname.toOsSpecific(), (st.st_atime, self.timestamp))
  132. os.chmod(pathname.toOsSpecific(), 0555)
  133. def checkHash(self, packageDir, pathname, st):
  134. """ Returns true if the file has the expected md5 hash, false
  135. otherwise. As a side effect, stores a FileSpec corresponding
  136. to the on-disk file in self.actualFile. """
  137. fileSpec = FileSpec()
  138. fileSpec.fromFile(packageDir, self.filename,
  139. pathname = pathname, st = st)
  140. self.actualFile = fileSpec
  141. return (fileSpec.hash == self.hash)