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
+
+
+
+