From 0707a51eaddec22ab760e27050e2fcefab2cdae5 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Mon, 11 Nov 2019 01:04:46 -0500 Subject: Bug 1423487 - Support multiple authors in RSS feeds. Tag #1273 --- mailnews/base/src/nsMsgDBView.cpp | 14 +++- mailnews/extensions/newsblog/content/FeedItem.js | 85 ++++++++++++++-------- mailnews/extensions/newsblog/content/FeedUtils.jsm | 1 + .../extensions/newsblog/content/feed-parser.js | 84 ++++++++++++++++----- .../newsblog/content/feed-subscriptions.xul | 5 +- 5 files changed, 133 insertions(+), 56 deletions(-) (limited to 'mailnews') diff --git a/mailnews/base/src/nsMsgDBView.cpp b/mailnews/base/src/nsMsgDBView.cpp index 76a843df7..a1867244b 100644 --- a/mailnews/base/src/nsMsgDBView.cpp +++ b/mailnews/base/src/nsMsgDBView.cpp @@ -400,10 +400,12 @@ nsresult nsMsgDBView::FetchAuthor(nsIMsgDBHdr * aHdr, nsAString &aSenderString) nsCString headerCharset; aHdr->GetEffectiveCharset(headerCharset); - nsCString emailAddress; nsString name; - ExtractFirstAddress(EncodedHeader(author, headerCharset.get()), name, - emailAddress); + nsCString emailAddress; + nsCOMArray addresses = EncodedHeader(author, headerCharset.get()); + bool multipleAuthors = addresses.Length() > 1; + + ExtractFirstAddress(addresses, name, emailAddress); if (showCondensedAddresses) GetDisplayNameInAddressBook(emailAddress, aSenderString); @@ -429,6 +431,12 @@ nsresult nsMsgDBView::FetchAuthor(nsIMsgDBHdr * aHdr, nsAString &aSenderString) } } + if (multipleAuthors) + { + aSenderString.AppendLiteral(" "); + aSenderString.Append(GetString(u"andOthers")); + } + UpdateCachedName(aHdr, "sender_name", aSenderString); return NS_OK; diff --git a/mailnews/extensions/newsblog/content/FeedItem.js b/mailnews/extensions/newsblog/content/FeedItem.js index 09e4eb861..5863c420a 100644 --- a/mailnews/extensions/newsblog/content/FeedItem.js +++ b/mailnews/extensions/newsblog/content/FeedItem.js @@ -22,7 +22,10 @@ FeedItem.prototype = content: null, enclosures: [], title: null, - author: "anonymous", + // Author must be angle bracket enclosed to function as an addr-spec, in the + // absence of an addr-spec portion of an RFC5322 email address, as other + // functionality (gloda search) depends on this. + author: "", inReplyTo: "", keywords: [], mURL: null, @@ -245,12 +248,10 @@ FeedItem.prototype = { FeedUtils.log.trace("FeedItem.writeToFolder: " + this.identity + " writing to message folder " + this.feed.name); - // Convert the title to UTF-16 before performing our HTML entity - // replacement reg expressions. - let title = this.title; // The subject may contain HTML entities. Convert these to their unencoded // state. i.e. & becomes '&'. + let title = this.title; title = this.mParserUtils.convertToPlainText( title, Ci.nsIDocumentEncoder.OutputSelectionOnly | @@ -274,32 +275,11 @@ FeedItem.prototype = ("References: " + this.inReplyTo + "\n" + "In-Reply-To: " + this.inReplyTo + "\n") : ""; - // If there are keywords (categories), create the headers. In the case of - // a longer than RFC5322 recommended line length, create multiple folded - // lines (easier to parse than multiple Keywords headers). - let keywordsStr = ""; - if (this.keywords.length) - { - let HEADER = "Keywords: "; - let MAXLEN = 78; - keywordsStr = HEADER; - let keyword; - let keywords = [].concat(this.keywords); - let lines = []; - while (keywords.length) - { - keyword = keywords.shift(); - if (keywordsStr.length + keyword.length > MAXLEN) - { - lines.push(keywordsStr) - keywordsStr = " ".repeat(HEADER.length); - } - keywordsStr += keyword + ","; - } - keywordsStr = keywordsStr.replace(/,$/,"\n"); - lines.push(keywordsStr) - keywordsStr = lines.join("\n"); - } + // Support multiple authors in From. + let fromStr = this.createHeaderStrFromArray("From: ", this.author); + + // If there are keywords (categories), create the headers. + let keywordsStr = this.createHeaderStrFromArray("Keywords: ", this.keywords); // Escape occurrences of "From " at the beginning of lines of // content per the mbox standard, since "From " denotes a new @@ -323,7 +303,7 @@ FeedItem.prototype = 'Received: by localhost; ' + FeedUtils.getValidRFC5322Date() + '\n' + 'Date: ' + this.mDate + '\n' + 'Message-Id: ' + this.normalizeMessageID(this.id) + '\n' + - 'From: ' + this.author + '\n' + + fromStr + 'MIME-Version: 1.0\n' + 'Subject: ' + this.title + '\n' + inreplytoHdrsStr + @@ -368,6 +348,49 @@ FeedItem.prototype = this.tagItem(msgDBHdr, this.keywords); }, +/** + * Create a header string from an array. Intended for comma separated headers + * like From or Keywords. In the case of a longer than RFC5322 recommended + * line length, create multiple folded lines (easier to parse than multiple + * headers). + * + * @param {String} headerName - Name of the header. + * @param {String[]} headerItemsArray - An Array of strings to concatenate. + * + * @returns {String} - The header string. + */ + createHeaderStrFromArray(headerName, headerItemsArray) { + let headerStr = ""; + if (!headerItemsArray || headerItemsArray.length == 0) { + return headerStr; + } + + const HEADER = headerName; + const LINELENGTH = 78; + const MAXLINELENGTH = 990; + let items = [].concat(headerItemsArray); + let lines = []; + headerStr = HEADER; + while (items.length) { + let item = items.shift(); + if (headerStr.length + item.length > LINELENGTH && + headerStr.length > HEADER.length) { + lines.push(headerStr); + headerStr = " ".repeat(HEADER.length); + } + + headerStr += headerStr.length + item.length > MAXLINELENGTH ? + item.substr(0, MAXLINELENGTH - headerStr.length) + "…, " : + item + ", "; + } + + headerStr = headerStr.replace(/,\s$/, "\n"); + lines.push(headerStr); + headerStr = lines.join("\n"); + + return headerStr; + }, + /** * Autotag messages. * diff --git a/mailnews/extensions/newsblog/content/FeedUtils.jsm b/mailnews/extensions/newsblog/content/FeedUtils.jsm index 6d5e64dd2..f8368ac25 100644 --- a/mailnews/extensions/newsblog/content/FeedUtils.jsm +++ b/mailnews/extensions/newsblog/content/FeedUtils.jsm @@ -42,6 +42,7 @@ var FeedUtils = { }, DC_NS: "http://purl.org/dc/elements/1.1/", + get DC_PUBLISHER() { return this.rdf.GetResource(this.DC_NS + "publisher"); }, get DC_CREATOR() { return this.rdf.GetResource(this.DC_NS + "creator") }, get DC_SUBJECT() { return this.rdf.GetResource(this.DC_NS + "subject") }, get DC_DATE() { return this.rdf.GetResource(this.DC_NS + "date") }, diff --git a/mailnews/extensions/newsblog/content/feed-parser.js b/mailnews/extensions/newsblog/content/feed-parser.js index 660333422..03fc72b22 100644 --- a/mailnews/extensions/newsblog/content/feed-parser.js +++ b/mailnews/extensions/newsblog/content/feed-parser.js @@ -222,9 +222,10 @@ FeedParser.prototype = tags = this.childrenByTagNameNS(itemNode, nsURI, "author"); if (!tags) tags = this.childrenByTagNameNS(itemNode, FeedUtils.DC_NS, "creator"); - item.author = this.getNodeValue(tags ? tags[0] : null) || - aFeed.title || - item.author; + let author = this.getNodeValue(tags ? tags[0] : null) || + aFeed.title; + author = this.cleanAuthorName(author); + item.author = author ? ["<" + author + ">"] : item.author; tags = this.childrenByTagNameNS(itemNode, nsURI, "pubDate"); if (!tags || !this.getNodeValue(tags[0])) @@ -386,10 +387,12 @@ FeedParser.prototype = item.id = item.url; item.url = this.validLink(item.url); - item.author = this.getRDFTargetValue(ds, itemResource, FeedUtils.DC_CREATOR) || - this.getRDFTargetValue(ds, channel, FeedUtils.DC_CREATOR) || - aFeed.title || - item.author; + let author = this.getRDFTargetValue(ds, itemResource, FeedUtils.DC_CREATOR) || + this.getRDFTargetValue(ds, channel, FeedUtils.DC_CREATOR) || + aFeed.title; + author = this.cleanAuthorName(author); + item.author = author ? ["<" + author + ">"] : item.author; + item.date = this.getRDFTargetValue(ds, itemResource, FeedUtils.DC_DATE) || item.date; item.content = this.getRDFTargetValueFormatted(ds, itemResource, @@ -608,7 +611,7 @@ FeedParser.prototype = continue; } - // XXX Support multiple authors. + // Support multiple authors. tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_IETF_NS, "source"); let source = tags ? tags[0] : null; @@ -618,22 +621,42 @@ FeedParser.prototype = if (!tags) tags = this.childrenByTagNameNS(channel, FeedUtils.ATOM_IETF_NS, "author"); - let authorEl = tags ? tags[0] : null; - - let author = ""; - if (authorEl) - { - tags = this.childrenByTagNameNS(authorEl, FeedUtils.ATOM_IETF_NS, "name"); + let authorTags = tags || []; + let authors = []; + for (let authorTag of authorTags) { + let author = ""; + tags = this.childrenByTagNameNS(authorTag, FeedUtils.ATOM_IETF_NS, "name"); let name = this.getNodeValue(tags ? tags[0] : null); - tags = this.childrenByTagNameNS(authorEl, FeedUtils.ATOM_IETF_NS, "email"); + tags = this.childrenByTagNameNS(authorTag, FeedUtils.ATOM_IETF_NS, "email"); let email = this.getNodeValue(tags ? tags[0] : null); - if (name) - author = name + (email ? " <" + email + ">" : ""); - else if (email) + if (name) { + name = this.cleanAuthorName(name); + if (email) { + if (!email.match(/^<.*>$/)) { + email = " <" + email + ">"; + } + author = name + email; + } else { + author = "<" + name + ">"; + } + } else if (email) { author = email; + } + if (author) { + authors.push(author); + } } - item.author = author || item.author || aFeed.title; + if (authors.length == 0) { + tags = this.childrenByTagNameNS(channel, FeedUtils.DC_NS, "publisher"); + let author = this.getNodeValue(tags ? tags[0] : null) || + aFeed.title; + author = this.cleanAuthorName(author); + item.author = author ? ["<" + author + ">"] : item.author; + } else { + item.author = authors; + } + FeedUtils.log.trace("FeedParser.parseAsAtomIETF: author(s) - " + item.author); tags = this.childrenByTagNameNS(itemNode, FeedUtils.ATOM_IETF_NS, "updated"); if (!tags || !this.getNodeValue(tags[0])) @@ -801,6 +824,29 @@ FeedParser.prototype = return content ? content : null; }, + /** + * Return a cleaned up author name value. + * + * @param {String} authorString - A string. + * @returns {String} - A clean string value. + */ + cleanAuthorName(authorString) { + if (!authorString) { + return ""; + } + FeedUtils.log.trace("FeedParser.cleanAuthor: author1 - " + authorString); + let author = authorString.replace(/[\n\r\t]+/g, " ") + .replace(/"/g, '\\"') + .trim(); + // If the name contains special chars, quote it. + if (author.match(/[<>@,"]/)) { + author = '"' + author + '"'; + } + FeedUtils.log.trace("FeedParser.cleanAuthor: author2 - " + author); + + return author; + }, + getRDFTargetValue: function(ds, source, property) { let nodeValue = this.getRDFTargetValueRaw(ds, source, property); diff --git a/mailnews/extensions/newsblog/content/feed-subscriptions.xul b/mailnews/extensions/newsblog/content/feed-subscriptions.xul index d6f4ea18f..d8dff29ee 100644 --- a/mailnews/extensions/newsblog/content/feed-subscriptions.xul +++ b/mailnews/extensions/newsblog/content/feed-subscriptions.xul @@ -17,12 +17,11 @@ ]>