/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Vladimir Vukicevic * Portions created by the Initial Developer are Copyright (C) 2004 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Masayuki Nakano * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsBookmarksService.h" #include "nsIDOMWindow.h" #include "nsIObserverService.h" #include "nsIRDFContainer.h" #include "nsIRDFContainerUtils.h" #include "nsIRDFService.h" #include "nsIRDFXMLSerializer.h" #include "nsIRDFXMLSource.h" #include "nsIRDFXMLParser.h" #include "nsRDFCID.h" #include "nsISupportsPrimitives.h" #include "rdf.h" #include "nsUnicharUtils.h" #include "nsInt64.h" #include "nsStringStream.h" #include "nsIScriptSecurityManager.h" #include "nsIURL.h" #include "nsIInputStream.h" #include "nsNetUtil.h" #include "nsICachingChannel.h" #include "nsICacheVisitor.h" #include "nsIDOMParser.h" #include "nsIDOMDocument.h" #include "nsIDOMElement.h" #include "nsIDOMCharacterData.h" #include "nsIDOMNodeList.h" #include "nsIDOM3Node.h" #include "nsIDOMDocumentTraversal.h" #include "nsIDOMTreeWalker.h" #include "nsIDOMNodeFilter.h" #include "nsIDOMDocumentFragment.h" #include "nsIParser.h" #include "nsParserCIID.h" #include "nsIFragmentContentSink.h" #include "nsIContentSink.h" #include "nsIDocument.h" static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID); /* These are defined in nsBookmarksService.cpp */ extern nsIRDFResource *kRDF_type; extern nsIRDFResource *kRDF_nextVal; extern nsIRDFResource *kNC_FeedURL; extern nsIRDFResource *kNC_LivemarkLock; extern nsIRDFResource *kNC_LivemarkExpiration; extern nsIRDFResource *kRSS09_channel; extern nsIRDFResource *kRSS09_item; extern nsIRDFResource *kRSS09_title; extern nsIRDFResource *kRSS09_link; extern nsIRDFResource *kRSS10_channel; extern nsIRDFResource *kRSS10_items; extern nsIRDFResource *kRSS10_title; extern nsIRDFResource *kRSS10_link; extern nsIRDFResource *kDC_date; extern nsIRDFLiteral *kTrueLiteral; extern nsIRDFService *gRDF; extern nsIRDFContainerUtils *gRDFC; static NS_DEFINE_CID(kRDFContainerCID, NS_RDFCONTAINER_CID); static NS_DEFINE_CID(kRDFInMemoryDataSourceCID, NS_RDFINMEMORYDATASOURCE_CID); nsresult nsBMSVCClearSeqContainer (nsIRDFDataSource* aDataSource, nsIRDFResource* aResource); nsresult nsBMSVCUnmakeSeq (nsIRDFDataSource* aDataSource, nsIRDFResource* aResource); //////////////////////////////////////////////////////////////////////// // nsFeedLoadListener // // An nsIStreamListener implementation that UpdateLivemarkChildren uses // to aysnchronously fetch and update a livemark's child entries. // // This could potentially be pulled out to its own file, or become // a nested class within the bookmarks service. // class nsFeedLoadListener : public nsIStreamListener { public: nsFeedLoadListener(nsBookmarksService *aBMSVC, nsIRDFDataSource *aInnerBMDataSource, nsIURI *aURI, nsIRDFResource *aLivemarkResource) : mBMSVC(aBMSVC), mInnerBMDataSource(aInnerBMDataSource), mURI(aURI), mResource(aLivemarkResource), mAborted(PR_FALSE) { NS_IF_ADDREF(mBMSVC); } virtual ~nsFeedLoadListener() { NS_IF_RELEASE(mBMSVC); } NS_DECL_ISUPPORTS NS_DECL_NSISTREAMLISTENER NS_DECL_NSIREQUESTOBSERVER void Abort () { mAborted = PR_TRUE; } protected: static NS_METHOD StreamReaderCallback(nsIInputStream *in, void *closure, const char *fromRawSegment, PRUint32 toOffset, PRUint32 count, PRUint32 *writeCount); NS_METHOD TryParseAsRDF(); NS_METHOD TryParseAsSimpleRSS(); NS_METHOD SetResourceTTL(PRInt32 ttl); // helpers NS_METHOD HandleRDFItem (nsIRDFDataSource *aDS, nsIRDFResource *itemResource, nsIRDFResource *aLinkResource, nsIRDFResource *aTitleResource); NS_METHOD FindTextInChildNodes (nsIDOMNode *aNode, nsAString &aString); NS_METHOD ParseHTMLFragment(nsAString &aFragString, nsIDocument* aTargetDocument, nsIDOMNode **outNode); PRBool IsLinkValid(const PRUnichar *aURI); nsBookmarksService *mBMSVC; nsCOMPtr mInnerBMDataSource; nsCOMPtr mURI; nsCOMPtr mResource; nsCOMPtr mCacheStream; nsCOMPtr mSecMan; PRBool mAborted; nsCString mBody; nsCOMPtr mLivemarkContainer; }; NS_IMPL_ISUPPORTS2(nsFeedLoadListener, nsIStreamListener, nsIRequestObserver) NS_IMETHODIMP nsFeedLoadListener::OnStartRequest(nsIRequest *aResult, nsISupports *ctxt) { if (mAborted) return NS_ERROR_UNEXPECTED; mBody.Truncate(); return NS_OK; } NS_METHOD nsFeedLoadListener::StreamReaderCallback(nsIInputStream *aInputStream, void *aClosure, const char *aRawSegment, PRUint32 aToOffset, PRUint32 aCount, PRUint32 *aWriteCount) { nsFeedLoadListener *rll = (nsFeedLoadListener *) aClosure; rll->mBody.Append(aRawSegment, aCount); *aWriteCount = aCount; return NS_OK; } NS_IMETHODIMP nsFeedLoadListener::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext, nsIInputStream *aInputStream, PRUint32 aSourceOffset, PRUint32 aCount) { PRUint32 totalRead; return aInputStream->ReadSegments(StreamReaderCallback, (void *)this, aCount, &totalRead); } NS_IMETHODIMP nsFeedLoadListener::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext, nsresult aStatus) { nsresult rv; if (mAborted) { mBMSVC->Unassert (mResource, kNC_LivemarkLock, kTrueLiteral); return NS_OK; } if (NS_FAILED(aStatus)) { // Something went wrong; try to load again in 5 minutes. SetResourceTTL (300); mBMSVC->Unassert (mResource, kNC_LivemarkLock, kTrueLiteral); return NS_OK; } /* Either turn the livemark into a Seq if it isn't one already, or clear out the * old data. */ do { PRBool isContainer = PR_FALSE; rv = gRDFC->IsContainer(mInnerBMDataSource, mResource, &isContainer); if (NS_FAILED(rv)) break; if (!isContainer) { rv = gRDFC->MakeSeq(mInnerBMDataSource, mResource, getter_AddRefs(mLivemarkContainer)); if (NS_FAILED(rv)) break; } else { rv = mBMSVC->ClearBookmarksContainer(mResource); if (NS_FAILED(rv)) break; mLivemarkContainer = do_CreateInstance (kRDFContainerCID, &rv); if (NS_FAILED(rv)) break; rv = mLivemarkContainer->Init (mInnerBMDataSource, mResource); if (NS_FAILED(rv)) break; } } while (0); if (NS_FAILED(rv)) { mBMSVC->Unassert (mResource, kNC_LivemarkLock, kTrueLiteral); return rv; } /* * Grab the security manager */ mSecMan = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); /* We need to parse the returned data here, stored in mBody. We * try parsing as RDF first, then as Atom and the "simple" RSS * (the userland 0.91/0.92/2.0 formats) */ /* Try parsing as RDF */ rv = TryParseAsRDF (); /* Try parsing as Atom/Simple RSS */ if (!NS_SUCCEEDED(rv)) { rv = TryParseAsSimpleRSS (); } /* If we weren't able to load with anything, attach a dummy bookmark */ if (!NS_SUCCEEDED(rv)) { mLivemarkContainer->AppendElement(mBMSVC->mLivemarkLoadFailedBookmark); } /* Set an expiration on the livemark, for reloading the data */ PRInt32 ttl; if (NS_FAILED(rv)) { // if we failed, try again in 1 hour, to avoid trying // to load a feed that doesn't parse over and over. ttl = 3600; } else { if (mBMSVC->mBookmarksPrefs) rv = mBMSVC->mBookmarksPrefs->GetIntPref("livemark_refresh_seconds", &ttl); if (!mBMSVC->mBookmarksPrefs || NS_FAILED(rv)) ttl = 3600; // 1 hr default else if (ttl < 60) ttl = 60; // 1 min minimum // ensure that the ttl is at least equal to the cache expiry time, since // otherwise a reload won't have much effect nsCOMPtr channel = do_QueryInterface(aRequest); if (channel) { nsCOMPtr cacheToken; channel->GetCacheToken(getter_AddRefs(cacheToken)); if (cacheToken) { nsCOMPtr entryInfo = do_QueryInterface(cacheToken); if (entryInfo) { PRUint32 expiresTime; if (NS_SUCCEEDED(entryInfo->GetExpirationTime(&expiresTime))) { PRInt64 temp64, nowtime = PR_Now(); PRUint32 nowsec; LL_I2L(temp64, PR_USEC_PER_SEC); LL_DIV(temp64, nowtime, temp64); LL_L2UI(nowsec, temp64); if (nowsec >= expiresTime) { expiresTime -= nowsec; if (ttl < (PRInt32) expiresTime) ttl = (PRInt32) expiresTime; } } } } } } rv = SetResourceTTL(ttl); if (NS_FAILED(rv)) { NS_WARNING ("SetResourceTTL failed on Livemark"); } rv = mBMSVC->Unassert (mResource, kNC_LivemarkLock, kTrueLiteral); if (NS_FAILED(rv)) { NS_WARNING ("LivemarkLock unassert failed!"); } return NS_OK; } /** * SetResourceTTL: Set the next time we should attempt to reload this * resource's feed */ nsresult nsFeedLoadListener::SetResourceTTL (PRInt32 aTTL) { nsresult rv; PRTime million, temp64, exptime = PR_Now(); LL_I2L (million, PR_USEC_PER_SEC); LL_I2L (temp64, aTTL); LL_MUL (temp64, temp64, million); LL_ADD (exptime, exptime, temp64); nsCOMPtr newNode; rv = gRDF->GetDateLiteral (exptime, getter_AddRefs(newNode)); if (NS_FAILED(rv)) return rv; nsCOMPtr oldNode; rv = mInnerBMDataSource->GetTarget (mResource, kNC_LivemarkExpiration, PR_TRUE, getter_AddRefs(oldNode)); if (NS_FAILED(rv)) return rv; if (rv == NS_OK) { rv = mInnerBMDataSource->Change (mResource, kNC_LivemarkExpiration, oldNode, newNode); } else { rv = mInnerBMDataSource->Assert (mResource, kNC_LivemarkExpiration, newNode, PR_TRUE); } if (NS_FAILED(rv)) return rv; return NS_OK; } /** * TryParseAsRDF: attempt to parse the read data in mBody as * RSS 0.90/1.0 data. This is supposed to be RDF, so we use * the RDF parser to do our work for us. */ nsresult nsFeedLoadListener::TryParseAsRDF () { nsresult rv; nsCOMPtr rdfparser(do_CreateInstance("@mozilla.org/rdf/xml-parser;1", &rv)); if (NS_FAILED(rv)) return rv; nsCOMPtr ds(do_CreateInstance(kRDFInMemoryDataSourceCID, &rv)); if (NS_FAILED(rv)) return rv; nsCOMPtr listener; rv = rdfparser->ParseAsync(ds, mURI, getter_AddRefs(listener)); if (NS_FAILED(rv)) return rv; if (!listener) return NS_ERROR_FAILURE; nsCOMPtr stream = do_CreateInstance("@mozilla.org/io/string-input-stream;1"); if (!stream) return NS_ERROR_FAILURE; rv = stream->SetData(mBody.get(), mBody.Length()); if (NS_FAILED(rv)) return rv; nsCOMPtr channel; rv = NS_NewInputStreamChannel(getter_AddRefs(channel), mURI, stream, NS_LITERAL_CSTRING("text/xml")); if (NS_FAILED(rv)) return rv; listener->OnStartRequest(channel, nsnull); listener->OnDataAvailable(channel, nsnull, stream, 0, mBody.Length()); listener->OnStopRequest(channel, nsnull, NS_OK); // Grab the (only) thing that's a channel // We try RSS 1.0 first, then RSS 0.9, and set up the remaining // resources accordingly nsIRDFResource *RSS_items = nsnull; nsIRDFResource *RSS_title = nsnull; nsIRDFResource *RSS_link = nsnull; nsCOMPtr channelResource = nsnull; rv = ds->GetSource(kRDF_type, kRSS10_channel, PR_TRUE, getter_AddRefs(channelResource)); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; if (rv == NS_OK) { RSS_items = kRSS10_items; RSS_title = kRSS10_title; RSS_link = kRSS10_link; } else { // try RSS 0.9 rv = ds->GetSource(kRDF_type, kRSS09_channel, PR_TRUE, getter_AddRefs(channelResource)); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; if (rv == NS_OK) { RSS_items = nsnull; RSS_title = kRSS09_title; RSS_link = kRSS09_link; } } if (!channelResource) { // no channel, either 1.0 or 0.9 return NS_ERROR_FAILURE; } // this will get filled in differently. nsCOMPtr itemsEnumerator; if (RSS_items) { // if there is something that should be rss:items, then it's RSS 1.0 nsCOMPtr itemsNode; rv = ds->GetTarget(channelResource, RSS_items, PR_TRUE, getter_AddRefs(itemsNode)); if (NS_FAILED(rv) || rv == NS_RDF_NO_VALUE) return NS_ERROR_FAILURE; /* items is a seq */ nsCOMPtr itemsContainer = do_CreateInstance (kRDFContainerCID, &rv); if (NS_FAILED(rv)) return rv; rv = itemsContainer->Init(ds, (nsIRDFResource *) itemsNode.get()); if (NS_FAILED(rv)) return rv; rv = itemsContainer->GetElements (getter_AddRefs(itemsEnumerator)); if (NS_FAILED(rv) || rv == NS_RDF_NO_VALUE) return NS_ERROR_FAILURE; } else { // // if there is no rss:items, but we still were able to parse it as RDF // and found a channel, then it's possibly RSS 0.9. For RSS 0.9, // we know that each item will be an , so we get everything // that has a type of item. rv = ds->GetSources(kRDF_type, kRSS09_item, PR_TRUE, getter_AddRefs(itemsEnumerator)); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; } /* Go through each resource and pull out its link/title, if present. */ PRBool more; while (NS_SUCCEEDED(rv = itemsEnumerator->HasMoreElements(&more)) && more) { nsCOMPtr iSupports; rv = itemsEnumerator->GetNext(getter_AddRefs(iSupports)); if (NS_FAILED(rv)) break; nsCOMPtr item(do_QueryInterface(iSupports)); if (!item) { rv = NS_ERROR_UNEXPECTED; break; } rv = HandleRDFItem (ds, item, RSS_link, RSS_title); // ignore rv } if (NS_FAILED(rv)) return rv; #ifdef DEBUG_vladimir NS_WARNING (">>>> Success from TryParseAsRDF!\n"); #endif return NS_OK; } nsresult nsFeedLoadListener::ParseHTMLFragment(nsAString &aFragString, nsIDocument* aTargetDocument, nsIDOMNode **outNode) { NS_ENSURE_ARG(aTargetDocument); nsresult rv; nsCOMPtr parser; parser = do_CreateInstance(kCParserCID, &rv); if (NS_FAILED(rv)) return rv; // create the html fragment sink nsCOMPtr sink; sink = do_CreateInstance(NS_HTMLFRAGMENTSINK2_CONTRACTID); NS_ENSURE_TRUE(sink, NS_ERROR_FAILURE); nsCOMPtr fragSink(do_QueryInterface(sink)); NS_ENSURE_TRUE(fragSink, NS_ERROR_FAILURE); fragSink->SetTargetDocument(aTargetDocument); // parse the fragment parser->SetContentSink(sink); parser->Parse(aFragString, (void*)0, NS_LITERAL_CSTRING("text/html"), PR_TRUE, eDTDMode_fragment); // get the fragment node nsCOMPtr contextfrag; rv = fragSink->GetFragment(getter_AddRefs(contextfrag)); NS_ENSURE_SUCCESS(rv, rv); rv = CallQueryInterface (contextfrag, outNode); return rv; } // find all of the text and CDATA nodes below aNode and return them as a string nsresult nsFeedLoadListener::FindTextInChildNodes (nsIDOMNode *aNode, nsAString &aString) { NS_ENSURE_ARG(aNode); nsresult rv; nsCOMPtr aDoc; aNode->GetOwnerDocument(getter_AddRefs(aDoc)); nsCOMPtr trav = do_QueryInterface(aDoc, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr walker; rv = trav->CreateTreeWalker(aNode, nsIDOMNodeFilter::SHOW_TEXT | nsIDOMNodeFilter::SHOW_CDATA_SECTION, nsnull, PR_TRUE, getter_AddRefs(walker)); if (NS_FAILED(rv)) return rv; nsCOMPtr currentNode; walker->GetCurrentNode(getter_AddRefs(currentNode)); nsCOMPtr charTextNode; nsAutoString tempString; while (currentNode) { charTextNode = do_QueryInterface(currentNode); if (charTextNode) { charTextNode->GetData(tempString); aString.Append(tempString); } walker->NextNode(getter_AddRefs(currentNode)); } if (aString.IsEmpty()) { return NS_ERROR_FAILURE; } else { return NS_OK; } } nsresult nsFeedLoadListener::HandleRDFItem (nsIRDFDataSource *aDS, nsIRDFResource *aItem, nsIRDFResource *aLinkResource, nsIRDFResource *aTitleResource) { nsresult rv; /* We care about this item's link and title */ nsCOMPtr linkNode; rv = aDS->GetTarget (aItem, aLinkResource, PR_TRUE, getter_AddRefs(linkNode)); if (NS_FAILED(rv) || rv == NS_RDF_NO_VALUE) return NS_ERROR_FAILURE; nsCOMPtr titleNode; rv = aDS->GetTarget (aItem, aTitleResource, PR_TRUE, getter_AddRefs(titleNode)); if (rv == NS_RDF_NO_VALUE) { rv = aDS->GetTarget (aItem, kDC_date, PR_TRUE, getter_AddRefs(titleNode)); } if (NS_FAILED(rv) || rv == NS_RDF_NO_VALUE) return NS_ERROR_FAILURE; nsCOMPtr linkLiteral(do_QueryInterface(linkNode)); nsCOMPtr titleLiteral(do_QueryInterface(titleNode)); // if the link/title points to something other than a literal skip it. if (!linkLiteral || !titleLiteral) return NS_ERROR_FAILURE; const PRUnichar *linkStr, *titleStr; rv = linkLiteral->GetValueConst(&linkStr); rv |= titleLiteral->GetValueConst(&titleStr); if (NS_FAILED(rv)) return rv; if (!IsLinkValid(linkStr)) return NS_OK; nsCOMPtr newBM; rv = mBMSVC->CreateBookmark (titleStr, linkStr, nsnull, nsnull, nsnull, nsnull, getter_AddRefs(newBM)); if (NS_FAILED(rv)) return rv; rv = mLivemarkContainer->AppendElement(newBM); if (NS_FAILED(rv)) return rv; return NS_OK; } /** * TryParseAsSimpleRSS * * Tries to parse the content as RSS (Userland) 0.91/0.92/2.0, or Atom * These are not RDF formats. */ nsresult nsFeedLoadListener::TryParseAsSimpleRSS () { nsresult rv; nsCOMPtr parser(do_CreateInstance("@mozilla.org/xmlextras/domparser;1", &rv)); if (NS_FAILED(rv)) return rv; nsCOMPtr xmldoc; // XXXbz is this the right principal? parser->Init(nsnull, mURI, nsnull); rv = parser->ParseFromBuffer ((const PRUint8*) mBody.get(), mBody.Length(), "text/xml", getter_AddRefs(xmldoc)); if (NS_FAILED(rv)) return rv; // becomes true if we figure out that this is an atom stream. PRBool isAtom = PR_FALSE; nsCOMPtr docElement; rv = xmldoc->GetDocumentElement(getter_AddRefs(docElement)); if (!docElement) return NS_ERROR_UNEXPECTED; if (NS_FAILED(rv)) return rv; nsCOMPtr node; rv = xmldoc->GetFirstChild(getter_AddRefs(node)); if (!node) return NS_ERROR_UNEXPECTED; if (NS_FAILED(rv)) return rv; PRBool lookingForChannel = PR_FALSE; while (node) { PRUint16 ntype; rv = node->GetNodeType(&ntype); if (NS_FAILED(rv)) return rv; if (ntype == nsIDOMNode::ELEMENT_NODE) { nsAutoString nname; rv = node->GetNodeName (nname); /* slight hack to get node pointing to the thing that * has items/entries as its children. We need to descend * into for RSS, but not for Atom. */ if (!lookingForChannel) { if (nname.Equals(NS_LITERAL_STRING("rss"))) { lookingForChannel = PR_TRUE; nsCOMPtr temp; rv = node->GetFirstChild(getter_AddRefs(temp)); if (!temp) return NS_ERROR_UNEXPECTED; if (NS_FAILED(rv)) return rv; node = temp; continue; } if (nname.Equals(NS_LITERAL_STRING("feed"))) { /* Atom has no ; instead, s are * children of */ isAtom = PR_TRUE; break; } } else { if (nname.Equals(NS_LITERAL_STRING("channel"))) { break; } } } nsCOMPtr temp; rv = node->GetNextSibling(getter_AddRefs(temp)); if (!temp) return NS_ERROR_UNEXPECTED; if (NS_FAILED(rv)) return rv; node = temp; } // we didn't find a rss/feed/channel or whatever if (!node) return NS_ERROR_FAILURE; nsCOMPtr chElement = do_QueryInterface(node); if (!chElement) return NS_ERROR_UNEXPECTED; /* Go through the / and do what we need * with or nodes */ int numMarksAdded = 0; rv = chElement->GetFirstChild(getter_AddRefs(node)); if (!node) return NS_ERROR_UNEXPECTED; if (NS_FAILED(rv)) return rv; while (node) { PRUint16 ntype; rv = node->GetNodeType(&ntype); if (NS_FAILED(rv)) return rv; if (ntype == nsIDOMNode::ELEMENT_NODE) { nsAutoString nname; rv = node->GetNodeName (nname); if ((!isAtom && nname.Equals(NS_LITERAL_STRING("item"))) || ( isAtom && nname.Equals(NS_LITERAL_STRING("entry")))) { /* We need to pull out the and <link> children */ nsAutoString titleStr; nsAutoString linkStr; nsAutoString dateStr; nsCOMPtr<nsIDOMNode> childNode; rv = node->GetFirstChild(getter_AddRefs(childNode)); if (NS_FAILED(rv)) return rv; while (childNode) { PRUint16 childNtype; rv = childNode->GetNodeType(&childNtype); if (NS_FAILED(rv)) return rv; if (childNtype == nsIDOMNode::ELEMENT_NODE) { nsAutoString childNname; rv = childNode->GetNodeName (childNname); if (childNname.Equals(NS_LITERAL_STRING("title"))) { if (isAtom) { /* Atom titles can contain HTML, so we need to find and remove it */ nsCOMPtr<nsIDOMElement> titleElem = do_QueryInterface(childNode); if (!titleElem) break; // out of while(childNode) loop nsAutoString titleMode; // Atom 0.3 only nsAutoString titleType; titleElem->GetAttribute(NS_LITERAL_STRING("type"), titleType); titleElem->GetAttribute(NS_LITERAL_STRING("mode"), titleMode); /* No one does this in <title> except standards pedants making test feeds, * Atom 0.3 is deprecated, and RFC 4287 doesn't allow it */ if (titleMode.EqualsLiteral("base64")) { break; // out of while(childNode) loop } /* Cover Atom 0.3 and RFC 4287 together */ if (titleType.EqualsLiteral("text") || titleType.EqualsLiteral("text/plain") || titleType.IsEmpty()) { rv = FindTextInChildNodes(childNode, titleStr); } else if (titleType.EqualsLiteral("html") || // RFC 4287 (titleType.EqualsLiteral("text/html") && // Atom 0.3 !titleMode.EqualsLiteral("xml")) || titleMode.EqualsLiteral("escaped")) // Atom 0.3 { nsAutoString escapedHTMLStr; rv = FindTextInChildNodes(childNode, escapedHTMLStr); if (NS_FAILED(rv)) break; // out of while(childNode) loop nsCOMPtr<nsIDOMNode> newNode; nsCOMPtr<nsIDocument> doc = do_QueryInterface(xmldoc); ParseHTMLFragment(escapedHTMLStr, doc, getter_AddRefs(newNode)); rv = FindTextInChildNodes(newNode, titleStr); }else if (titleType.EqualsLiteral("xhtml") || // RFC 4287 titleType.EqualsLiteral("application/xhtml") || // Atom 0.3 titleMode.EqualsLiteral("xml")) // Atom 0.3 { rv = FindTextInChildNodes(childNode, titleStr); } else { break; // out of while(childNode) loop } } else { rv = FindTextInChildNodes(childNode, titleStr); } if (NS_FAILED(rv)) break; } else if (dateStr.IsEmpty() && (childNname.Equals(NS_LITERAL_STRING("pubDate")) || childNname.Equals(NS_LITERAL_STRING("updated")))) { rv = FindTextInChildNodes (childNode, dateStr); if (NS_FAILED(rv)) break; } else if (!isAtom && childNname.Equals(NS_LITERAL_STRING("guid")) && linkStr.IsEmpty()) { nsCOMPtr<nsIDOMElement> linkElem = do_QueryInterface(childNode); if (!linkElem) break; // out of while(childNode) loop nsAutoString isPermaLink; linkElem->GetAttribute(NS_LITERAL_STRING("isPermaLink"), isPermaLink); // Ignore failures. isPermaLink defaults to true. if (!isPermaLink.Equals(NS_LITERAL_STRING("false"))) { // in node's TEXT rv = FindTextInChildNodes (childNode, linkStr); if (NS_FAILED(rv)) break; } } else if (childNname.Equals(NS_LITERAL_STRING("link"))) { if (isAtom) { // in HREF attribute nsCOMPtr<nsIDOMElement> linkElem = do_QueryInterface(childNode); if (!linkElem) break; // out of while(childNode) loop nsAutoString rel; linkElem->GetAttribute(NS_LITERAL_STRING("rel"), rel); if (rel.Equals(NS_LITERAL_STRING("alternate")) || rel.IsEmpty()) { rv = linkElem->GetAttribute(NS_LITERAL_STRING("href"), linkStr); if (NS_FAILED(rv)) break; // out of while(childNode) loop nsCOMPtr<nsIDOM3Node> linkElem3 = do_QueryInterface(childNode); if (linkElem3) { // get the BaseURI (as string) nsAutoString base; rv = linkElem3->GetBaseURI(base); if (NS_SUCCEEDED(rv) && !base.IsEmpty()) { // convert a baseURI (string) to a nsIURI nsCOMPtr<nsIURI> baseURI; rv = NS_NewURI(getter_AddRefs(baseURI), base); if (baseURI) { nsString absLinkStr; if (NS_SUCCEEDED(NS_MakeAbsoluteURI(absLinkStr, linkStr, baseURI))) linkStr = absLinkStr; } } } } } else if (linkStr.IsEmpty()) { // in node's TEXT rv = FindTextInChildNodes (childNode, linkStr); if (NS_FAILED(rv)) break; } } } if (!titleStr.IsEmpty() && !linkStr.IsEmpty()) break; nsCOMPtr<nsIDOMNode> temp; rv = childNode->GetNextSibling(getter_AddRefs(temp)); childNode = temp; if (!childNode || NS_FAILED(rv)) break; } // Clean up whitespace CompressWhitespace(titleStr); linkStr.Trim("\b\t\r\n "); CompressWhitespace(dateStr); if (titleStr.IsEmpty() && !dateStr.IsEmpty()) titleStr.Assign(dateStr); if (!titleStr.IsEmpty() && !linkStr.IsEmpty() && IsLinkValid(linkStr.get())) { nsCOMPtr<nsIRDFResource> newBM; rv = mBMSVC->CreateBookmark (titleStr.get(), linkStr.get(), nsnull, nsnull, nsnull, nsnull, getter_AddRefs(newBM)); if (NS_FAILED(rv)) return rv; rv = mLivemarkContainer->AppendElement(newBM); if (NS_FAILED(rv)) return rv; numMarksAdded++; } } } nsCOMPtr<nsIDOMNode> temp; rv = node->GetNextSibling(getter_AddRefs(temp)); if (NS_FAILED(rv)) return rv; node = temp; } if (numMarksAdded > 0) { #ifdef DEBUG_vladimir NS_WARNING (">>>> Success from TryParseAsSimpleRSS!\n"); #endif return NS_OK; } // not finding any items would amount to NS_ERROR_FAILURE if we had // another parser to try, but could just be an empty feed // so returning NS_OK lets it pick up the default '(Empty)' item return NS_OK; } // return true if this link is valid and a livemark should be created; // otherwise, false. PRBool nsFeedLoadListener::IsLinkValid(const PRUnichar *aURI) { nsCOMPtr<nsIURI> linkuri; nsresult rv = NS_NewURI(getter_AddRefs(linkuri), nsDependentString(aURI)); if (NS_FAILED(rv)) return PR_FALSE; // Er, where'd our security manager go? if (!mSecMan) return PR_FALSE; rv = mSecMan->CheckLoadURI(mURI, linkuri, nsIScriptSecurityManager::DISALLOW_FROM_MAIL | nsIScriptSecurityManager::DISALLOW_SCRIPT_OR_DATA); if (NS_FAILED(rv)) return PR_FALSE; return PR_TRUE; } /////////////////////////////////////////////////////////////////////////// //// Main entry point for nsBookmarksService to deal with Livemarks //// PRBool nsBookmarksService::LivemarkNeedsUpdate(nsIRDFResource* aSource) { nsresult rv; PRBool locked = PR_FALSE; if (NS_SUCCEEDED(mInner->HasAssertion (aSource, kNC_LivemarkLock, kTrueLiteral, PR_TRUE, &locked)) && locked) { /* We're already loading the livemark */ return PR_FALSE; } // Check the TTL/expiration on this. If there isn't one, // then we assume it's never been loaded. nsCOMPtr<nsIRDFNode> expirationNode; rv = mInner->GetTarget(aSource, kNC_LivemarkExpiration, PR_TRUE, getter_AddRefs(expirationNode)); if (rv == NS_OK) { nsCOMPtr<nsIRDFDate> expirationTime = do_QueryInterface (expirationNode); PRTime exprTime, nowTime = PR_Now(); expirationTime->GetValue(&exprTime); if (exprTime > nowTime) { return PR_FALSE; } } return PR_TRUE; } /* * Update the child elements of a livemark; take care of cache checking, * channel setup and firing off the async load and parse. */ nsresult nsBookmarksService::UpdateLivemarkChildren(nsIRDFResource* aSource) { nsresult rv; // Check whether we have a Feed URL first, before locking; // we'll hit this often while we're making a new livemark, // and no sense in going through the lock/unlock cycle // umpteen times. nsCOMPtr<nsIRDFNode> feedUrlNode; rv = mInner->GetTarget(aSource, kNC_FeedURL, PR_TRUE, getter_AddRefs(feedUrlNode)); if (NS_FAILED(rv)) return rv; // if there's no feed attached, ignore this for now; hopefully // one will get attached soon. if (rv == NS_RDF_NO_VALUE) return NS_OK; nsCOMPtr<nsIRDFLiteral> feedUrlLiteral = do_QueryInterface(feedUrlNode, &rv); if (NS_FAILED(rv)) return rv; const PRUnichar *feedUrl = nsnull; rv = feedUrlLiteral->GetValueConst(&feedUrl); if (NS_FAILED(rv)) return rv; nsCString feedUrlString = NS_ConvertUTF16toUTF8(feedUrl); if (feedUrlString.IsEmpty()) return rv; #define UNLOCK_AND_RETURN_RV do { Unassert (aSource, kNC_LivemarkLock, kTrueLiteral); return rv; } while (0) PRBool locked = PR_FALSE; if (NS_SUCCEEDED(mInner->HasAssertion (aSource, kNC_LivemarkLock, kTrueLiteral, PR_TRUE, &locked)) && locked) { /* We're already loading the livemark */ return NS_OK; } rv = mInner->Assert (aSource, kNC_LivemarkLock, kTrueLiteral, PR_TRUE); if (NS_FAILED(rv)) return rv; // Check the TTL/expiration on this. If there isn't one, // then we assume it's never been loaded. nsCOMPtr<nsIRDFNode> expirationNode; rv = mInner->GetTarget(aSource, kNC_LivemarkExpiration, PR_TRUE, getter_AddRefs(expirationNode)); if (rv == NS_OK) { nsCOMPtr<nsIRDFDate> expirationTime = do_QueryInterface (expirationNode); PRTime exprTime, nowTime = PR_Now(); expirationTime->GetValue(&exprTime); if (LL_CMP(exprTime, >, nowTime)) { // no need to refresh yet rv = Unassert (aSource, kNC_LivemarkLock, kTrueLiteral); if (NS_FAILED(rv)) return rv; return NS_OK; } } else { do { // it's never been loaded. add a dummy "Livemark Loading..." entry nsCOMPtr<nsIRDFContainer> container(do_CreateInstance(kRDFContainerCID, &rv)); if (NS_FAILED(rv)) break; rv = container->Init(mInner, aSource); if (NS_FAILED(rv)) break; rv = container->AppendElement(mLivemarkLoadingBookmark); } while (0); } nsCOMPtr<nsIURI> uri; rv = NS_NewURI(getter_AddRefs(uri), feedUrlString, nsnull, nsnull); if (NS_FAILED(rv)) UNLOCK_AND_RETURN_RV; nsCOMPtr<nsFeedLoadListener> listener = new nsFeedLoadListener(this, mInner, uri, aSource); nsCOMPtr<nsIChannel> channel; rv = NS_NewChannel(getter_AddRefs(channel), uri, nsnull, nsnull, nsnull, nsIRequest::LOAD_BACKGROUND | nsIRequest::VALIDATE_ALWAYS); if (NS_FAILED(rv)) UNLOCK_AND_RETURN_RV; nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel); if (httpChannel) { // Add a request header so that the request can easily be detected as a // live bookmark update request. rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("X-Moz"), NS_LITERAL_CSTRING("livebookmarks"), PR_FALSE); } rv = channel->AsyncOpen(listener, nsnull); if (NS_FAILED(rv)) UNLOCK_AND_RETURN_RV; return NS_OK; #undef UNLOCK_AND_RETURN_RV } /////////////////////////////////////////////////////////////////////////// //// Utility methods /* Clear out all elements from the container */ /* FIXME - why is there no RDFC method to clear a container? */ nsresult nsBMSVCClearSeqContainer (nsIRDFDataSource* aDataSource, nsIRDFResource* aResource) { nsresult rv; nsCOMPtr<nsIRDFContainer> itemsContainer = do_CreateInstance (kRDFContainerCID, &rv); rv = itemsContainer->Init (aDataSource, aResource); if (NS_FAILED(rv)) return rv; PRInt32 itemsCount = 0; rv = itemsContainer->GetCount(&itemsCount); if (NS_FAILED(rv)) return rv; if (itemsCount) { do { nsCOMPtr<nsIRDFNode> removed; rv = itemsContainer->RemoveElementAt(itemsCount, PR_TRUE, getter_AddRefs(removed)); // er, ignore the error, I think } while (--itemsCount > 0); } return NS_OK; } nsresult nsBMSVCUnmakeSeq (nsIRDFDataSource* aDataSource, nsIRDFResource* aResource) { nsresult rv; rv = nsBMSVCClearSeqContainer(aDataSource, aResource); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIRDFNode> old; rv = aDataSource->GetTarget(aResource, kRDF_nextVal, PR_TRUE, getter_AddRefs(old)); NS_ENSURE_SUCCESS(rv, rv); rv = aDataSource->Unassert(aResource, kRDF_nextVal, (nsIRDFResource*) old.get()); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIRDFResource> RDF_instanceOf; nsCOMPtr<nsIRDFResource> RDF_Seq; gRDF->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"), getter_AddRefs(RDF_instanceOf)); gRDF->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Seq"), getter_AddRefs(RDF_Seq)); rv = aDataSource->Unassert(aResource, RDF_instanceOf, RDF_Seq.get()); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; }