Initial release.
[FeedLint.git] / feedlint
blob7b6a3523e7d3bec812b843b4877ffcf13a7a803c
1 #!/usr/bin/env python
3 from optparse import OptionParser
4 import sys, shutil, tempfile, urlparse
5 import urllib2, os, httplib
6 import logging
7 from logging import info
9 from zeroinstall import SafeException
10 from zeroinstall.injector import model, gpg, reader
12 version = '0.1'
14 parser = OptionParser(usage="usage: %prog [options] feed.xml")
15 parser.add_option("-v", "--verbose", help="more verbose output", action='count')
16 parser.add_option("-V", "--version", help="display version information", action='store_true')
18 (options, args) = parser.parse_args()
20 if options.version:
21 print "FeedLint (zero-install) " + version
22 print "Copyright (C) 2007 Thomas Leonard"
23 print "This program comes with ABSOLUTELY NO WARRANTY,"
24 print "to the extent permitted by law."
25 print "You may redistribute copies of this program"
26 print "under the terms of the GNU General Public License."
27 print "For more information about these matters, see the file named COPYING."
28 sys.exit(0)
30 if options.verbose:
31 logger = logging.getLogger()
32 if options.verbose == 1:
33 logger.setLevel(logging.INFO)
34 else:
35 logger.setLevel(logging.DEBUG)
37 if len(args) < 1:
38 parser.print_help()
39 sys.exit(1)
41 checked = set()
43 try:
44 to_check = [model.canonical_iface_uri(a) for a in args]
45 except SafeException, ex:
46 if options.verbose: raise
47 print >>sys.stderr, ex
48 sys.exit(1)
50 def check_key(feed, fingerprint):
51 for line in os.popen('gpg --with-colons --list-keys %s' % s.fingerprint):
52 if line.startswith('pub:'):
53 key_id = line.split(':')[4]
54 break
55 else:
56 raise SafeException('Failed to find key with fingerprint %s on your keyring' % fingerprint)
58 key_url = urlparse.urljoin(feed, '%s.gpg' % key_id)
59 print "Checking", key_url
61 if key_url in checked:
62 info("(already checked key URL)")
63 else:
64 urllib2.urlopen(key_url).read()
65 checked.add(key_url)
67 def check_source(source):
68 if hasattr(source, 'url'):
69 print " Checking archive", source.url
70 address = urlparse.urlparse(source.url)
71 http = httplib.HTTPConnection(address.netloc, address.port or 80)
72 http.request('HEAD', source.url)
73 response = http.getresponse()
74 try:
75 if response.status != 200:
76 raise SafeException("HTTP error: got status code %s" % response.status)
77 actual_size = int(response.getheader('Content-Length'))
78 if actual_size != source.size:
79 raise SafeException("Expected archive to have a size of %d, but server says it is %d",
80 source.size, actual_size)
81 finally:
82 response.close()
84 elif hasattr(source, 'steps'):
85 for step in source.steps:
86 check_source(step)
88 n_errors = 0
90 while to_check:
91 feed = to_check.pop()
92 if feed in checked:
93 info("Already checked feed %s", feed)
94 continue
96 checked.add(feed)
98 print "Checking", feed
100 try:
101 tmp = tempfile.TemporaryFile(prefix = 'feedlint-')
102 try:
103 stream = urllib2.urlopen(feed)
104 shutil.copyfileobj(stream, tmp)
105 data, sigs = gpg.check_stream(tmp)
107 for s in sigs:
108 if isinstance(s, gpg.ValidSig):
109 check_key(feed, s.fingerprint)
110 else:
111 raise SafeException("Can't check sig: %s" % s)
113 feed_tmp = tempfile.NamedTemporaryFile(prefix = 'feedlint-')
114 try:
115 shutil.copyfileobj(data, feed_tmp)
116 feed_tmp.seek(0)
117 iface = model.Interface(feed)
118 reader.update(iface, feed_tmp.name)
120 for f in iface.feeds:
121 info("Will check feed %s", f.uri)
122 to_check.append(f.uri)
123 finally:
124 feed_tmp.close()
125 finally:
126 tmp.close()
128 for impl in iface.implementations.values():
129 for r in impl.dependencies.values():
130 if r.interface not in checked:
131 info("Will check dependency %s", r)
132 to_check.append(r.interface)
133 if hasattr(impl, 'download_sources'):
134 for source in impl.download_sources:
135 check_source(source)
138 except urllib2.HTTPError, ex:
139 print >>sys.stderr, ex
140 n_errors += 1
141 except SafeException, ex:
142 if options.verbose: raise
143 print >>sys.stderr, ex
144 n_errors += 1
146 if n_errors == 0:
147 print "OK"
148 else:
149 print "\nERRORS FOUND:", n_errors
150 sys.exit(1)