Commit | Line | Data |
---|---|---|
c638d827 CR |
1 | # |
2 | # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/) | |
3 | # This module is free software, and you may redistribute it and/or modify | |
4 | # under the same terms as Python, so long as this copyright message and | |
5 | # disclaimer are retained in their original form. | |
6 | # | |
7 | # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR | |
8 | # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING | |
9 | # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE | |
10 | # POSSIBILITY OF SUCH DAMAGE. | |
11 | # | |
12 | # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, | |
13 | # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
14 | # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" | |
15 | # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, | |
16 | # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | |
17 | # | |
18 | # $Id: install_util.py,v 1.11 2006-01-25 03:11:43 richard Exp $ | |
19 | ||
20 | """Support module to generate and check fingerprints of installed files. | |
21 | """ | |
22 | __docformat__ = 'restructuredtext' | |
23 | ||
24 | import os, shutil | |
25 | from roundup.anypy.hashlib_ import sha1 | |
26 | ||
27 | sgml_file_types = [".xml", ".ent", ".html"] | |
28 | hash_file_types = [".py", ".sh", ".conf", ".cgi"] | |
29 | slast_file_types = [".css"] | |
30 | ||
31 | digested_file_types = sgml_file_types + hash_file_types + slast_file_types | |
32 | ||
33 | def extractFingerprint(lines): | |
34 | # get fingerprint from last line | |
35 | if lines[-1].startswith("#SHA: "): | |
36 | # handle .py/.sh comment | |
37 | return lines[-1][6:].strip() | |
38 | elif lines[-1].startswith("<!-- SHA: "): | |
39 | # handle xml/html files | |
40 | fingerprint = lines[-1][10:] | |
41 | fingerprint = fingerprint.replace('-->', '') | |
42 | return fingerprint.strip() | |
43 | elif lines[-1].startswith("/* SHA: "): | |
44 | # handle css files | |
45 | fingerprint = lines[-1][8:] | |
46 | fingerprint = fingerprint.replace('*/', '') | |
47 | return fingerprint.strip() | |
48 | return None | |
49 | ||
50 | def checkDigest(filename): | |
51 | """Read file, check for valid fingerprint, return TRUE if ok""" | |
52 | # open and read file | |
53 | inp = open(filename, "r") | |
54 | lines = inp.readlines() | |
55 | inp.close() | |
56 | ||
57 | fingerprint = extractFingerprint(lines) | |
58 | if fingerprint is None: | |
59 | return 0 | |
60 | del lines[-1] | |
61 | ||
62 | # calculate current digest | |
63 | digest = sha1() | |
64 | for line in lines: | |
65 | digest.update(line) | |
66 | ||
67 | # compare current to stored digest | |
68 | return fingerprint == digest.hexdigest() | |
69 | ||
70 | ||
71 | class DigestFile: | |
72 | """ A class that you can use like open() and that calculates | |
73 | and writes a SHA digest to the target file. | |
74 | """ | |
75 | ||
76 | def __init__(self, filename): | |
77 | self.filename = filename | |
78 | self.digest = sha1() | |
79 | self.file = open(self.filename, "w") | |
80 | ||
81 | def write(self, data): | |
82 | lines = data.splitlines() | |
83 | # if the file is coming from an installed tracker being used as a | |
84 | # template, then we will want to re-calculate the SHA | |
85 | fingerprint = extractFingerprint(lines) | |
86 | if fingerprint is not None: | |
87 | data = '\n'.join(lines[:-1]) + '\n' | |
88 | self.file.write(data) | |
89 | self.digest.update(data) | |
90 | ||
91 | def close(self): | |
92 | file, ext = os.path.splitext(self.filename) | |
93 | ||
94 | if ext in sgml_file_types: | |
95 | self.file.write("<!-- SHA: %s -->\n" % (self.digest.hexdigest(),)) | |
96 | elif ext in hash_file_types: | |
97 | self.file.write("#SHA: %s\n" % (self.digest.hexdigest(),)) | |
98 | elif ext in slast_file_types: | |
99 | self.file.write("/* SHA: %s */\n" % (self.digest.hexdigest(),)) | |
100 | ||
101 | self.file.close() | |
102 | ||
103 | ||
104 | def copyDigestedFile(src, dst, copystat=1): | |
105 | """ Copy data from `src` to `dst`, adding a fingerprint to `dst`. | |
106 | If `copystat` is true, the file status is copied, too | |
107 | (like shutil.copy2). | |
108 | """ | |
109 | if os.path.isdir(dst): | |
110 | dst = os.path.join(dst, os.path.basename(src)) | |
111 | ||
112 | dummy, ext = os.path.splitext(src) | |
113 | if ext not in digested_file_types: | |
114 | if copystat: | |
115 | return shutil.copy2(src, dst) | |
116 | else: | |
117 | return shutil.copyfile(src, dst) | |
118 | ||
119 | fsrc = None | |
120 | fdst = None | |
121 | try: | |
122 | fsrc = open(src, 'r') | |
123 | fdst = DigestFile(dst) | |
124 | shutil.copyfileobj(fsrc, fdst) | |
125 | finally: | |
126 | if fdst: fdst.close() | |
127 | if fsrc: fsrc.close() | |
128 | ||
129 | if copystat: shutil.copystat(src, dst) | |
130 | ||
131 | ||
132 | def test(): | |
133 | import sys | |
134 | ||
135 | testdata = open(sys.argv[0], 'r').read() | |
136 | ||
137 | for ext in digested_file_types: | |
138 | testfile = "__digest_test" + ext | |
139 | ||
140 | out = DigestFile(testfile) | |
141 | out.write(testdata) | |
142 | out.close() | |
143 | ||
144 | assert checkDigest(testfile), "digest ok w/o modification" | |
145 | ||
146 | mod = open(testfile, 'r+') | |
147 | mod.seek(0) | |
148 | mod.write('# changed!') | |
149 | mod.close() | |
150 | ||
151 | assert not checkDigest(testfile), "digest fails after modification" | |
152 | ||
153 | os.remove(testfile) | |
154 | ||
155 | ||
156 | if __name__ == '__main__': | |
157 | test() | |
158 | ||
159 | # vim: set filetype=python ts=4 sw=4 et si |