/[secure-testing]/lib/python/bugs.py
ViewVC logotype

Contents of /lib/python/bugs.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1994 - (show annotations) (download) (as text)
Thu Sep 15 10:11:44 2005 UTC (7 years, 8 months ago) by fw
File MIME type: text/x-python
File size: 28783 byte(s)
Implement bin/update-db, to update the database with a single command.
Most processing is skipped if no input files have been modified.

lib/python/security_db.py (SchemaMismatch):
  New exception.
(DB):
  Handle schema versioning.
(DB.initSchema):
  Add subrelease column to source_packages and binary_packages.
  Set user_version.
  Remove stray commit.
(DB._parseFile):
  Return information to the caller if the file is unchanged.
(DB.readPackages):
  Move deletion code to callees.
(DB._readSourcePackages, DB._readBinaryPackages):
  Implement incremental updates.  Add subrelease.
  Need to invoke _clearVersions if any changes are made.
(DB.deleteBugs, DB.finishBugs):
  Moved into readBugs.
(DB.insertBugs):
  Rename ...
(DB.readBugs):
  ... to this one.  Implement incremental updates.
  Invoke _clearVersions if necessary.
(DB._clearVersions):
  Add.
(DB._updateVersions):
  Skip processing if _clearVersions has not been invoked.
(DB.getVersion, DB.releaseContainsPackage, DB._synthesizeReleases):
  Obsolete, remove.
(test):
  Update.

lib/python/bugs.py (CANFile, CVEFile):
  Split into two classes, which handle the differences between the two
  files.

bin/check-syntax:
  Update accordingly.

bin/update-db:
  New database update script.  Implements incremental updates.

Makefile:
  Remove references to bin/update-packages.  Simplify drastically.
1 # bugs.py -- read bug lists used by Debian's testing security team
2 # Copyright (C) 2005 Florian Weimer <fw@deneb.enyo.de>
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
18 import debian_support
19 import re
20 import types
21
22 class Urgency(debian_support.PseudoEnum): pass
23
24 def listUrgencies():
25 urgencies = {}
26 urgs = ("high", "medium", "low", "unimportant", "unknown")
27 for u in range(len(urgs)):
28 urgencies[urgs[u]] = Urgency(urgs[u], -u)
29 Urgency.urgencies = urgencies
30 return urgencies
31 def internUrgency(name, urgencies=listUrgencies()):
32 if urgencies.has_key(name):
33 return urgencies[name]
34 else:
35 return None
36 del listUrgencies
37
38 class PackageNote:
39 """A package note.
40
41 The following member variables are defined:
42
43 release - the release the package note applies to; None means "testing",
44 notes for other releases never apply to testing
45 """
46
47 def __init__(self, package, fixed_version, release, urgency):
48 self.id = None
49 self.package = package
50 if (fixed_version is not None
51 and type(fixed_version) == types.StringType):
52 self.fixed_version = debian_support.Version(fixed_version)
53 else:
54 self.fixed_version = fixed_version
55 if release == '':
56 self.release = None
57 else:
58 if type(release) == types.StringType:
59 release = debian_support.internRelease(release)
60 if release is None:
61 raise ValueError, "invalid release"
62 self.release = release
63 if type(urgency) == types.StringType:
64 urgency = internUrgency(urgency)
65 if urgency is None:
66 raise ValueError, "invalid urgency"
67 self.urgency = urgency
68 self.bugs = []
69
70 def affects(self, version, release=None):
71 """Returns true if this package note affects the given version.
72
73 Both version and release can be strings. In this case, they
74 are automatically promoted to the correct Python objects.
75 """
76
77 if type(version) == types.StringType:
78 version = debian_support.Version(version)
79 if type(release) == types.StringType:
80 release = Release(release)
81
82 if release is None:
83 if self.release is not None:
84 # If there's a release spec, and we are running for
85 # testing, this note does apply.
86 return False
87 else:
88 if self.release is not None and self.release <> release:
89 # If there's a release spec, it must match ours.
90 return False
91 # Standard version comparison if the releases match.
92 return self.fixed_version is None or version < self.fixed_version
93
94 def affectsKernel(self, regexp=re.compile
95 (r'^kernel-(?:source|image|patch)-[0-9.]{3}')):
96 """Crude check if this is a kernel package."""
97 return regexp.match(self.package) is not None
98
99 def fixedVersion(self):
100 """ Returns a string noting that the bug was fixed, or 'unfixed'."""
101 if self.fixed_version:
102 return "fixed in %s" % self.fixed_version
103 else:
104 return "unfixed"
105
106 def sourceStatus(self, cursor):
107 """Returns a pair of lists (VERSION, RELEASE-LIST).
108
109 The first one is the list with the vulnerable versions, the
110 second one contains fixed versions.
111 """
112
113 vulnerable_versions = {}
114 fixed_versions = {}
115 for (release, archive, version, vulnerable) in cursor.execute(
116 """SELECT p.release, p.archive, p.version, s.vulnerable
117 FROM source_package_status AS s, source_packages AS p
118 WHERE s.note = ? AND p.rowid = s.package""", (self.id,)):
119 if vulnerable:
120 versions = vulnerable_versions
121 else:
122 versions = fixed_versions
123 if not versions.has_key(version):
124 versions[version] = []
125 versions[version].append((release, archive))
126
127 def sort(versions):
128 l = map(debian_support.Version, versions.keys())
129 l.sort()
130 return map(lambda ver: (ver, versions[str(ver)]), l)
131 return (sort(vulnerable_versions), sort(fixed_versions))
132
133 def writeDB(self, cursor, bug_name):
134 """Writes the object to an SQLite database.
135
136 If the id attibute is already set, it is assumed that the
137 object has already been written.
138 """
139
140 if self.id is not None:
141 return
142
143 if self.fixed_version:
144 v = str(self.fixed_version)
145 else:
146 v = None
147 if self.release:
148 r = str(self.release)
149 else:
150 r = ''
151 cursor.execute("""INSERT INTO package_notes
152 (bug_name, package, fixed_version, release, urgency)
153 VALUES (?, ?, ?, ?, ?)""",
154 (bug_name, self.package, v, r,
155 str(self.urgency)))
156 for (rowid,) in cursor.execute('SELECT last_insert_rowid()'):
157 self.id = rowid
158 for b in self.bugs:
159 cursor.execute("""INSERT INTO debian_bugs (bug, note)
160 VALUES (?, ?)""", (b, rowid))
161 return
162 assert False
163
164 def loadBugs(self, cursor):
165 assert type(self.id) == types.IntType, self.id
166 assert len(self.bugs) == 0
167 for (b,) in cursor.execute\
168 ("SELECT bug FROM debian_bugs WHERE note = ?", (self.id,)):
169 self.bugs.append(int(b))
170
171 class PackageNoteFromDB(PackageNote):
172 def __init__(self, cursor, nid):
173 for bug_name, package, fixed_version, release, urgency \
174 in cursor.execute\
175 ("""SELECT bug_name, package, fixed_version, release, urgency
176 FROM package_notes WHERE id = ?""", (nid,)):
177 PackageNote.__init__(package, fixed_version, release, urgency)
178 self.id = nid
179 self.bug_name = bug_name
180 self.loadBugs(cursor)
181 return
182 raise ValueError, "invalid package note ID %d" % id
183
184 class PackageNoteParsed(PackageNote):
185 """Subclass with a constructor that parses package notes."""
186
187 re_bug = re.compile(r'^bug #(\d+)$')
188 re_notes_split = re.compile(r'\s*;\s+')
189
190 def __init__(self, package, version, notes, release=None):
191 bugs = []
192 urgency = "unknown"
193 if notes is not None:
194 for n in self.re_notes_split.split(notes):
195 u = internUrgency(n)
196 if u:
197 urgency = u
198 continue
199
200 match = self.re_bug.match(n)
201 if match:
202 (bug,) = match.groups()
203 bugs.append(int(bug))
204 continue
205
206 if n == 'unfixed':
207 self.unfixed = True
208 continue
209
210 raise SyntaxError , 'unknown package note %s\n' % `n`
211 PackageNote.__init__(self, package, version, release, urgency)
212 self.bugs = bugs
213
214 class BugBase:
215 "Base class for entries in the bug list."""
216
217 re_cve_name = re.compile(r'^(?:CAN|CVE)-\d{4}-\d{4}$')
218
219 def __init__(self, fname, lineno, date, name, description, comments):
220 assert type(fname) == types.StringType
221 assert type(lineno) == types.IntType
222 self.source_file = fname
223 self.source_line = lineno
224 self.date = date
225 self.name = name
226 self.description = description
227 self.comments = comments
228 self.notes = []
229 self.xref = []
230 self.not_for_us = False
231
232 def isFromCVE(self):
233 """Returns True if the name has been officially assigned.
234
235 Our database is mostly CVE-driven, but sometimes we need names
236 which have not been assigned yet. Therefore, we generate
237 identifiers on the fly.
238 """
239 return self.re_cve_name.match(self.name) is not None
240
241 def cveStatus(self):
242 if self.isFromCVE():
243 if self.name[0:4] == 'CVE':
244 return 'ASSIGNED'
245 return 'CANDIDATE'
246 else:
247 return ''
248
249 def hasTODO(self):
250 """Returns True if the bug has a TODO item."""
251 for (t, c) in self.comments:
252 if t == "TODO":
253 return True
254 return False
255
256 def isKernelOnly(self):
257 """Returns True if this bug contains notes which refer to kernels."""
258 if len(self.notes) == 0:
259 return False
260 for n in self.notes:
261 if not n.affectsKernel():
262 return False
263 return True
264
265 def writeDB(self, cursor):
266 """Writes the record to an SQLite3 database."""
267
268 if self.not_for_us:
269 not_for_us = 1
270 else:
271 not_for_us = 0
272
273 import apsw
274 try:
275 cursor.execute("""INSERT INTO bugs
276 (name, cve_status, not_for_us, description,
277 source_file, source_line)
278 VALUES (?, ?, ?, ?, ?, ?)""",
279 (self.name, self.cveStatus(), not_for_us,
280 self.description,
281 self.source_file, self.source_line))
282 except apsw.ConstraintError:
283 raise ValueError, "bug name %s is not unique" % self.name
284
285 for (typ, c) in self.comments:
286 cursor.execute("""INSERT INTO bugs_notes
287 (bug_name, typ, comment) VALUES (?, ?, ?)""",
288 (self.name, typ, c))
289
290 for n in self.notes:
291 n.writeDB(cursor, self.name)
292
293 for x in self.xref:
294 try:
295 cursor.execute("""INSERT INTO bugs_xref
296 (source, target) VALUES (?, ?)""",
297 (self.name, x))
298 except apsw.ConstraintError:
299 raise ValueError, \
300 "cross reference to %s appears multiple times" % x
301
302 class Bug(BugBase):
303 """Class for bugs for which we have some data."""
304
305 def __init__(self, fname, lineno, date, name, description, comments, notes,
306 xref, not_for_us=False):
307 assert len(notes) == 0 or isinstance(notes[0], PackageNote)
308 assert len(xref) == 0 or type(xref[0]) == types.StringType
309 assert type(not_for_us) == types.BooleanType
310 BugBase.__init__(self, fname, lineno, date, name,
311 description, comments)
312 self.notes = notes
313 self.xref = xref
314 self.not_for_us = not_for_us
315
316 class BugFromDB(Bug):
317 def __init__(self, cursor, name):
318 assert type(name) == types.StringType
319 for r in cursor.execute('SELECT * FROM bugs WHERE name = ?', (name,)):
320 rdesc = cursor.getdescription()
321 data = {}
322 for j in range(len(rdesc)):
323 data[rdesc[j][0]] = r[j]
324 # FIXME: load date
325 Bug.__init__(self, data['source_file'], data['source_line'],
326 None, name, data['description'], comments=[],
327 notes=[], xref=[],
328 not_for_us=not not data['not_for_us'])
329 for (x,) in cursor.execute\
330 ('SELECT target FROM bugs_xref WHERE source = ?', (name,)):
331 self.xref.append(x)
332 for (t, c) in cursor.execute\
333 ("""SELECT typ, comment FROM bugs_notes
334 WHERE bug_name = ?
335 ORDER BY rowid""",
336 (name,)):
337 self.comments.append((t, c))
338
339 # temporary list required because loadBugs needs the cursor
340 for nid, package, fixed_version, release, urgency \
341 in list(cursor.execute
342 ("""SELECT id, package, fixed_version, release, urgency
343 FROM package_notes WHERE bug_name = ?""", (name,))):
344 n = PackageNote(package, fixed_version, release, urgency)
345 n.id = nid
346 n.bug_name = name
347 n.loadBugs(cursor)
348 self.notes.append(n)
349 return
350 raise ValueError, "unknown bug " + `name`
351
352 def getDebianBugs(self, cursor):
353 """Returns a list of Debian bugs to which the bug report refers."""
354 return map(lambda (x,): x, cursor.execute(
355 """SELECT DISTINCT bug FROM debian_bugs, package_notes
356 WHERE package_notes.bug_name = ?
357 AND debian_bugs.note = package_notes.id
358 ORDER BY bug""", (self.name,)))
359
360 class BugReservedCVE(BugBase):
361 """Class for reserved CVE entries."""
362 def __init__(self, fname, lineno, name, comments=None):
363 if comments is None:
364 comments = []
365 BugBase.__init__(self, fname, lineno, None, name, "RESERVED", comments)
366 # for-us bugs are upgraded to real Bug objects.
367 self.not_for_us = True
368 def cveStatus(self):
369 return 'RESERVED'
370
371 class BugRejectedCVE(BugBase):
372 """Class for rejected CVE entries."""
373 def __init__(self, fname, lineno, name):
374 BugBase.__init__(self, fname, lineno, None, name, "REJECTED", [])
375 # for-us bugs are upgraded to real Bug objects.
376 self.not_for_us = True
377 def cveStatus(self):
378 return 'REJECTED'
379
380 class FileBase(debian_support.PackageFile):
381 re_non_ascii = re.compile(r'.*([^\n\t -~]).*')
382 re_empty = re.compile(r'^(?:\s*$|--)')
383 re_indent = re.compile(r'^\s+(.*?)\s*$')
384 re_begin_claim = re.compile(r'^begin claimed by (\S+)\s*$')
385 re_end_claim = re.compile(r'^end claimed by (\S+)\s*$')
386 re_stop = re.compile(r'^STOP:')
387
388 re_xref_required = re.compile(r'^\{')
389 re_xref = re.compile(r'^\{\s*([^\}]+?)\s*\}$')
390 re_whitespace = re.compile(r'\s+')
391 re_xref_entry = re.compile('^(?:(?:CAN|CVE)-\d{4}-\d{4}'
392 + r'|VU#\d{6}'
393 + r'|DSA-\d+(?:-\d+)?|DTSA-\d+-\d+)$')
394
395 # temporary hack, until we know what "!" actually means.
396 re_package_required = re.compile(r'^(?:\[.*\]\s*)?[-!]')
397 re_package = re.compile(r'^(?:\[([a-z]+)\] )?[-!] ([A-Za-z0-9:.+-]+)'
398 + r'(?:\s+([A-Za-z0-9:.+-]+))?\s*(?:\((.*)\))?$')
399 re_not_for_us_required = re.compile(r'^NOTE:\s+not?e?-fo?r-u')
400 re_not_for_us = re.compile(r'^NOTE:\s+not-for-us(?:\s+\((.*)\))?\s*$')
401 re_reserved = re.compile(r'^NOTE:\s+reserved\s*$')
402 re_rejected = re.compile(r'^NOTE:\s+rejected\s*$')
403 re_note = re.compile(r'^NOTE:\s+(.*)$')
404 re_todo = re.compile(r'^TODO:\s+(.*)$')
405
406 def isUniqueName(self, name):
407 """Returns True if the name is a real, unique name."""
408 return True
409
410 def matchHeader(self, line):
411 """Parses the header of a record.
412
413 Must be overriden by child classes."""
414 assert False
415
416 def getLine(self):
417 while 1:
418 self.line = self.file.readline()
419 self.lineno += 1
420
421 if self.line == '' or not self.re_empty.match(self.line):
422 break
423
424 match = self.re_non_ascii.match(self.line)
425 if match is not None:
426 self.raiseSyntaxError('invalid non-printable character %s'
427 % `match.groups()[0]`)
428
429 def rawRecords(self):
430 """Generator which returns raw records.
431
432 These records are 4-tuples with the following contents:
433
434 - line number of the start of the record
435 - release data; can be None
436 - something which resembles a CVE name; is not necessarily unique
437 if it does not match the CVE syntax
438 - part of the CVE description
439 - subrecords, a list of pairs line number/string
440 """
441
442 self.getLine()
443 record = []
444 after_stop = False
445 while self.line:
446 first_line = self.lineno
447
448 if self.re_stop.match(self.line):
449 after_stop = True
450 self.getLine()
451 continue
452
453 # We ignore claims, but check their syntax nevertheless.
454 match = self.re_begin_claim.match(self.line)
455 if match:
456 self.getLine()
457 continue
458 match = self.re_end_claim.match(self.line)
459 if match:
460 self.getLine()
461 continue
462
463 (date, record_name, description) = self.matchHeader(self.line)
464
465 record = []
466 while self.line:
467 self.getLine()
468
469 match = self.re_indent.match(self.line)
470 if match:
471 (r,) = match.groups()
472 record.append((self.lineno, r))
473 else:
474 break
475 # line contains the next line at this point.
476
477 if after_stop and len(record) == 0:
478 # Patch in not-for-us field, so that bugs after STOP:
479 # are ignored.
480 record = [(first_line, 'NOTE: not-for-us (entry too old)')]
481
482 yield (first_line, date, record_name, description, record)
483
484 def __iter__(self):
485 """Generator for Bug objects."""
486 for (first_lineno, date, record_name, description, record)\
487 in self.rawRecords():
488
489 not_for_us = None
490 xref = []
491 pkg_notes = []
492 comments = []
493 cve_reserved = False
494 cve_rejected = False
495 first_bug = 0
496
497 for (lineno, r) in record:
498 if self.re_xref_required.match(r):
499 match = self.re_xref.match(r)
500 if match:
501 (xref_string,) = match.groups()
502 for x in self.re_whitespace.split(xref_string):
503 if self.re_xref_entry.match(x):
504 xref.append(x)
505 else:
506 self.raiseSyntaxError\
507 ("invalid cross reference " + `x`, lineno)
508 continue
509 else:
510 self.raiseSyntaxError("expected cross reference, got: "
511 + `r`, lineno)
512
513 if self.re_package_required.match(r):
514 match = self.re_package.match(r)
515 if match:
516 (release, p, v, d) = match.groups()
517 if v is None and d is None and \
518 self.no_version_needs_note:
519 raise SyntaxError, \
520 'version-less package entry requires note'
521
522 if v == 'not-affected':
523 # '0' is the minimum version number possible.
524 pkg_notes.append(PackageNoteParsed
525 (p, '0', None, release=release))
526 # 'd' is a free-form field in this case.
527 comments.append(('NOTE', d))
528 else:
529 x = PackageNoteParsed(p, v, d, release=release)
530 pkg_notes.append(x)
531 if first_bug == 0 and len(x.bugs) > 0:
532 first_bug = x.bugs[0]
533 else:
534 self.raiseSyntaxError("expected package entry, got: "
535 + `r`, lineno)
536 continue
537
538 if self.re_not_for_us_required.match(r):
539 match = self.re_not_for_us.match(r)
540 if match:
541 (not_for_us,) = match.groups()
542 if not_for_us is None:
543 not_for_us = ''
544 continue
545 else:
546 self.raiseSyntaxError("expected not-for-us entry, "
547 + "got: " + `r`, lineno)
548
549 match = self.re_reserved.match(r)
550 if match:
551 cve_reserved = True
552 continue
553
554 match = self.re_rejected.match(r)
555 if match:
556 cve_rejected = True
557 continue
558
559 match = self.re_note.match(r)
560 if match:
561 (note,) = match.groups()
562 comments.append(('NOTE', note))
563 continue
564
565 match = self.re_todo.match(r)
566 if match:
567 (todo,) = match.groups()
568 comments.append(('TODO', todo))
569 continue
570
571 self.raiseSyntaxError('expected CAN/CVE annotation, got: %s'
572 % `r`, lineno)
573 break
574
575 if cve_reserved:
576 if not self.isUniqueName(record_name):
577 self.raiseSyntaxError\
578 ('reserved CVE entries must have CAN/CVE names',
579 first_lineno)
580 if len(pkg_notes) > 0:
581 # The bug has extra data even though it is marked
582 # reserved by CVE, we have to issue the full
583 # version because the official CVE lags a bit.
584 yield Bug(self.file.name, first_lineno, date,
585 record_name, description, comments,
586 notes=pkg_notes, xref=xref)
587 else:
588 yield BugReservedCVE(self.file.name, first_lineno,
589 record_name, comments)
590
591 elif cve_rejected:
592 if not self.isUniqueName(record_name):
593 self.raiseSyntaxError\
594 ('rjeected CVE entries must have CAN/CVE names',
595 first_lineno)
596 if len(pkg_notes) > 0:
597 self.raiseSyntaxError\
598 ('rejected CVE entries must not have notes',
599 first_lineno)
600 yield BugRejectedCVE(self.file.name, first_lineno, record_name)
601
602 elif not_for_us is not None:
603 if not self.isUniqueName(record_name):
604 self.raiseSyntaxError\
605 ('not-for-us bug must have CAN/CVE name', first_lineno)
606 if len(pkg_notes) > 0:
607 self.raiseSyntaxError\
608 ('package information not allowed in not-for-us bugs',
609 first_lineno)
610 yield Bug(self.file.name, first_lineno, date,
611 record_name, description, comments, notes=[],
612 xref=xref, not_for_us=True)
613 else:
614 if not self.isUniqueName(record_name):
615 record_name = 'FAKE-%07d-%06d' % (first_bug, first_lineno)
616 yield Bug(self.file.name, first_lineno, date,
617 record_name, description,
618 comments, notes=pkg_notes, xref=xref)
619
620 class CANFile(FileBase):
621 """A CAN file, as used by the Debian testing security team."""
622
623 re_cve = re.compile(r'^(CAN-\d{4}-(?:\d{4}|XXXX))\s+(.*?)\s*$')
624
625 def __init__(self, name, fileObj=None):
626 FileBase.__init__(self, name, fileObj)
627 self.no_version_needs_note = True
628
629 def isUniqueName(self, name):
630 return BugBase.re_cve_name.match(name) is not None
631
632 def matchHeader(self, line):
633 match = self.re_cve.match(line)
634 if not match:
635 self.raiseSyntaxError("expected CAN record, got: %s" % `line`)
636 (record_name, description) = match.groups()
637 (cve, desc) = match.groups()
638 if desc:
639 if desc[0] == '(':
640 if desc[-1] <> ')':
641 self.raiseSyntaxError("missing closing parenthesis")
642 else:
643 desc = desc[1:-1]
644 elif desc[0] == '[':
645 if desc[-1] <> ']':
646 self.raiseSyntaxError("missing closing bracket")
647 else:
648 desc = desc[1:-1]
649 return (None, cve, desc)
650
651 class CVEFile(FileBase):
652 """A CVE file, as used by the Debian testing security team."""
653
654 re_cve = re.compile(r'^(CVE-\d{4}-\d{4})\s+(.*?)\s*$')
655
656 def __init__(self, name, fileObj=None):
657 FileBase.__init__(self, name, fileObj)
658 self.no_version_needs_note = False
659
660 def matchHeader(self, line):
661 match = self.re_cve.match(line)
662 if not match:
663 self.raiseSyntaxError("expected CVE record, got: %s" % `line`)
664 (record_name, description) = match.groups()
665 (cve, desc) = match.groups()
666 if desc:
667 if desc[0] == '(':
668 if desc[-1] <> ')':
669 self.raiseSyntaxError("missing closing parenthesis")
670 else:
671 desc = desc[1:-1]
672 elif desc[0] == '[':
673 if desc[-1] <> ']':
674 self.raiseSyntaxError("missing closing bracket")
675 else:
676 desc = desc[1:-1]
677 return (None, cve, desc)
678
679 class DSAFile(FileBase):
680 """A DSA file.
681
682 Similar to a CVE file, only that it contains DSAs as its main
683 reference point, and release dates.
684 """
685
686 re_dsa = re.compile(r'^\[(\d\d) ([A-Z][a-z][a-z]) (\d{4})\] '
687 + r'(DSA-\d+(?:-\d+)?)\s+'
688 + r'(.*?)\s*$')
689
690 month_names = {'Jan': 1,
691 'Feb': 2,
692 'Mar': 3,
693 'Apr': 4,
694 'May': 5,
695 'Jun': 6,
696 'Jul': 7,
697 'Aug': 8,
698 'Sep': 9,
699 'Oct': 10,
700 'Nov': 11,
701 'Dec': 12}
702
703 def matchHeader(self, line):
704 match = self.re_dsa.match(line)
705 if not match:
706 self.raiseSyntaxError("expected DSA record, got: %s" % `line`)
707 (record_name, description) = match.groups()
708 (day, month, year, name, desc) = match.groups()
709 try:
710 month = self.month_names[month]
711 except KeyError:
712 self.raiseSyntaxError("invalid month name %s" % `month`)
713 return ("%s-%02d-%s" % (year, month, day), name, desc)
714
715 class DTSAFile(FileBase):
716 """A DTSA file.
717
718 Like a DSA file, but the date format is different.
719 """
720
721 re_dsa = re.compile\
722 (r'^\[([A-Z][a-z]{3,}) (\d\d?)(?:st|nd|rd|th), (\d{4})\] '
723 + r'(DTSA-\d+-\d+)\s+'
724 + r'(.*?)\s*$')
725 month_names = {'January': 1,
726 'February': 2,
727 'March': 3,
728 'April': 4,
729 'May': 5,
730 'June': 6,
731 'July': 7,
732 'August': 8,
733 'September': 9,
734 'October': 10,
735 'November': 11,
736 'December': 12}
737
738 def matchHeader(self, line):
739 match = self.re_dsa.match(line)
740 if not match:
741 self.raiseSyntaxError("expected DTSA record, got: %s" % `line`)
742 (record_name, description) = match.groups()
743 (month, day, year, name, desc) = match.groups()
744 try:
745 month = self.month_names[month]
746 except KeyError:
747 self.raiseSyntaxError("invalid month name %s" % `month`)
748 return ("%s-%02d-%02d" % (year, month, int(day)), name, desc)
749
750 def test():
751 assert internUrgency("high") > internUrgency("medium")
752
753 assert FileBase.re_non_ascii.match('illegal \xf6 character\n')
754
755 note = PackageNoteParsed('chmlib', '0.36-1', 'bug #327431; medium')
756 assert note.bugs == [327431]
757 assert note.package == 'chmlib'
758 assert note.fixed_version == debian_support.Version('0.36-1')
759 assert note.urgency == internUrgency('medium')
760
761 for p in CVEFile('../../data/CAN/list'):
762 pass
763
764 if __name__ == "__main__":
765 test()

  ViewVC Help
Powered by ViewVC 1.1.5