Hi, attached is an updated patch. I sanitized the body pseudo-header parsing, especially when combined with an OpenPGP signature. On 03/13/2013 08:29 AM, Raphael Hertzog wrote: >>> In the news.xml file, I replaced the "from" attribute of the news item >>> with more fine grained "from_address" and "from_realname". However, I >>> think existing entries will be kept, so the XSL-templates need to be >>> able to parse both. At least that's how I've implemented it. If a >>> complete rewrite of all news.xml files is feasible, the XSLTs could be >>> simplified quite a bit. >> >> I guess this is needed for the developer.php links, fair enough. > > I don't understand your comment. Markus, it's certainly possible to update > all news.xml once with a new structure. > > $ bin/list_packages.sh | bin/update_news.py Okay, so it doesn't just parse the mails, but stores them for later processing as well. Perfect. > So if you can simplify, please do it. Done. > I noticed in particular that you used <xsl:when> a few times where a simple > <xsl:if> would have been more appropriate. I didn't find any place where <xsl:if> would have been sufficient. However, I guess that's also a bit a matter of taste. I prefer <xsl:choose> whenever I need <xsl:otherwise> (i.e. an else clause). AFAIK there's no such thing for an <xsl:if>. With that, you'd have to repeat the test with a negation. While a bit less clutter with xml tags, I think that's more prone to error. > I also saw a "print" that you > probably left while debugging (print "signature uid: …). Removed. Regards Markus
Index: xsl/news2rss.xsl
===================================================================
--- xsl/news2rss.xsl (revision 2953)
+++ xsl/news2rss.xsl (working copy)
@@ -60,9 +60,25 @@
<item>
<title>
<xsl:value-of select="." />
- <xsl:text> (</xsl:text>
- <xsl:value-of select="@from" />
- <xsl:text>)</xsl:text>
+ <xsl:if test="@from_address or @from_realname">
+ <xsl:text> (</xsl:text>
+ <xsl:choose>
+ <xsl:when test="@from_address and @sign_address and @from_address != @sign_address">
+ <xsl:choose>
+ <xsl:when test="@from_realname"><xsl:value-of select="@from_realname"/></xsl:when>
+ <xsl:otherwise><xsl:value-of select="@from_address"/></xsl:otherwise>
+ </xsl:choose>
+ <xsl:text> - signed by: </xsl:text>
+ <xsl:choose>
+ <xsl:when test="@sign_realname"><xsl:value-of select="@sign_realname"/></xsl:when>
+ <xsl:otherwise><xsl:value-of select="@sign_address"/></xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="@from_realname"><xsl:value-of select="@from_realname"/></xsl:when>
+ <xsl:otherwise><xsl:value-of select="@from_address"/></xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>)</xsl:text>
+ </xsl:if>
</title>
<xsl:variable name="id">
<xsl:value-of select="$ptsurl" />
@@ -81,9 +97,26 @@
<xsl:value-of select="@date" />
<xsl:text>] </xsl:text>
<xsl:value-of select="." />
- <xsl:text> (</xsl:text>
- <xsl:value-of select="@from" />
- <xsl:text>)</a></xsl:text>
+ <xsl:if test="@from_address or @from_realname">
+ <xsl:text> (</xsl:text>
+ <xsl:choose>
+ <xsl:when test="@from_address and @sign_address and @from_address != @sign_address">
+ <xsl:choose>
+ <xsl:when test="@from_realname"><xsl:value-of select="@from_realname"/></xsl:when>
+ <xsl:otherwise><xsl:value-of select="@from_address"/></xsl:otherwise>
+ </xsl:choose>
+ <xsl:text> - signed by: </xsl:text>
+ <xsl:choose>
+ <xsl:when test="@sign_realname"><xsl:value-of select="@sign_realname"/></xsl:when>
+ <xsl:otherwise><xsl:value-of select="@sign_address"/></xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="@from_realname"><xsl:value-of select="@from_realname"/></xsl:when>
+ <xsl:otherwise><xsl:value-of select="@from_address"/></xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>)</xsl:text>
+ </xsl:if>
+ <xsl:text></a></xsl:text>
</description>
</item>
</xsl:template>
Index: xsl/pts.xsl
===================================================================
--- xsl/pts.xsl (revision 2953)
+++ xsl/pts.xsl (working copy)
@@ -75,6 +75,38 @@
<xsl:variable name="security-mirror">http://security.debian.org/</xsl:variable>
<xsl:variable name="backports-mirror">http://http.debian.net/debian-backports</xsl:variable>
+<xsl:template name="name_email_link">
+ <!-- may be called with realname, address or both -->
+ <xsl:param name="realname"/>
+ <xsl:param name="address"/>
+ <xsl:param name="title"/>
+
+ <xsl:choose>
+ <xsl:when test="string-length($address) > 0">
+ <xsl:element name="a">
+ <xsl:attribute name="href">
+ <xsl:text>http://qa.debian.org/developer.php?login=</xsl:text>
+ <xsl:call-template name="escape-name">
+ <xsl:with-param name="text"><xsl:value-of select="$address"/></xsl:with-param>
+ </xsl:call-template>
+ </xsl:attribute>
+ <span class="name">
+ <xsl:if test="string-length($title) > 0">
+ <xsl:attribute name="title"><xsl:value-of select="$title"/></xsl:attribute>
+ </xsl:if>
+ <xsl:choose>
+ <xsl:when test="string-length($realname) > 0"><xsl:value-of select="$realname"/></xsl:when>
+ <xsl:otherwise><xsl:value-of select="$address"/></xsl:otherwise>
+ </xsl:choose>
+ </span>
+ </xsl:element>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$realname"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
<xsl:template name="outputitem">
<xsl:choose>
<xsl:when test="@url">
@@ -83,10 +115,34 @@
<xsl:value-of select="@date"/>
<xsl:text>] </xsl:text>
</xsl:if><a href="{@url}">
- <xsl:value-of select="text()"/></a><xsl:if test="@from">
+ <xsl:value-of select="text()"/></a>
+
+ <xsl:if test="@from_address">
<xsl:text> (</xsl:text>
- <xsl:value-of select="@from"/>
- <xsl:text>)</xsl:text></xsl:if></li>
+ <xsl:choose>
+ <xsl:when test="@from_address and @sign_address and @from_address != @sign_address">
+ <xsl:text></xsl:text><xsl:call-template name="name_email_link">
+ <xsl:with-param name="title">email sender</xsl:with-param>
+ <xsl:with-param name="realname"><xsl:value-of select="@from_realname"/></xsl:with-param>
+ <xsl:with-param name="address"><xsl:value-of select="@from_address"/></xsl:with-param>
+ </xsl:call-template>
+ <xsl:text> - </xsl:text>
+ <xsl:text>signed by: </xsl:text><xsl:call-template name="name_email_link">
+ <xsl:with-param name="title">signer</xsl:with-param>
+ <xsl:with-param name="realname"><xsl:value-of select="@sign_realname"/></xsl:with-param>
+ <xsl:with-param name="address"><xsl:value-of select="@sign_address"/></xsl:with-param>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="name_email_link">
+ <xsl:with-param name="realname"><xsl:value-of select="@from_realname"/></xsl:with-param>
+ <xsl:with-param name="address"><xsl:value-of select="@from_address"/></xsl:with-param>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ <xsl:text>)</xsl:text>
+ </xsl:if>
+ </li>
</xsl:when>
<xsl:when test="@type='raw'">
<li>
@@ -208,13 +264,6 @@
</xsl:if>
</xsl:template>
-<xsl:template name="maintainer-email">
- <xsl:param name="email" />
- <a class="email" href="mailto:{$email}">
- <img alt="[email]" src="../common/email.png" title="email" />
- </a>
-</xsl:template>
-
<xsl:template name="general-information">
<div class="block info">
<a name="general" />
@@ -245,17 +294,11 @@
<dt title="Maintainer and Uploaders">maint</dt>
<dd class="maintainer">
- <xsl:element name="a">
- <xsl:attribute name="href">
- <xsl:text>http://qa.debian.org/developer.php?login=</xsl:text>
- <xsl:call-template name="escape-name">
- <xsl:with-param name="text"><xsl:value-of select="maintainer/email"/></xsl:with-param>
- </xsl:call-template>
- </xsl:attribute>
- <span class="name" title="maintainer">
- <xsl:value-of select="maintainer/name"/>
- </span>
- </xsl:element>
+ <xsl:call-template name="name_email_link">
+ <xsl:with-param name="title">maintainer</xsl:with-param>
+ <xsl:with-param name="realname"><xsl:value-of select="maintainer/name"/></xsl:with-param>
+ <xsl:with-param name="address"><xsl:value-of select="maintainer/email"/></xsl:with-param>
+ </xsl:call-template>
<xsl:if test="$hasother and $other/dms/item/@email=maintainer/email">
(<small>dm</small>)
</xsl:if>
@@ -264,19 +307,11 @@
<xsl:text>, </xsl:text>
<span class="uploader">
<small>
- <xsl:element name="a">
- <xsl:attribute name="href">
- <xsl:text>http://qa.debian.org/developer.php?login=</xsl:text>
- <xsl:call-template name="escape-name">
- <xsl:with-param name="text">
- <xsl:value-of select="email"/>
- </xsl:with-param>
- </xsl:call-template>
- </xsl:attribute>
- <span class="name" title="uploader">
- <xsl:value-of select="name"/>
- </span>
- </xsl:element>
+ <xsl:call-template name="name_email_link">
+ <xsl:with-param name="title">uploader</xsl:with-param>
+ <xsl:with-param name="realname"><xsl:value-of select="name"/></xsl:with-param>
+ <xsl:with-param name="address"><xsl:value-of select="email"/></xsl:with-param>
+ </xsl:call-template>
<xsl:text> (u</xsl:text>
<xsl:if test="$hasother and $other/dms/item/@email=email">
<xsl:text>, dm</xsl:text>
Index: bin/update_news.py
===================================================================
--- bin/update_news.py (revision 2953)
+++ bin/update_news.py (working copy)
@@ -79,8 +79,12 @@
sub_elt.setAttribute("date", info["date"])
sub_elt.setAttribute("rfc822date",
timestamp_to_rfc822date(info["timestamp"]))
- if info.has_key("from_name"):
- sub_elt.setAttribute("from", info["from_name"])
+ # Copy these attributes as-is
+ for attname in ("from_realname", "from_address",
+ "sign_realname", "sign_address",
+ "sign_valid", "sign_fingerprint", "sign_missing_pubkey"):
+ if info.has_key(attname):
+ sub_elt.setAttribute(attname, info[attname])
elt.appendChild(sub_elt)
Index: bin/common.py
===================================================================
--- bin/common.py (revision 2953)
+++ bin/common.py (working copy)
@@ -7,8 +7,12 @@
# This file is distributed under the terms of the General Public License
# version 2 or (at your option) any later version.
-import hashlib, os, os.path, re, rfc822, time, email, sys
+import hashlib, os, os.path, re, rfc822, time, email, sys, gpgme
from email import Utils, Header
+try:
+ from io import BytesIO
+except ImportError:
+ from StringIO import StringIO as BytesIO
from config import root
@@ -45,16 +49,30 @@
break
else:
body = msg.get_payload(None, 1)
- lines = body.split("\n", 3)
- for i in range(3):
- res = re.match(r"^(\w+): (\S+)(.*)", lines[i])
- if res:
- field = res.group(1).lower()
- info[field] = unicode(res.group(2), "UTF-8", "replace")
- if field == "subject":
- info[field] = info[field] + unicode(res.group(3), "UTF-8", "replace")
+
+ # Analize first few lines for OpenPGP signature or additional pseudo-
+ # header fields.
+ lines = body.split("\n", 10)
+ i = 0
+ isSigned = False
+ while i < len(lines):
+ if lines[i].startswith("-----BEGIN PGP"):
+ isSigned = True
+ i += 1
+ # Skip all OpenPGP Armor Headers, which are also of the form
+ # Key Colon Space Value Newline.
+ while lines[i].strip() != "" and i < len(lines):
+ i += 1
else:
- break
+ res = re.match(r"^(\w+): (\S+)(.*)", lines[i])
+ if res:
+ field = res.group(1).lower()
+ info[field] = unicode(res.group(2), "UTF-8", "replace")
+ if field == "subject":
+ info[field] = info[field] + unicode(res.group(3), "UTF-8", "replace")
+ else:
+ break
+ i += 1
if not info.has_key("subject"):
subject = decode_header(msg.get("subject"))
@@ -93,12 +111,70 @@
frm = msg.get("From")
if msg.has_key("X-PTS-From"): frm = msg.get("X-PTS-From")
(realname, address) = rfc822.parseaddr(frm)
+
if realname:
- frm = realname
- else:
- frm = address
- frm = decode_header(frm)
- info["from_name"] = frm
+ info["from_realname"] = decode_header(realname)
+ info["from_address"] = decode_header(address)
+
+ if isSigned:
+ ctx = gpgme.Context()
+ plain = BytesIO()
+ result = ctx.verify(BytesIO(body), None, plain)
+ for sig in result:
+ # All flags returned by GPGME
+ valid = bool(sig.summary & gpgme.SIGSUM_VALID)
+ green = bool(sig.summary & gpgme.SIGSUM_GREEN)
+ key_revoked = bool(sig.summary & gpgme.SIGSUM_KEY_REVOKED)
+ key_expired = bool(sig.summary & gpgme.SIGSUM_KEY_EXPIRED)
+ key_expired = bool(sig.summary & gpgme.SIGSUM_SIG_EXPIRED)
+ key_missing = bool(sig.summary & gpgme.SIGSUM_KEY_MISSING)
+ crl_missing = bool(sig.summary & gpgme.SIGSUM_CRL_MISSING)
+ crl_too_old = bool(sig.summary & gpgme.SIGSUM_CRL_TOO_OLD)
+ bad_policy = bool(sig.summary & gpgme.SIGSUM_BAD_POLICY)
+ sys_error = bool(sig.summary & gpgme.SIGSUM_SYS_ERROR)
+
+ if key_missing:
+ # Missing public key of the signer. We cannot check
+ # validity.
+ info['sign_fingerprint'] = sig.fpr
+ info['sign_missing_pubkey'] = "true"
+ elif sys_error:
+ continue
+ else:
+ # We found a signature and successfully parsed it and the
+ # accompaining public key. We pass on a simple boolean for
+ # validity of the signature, but the main piece of
+ # information the PTS needs to show is *who* signed.
+ try:
+ info['sign_fingerprint'] = sig.fpr
+ key = ctx.get_key(sig.fpr)
+
+ best = None
+ for uid in key.uids:
+ if not best:
+ best = (uid.name, uid.email)
+ # We continue the loop, because we prefer a
+ # @debian.org address.
+
+ if uid.email.endswith("@debian.org"):
+ best = (uid.name, uid.email)
+ break
+
+ if best:
+ info['sign_realname'] = best[0]
+ info['sign_address'] = best[1]
+ if valid and green:
+ info['sign_valid'] = "true"
+ else:
+ info['sign_valid'] = "false"
+
+ # Simply use the first valid signature found
+ break
+ except:
+ # Catch all exceptions. At the worst, we do not display
+ # signature info, so that's not too critical.
+ continue
+
return info
def hash_name(pkg):
Attachment:
signature.asc
Description: OpenPGP digital signature