Others
------
+ o need to decide on whether we're tying for most errors at once.. if
+ so (probably) then make sure code doesn't assume variables exist and
+ either way do something about checking error code of check_dsc and
+ later functions so we skip later checks if they're bailing.
+
+ o the .katie stuff is fundamentally braindamaged, it's not versioned
+ so there's no way to change the format, yay me. need to fix.
+ probably by putting a version var as the first thing and checking
+ that.. auto-upgrade at least from original format would be good.
+ might also be a good idea to put everything in one big dict after
+ that?
+
o reject sparc64 binaries in a non '*64*' package.
o katie.py(source_exists): a) we take arguments as parameters that
# Dump variables from a .katie file to stdout
# Copyright (C) 2001, 2002, 2004 James Troup <james@nocrew.org>
-# $Id: ashley,v 1.9 2004-04-01 17:14:25 troup Exp $
+# $Id: ashley,v 1.10 2004-04-03 02:49:46 troup Exp $
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
changes = k.pkg.changes;
print " Changes:";
# Mandatory changes fields
- for i in [ "source", "version", "maintainer", "urgency", "changedby822", "changedbyname", "maintainername", "maintaineremail", "fingerprint" ]:
+ for i in [ "source", "version", "maintainer", "urgency", "changedby822",
+ "changedby2047", "changedbyname", "maintainer822",
+ "maintainer2047", "maintainername", "maintaineremail",
+ "fingerprint", "changes" ]:
print " %s: %s" % (i.capitalize(), changes[i]);
del changes[i];
# Mandatory changes lists
print " %s: %s" % (i.capitalize(), " ".join(changes[i].keys()));
del changes[i];
# Optional changes fields
- for i in [ "changed-by", "maintainer822", "filecontents", "format" ]:
+ for i in [ "changed-by", "filecontents", "format" ]:
if changes.has_key(i):
print " %s: %s" % (i.capitalize(), changes[i]);
del changes[i];
for i in [ "package", "version", "architecture", "type", "size",
"md5sum", "component", "location id", "source package",
"source version", "maintainer", "dbtype", "files id",
- "new", "section", "priority" ]:
+ "new", "section", "priority", "pool name" ]:
if files[file].has_key(i):
print " %s: %s" % (i.capitalize(), files[file][i]);
del files[file][i];
Package: katie
Architecture: any
-Depends: ${python:Depends}, python-pygresql, python-apt, apt-utils, gnupg (>= 1.0.6-1), ${shlibs:Depends}
+Depends: ${python:Depends}, python-pygresql, python2.1-email | python (>= 2.2), python-apt, apt-utils, gnupg (>= 1.0.6-1), ${shlibs:Depends}
Suggests: lintian, linda, less, binutils-multiarch, symlinks, postgresql (>= 7.1.0), dsync
Description: Debian's archive maintenance scripts
This is a collection of archive maintenance scripts used by the
# Checks Debian packages from Incoming
# Copyright (C) 2000, 2001, 2002, 2003, 2004 James Troup <james@nocrew.org>
-# $Id: jennifer,v 1.46 2004-04-01 17:14:25 troup Exp $
+# $Id: jennifer,v 1.47 2004-04-03 02:49:46 troup Exp $
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
################################################################################
# Globals
-jennifer_version = "$Revision: 1.46 $";
+jennifer_version = "$Revision: 1.47 $";
Cnf = None;
Options = None;
def check_changes():
filename = pkg.changes_file;
- # Default in case we bail out
- changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"];
- changes["changedby822"] = Cnf["Dinstall::MyEmailAddress"];
+ # Defaults in case we bail out
+ changes["maintainer2047"] = Cnf["Dinstall::MyEmailAddress"];
+ changes["changedby2047"] = Cnf["Dinstall::MyEmailAddress"];
changes["architecture"] = {};
# Parse the .changes field into a dictionary
return 0;
# Check for mandatory fields
- for i in ("source", "binary", "architecture", "version", "distribution", "maintainer", "files"):
+ for i in ("source", "binary", "architecture", "version", "distribution",
+ "maintainer", "files", "changes"):
if not changes.has_key(i):
reject("%s: Missing mandatory field `%s'." % (filename, i));
return 0 # Avoid <undef> errors during later tests
for j in o.split():
changes[i][j] = 1
- # Fix the Maintainer: field to be RFC822 compatible
- (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
-
- # Fix the Changed-By: field to be RFC822 compatible; if it exists.
- (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]) = utils.fix_maintainer(changes.get("changed-by",""));
+ # Fix the Maintainer: field to be RFC822/2047 compatible
+ try:
+ (changes["maintainer822"], changes["maintainer2047"],
+ changes["maintainername"], changes["maintaineremail"]) = \
+ utils.fix_maintainer (changes["maintainer"]);
+ except utils.ParseMaintError, msg:
+ reject("%s: Maintainer field ('%s') failed to parse: %s" \
+ % (filename, changes["maintainer"], msg));
+
+ # ...likewise for the Changed-By: field if it exists.
+ try:
+ (changes["changedby822"], changes["changedby2047"],
+ changes["changedbyname"], changes["changedbyemail"]) = \
+ utils.fix_maintainer (changes.get("changed-by", ""));
+ except utils.ParseMaintError, msg:
+ reject("%s: Changed-By field ('%s') failed to parse: %s" \
+ % (filename, changes["changed-by"], msg));
# Ensure all the values in Closes: are numbers
if changes.has_key("closes"):
for i in ("format", "source", "version", "binary", "maintainer", "architecture", "files"):
if not dsc.has_key(i):
reject("%s: missing mandatory field `%s'." % (dsc_filename, i));
+ return;
# Validate the source and version fields
- if dsc.has_key("source") and not re_valid_pkg_name.match(dsc["source"]):
+ if not re_valid_pkg_name.match(dsc["source"]):
reject("%s: invalid source name '%s'." % (dsc_filename, dsc["source"]));
- if dsc.has_key("version") and not re_valid_version.match(dsc["version"]):
+ if not re_valid_version.match(dsc["version"]):
reject("%s: invalid version number '%s'." % (dsc_filename, dsc["version"]));
# Bumping the version number of the .dsc breaks extraction by stable's
if dsc["format"] != "1.0":
reject("%s: incompatible 'Format' version produced by a broken version of dpkg-dev 1.9.1{3,4}." % (dsc_filename));
+ # Validate the Maintainer field
+ try:
+ utils.fix_maintainer (dsc["maintainer"]);
+ except utils.ParseMaintError, msg:
+ reject("%s: Maintainer field ('%s') failed to parse: %s" \
+ % (dsc_filename, changes["changed-by"], msg));
+
# Validate the build-depends field(s)
for field_name in [ "build-depends", "build-depends-indep" ]:
field = dsc.get(field_name);
pass;
# Ensure the version number in the .dsc matches the version number in the .changes
- epochless_dsc_version = utils.re_no_epoch.sub('', dsc.get("version"));
+ epochless_dsc_version = utils.re_no_epoch.sub('', dsc["version"]);
changes_version = files[dsc_filename]["version"];
if epochless_dsc_version != files[dsc_filename]["version"]:
reject("version ('%s') in .dsc does not match version ('%s') in .changes." % (epochless_dsc_version, changes_version));
type = m.group(3);
if type == "orig.tar.gz" and pkg.orig_tar_gz:
continue;
- else:
- dest = os.path.join(os.getcwd(), f);
+ dest = os.path.join(os.getcwd(), f);
os.symlink(src, dest);
# If the orig.tar.gz is not a part of the upload, create a symlink to the
# Utility functions for katie
# Copyright (C) 2001, 2002, 2003, 2004 James Troup <james@nocrew.org>
-# $Id: katie.py,v 1.45 2004-04-01 17:14:25 troup Exp $
+# $Id: katie.py,v 1.46 2004-04-03 02:49:46 troup Exp $
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
changes = pkg.changes;
dsc = pkg.dsc;
- (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]).lower());
+ i = utils.fix_maintainer (dsc.get("maintainer",
+ Cnf["Dinstall::MyEmailAddress"]).lower());
+ (dsc_rfc822, dsc_rfc2047, dsc_name, dsc_email) = i;
# changes["changedbyname"] == dsc_name is probably never true, but better safe than sorry
if dsc_name == changes["maintainername"].lower() and \
(changes["changedby822"] == "" or changes["changedbyname"].lower() == dsc_name):
uploaders = dsc["uploaders"].lower().split(",");
uploadernames = {};
for i in uploaders:
- (rfc822, name, email) = utils.fix_maintainer (i.strip());
+ (rfc822, rfc2047, name, email) = utils.fix_maintainer (i.strip());
uploadernames[name] = "";
if uploadernames.has_key(changes["changedbyname"].lower()):
return 0;
d_files[file][i] = files[file][i];
## changes
# Mandatory changes fields
- for i in [ "distribution", "source", "architecture", "version", "maintainer",
- "urgency", "fingerprint", "changedby822", "changedbyname",
- "maintainername", "maintaineremail", "closes" ]:
+ for i in [ "distribution", "source", "architecture", "version",
+ "maintainer", "urgency", "fingerprint", "changedby822",
+ "changedby2047", "changedbyname", "maintainer822",
+ "maintainer2047", "maintainername", "maintaineremail",
+ "closes", "changes" ]:
d_changes[i] = changes[i];
# Optional changes fields
- # FIXME: changes should be mandatory
- for i in [ "changed-by", "maintainer822", "filecontents", "format",
- "changes", "lisa note" ]:
+ for i in [ "changed-by", "filecontents", "format", "lisa note" ]:
if changes.has_key(i):
d_changes[i] = changes[i];
## dsc
- for i in [ "source", "version", "maintainer", "fingerprint", "uploaders",
- "bts changelog" ]:
+ for i in [ "source", "version", "maintainer", "fingerprint",
+ "uploaders", "bts changelog" ]:
if dsc.has_key(i):
d_dsc[i] = dsc[i];
## dsc_files
# If jennifer crashed out in the right place, architecture may still be a string.
if not changes.has_key("architecture") or not isinstance(changes["architecture"], DictType):
changes["architecture"] = { "Unknown" : "" };
- # and maintainer822 may not exist.
- if not changes.has_key("maintainer822"):
- changes["maintainer822"] = self.Cnf["Dinstall::MyEmailAddress"];
+ # and maintainer2047 may not exist.
+ if not changes.has_key("maintainer2047"):
+ changes["maintainer2047"] = self.Cnf["Dinstall::MyEmailAddress"];
Subst["__ARCHITECTURE__"] = " ".join(changes["architecture"].keys());
Subst["__CHANGES_FILENAME__"] = os.path.basename(self.pkg.changes_file);
# For source uploads the Changed-By field wins; otherwise Maintainer wins.
if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]):
- Subst["__MAINTAINER_FROM__"] = changes["changedby822"];
- Subst["__MAINTAINER_TO__"] = changes["changedby822"] + ", " + changes["maintainer822"];
+ Subst["__MAINTAINER_FROM__"] = changes["changedby2047"];
+ Subst["__MAINTAINER_TO__"] = "%s, %s" % (changes["changedby2047"],
+ changes["maintainer2047"]);
Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown");
else:
- Subst["__MAINTAINER_FROM__"] = changes["maintainer822"];
- Subst["__MAINTAINER_TO__"] = changes["maintainer822"];
+ Subst["__MAINTAINER_FROM__"] = changes["maintainer2047"];
+ Subst["__MAINTAINER_TO__"] = changes["maintainer2047"];
Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown");
if self.Cnf.has_key("Dinstall::TrackingServer") and changes.has_key("source"):
Subst["__MAINTAINER_TO__"] += "\nBcc: %s@%s" % (changes["source"], self.Cnf["Dinstall::TrackingServer"])
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Test utils.fix_maintainer()
+# Copyright (C) 2004 James Troup <james@nocrew.org>
+# $Id: test.py,v 1.1 2004-04-03 02:49:54 troup Exp $
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+################################################################################
+
+import os, sys
+
+sys.path.append(os.path.abspath('../../'));
+
+import utils
+
+################################################################################
+
+def fail(message):
+ sys.stderr.write("%s\n" % (message));
+ sys.exit(1);
+
+################################################################################
+
+def check_valid(s, xa, xb, xc, xd):
+ (a, b, c, d) = utils.fix_maintainer(s)
+ if a != xa:
+ fail("rfc822_maint: %s (returned) != %s (expected [From: '%s']" % (a, xa, s));
+ if b != xb:
+ fail("rfc2047_maint: %s (returned) != %s (expected [From: '%s']" % (b, xb, s));
+ if c != xc:
+ fail("name: %s (returned) != %s (expected [From: '%s']" % (c, xc, s));
+ if d != xd:
+ fail("email: %s (returned) != %s (expected [From: '%s']" % (d, xd, s));
+
+def check_invalid(s):
+ try:
+ utils.fix_maintainer(s);
+ fail("%s was parsed successfully but is expected to be invalid." % (s));
+ except utils.ParseMaintError, unused:
+ pass;
+
+def main ():
+ # Check Valid UTF-8 maintainer field
+ s = "Noèl Köthe <noel@debian.org>"
+ xa = "Noèl Köthe <noel@debian.org>"
+ xb = "=?utf-8?b?Tm/DqGwgS8O2dGhl?= <noel@debian.org>"
+ xc = "Noèl Köthe"
+ xd = "noel@debian.org"
+ check_valid(s, xa, xb, xc, xd);
+
+ # Check valid ISO-8859-1 maintainer field
+ s = "Noèl Köthe <noel@debian.org>"
+ xa = "Noèl Köthe <noel@debian.org>"
+ xb = "=?iso-8859-1?q?No=E8l_K=F6the?= <noel@debian.org>"
+ xc = "Noèl Köthe"
+ xd = "noel@debian.org"
+ check_valid(s, xa, xb, xc, xd);
+
+ # Check valid ASCII maintainer field
+ s = "James Troup <james@nocrew.org>"
+ xa = "James Troup <james@nocrew.org>"
+ xb = "James Troup <james@nocrew.org>"
+ xc = "James Troup"
+ xd = "james@nocrew.org"
+ check_valid(s, xa, xb, xc, xd);
+
+ # Check "Debian vs RFC822" fixup of names with '.' or ',' in them
+ s = "James J. Troup <james@nocrew.org>"
+ xa = "james@nocrew.org (James J. Troup)"
+ xb = "james@nocrew.org (James J. Troup)"
+ xc = "James J. Troup"
+ xd = "james@nocrew.org"
+ check_valid(s, xa, xb, xc, xd);
+ s = "James J, Troup <james@nocrew.org>"
+ xa = "james@nocrew.org (James J, Troup)"
+ xb = "james@nocrew.org (James J, Troup)"
+ xc = "James J, Troup"
+ xd = "james@nocrew.org"
+ check_valid(s, xa, xb, xc, xd);
+
+ # Check just-email form
+ s = "james@nocrew.org"
+ xa = " <james@nocrew.org>"
+ xb = " <james@nocrew.org>"
+ xc = ""
+ xd = "james@nocrew.org"
+ check_valid(s, xa, xb, xc, xd);
+
+ # Check Krazy quoted-string local part email address
+ s = "Cris van Pelt <\"Cris van Pelt\"@tribe.eu.org>"
+ xa = "Cris van Pelt <\"Cris van Pelt\"@tribe.eu.org>"
+ xb = "Cris van Pelt <\"Cris van Pelt\"@tribe.eu.org>"
+ xc = "Cris van Pelt"
+ xd = "\"Cris van Pelt\"@tribe.eu.org"
+ check_valid(s, xa, xb, xc, xd);
+
+ # Check empty string
+ s = xa = xb = xc = xd = "";
+ check_valid(s, xa, xb, xc, xd);
+
+ # Check for missing email address
+ check_invalid("James Troup");
+ # Check for invalid email address
+ check_invalid("James Troup <james@nocrew.org");
+
+################################################################################
+
+if __name__ == '__main__':
+ main()
# Utility functions
# Copyright (C) 2000, 2001, 2002, 2003, 2004 James Troup <james@nocrew.org>
-# $Id: utils.py,v 1.65 2004-04-01 17:13:10 troup Exp $
+# $Id: utils.py,v 1.66 2004-04-03 02:49:46 troup Exp $
################################################################################
################################################################################
-import commands, os, pwd, re, select, socket, shutil, string, sys, tempfile, traceback;
+import commands, encodings.ascii, encodings.utf_8, encodings.latin_1, \
+ email.Header, os, pwd, re, select, socket, shutil, string, sys, \
+ tempfile, traceback;
import apt_pkg;
import db_access;
re_multi_line_field = re.compile(r"^\s(.*)");
re_taint_free = re.compile(r"^[-+~\.\w]+$");
-re_parse_maintainer = re.compile(r"^\s*(\S.*\S)\s*\<([^\> \t]+)\>");
+re_parse_maintainer = re.compile(r"^\s*(\S.*\S)\s*\<([^\>]+)\>");
changes_parse_error_exc = "Can't parse line in .changes file";
invalid_dsc_format_exc = "Invalid .dsc file";
################################################################################
+class Error(Exception):
+ """Base class for exceptions in this module."""
+ pass;
+
+class ParseMaintError(Error):
+ """Exception raised for errors in parsing a maintainer field.
+
+ Attributes:
+ message -- explanation of the error
+ """
+
+ def __init__(self, message):
+ self.args = message,;
+ self.message = message;
+
+################################################################################
+
def open_file(filename, mode='r'):
try:
f = open(filename, mode);
################################################################################
-# Fix the `Maintainer:' field to be an RFC822 compatible address.
-# cf. Debian Policy Manual (D.2.4)
-#
-# 06:28|<Culus> 'The standard sucks, but my tool is supposed to
-# interoperate with it. I know - I'll fix the suckage
-# and make things incompatible!'
+def force_to_utf8(s):
+ """Forces a string to UTF-8. If the string isn't already UTF-8,
+it's assumed to be ISO-8859-1."""
+ try:
+ unicode(s, 'utf-8');
+ return s;
+ except UnicodeError:
+ latin1_s = unicode(s,'iso8859-1');
+ return latin1_s.encode('utf-8');
+
+def rfc2047_encode(s):
+ """Encodes a (header) string per RFC2047 if necessary. If the
+string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
+ try:
+ encodings.ascii.Codec().decode(s);
+ return s;
+ except UnicodeError:
+ pass;
+ try:
+ encodings.utf_8.Codec().decode(s);
+ h = email.Header.Header(s, 'utf-8', 998);
+ return str(h);
+ except UnicodeError:
+ h = email.Header.Header(s, 'iso-8859-1', 998);
+ return str(h);
+
+################################################################################
+
+# <Culus> 'The standard sucks, but my tool is supposed to interoperate
+# with it. I know - I'll fix the suckage and make things
+# incompatible!'
def fix_maintainer (maintainer):
- m = re_parse_maintainer.match(maintainer);
- rfc822 = maintainer;
- name = "";
- email = "";
- if m and len(m.groups()) == 2:
+ """Parses a Maintainer or Changed-By field and returns:
+ (1) an RFC822 compatible version,
+ (2) an RFC2047 compatible version,
+ (3) the name
+ (4) the email
+
+The name is forced to UTF-8 for both (1) and (3). If the name field
+contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
+switched to 'email (name)' format."""
+ maintainer = maintainer.strip()
+ if not maintainer:
+ return ('', '', '', '');
+
+ if maintainer.find("<") == -1 or (maintainer[0] == "<" and \
+ maintainer[-1:] == ">"):
+ email = maintainer;
+ name = "";
+ else:
+ m = re_parse_maintainer.match(maintainer);
+ if not m:
+ raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
name = m.group(1);
email = m.group(2);
- if name.find(',') != -1 or name.find('.') != -1:
- rfc822 = "%s (%s)" % (email, name);
- return (rfc822, name, email)
+
+ # Get an RFC2047 compliant version of the name
+ rfc2047_name = rfc2047_encode(name);
+
+ # Force the name to be UTF-8
+ name = force_to_utf8(name);
+
+ if name.find(',') != -1 or name.find('.') != -1:
+ rfc822_maint = "%s (%s)" % (email, name);
+ rfc2047_maint = "%s (%s)" % (email, rfc2047_name);
+ else:
+ rfc822_maint = "%s <%s>" % (name, email);
+ rfc2047_maint = "%s <%s>" % (rfc2047_name, email);
+
+ if email.find("@") == -1 and email.find("buildd_") != 0:
+ raise ParseMaintError, "No @ found in email address part."
+
+ return (rfc822_maint, rfc2047_maint, name, email);
################################################################################