diff --git a/toolkit/components/feeds/public/nsIFeedContainer.idl b/toolkit/components/feeds/public/nsIFeedContainer.idl index f3ef0540e9ec..ce848c7c237c 100644 --- a/toolkit/components/feeds/public/nsIFeedContainer.idl +++ b/toolkit/components/feeds/public/nsIFeedContainer.idl @@ -54,6 +54,11 @@ interface nsIFeedContainer : nsISupports */ attribute AString identifier; + /** + * The baseURI for the Entry or Feed. + */ + attribute nsIURI baseURI; + /** * The fields found in the document. Common Atom * and RSS fields are normalized. This includes some namespaced diff --git a/toolkit/components/feeds/src/FeedProcessor.js b/toolkit/components/feeds/src/FeedProcessor.js index 08da9b76cdb8..ea350bc04a05 100644 --- a/toolkit/components/feeds/src/FeedProcessor.js +++ b/toolkit/components/feeds/src/FeedProcessor.js @@ -54,6 +54,8 @@ var gIoService = Cc[IO_CONTRACTID].getService(Ci.nsIIOService); var gUnescapeHTML = Cc[UNESCAPE_CONTRACTID]. getService(Ci.nsIScriptableUnescapeHTML); +const XMLNS = "http://www.w3.org/XML/1998/namespace"; + /***** Some general utils *****/ function strToURI(link, base) { var base = base || null; @@ -120,6 +122,33 @@ function plainTextFromTextConstruct(textConstruct) { return textConstruct; } +/** + * Searches through an array of links and returns a JS array + * of matching property bags. + */ +const IANA_URI = "http://www.iana.org/assignments/relation/"; +function findAtomLinks(rel, links) { + var rvLinks = []; + for (var i = 0; i < links.length; ++i) { + var linkElement = links.queryElementAt(i, Ci.nsIPropertyBag2); + // atom:link MUST have @href + if (bagHasKey(linkElement, "href")) { + var relAttribute = null; + if (bagHasKey(linkElement, "rel")) + relAttribute = linkElement.getPropertyAsAString("rel") + if ((!relAttribute && rel == "alternate") || relAttribute == rel) { + rvLinks.push(linkElement); + continue; + } + // catch relations specified by IANA URI + if (relAttribute == IANA_URI + rel) { + rvLinks.push(linkElement); + } + } + } + return rvLinks; +} + function xmlEscape(s) { s = s.replace(/&/g, "&"); s = s.replace(/>/g, ">"); @@ -138,6 +167,17 @@ function arrayContains(array, element) { return false; } +// XXX add hasKey to nsIPropertyBag +function bagHasKey(bag, key) { + try { + bag.getProperty(key); + return true; + } + catch (e) { + return false; + } +} + /** * XXX Thunderbird's W3C-DTF function * @@ -235,7 +275,8 @@ var gNamespaces = { "http://www.w3.org/1999/02/22-rdf-syntax-ns#":"rdf", "http://purl.org/rss/1.0/":"rss1", "http://wellformedweb.org/CommentAPI/":"wfw", - "http://purl.org/rss/1.0/modules/wiki/":"wiki" + "http://purl.org/rss/1.0/modules/wiki/":"wiki", + "http://www.w3.org/XML/1998/namespace":"xml" } // lets us know to ignore extraneous attributes like @@ -284,6 +325,7 @@ function Feed() { this._title = null; this.items = []; this.link = null; + this.baseURI = null; } Feed.prototype = { subtitle: function Feed_subtitle(doStripTags) { @@ -319,11 +361,33 @@ Feed.prototype = { normalize: function Feed_normalize() { fieldsToObj(this, this.searchLists); - if (this.skipDays) { + if (this.skipDays) this.skipDays = this.skipDays.getProperty("days"); - } - if (this.skipHours) { + if (this.skipHours) this.skipHours = this.skipHours.getProperty("hours"); + + // Assign Atom link if needed + if (bagHasKey(this.fields, "links")) + this._atomLinksToURI(); + }, + + _atomLinksToURI: function Feed_linkToURI() { + var links = this.fields.getPropertyAsInterface("links", Ci.nsIArray); + var alternates = findAtomLinks("alternate", links); + if (alternates.length > 0) { + try { + var href = alternates[0].getPropertyAsAString("href"); + var base; + if (bagHasKey(alternates[0], "xml:base")) + base = strToURI(alternates[0].getPropertyAsAString("xml:base"), + this.baseURI); + else + base = this.baseURI; + this.link = strToURI(alternates[0].getPropertyAsAString("href"), base); + } + catch(e) { + LOG(e); + } } }, @@ -343,6 +407,7 @@ function Entry() { this.fields = Cc["@mozilla.org/hash-property-bag;1"]. createInstance(Ci.nsIWritablePropertyBag2); this.link = null; + this.baseURI = null; } Entry.prototype = { @@ -386,8 +451,11 @@ Entry.prototype = { _content: ["content:encoded","atom03:content","atom:content"] }, - normalize: function Feed_normalize() { + normalize: function Entry_normalize() { fieldsToObj(this, this.searchLists); + // Assign Atom link if needed + if (bagHasKey(this.fields, "links")) + this._atomLinksToURI(); }, QueryInterface: function(iid) { @@ -400,6 +468,8 @@ Entry.prototype = { } } +Entry.prototype._atomLinksToURI = Feed.prototype._atomLinksToURI; + // TextConstruct represents and element that could contain (X)HTML function TextConstruct() { this.lang = null; @@ -752,6 +822,7 @@ function FeedProcessor() { this._buf = ""; this._feed = Cc[BAG_CONTRACTID].createInstance(Ci.nsIWritablePropertyBag2); this._handlerStack = []; + this._xmlBaseStack = []; this._depth = 0; this._state = "START"; this._result = null; @@ -877,6 +948,7 @@ FeedProcessor.prototype = { if (uri) { this._result.uri = uri; this._reader.baseURI = uri; + this._xmlBaseStack[0] = uri; } }, @@ -885,6 +957,8 @@ FeedProcessor.prototype = { // than the root. _docVerified: function FP_docVerified(version) { this._result.doc = Cc[FEED_CONTRACTID].createInstance(Ci.nsIFeed); + this._result.doc.baseURI = + this._xmlBaseStack[this._xmlBaseStack.length - 1]; this._result.doc.fields = this._feed; this._result.version = version; }, @@ -991,6 +1065,13 @@ FeedProcessor.prototype = { ++this._depth; var elementInfo; + // Check for xml:base + var base = attributes.getValueFromName(XMLNS, "base"); + if (base) { + this._xmlBaseStack[this._depth] = + strToURI(base, this._xmlBaseStack[this._xmlBaseStack.length - 1]); + } + // To identify the element we're dealing with, we look up the // namespace URI in our gNamespaces dictionary, which will give us // a "canonical" prefix for a namespace URI. For example, this @@ -1071,6 +1152,10 @@ FeedProcessor.prototype = { if (elementInfo && !elementInfo.isWrapper) this._closeComplexElement(elementInfo); + // cut down xml:base context + if (this._xmlBaseStack.length == this._depth + 1) + this._xmlBaseStack = this._xmlBaseStack.slice(0, this._depth); + // our new state is whatever is at the top of the stack now if (this._stack.length > 0) this._state = this._stack[this._stack.length - 1][1]; @@ -1115,6 +1200,7 @@ FeedProcessor.prototype = { obj = elementInfo.containerClass.createInstance(Ci.nsIFeedEntry); // Set the parent property of the entry. obj.parent = this._result.doc; + obj.baseURI = this._xmlBaseStack[this._xmlBaseStack.length - 1]; props = obj.fields; } else { diff --git a/toolkit/components/feeds/test/xml/rfc4287/entry_link_2alts.xml b/toolkit/components/feeds/test/xml/rfc4287/entry_link_2alts.xml new file mode 100644 index 000000000000..c7cebe4cd233 --- /dev/null +++ b/toolkit/components/feeds/test/xml/rfc4287/entry_link_2alts.xml @@ -0,0 +1,27 @@ + + + + tag:snellspace.com,2006:/atom/conformance/linktest/ + Atom Link Tests + 2005-01-18T15:10:00Z + James Snell + + + + + tag:snellspace.com,2006:/atom/conformance/linktest/2 + Two alternate links + 2005-01-18T15:00:02Z + The aggregator should pick either the second or third link below as the alternate + + + + + + + diff --git a/toolkit/components/feeds/test/xml/rfc4287/entry_link_2alts_allcore.xml b/toolkit/components/feeds/test/xml/rfc4287/entry_link_2alts_allcore.xml new file mode 100644 index 000000000000..56675b1a9ef9 --- /dev/null +++ b/toolkit/components/feeds/test/xml/rfc4287/entry_link_2alts_allcore.xml @@ -0,0 +1,28 @@ + + + + tag:snellspace.com,2006:/atom/conformance/linktest/ + Atom Link Tests + 2005-01-18T15:10:00Z + James Snell + + + + + tag:snellspace.com,2006:/atom/conformance/linktest/3 + One of each core link rel type + + 2005-01-18T15:00:03Z + The aggregator should pick the first link as the alternate + + + + + + + diff --git a/toolkit/components/feeds/test/xml/rfc4287/entry_link_2alts_allcore2.xml b/toolkit/components/feeds/test/xml/rfc4287/entry_link_2alts_allcore2.xml new file mode 100644 index 000000000000..b5b73cfe0205 --- /dev/null +++ b/toolkit/components/feeds/test/xml/rfc4287/entry_link_2alts_allcore2.xml @@ -0,0 +1,33 @@ + + + + tag:snellspace.com,2006:/atom/conformance/linktest/ + Atom Link Tests + 2005-01-18T15:10:00Z + James Snell + + + + + + + + tag:snellspace.com,2006:/atom/conformance/linktest/4 + One of each core link rel type + An additional alternate link + 2005-01-18T15:00:04Z + The aggregator should pick either the first or last links as the alternate. First link is likely better. + + + + + + + + + + diff --git a/toolkit/components/feeds/test/xml/rfc4287/entry_link_IANA.xml b/toolkit/components/feeds/test/xml/rfc4287/entry_link_IANA.xml new file mode 100644 index 000000000000..af6563f2eac5 --- /dev/null +++ b/toolkit/components/feeds/test/xml/rfc4287/entry_link_IANA.xml @@ -0,0 +1,29 @@ + + + + tag:snellspace.com,2006:/atom/conformance/linktest/ + Atom Link Tests + 2005-01-18T15:10:00Z + James Snell + + + + + tag:example.org,2006:/linkreltest/1 + Does your reader support http://www.iana.org/assignments/relation/alternate properly? + 2006-04-25T12:12:12Z + + + + This entry uses link/@rel="http://www.iana.org/assignments/relation/alternate". + + + diff --git a/toolkit/components/feeds/test/xml/rfc4287/entry_link_alt_extension.xml b/toolkit/components/feeds/test/xml/rfc4287/entry_link_alt_extension.xml new file mode 100644 index 000000000000..e37421864ad8 --- /dev/null +++ b/toolkit/components/feeds/test/xml/rfc4287/entry_link_alt_extension.xml @@ -0,0 +1,25 @@ + + + + tag:snellspace.com,2006:/atom/conformance/linktest/ + Atom Link Tests + 2005-01-18T15:10:00Z + James Snell + + + + tag:snellspace.com,2006:/atom/conformance/linktest/5 + Entry with a link relation registered by an extension + 2005-01-18T15:00:05Z + The aggregator should ignore the license link without throwing any errors. The first link should be picked as the alternate. + + + + + + diff --git a/toolkit/components/feeds/test/xml/rfc4287/entry_link_otherURI_alt.xml b/toolkit/components/feeds/test/xml/rfc4287/entry_link_otherURI_alt.xml new file mode 100644 index 000000000000..3dc0c8dd63ba --- /dev/null +++ b/toolkit/components/feeds/test/xml/rfc4287/entry_link_otherURI_alt.xml @@ -0,0 +1,25 @@ + + + + tag:snellspace.com,2006:/atom/conformance/linktest/ + Atom Link Tests + 2005-01-18T15:10:00Z + James Snell + + + + + tag:snellspace.com,2006:/atom/conformance/linktest/6 + Entry with a link relation identified by URI + 2005-01-18T15:00:06Z + The aggregator should ignore the second link without throwing any errors. The first link should be picked as the alternate. + + + + + diff --git a/toolkit/components/feeds/test/xml/rfc4287/entry_link_payment_alt.xml b/toolkit/components/feeds/test/xml/rfc4287/entry_link_payment_alt.xml new file mode 100644 index 000000000000..51e1524b4a1b --- /dev/null +++ b/toolkit/components/feeds/test/xml/rfc4287/entry_link_payment_alt.xml @@ -0,0 +1,27 @@ + + + + tag:snellspace.com,2006:/atom/conformance/linktest/ + Atom Link Tests + 2005-01-18T15:10:00Z + James Snell + + + + + + tag:snellspace.com,2006:/atom/conformance/linktest/5 + Entry with a link relation registered by an extension + 2005-01-18T15:00:05Z + The aggregator should ignore the license link without throwing any errors. The first link should be picked as the alternate. + + + + + + diff --git a/toolkit/components/feeds/test/xml/rfc4287/entry_link_random.xml b/toolkit/components/feeds/test/xml/rfc4287/entry_link_random.xml new file mode 100644 index 000000000000..cb370e5168c6 --- /dev/null +++ b/toolkit/components/feeds/test/xml/rfc4287/entry_link_random.xml @@ -0,0 +1,28 @@ + + + + tag:snellspace.com,2006:/atom/conformance/linktest/ + Atom Link Tests + 2005-01-18T15:10:00Z + James Snell + + + + + tag:snellspace.com,2006:/atom/conformance/linktest/1 + Just a single Alternate Link + 2005-01-18T15:00:01Z + The aggregator should pick the second link as the alternate + + + + + + diff --git a/toolkit/components/feeds/test/xml/rfc4287/entry_xmlBase.xml b/toolkit/components/feeds/test/xml/rfc4287/entry_xmlBase.xml new file mode 100644 index 000000000000..1656e2174242 --- /dev/null +++ b/toolkit/components/feeds/test/xml/rfc4287/entry_xmlBase.xml @@ -0,0 +1,21 @@ + + + + tag:example.com,2006:/atom/conformance/linktest/ + Atom Link Tests + 2005-06-18T6:23:00Z + + + + tag:example.org,2006:/linkreltest/1 + Does your reader support xml:base properly? + 2006-06-23T12:12:12Z + + + + diff --git a/toolkit/components/feeds/test/xml/rfc4287/entry_xmlBase_on_link.xml b/toolkit/components/feeds/test/xml/rfc4287/entry_xmlBase_on_link.xml new file mode 100644 index 000000000000..0ead142558b8 --- /dev/null +++ b/toolkit/components/feeds/test/xml/rfc4287/entry_xmlBase_on_link.xml @@ -0,0 +1,22 @@ + + + + + tag:example.com,2006:/atom/conformance/linktest/ + Atom Link Tests + 2005-06-18T16:13:00Z + + + + tag:example.org,2006:/linkreltest/1 + Does your reader support xml:base properly? + 2006-06-23T12:12:12Z + + + + diff --git a/toolkit/components/feeds/test/xml/rfc4287/feed_xmlBase.xml b/toolkit/components/feeds/test/xml/rfc4287/feed_xmlBase.xml new file mode 100644 index 000000000000..d5760e333e6e --- /dev/null +++ b/toolkit/components/feeds/test/xml/rfc4287/feed_xmlBase.xml @@ -0,0 +1,23 @@ + + + + + tag:example.com,2006:/atom/conformance/linktest/ + Atom Link Tests + 2005-06-18T16:13:00Z + + + + tag:example.org,2006:/linkreltest/1 + Does your reader support xml:base properly? + 2006-06-23T12:12:12Z + + + +