/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** 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 History System * * The Initial Developer of the Original Code is * Google Inc. * Portions created by the Initial Developer are Copyright (C) 2005 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Brett Wilson * Dietrich Ayala * * 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 ***** */ /** * Importer/exporter between the mozStorage-based bookmarks and the old-style * "bookmarks.html" * * Format: * * Primary heading := h1 * Old version used this to set attributes on the bookmarks RDF root, such * as the last modified date. We only use H1 to check for the attribute * PLACES_ROOT, which tells us that this hierarchy root is the places root. * For backwards compatability, if we don't find this, we assume that the * hierarchy is rooted at the bookmarks menu. * Heading := any heading other than h1 * Old version used this to set attributes on the current container. We only * care about the content of the heading container, which contains the title * of the bookmark container. * Bookmark := a * HREF is the destination of the bookmark * FEEDURL is the URI of the RSS feed if this is a livemark. * LAST_CHARSET is stored as an annotation so that the next time we go to * that page we remember the user's preference. * WEB_PANEL is set to "true" if the bookmark should be loaded in the sidebar. * ICON will be stored in the favicon service * ICON_URI is new for places bookmarks.html, it refers to the original * URI of the favicon so we don't have to make up favicon URLs. * Text of the container is the name of the bookmark * Ignored: LAST_VISIT, ID (writing out non-RDF IDs can confuse Firefox 2) * Bookmark comment := dd * This affects the previosly added bookmark * Separator := hr * Insert a separator into the current container * The folder hierarchy is defined by
/
    / (the old importing code * handles all these cases, when we write, use
    ). * * Overall design * -------------- * * We need to emulate a recursive parser. A "Bookmark import frame" is created * corresponding to each folder we encounter. These are arranged in a stack, * and contain all the state we need to keep track of. * * A frame is created when we find a heading, which defines a new container. * The frame also keeps track of the nesting of
    s, (in well-formed * bookmarks files, these will have a 1-1 correspondence with frames, but we * try to be a little more flexible here). When the nesting count decreases * to 0, then we know a frame is complete and to pop back to the previous * frame. * * Note that a lot of things happen when tags are CLOSED because we need to * get the text from the content of the tag. For example, link and heading tags * both require the content (= title) before actually creating it. */ #include "nsPlacesImportExportService.h" #include "nsNetUtil.h" #include "nsParserCIID.h" #include "nsStringAPI.h" #include "nsUnicharUtils.h" #include "plbase64.h" #include "nsAppDirectoryServiceDefs.h" #include "nsDirectoryServiceUtils.h" #include "nsIPrefService.h" #include "nsToolkitCompsCID.h" #include "nsIHTMLContentSink.h" #include "nsIParser.h" #include "prprf.h" #include "nsVoidArray.h" static NS_DEFINE_CID(kParserCID, NS_PARSER_CID); #define KEY_TOOLBARFOLDER_LOWER "personal_toolbar_folder" #define KEY_BOOKMARKSMENU_LOWER "bookmarks_menu" #define KEY_PLACESROOT_LOWER "places_root" #define KEY_HREF_LOWER "href" #define KEY_FEEDURL_LOWER "feedurl" #define KEY_WEB_PANEL_LOWER "web_panel" #define KEY_LASTCHARSET_LOWER "last_charset" #define KEY_ICON_LOWER "icon" #define KEY_ICON_URI_LOWER "icon_uri" #define KEY_SHORTCUTURL_LOWER "shortcuturl" #define KEY_POST_DATA_LOWER "post_data" #define KEY_NAME_LOWER "name" #define KEY_MICSUM_GEN_URI_LOWER "micsum_gen_uri" #define KEY_DATE_ADDED_LOWER "add_date" #define KEY_LAST_MODIFIED_LOWER "last_modified" #define KEY_GENERATED_TITLE_LOWER "generated_title" #define LOAD_IN_SIDEBAR_ANNO NS_LITERAL_CSTRING("bookmarkProperties/loadInSidebar") #define DESCRIPTION_ANNO NS_LITERAL_CSTRING("bookmarkProperties/description") #define POST_DATA_ANNO NS_LITERAL_CSTRING("URIProperties/POSTData") #define LAST_CHARSET_ANNO NS_LITERAL_CSTRING("URIProperties/characterSet") #define STATIC_TITLE_ANNO NS_LITERAL_CSTRING("bookmarks/staticTitle") #define BOOKMARKS_MENU_ICON_URI "chrome://browser/skin/places/bookmarksMenu.png" // define to get debugging messages on console about import/export //#define DEBUG_IMPORT //#define DEBUG_EXPORT #if defined(XP_WIN) || defined(XP_OS2) #define NS_LINEBREAK "\015\012" #else #define NS_LINEBREAK "\012" #endif class nsIOutputStream; static const char kWhitespace[] = " \r\n\t\b"; static nsresult WriteEscapedUrl(const nsCString &aString, nsIOutputStream* aOutput); class BookmarkImportFrame { public: BookmarkImportFrame(PRInt64 aID) : mContainerID(aID), mContainerNesting(0), mLastContainerType(Container_Normal), mInDescription(PR_FALSE), mPreviousId(0), mPreviousDateAdded(0), mPreviousLastModifiedDate(0) { } enum ContainerType { Container_Normal, Container_Places, Container_Menu, Container_Toolbar }; PRInt64 mContainerID; // How many
    s have been nested. Each frame/container should start // with a heading, and is then followed by a
    ,
      , or . When // that list is complete, then it is the end of this container and we need // to pop back up one level for new items. If we never get an open tag for // one of these things, we should assume that the container is empty and // that things we find should be siblings of it. Normally, these
      s won't // be nested so this will be 0 or 1. PRInt32 mContainerNesting; // when we find a heading tag, it actually affects the title of the NEXT // container in the list. This stores that heading tag and whether it was // special. 'ConsumeHeading' resets this. ContainerType mLastContainerType; // this contains the text from the last begin tag until now. It is reset // at every begin tag. We can check it when we see a , or // to see what the text content of that node should be. nsString mPreviousText; // true when we hit a
      , which contains the description for the preceeding // tag. We can't just check for
      like we can for or // because if there is a sub-folder, it is actually a child of the
      // because the tag is never explicitly closed. If this is true and we see a // new open tag, that means to commit the description to the previous // bookmark. // // Additional weirdness happens when the previous
      tag contains a

      : // this means there is a new folder with the given description, and whose // children are contained in the following
      list. // // This is handled in OpenContainer(), which commits previous text if // necessary. PRBool mInDescription; // contains the URL of the previous bookmark created. This is used so that // when we encounter a
      , we know what bookmark to associate the text with. // This is cleared whenever we hit a

      , so that we know NOT to save this // with a bookmark, but to keep it until nsCOMPtr mPreviousLink; // contains the URL of the previous livemark, so that when the link ends, // and the livemark title is known, we can create it. nsCOMPtr mPreviousFeed; // contains the text content of the previous microsummary, so that when the // link ends, we can replace the bookmark's title with it and store the user's // title in the staticTitle annotation. nsString mPreviousMicrosummaryText; nsCOMPtr mPreviousMicrosummary; void ConsumeHeading(nsAString* aHeading, ContainerType* aContainerType) { *aHeading = mPreviousText; *aContainerType = mLastContainerType; mPreviousText.Truncate(); } // Contains the id of an imported, or newly created bookmark. PRInt64 mPreviousId; // Contains the date-added and last-modified-date of an imported item. // Used to override the values set by insertBookmark, createFolder, etc. PRTime mPreviousDateAdded; PRTime mPreviousLastModifiedDate; }; /** * copied from nsEscape.cpp, which requires internal string API */ char * nsEscapeHTML(const char * string) { /* XXX Hardcoded max entity len. The +1 is for the trailing null. */ char *rv = (char *) nsMemory::Alloc(strlen(string) * 6 + 1); char *ptr = rv; if(rv) { for(; *string != '\0'; string++) { if(*string == '<') { *ptr++ = '&'; *ptr++ = 'l'; *ptr++ = 't'; *ptr++ = ';'; } else if(*string == '>') { *ptr++ = '&'; *ptr++ = 'g'; *ptr++ = 't'; *ptr++ = ';'; } else if(*string == '&') { *ptr++ = '&'; *ptr++ = 'a'; *ptr++ = 'm'; *ptr++ = 'p'; *ptr++ = ';'; } else if (*string == '"') { *ptr++ = '&'; *ptr++ = 'q'; *ptr++ = 'u'; *ptr++ = 'o'; *ptr++ = 't'; *ptr++ = ';'; } else if (*string == '\'') { *ptr++ = '&'; *ptr++ = '#'; *ptr++ = '3'; *ptr++ = '9'; *ptr++ = ';'; } else { *ptr++ = *string; } } *ptr = '\0'; } return(rv); } NS_IMPL_ISUPPORTS2(nsPlacesImportExportService, nsIPlacesImportExportService, nsINavHistoryBatchCallback) nsPlacesImportExportService::nsPlacesImportExportService() { nsresult rv; mHistoryService = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID, &rv); NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "could not get history service"); mFaviconService = do_GetService(NS_FAVICONSERVICE_CONTRACTID, &rv); NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "could not get favicon service"); mAnnotationService = do_GetService(NS_ANNOTATIONSERVICE_CONTRACTID, &rv); NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "could not get annotation service"); mBookmarksService = do_GetService(NS_NAVBOOKMARKSSERVICE_CONTRACTID, &rv); NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "could not get bookmarks service"); mLivemarkService = do_GetService(NS_LIVEMARKSERVICE_CONTRACTID, &rv); NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "could not get livemark service"); mMicrosummaryService = do_GetService("@mozilla.org/microsummary/service;1", &rv); NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "could not get microsummary service"); } nsPlacesImportExportService::~nsPlacesImportExportService() { } /** * The content sink stuff is based loosely on */ class BookmarkContentSink : public nsIHTMLContentSink { public: nsresult Init(PRBool aAllowRootChanges, nsINavBookmarksService* bookmarkService, PRInt64 aFolder, PRBool aIsImportDefaults); NS_DECL_ISUPPORTS // nsIContentSink (superclass of nsIHTMLContentSink) NS_IMETHOD WillTokenize() { return NS_OK; } NS_IMETHOD WillBuildModel() { return NS_OK; } NS_IMETHOD DidBuildModel() { return NS_OK; } NS_IMETHOD WillInterrupt() { return NS_OK; } NS_IMETHOD WillResume() { return NS_OK; } NS_IMETHOD SetParser(nsIParser* aParser) { return NS_OK; } virtual void FlushPendingNotifications(mozFlushType aType) { } NS_IMETHOD SetDocumentCharset(nsACString& aCharset) { return NS_OK; } virtual nsISupports *GetTarget() { return nsnull; } // nsIHTMLContentSink NS_IMETHOD OpenHead() { return NS_OK; } NS_IMETHOD BeginContext(PRInt32 aPosition) { return NS_OK; } NS_IMETHOD EndContext(PRInt32 aPosition) { return NS_OK; } NS_IMETHOD IsEnabled(PRInt32 aTag, PRBool* aReturn) { *aReturn = PR_TRUE; return NS_OK; } NS_IMETHOD WillProcessTokens() { return NS_OK; } NS_IMETHOD DidProcessTokens() { return NS_OK; } NS_IMETHOD WillProcessAToken() { return NS_OK; } NS_IMETHOD DidProcessAToken() { return NS_OK; } NS_IMETHOD OpenContainer(const nsIParserNode& aNode); NS_IMETHOD CloseContainer(const nsHTMLTag aTag); NS_IMETHOD AddLeaf(const nsIParserNode& aNode); NS_IMETHOD AddComment(const nsIParserNode& aNode) { return NS_OK; } NS_IMETHOD AddProcessingInstruction(const nsIParserNode& aNode) { return NS_OK; } NS_IMETHOD AddDocTypeDecl(const nsIParserNode& aNode) { return NS_OK; } NS_IMETHOD NotifyTagObservers(nsIParserNode* aNode) { return NS_OK; } NS_IMETHOD_(PRBool) IsFormOnStack() { return PR_FALSE; } protected: nsCOMPtr mBookmarksService; nsCOMPtr mHistoryService; nsCOMPtr mAnnotationService; nsCOMPtr mLivemarkService; nsCOMPtr mMicrosummaryService; // If set, we will move root items to from their existing position // in the hierarchy, to where we find them in the bookmarks file // being imported. This should be set when we are loading // the default places html file, and should be unset when doing // normal imports so that root folders will not get moved when // importing bookmarks.html files. PRBool mAllowRootChanges; // if set, this is an import of initial bookmarks.html content, // so we don't want to kick off HTTP traffic // and we want the imported personal toolbar folder // to be set as the personal toolbar folder. (if not set // we will treat it as a normal folder.) PRBool mIsImportDefaults; // If a folder was specified to import into, then ignore flags to put // bookmarks in the bookmarks menu or toolbar and keep them inside // the folder. PRBool mFolderSpecified; void HandleContainerBegin(const nsIParserNode& node); void HandleContainerEnd(); void HandleHead1Begin(const nsIParserNode& node); void HandleHeadBegin(const nsIParserNode& node); void HandleHeadEnd(); void HandleLinkBegin(const nsIParserNode& node); void HandleLinkEnd(); void HandleSeparator(const nsIParserNode& node); // This is a list of frames. We really want a recursive parser, but the HTML // parser gives us tags as a stream. This implements all the state on a stack // so we can get the recursive information we need. Use "CurFrame" to get the // top "stack frame" with the current state in it. nsTArray mFrames; BookmarkImportFrame& CurFrame() { NS_ASSERTION(mFrames.Length() > 0, "Asking for frame when there are none!"); return mFrames[mFrames.Length() - 1]; } BookmarkImportFrame& PreviousFrame() { NS_ASSERTION(mFrames.Length() > 1, "Asking for frame when there are not enough!"); return mFrames[mFrames.Length() - 2]; } nsresult NewFrame(); nsresult PopFrame(); nsresult SetFaviconForURI(nsIURI* aPageURI, nsIURI* aFaviconURI, const nsCString& aData); PRInt64 ConvertImportedIdToInternalId(const nsCString& aId); PRTime ConvertImportedDateToInternalDate(const nsACString& aDate); #ifdef DEBUG_IMPORT // prints spaces for indenting to the current frame depth void PrintNesting() { for (PRUint32 i = 0; i < mFrames.Length(); i ++) printf(" "); } #endif }; // BookmarkContentSink::Init // // Note that the bookmark service pointer is passed in. We can not create // the bookmark service from here because this can be called from bookmark // service creation, making a weird reentrant loop. nsresult BookmarkContentSink::Init(PRBool aAllowRootChanges, nsINavBookmarksService* bookmarkService, PRInt64 aFolder, PRBool aIsImportDefaults) { nsresult rv; mBookmarksService = bookmarkService; mHistoryService = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); mAnnotationService = do_GetService(NS_ANNOTATIONSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); mLivemarkService = do_GetService(NS_LIVEMARKSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); mMicrosummaryService = do_GetService("@mozilla.org/microsummary/service;1", &rv); NS_ENSURE_SUCCESS(rv, rv); mAllowRootChanges = aAllowRootChanges; mIsImportDefaults = aIsImportDefaults; // initialize the root frame with the menu root PRInt64 menuRoot; if (aFolder == 0) { rv = mBookmarksService->GetBookmarksMenuFolder(&menuRoot); NS_ENSURE_SUCCESS(rv, rv); mFolderSpecified = false; } else { menuRoot = aFolder; mFolderSpecified = true; } if (!mFrames.AppendElement(BookmarkImportFrame(menuRoot))) return NS_ERROR_OUT_OF_MEMORY; return NS_OK; } NS_IMPL_ISUPPORTS2(BookmarkContentSink, nsIContentSink, nsIHTMLContentSink) // nsIContentSink ************************************************************** NS_IMETHODIMP BookmarkContentSink::OpenContainer(const nsIParserNode& aNode) { switch(aNode.GetNodeType()) { case eHTMLTag_h1: HandleHead1Begin(aNode); break; case eHTMLTag_h2: case eHTMLTag_h3: case eHTMLTag_h4: case eHTMLTag_h5: case eHTMLTag_h6: HandleHeadBegin(aNode); break; case eHTMLTag_a: HandleLinkBegin(aNode); break; case eHTMLTag_dl: case eHTMLTag_ul: case eHTMLTag_menu: HandleContainerBegin(aNode); break; case eHTMLTag_dd: CurFrame().mInDescription = PR_TRUE; break; } return NS_OK; } NS_IMETHODIMP BookmarkContentSink::CloseContainer(const nsHTMLTag aTag) { BookmarkImportFrame& frame = CurFrame(); // see the comment for the definition of mInDescription. Basically, we commit // any text in mPreviousText to the description of the node/folder if there // is any. if (frame.mInDescription) { frame.mPreviousText.Trim(kWhitespace); // important! if (!frame.mPreviousText.IsEmpty()) { PRInt64 itemId = !frame.mPreviousLink ? frame.mContainerID : frame.mPreviousId; PRBool hasDescription = PR_FALSE; nsresult rv = mAnnotationService->ItemHasAnnotation(itemId, DESCRIPTION_ANNO, &hasDescription); if (NS_SUCCEEDED(rv) && !hasDescription) { mAnnotationService->SetItemAnnotationString(itemId, DESCRIPTION_ANNO, frame.mPreviousText, 0, nsIAnnotationService::EXPIRE_NEVER); } frame.mPreviousText.Truncate(); // Set last-modified a 2nd time for all items with descriptions // we need to set last-modified as the *last* step in processing // any item type in the bookmarks.html file, so that we do // not overwrite the imported value. for items without descriptions, // setting this value after setting the item title is that // last point at which we can save this value before it gets reset. // for items with descriptions, it must set after that point. // however, at the point at which we set the title, there's no way // to determine if there will be a description following, // so we need to set the last-modified-date at both places. PRTime lastModified; if (!frame.mPreviousLink) { lastModified = PreviousFrame().mPreviousLastModifiedDate; } else { lastModified = frame.mPreviousLastModifiedDate; } if (itemId > 0 && lastModified > 0) { rv = mBookmarksService->SetItemLastModified(itemId, lastModified); NS_ASSERTION(NS_SUCCEEDED(rv), "SetItemLastModified failed"); } } frame.mInDescription = PR_FALSE; } switch (aTag) { case eHTMLTag_dl: case eHTMLTag_ul: case eHTMLTag_menu: HandleContainerEnd(); break; case eHTMLTag_dt: break; case eHTMLTag_h1: // ignore break; case eHTMLTag_h2: case eHTMLTag_h3: case eHTMLTag_h4: case eHTMLTag_h5: case eHTMLTag_h6: HandleHeadEnd(); break; case eHTMLTag_a: HandleLinkEnd(); break; default: break; } return NS_OK; } // BookmarkContentSink::AddLeaf // // XXX on the branch, we should be calling CollectSkippedContent as in // nsHTMLFragmentContentSink.cpp:AddLeaf when we encounter title, script, // style, or server tags. Apparently if we don't, we'll leak the next DOM // node. However, this requires that we keep a reference to the parser we'll // introduce a circular reference because it has a reference to us. // // This is annoying to fix and these elements are not allowed in bookmarks // files anyway. So if somebody tries to import a crazy bookmarks file, it // will leak a little bit. NS_IMETHODIMP BookmarkContentSink::AddLeaf(const nsIParserNode& aNode) { switch (aNode.GetNodeType()) { case eHTMLTag_text: // save any text we find CurFrame().mPreviousText += aNode.GetText(); break; case eHTMLTag_entity: { nsAutoString tmp; PRInt32 unicode = aNode.TranslateToUnicodeStr(tmp); if (unicode < 0) { // invalid entity - just use the text of it CurFrame().mPreviousText += aNode.GetText(); } else { CurFrame().mPreviousText.Append(unicode); } break; } case eHTMLTag_whitespace: CurFrame().mPreviousText.Append(PRUnichar(' ')); break; case eHTMLTag_hr: HandleSeparator(aNode); break; } return NS_OK; } // BookmarkContentSink::HandleContainerBegin void BookmarkContentSink::HandleContainerBegin(const nsIParserNode& node) { CurFrame().mContainerNesting ++; } // BookmarkContentSink::HandleContainerEnd // // Our "indent" count has decreased, and when we hit 0 that means that this // container is complete and we need to pop back to the outer frame. Never // pop the toplevel frame void BookmarkContentSink::HandleContainerEnd() { BookmarkImportFrame& frame = CurFrame(); if (frame.mContainerNesting > 0) frame.mContainerNesting --; if (mFrames.Length() > 1 && frame.mContainerNesting == 0) { // we also need to re-set the imported last-modified date here. Otherwise // the addition of items will override the imported field. BookmarkImportFrame& prevFrame = PreviousFrame(); if (prevFrame.mPreviousLastModifiedDate > 0) { nsresult rv = mBookmarksService->SetItemLastModified(frame.mContainerID, prevFrame.mPreviousLastModifiedDate); NS_ASSERTION(NS_SUCCEEDED(rv), "SetItemLastModified failed"); } PopFrame(); } } // BookmarkContentSink::HandleHead1Begin // // Handles

      . We check for the attribute PLACES_ROOT and reset the // container id if it's found. Otherwise, the default bookmark menu // root is assumed and imported things will go into the bookmarks menu. void BookmarkContentSink::HandleHead1Begin(const nsIParserNode& node) { PRInt32 attrCount = node.GetAttributeCount(); for (PRInt32 i = 0; i < attrCount; i ++) { if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_PLACESROOT_LOWER)) { if (mFrames.Length() > 1) { NS_WARNING("Trying to set the places root from the middle of the hierarchy. " "This can only be set at the beginning."); return; } PRInt64 placesRoot; nsresult rv = mBookmarksService->GetPlacesRoot(&placesRoot); NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "could not get placesRoot"); CurFrame().mContainerID = placesRoot; break; } } } // BookmarkContentSink::HandleHeadBegin // // Called for h2,h3,h4,h5,h6. This just stores the correct information in // the current frame; the actual new frame corresponding to the container // associated with the heading will be created when the tag has been closed // and we know the title (we don't know to create a new folder or to merge // with an existing one until we have the title). void BookmarkContentSink::HandleHeadBegin(const nsIParserNode& node) { BookmarkImportFrame& frame = CurFrame(); // after a heading, a previous bookmark is not applicable (for example, for // the descriptions contained in a
      ). Neither is any previous head type frame.mPreviousLink = nsnull; frame.mLastContainerType = BookmarkImportFrame::Container_Normal; // It is syntactically possible for a heading to appear after another heading // but before the
      that encloses that folder's contents. This should not // happen in practice, as the file will contain "
      " sequence for // empty containers. // // Just to be on the safe side, if we encounter //

      FOO

      //

      BAR

      //
      ...content 1...
      //
      ...content 2...
      // we'll pop the stack when we find the h3 for BAR, treating that as an // implicit ending of the FOO container. The output will be FOO and BAR as // siblings. If there's another
      following (as in "content 2"), those // items will be treated as further siblings of FOO and BAR if (frame.mContainerNesting == 0) PopFrame(); // We have to check for some attributes to see if this is a "special" // folder, which will have different creation rules when the end tag is // processed. PRInt32 attrCount = node.GetAttributeCount(); frame.mLastContainerType = BookmarkImportFrame::Container_Normal; if (!mFolderSpecified) { for (PRInt32 i = 0; i < attrCount; i ++) { if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_TOOLBARFOLDER_LOWER)) { if (mIsImportDefaults) frame.mLastContainerType = BookmarkImportFrame::Container_Toolbar; break; } else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_BOOKMARKSMENU_LOWER)) { if (mIsImportDefaults) frame.mLastContainerType = BookmarkImportFrame::Container_Menu; break; } else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_PLACESROOT_LOWER)) { if (mIsImportDefaults) frame.mLastContainerType = BookmarkImportFrame::Container_Places; break; } else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_DATE_ADDED_LOWER)) { frame.mPreviousDateAdded = ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(node.GetValueAt(i))); } else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_LAST_MODIFIED_LOWER)) { frame.mPreviousLastModifiedDate = ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(node.GetValueAt(i))); } } } CurFrame().mPreviousText.Truncate(); } // BookmarkContentSink::HandleHeadEnd // // Creates the new frame for this heading now that we know the name of the // container (tokens since the heading open tag will have been placed in // mPreviousText). void BookmarkContentSink::HandleHeadEnd() { NewFrame(); } // BookmarkContentSink::HandleLinkBegin // // Handles " tags that have no href: we don't know what to do with them. if (href.IsEmpty()) { frame.mPreviousLink = nsnull; // The exception is for feeds, where the href is an optional component // indicating the source web site. if (!frame.mPreviousFeed) return; } else { // Save this so the link text and descriptions can be associated with it. // Note that we ignore errors if this is a feed: URLs aren't strictly // necessary in these cases. nsresult rv = NS_NewURI(getter_AddRefs(frame.mPreviousLink), href, nsnull); if (NS_FAILED(rv) && !frame.mPreviousFeed) { frame.mPreviousLink = nsnull; return; // invalid link } } // if there's a pre-existing Places bookmark ITEM_ID, use it frame.mPreviousId = ConvertImportedIdToInternalId(NS_ConvertUTF16toUTF8(itemId)); // Save last-modified-date, for setting after all the bookmark properties have been set. if (!lastModified.IsEmpty()) { frame.mPreviousLastModifiedDate = ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(lastModified)); } // if there is a feedURL, this is a livemark, which is a special case // that we handle in HandleLinkEnd(): don't create normal bookmarks if (frame.mPreviousFeed) return; // attempt to get a property for the supposedly pre-existing bookmark PRInt64 parent; if (frame.mPreviousId > 0) { rv = mBookmarksService->GetFolderIdForItem(frame.mPreviousId, &parent); if (NS_FAILED(rv) || frame.mContainerID != parent) frame.mPreviousId = 0; } // if no previous id (or a legacy id), create a new bookmark if (frame.mPreviousId == 0) { // create the bookmark rv = mBookmarksService->InsertBookmark(frame.mContainerID, frame.mPreviousLink, mBookmarksService->DEFAULT_INDEX, EmptyString(), &frame.mPreviousId); NS_ASSERTION(NS_SUCCEEDED(rv), "InsertBookmark failed"); // set the date added value, if we have it // important: this has to happen after InsertBookmark // so that we set the imported value if (!dateAdded.IsEmpty()) { PRTime convertedDateAdded = ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(dateAdded)); if (convertedDateAdded) { rv = mBookmarksService->SetItemDateAdded(frame.mPreviousId, convertedDateAdded); NS_ASSERTION(NS_SUCCEEDED(rv), "SetItemDateAdded failed"); } } } // save the favicon, ignore errors if (!icon.IsEmpty() || !iconUri.IsEmpty()) { nsCOMPtr iconUriObject; NS_NewURI(getter_AddRefs(iconUriObject), iconUri); if (!icon.IsEmpty() || iconUriObject) { rv = SetFaviconForURI(frame.mPreviousLink, iconUriObject, NS_ConvertUTF16toUTF8(icon)); } } // save the keyword, ignore errors if (!keyword.IsEmpty()) { mBookmarksService->SetKeywordForBookmark(frame.mPreviousId, keyword); // post data if (!postData.IsEmpty()) { mAnnotationService->SetPageAnnotationString(frame.mPreviousLink, POST_DATA_ANNO, postData, 0, nsIAnnotationService::EXPIRE_NEVER); } } if (webPanel.LowerCaseEqualsLiteral("true")) { // set load-in-sidebar annotation for the bookmark mAnnotationService->SetItemAnnotationInt32(frame.mPreviousId, LOAD_IN_SIDEBAR_ANNO, 1, 0, nsIAnnotationService::EXPIRE_NEVER); } // import microsummary if (!micsumGenURI.IsEmpty()) { nsCOMPtr micsumGenURIObject; if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(micsumGenURIObject), micsumGenURI))) { mMicrosummaryService->CreateMicrosummary(frame.mPreviousLink, micsumGenURIObject, getter_AddRefs(frame.mPreviousMicrosummary)); frame.mPreviousMicrosummaryText = generatedTitle; } } // import last charset if (!lastCharset.IsEmpty()) { PRBool hasCharset = PR_FALSE; mAnnotationService->PageHasAnnotation(frame.mPreviousLink, LAST_CHARSET_ANNO, &hasCharset); if (!hasCharset) mAnnotationService->SetPageAnnotationString(frame.mPreviousLink, LAST_CHARSET_ANNO, lastCharset, 0, nsIAnnotationService::EXPIRE_NEVER); } } // BookmarkContentSink::HandleLinkEnd // // Saves the title for the given bookmark. This only writes the user title. // Any previous title will be untouched. If this is a new entry, it will have // an empty "official" title until you visit it. void BookmarkContentSink::HandleLinkEnd() { nsresult rv; BookmarkImportFrame& frame = CurFrame(); frame.mPreviousText.Trim(kWhitespace); if (frame.mPreviousFeed) { // The bookmark is actually a livemark. Create it here. // (It gets created here instead of in HandleLinkBegin() // because we need to know the title before creating it.) // check id validity if (frame.mPreviousId > 0) { PRInt64 parent; nsresult rv = mBookmarksService->GetFolderIdForItem(frame.mPreviousId, &parent); if (NS_FAILED(rv) || parent != frame.mContainerID) { frame.mPreviousId = 0; } } PRBool isLivemark = PR_FALSE; if (frame.mPreviousId > 0) { mLivemarkService->IsLivemark(frame.mPreviousId, &isLivemark); if (isLivemark) { // It's a pre-existing livemark, so update its properties #ifdef DEBUG_IMPORT PrintNesting(); printf("Updating livemark '%s' %lld\n", NS_ConvertUTF16toUTF8(frame.mPreviousText).get(), frame.mPreviousId); #endif rv = mLivemarkService->SetSiteURI(frame.mPreviousId, frame.mPreviousLink); NS_ASSERTION(NS_SUCCEEDED(rv), "SetSiteURI failed!"); rv = mLivemarkService->SetFeedURI(frame.mPreviousId, frame.mPreviousFeed); NS_ASSERTION(NS_SUCCEEDED(rv), "SetFeedURI failed!"); rv = mBookmarksService->SetItemTitle(frame.mPreviousId, frame.mPreviousText); NS_ASSERTION(NS_SUCCEEDED(rv), "SetItemTitle failed!"); } } if (!isLivemark) { if (mIsImportDefaults) { rv = mLivemarkService->CreateLivemarkFolderOnly(mBookmarksService, frame.mContainerID, frame.mPreviousText, frame.mPreviousLink, frame.mPreviousFeed, -1, &frame.mPreviousId); NS_ASSERTION(NS_SUCCEEDED(rv), "CreateLivemarkFolderOnly failed!"); } else { rv = mLivemarkService->CreateLivemark(frame.mContainerID, frame.mPreviousText, frame.mPreviousLink, frame.mPreviousFeed, -1, &frame.mPreviousId); NS_ASSERTION(NS_SUCCEEDED(rv), "CreateLivemark failed!"); } #ifdef DEBUG_IMPORT PrintNesting(); printf("Creating livemark '%s' %lld\n", NS_ConvertUTF16toUTF8(frame.mPreviousText).get(), frame.mPreviousId); #endif } } else if (frame.mPreviousLink) { #ifdef DEBUG_IMPORT PrintNesting(); printf("Creating bookmark '%s' %lld\n", NS_ConvertUTF16toUTF8(frame.mPreviousText).get(), frame.mPreviousId); #endif if (frame.mPreviousMicrosummary) { rv = mAnnotationService->SetItemAnnotationString(frame.mPreviousId, STATIC_TITLE_ANNO, frame.mPreviousText, 0, nsIAnnotationService::EXPIRE_NEVER); NS_ASSERTION(NS_SUCCEEDED(rv), "Could not store user's bookmark title!"); mBookmarksService->SetItemTitle(frame.mPreviousId, frame.mPreviousMicrosummaryText); mMicrosummaryService->SetMicrosummary(frame.mPreviousId, frame.mPreviousMicrosummary); } else mBookmarksService->SetItemTitle(frame.mPreviousId, frame.mPreviousText); } // Set last-modified-date for bookmarks and livemarks here so that the // imported date overrides the date from the call to that sets the description // that we made above. if (frame.mPreviousId > 0 && frame.mPreviousLastModifiedDate > 0) { rv = mBookmarksService->SetItemLastModified(frame.mPreviousId, frame.mPreviousLastModifiedDate); NS_ASSERTION(NS_SUCCEEDED(rv), "SetItemLastModified failed"); // Note: don't clear mPreviousLastModifiedDate, because if this item has a // description, we'll need to set it again. } frame.mPreviousText.Truncate(); } // BookmarkContentSink::HandleSeparator // // Inserts a separator into the current container void BookmarkContentSink::HandleSeparator(const nsIParserNode& aNode) { BookmarkImportFrame& frame = CurFrame(); // create the separator #ifdef DEBUG_IMPORT PrintNesting(); printf("--------\n"); #endif mBookmarksService->InsertSeparator(frame.mContainerID, mBookmarksService->DEFAULT_INDEX, &frame.mPreviousId); // Import separator title if set nsAutoString name; PRInt32 attrCount = aNode.GetAttributeCount(); for (PRInt32 i = 0; i < attrCount; i ++) { const nsAString& key = aNode.GetKeyAt(i); if (key.LowerCaseEqualsLiteral(KEY_NAME_LOWER)) name = aNode.GetValueAt(i); } name.Trim(kWhitespace); if (!name.IsEmpty()) mBookmarksService->SetItemTitle(frame.mPreviousId, name); // Note: we do not need to import ADD_DATE or LAST_MODIFIED for separators // because pre-Places bookmarks does not support them. // and we can't write them out because attributes other than NAME // will make Firefox 2.x crash/hang - see bug #381129 } // BookmarkContentSink::NewFrame // // This is called when there is a new folder found. The folder takes the // name from the previous frame's heading. nsresult BookmarkContentSink::NewFrame() { nsresult rv; PRInt64 ourID = 0; nsString containerName; BookmarkImportFrame::ContainerType containerType; BookmarkImportFrame& frame = CurFrame(); frame.ConsumeHeading(&containerName, &containerType); PRBool updateFolder = PR_FALSE; switch (containerType) { case BookmarkImportFrame::Container_Normal: // append a new folder rv = mBookmarksService->CreateFolder(CurFrame().mContainerID, containerName, mBookmarksService->DEFAULT_INDEX, &ourID); NS_ENSURE_SUCCESS(rv, rv); break; case BookmarkImportFrame::Container_Places: // places root, never reparent here, when we're building the initial // hierarchy, it will only be defined at the top level rv = mBookmarksService->GetPlacesRoot(&ourID); NS_ENSURE_SUCCESS(rv, rv); break; case BookmarkImportFrame::Container_Menu: // menu root rv = mBookmarksService->GetBookmarksMenuFolder(&ourID); NS_ENSURE_SUCCESS(rv, rv); if (mAllowRootChanges) updateFolder = PR_TRUE; break; case BookmarkImportFrame::Container_Toolbar: // get toolbar folder rv = mBookmarksService->GetToolbarFolder(&ourID); NS_ENSURE_SUCCESS(rv, rv); // In Fx2, the toolbar folder is a child of the bookmarks menu, listed // between two separators: // 1) Get Bookmarks Addons 2) Separator 3) Bookmarks Toolbar Folder // 4) Separator 5) Mozilla Firefox Folder // In Places, the toolbar folder is a direct child of the places root, // meaning that we end up with two sequential separators. if (frame.mPreviousId > 0) { PRUint16 itemType; rv = mBookmarksService->GetItemType(frame.mPreviousId, &itemType); NS_ENSURE_SUCCESS(rv, rv); if (itemType == nsINavBookmarksService::TYPE_SEPARATOR) { // remove it rv = mBookmarksService->RemoveItem(frame.mPreviousId); NS_ENSURE_SUCCESS(rv, rv); } } break; default: NS_NOTREACHED("Unknown container type"); } #ifdef DEBUG_IMPORT PrintNesting(); printf("Folder %lld \'%s\'", ourID, NS_ConvertUTF16toUTF8(containerName).get()); #endif if (updateFolder) { // move the menu folder to the current position mBookmarksService->MoveItem(ourID, CurFrame().mContainerID, -1); mBookmarksService->SetItemTitle(ourID, containerName); #ifdef DEBUG_IMPORT printf(" [reparenting]"); #endif } #ifdef DEBUG_IMPORT printf("\n"); #endif if (frame.mPreviousDateAdded > 0) { nsresult rv = mBookmarksService->SetItemDateAdded(ourID, frame.mPreviousDateAdded); NS_ASSERTION(NS_SUCCEEDED(rv), "SetItemDateAdded failed"); frame.mPreviousDateAdded = 0; } if (frame.mPreviousLastModifiedDate > 0) { nsresult rv = mBookmarksService->SetItemLastModified(ourID, frame.mPreviousLastModifiedDate); NS_ASSERTION(NS_SUCCEEDED(rv), "SetItemLastModified failed"); // don't clear last-modified, in case there's a description } if (!mFrames.AppendElement(BookmarkImportFrame(ourID))) return NS_ERROR_OUT_OF_MEMORY; frame.mPreviousId = ourID; return NS_OK; } // BookmarkContentSink::PopFrame // nsresult BookmarkContentSink::PopFrame() { // we must always have one frame if (mFrames.Length() <= 1) { NS_NOTREACHED("Trying to complete more bookmark folders than you started"); return NS_ERROR_FAILURE; } mFrames.RemoveElementAt(mFrames.Length() - 1); return NS_OK; } // BookmarkContentSink::SetFaviconForURI // // aData is a string that is a data URI for the favicon. Our job is to // decode it and store it in the favicon service. // // When aIconURI is non-null, we will use that as the URI of the favicon // when storing in the favicon service. // // When aIconURI is null, we have to make up a URI for this favicon so that // it can be stored in the service. The real one will be set the next time // the user visits the page. Our made up one should get expired when the // page no longer references it. nsresult BookmarkContentSink::SetFaviconForURI(nsIURI* aPageURI, nsIURI* aIconURI, const nsCString& aData) { nsresult rv; static PRUint32 serialNumber = 0; // for made-up favicon URIs nsCOMPtr faviconService(do_GetService(NS_FAVICONSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); // if the input favicon URI is a chrome: URI, then we just save it and don't // worry about data if (aIconURI) { nsCString faviconScheme; aIconURI->GetScheme(faviconScheme); if (faviconScheme.EqualsLiteral("chrome")) { return faviconService->SetFaviconUrlForPage(aPageURI, aIconURI); } } // some bookmarks have placeholder URIs that contain just "data:" // ignore these if (aData.Length() <= 5) return NS_OK; nsCOMPtr faviconURI; if (aIconURI) { faviconURI = aIconURI; } else { // make up favicon URL nsCAutoString faviconSpec; faviconSpec.AssignLiteral("http://www.mozilla.org/2005/made-up-favicon/"); faviconSpec.AppendInt(serialNumber); faviconSpec.AppendLiteral("-"); char buf[32]; PR_snprintf(buf, sizeof(buf), "%lld", PR_Now()); faviconSpec.Append(buf); rv = NS_NewURI(getter_AddRefs(faviconURI), faviconSpec); NS_ENSURE_SUCCESS(rv, rv); serialNumber++; } nsCOMPtr dataURI; rv = NS_NewURI(getter_AddRefs(dataURI), aData); NS_ENSURE_SUCCESS(rv, rv); // use the data: protocol handler to convert the data nsCOMPtr ioService = do_GetIOService(&rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr protocolHandler; rv = ioService->GetProtocolHandler("data", getter_AddRefs(protocolHandler)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr channel; rv = protocolHandler->NewChannel(dataURI, getter_AddRefs(channel)); NS_ENSURE_SUCCESS(rv, rv); // blocking stream is OK for data URIs nsCOMPtr stream; rv = channel->Open(getter_AddRefs(stream)); NS_ENSURE_SUCCESS(rv, rv); PRUint32 available; rv = stream->Available(&available); NS_ENSURE_SUCCESS(rv, rv); if (available == 0) return NS_ERROR_FAILURE; // read all the decoded data PRUint8* buffer = static_cast (nsMemory::Alloc(sizeof(PRUint8) * available)); if (!buffer) return NS_ERROR_OUT_OF_MEMORY; PRUint32 numRead; rv = stream->Read(reinterpret_cast(buffer), available, &numRead); if (NS_FAILED(rv) || numRead != available) { nsMemory::Free(buffer); return rv; } nsCAutoString mimeType; rv = channel->GetContentType(mimeType); NS_ENSURE_SUCCESS(rv, rv); // save in service rv = faviconService->SetFaviconData(faviconURI, buffer, available, mimeType, 0); nsMemory::Free(buffer); NS_ENSURE_SUCCESS(rv, rv); rv = faviconService->SetFaviconUrlForPage(aPageURI, faviconURI); return NS_OK; } // Converts a string id (ITEM_ID) into an int id PRInt64 BookmarkContentSink::ConvertImportedIdToInternalId(const nsCString& aId) { PRInt64 intId = 0; if (aId.IsEmpty()) return intId; nsresult rv; intId = aId.ToInteger(&rv); if (NS_FAILED(rv)) intId = 0; return intId; } // Converts a string date in seconds to an int date in microseconds PRTime BookmarkContentSink::ConvertImportedDateToInternalDate(const nsACString& aDate) { PRTime convertedDate = 0; if (!aDate.IsEmpty()) { nsresult rv; convertedDate = aDate.ToInteger(&rv); if (NS_SUCCEEDED(rv)) { convertedDate *= 1000000; // in bookmarks.html this value is in seconds, not microseconds } else { convertedDate = 0; } } return convertedDate; } // SyncChannelStatus // // If a function returns an error, we need to set the channel status to be // the same, but only if the channel doesn't have its own error. This returns // the error code that should be sent to OnStopRequest. static nsresult SyncChannelStatus(nsIChannel* channel, nsresult status) { nsresult channelStatus; channel->GetStatus(&channelStatus); if (NS_FAILED(channelStatus)) return channelStatus; if (NS_SUCCEEDED(status)) return NS_OK; // caller and the channel are happy // channel was OK, but caller wasn't: set the channel state channel->Cancel(status); return status; } static char kFileIntro[] = "" NS_LINEBREAK // Note: we write bookmarks in UTF-8 "" NS_LINEBREAK "" NS_LINEBREAK "Bookmarks" NS_LINEBREAK; static const char kRootIntro[] = "

      // // Goes after the container header (Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kBookmarkIntro, sizeof(kBookmarkIntro)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } // WriteContainerEpilogue // //

      // // Goes after the container contents to close the container static nsresult WriteContainerEpilogue(const nsACString& aIndent, nsIOutputStream* aOutput) { PRUint32 dummy; nsresult rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kBookmarkClose, sizeof(kBookmarkClose)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } // DataToDataURI static nsresult DataToDataURI(PRUint8* aData, PRUint32 aDataLen, const nsACString& aMimeType, nsACString& aDataURI) { char* encoded = PL_Base64Encode(reinterpret_cast(aData), aDataLen, nsnull); if (!encoded) return NS_ERROR_OUT_OF_MEMORY; aDataURI.AssignLiteral("data:"); aDataURI.Append(aMimeType); aDataURI.AppendLiteral(";base64,"); aDataURI.Append(encoded); nsMemory::Free(encoded); return NS_OK; } // WriteFaviconAttribute // // This writes the 'ICON="data:asdlfkjas;ldkfja;skdljfasdf"' attribute for // an item. We special-case chrome favicon URIs by just writing the chrome: // URI. static nsresult WriteFaviconAttribute(const nsACString& aURI, nsIOutputStream* aOutput) { nsresult rv; PRUint32 dummy; nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), aURI); NS_ENSURE_SUCCESS(rv, rv); // get favicon nsCOMPtr faviconService = do_GetService(NS_FAVICONSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr faviconURI; rv = faviconService->GetFaviconForPage(uri, getter_AddRefs(faviconURI)); if (rv == NS_ERROR_NOT_AVAILABLE) return NS_OK; // no favicon NS_ENSURE_SUCCESS(rv, rv); // anything else is error nsCAutoString faviconScheme; nsCAutoString faviconSpec; rv = faviconURI->GetSpec(faviconSpec); NS_ENSURE_SUCCESS(rv, rv); rv = faviconURI->GetScheme(faviconScheme); NS_ENSURE_SUCCESS(rv, rv); // write favicon URI: 'ICON_URI="..."' rv = aOutput->Write(kIconURIAttribute, sizeof(kIconURIAttribute)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); rv = WriteEscapedUrl(faviconSpec, aOutput); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); if (!faviconScheme.EqualsLiteral("chrome")) { // only store data for non-chrome URIs // get the data - BE SURE TO FREE nsCAutoString mimeType; PRUint32 dataLen; PRUint8* data; rv = faviconService->GetFaviconData(faviconURI, mimeType, &dataLen, &data); NS_ENSURE_SUCCESS(rv, rv); if (dataLen > 0) { // convert to URI nsCString faviconContents; rv = DataToDataURI(data, dataLen, mimeType, faviconContents); nsMemory::Free(data); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kIconAttribute, sizeof(kIconAttribute)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(faviconContents.get(), faviconContents.Length(), &dummy); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); } } return NS_OK; } // WriteDateAttribute // // This writes the '{attr value=}"{time in seconds}"' attribute for // an item. static nsresult WriteDateAttribute(const char aAttributeStart[], PRInt32 aLength, PRTime aAttributeValue, nsIOutputStream* aOutput) { // write attribute start PRUint32 dummy; nsresult rv = aOutput->Write(aAttributeStart, aLength, &dummy); NS_ENSURE_SUCCESS(rv, rv); // in bookmarks.html this value is in seconds, not microseconds aAttributeValue /= 1000000; // write attribute value char dateInSeconds[32]; PR_snprintf(dateInSeconds, sizeof(dateInSeconds), "%lld", aAttributeValue); rv = aOutput->Write(dateInSeconds, strlen(dateInSeconds), &dummy); NS_ENSURE_SUCCESS(rv, rv); return aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); } // nsPlacesImportExportService::WriteContainer // // Writes out all the necessary parts of a bookmarks folder. nsresult nsPlacesImportExportService::WriteContainer(nsINavHistoryResultNode* aFolder, const nsACString& aIndent, nsIOutputStream* aOutput) { nsresult rv = WriteContainerHeader(aFolder, aIndent, aOutput); NS_ENSURE_SUCCESS(rv, rv); rv = WriteContainerPrologue(aIndent, aOutput); NS_ENSURE_SUCCESS(rv, rv); rv = WriteContainerContents(aFolder, aIndent, aOutput); NS_ENSURE_SUCCESS(rv, rv); rv = WriteContainerEpilogue(aIndent, aOutput); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } // nsPlacesImportExportService::WriteContainerHeader // // This writes '

      Title

      ' // Remember folders can also have favicons, which we put in the H3 tag nsresult nsPlacesImportExportService::WriteContainerHeader(nsINavHistoryResultNode* aFolder, const nsACString& aIndent, nsIOutputStream* aOutput) { PRUint32 dummy; nsresult rv; // indent if (!aIndent.IsEmpty()) { rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); NS_ENSURE_SUCCESS(rv, rv); } // "
      Write(kContainerIntro, sizeof(kContainerIntro)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); // get folder id PRInt64 folderId; rv = aFolder->GetItemId(&folderId); NS_ENSURE_SUCCESS(rv, rv); // write ADD_DATE PRTime dateAdded = 0; rv = aFolder->GetDateAdded(&dateAdded); NS_ENSURE_SUCCESS(rv, rv); if (dateAdded) { rv = WriteDateAttribute(kDateAddedAttribute, sizeof(kDateAddedAttribute)-1, dateAdded, aOutput); NS_ENSURE_SUCCESS(rv, rv); } // write LAST_MODIFIED PRTime lastModified = 0; rv = aFolder->GetLastModified(&lastModified); NS_ENSURE_SUCCESS(rv, rv); if (lastModified) { rv = WriteDateAttribute(kLastModifiedAttribute, sizeof(kLastModifiedAttribute)-1, lastModified, aOutput); NS_ENSURE_SUCCESS(rv, rv); } PRInt64 placesRoot; rv = mBookmarksService->GetPlacesRoot(&placesRoot); NS_ENSURE_SUCCESS(rv,rv); PRInt64 bookmarksMenuFolder; rv = mBookmarksService->GetBookmarksMenuFolder(&bookmarksMenuFolder); NS_ENSURE_SUCCESS(rv,rv); PRInt64 toolbarFolder; rv = mBookmarksService->GetToolbarFolder(&toolbarFolder); NS_ENSURE_SUCCESS(rv,rv); // " PERSONAL_TOOLBAR_FOLDER="true"", etc. if (folderId == placesRoot) { rv = aOutput->Write(kPlacesRootAttribute, sizeof(kPlacesRootAttribute)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); } else if (folderId == bookmarksMenuFolder) { rv = aOutput->Write(kBookmarksRootAttribute, sizeof(kBookmarksRootAttribute)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); } else if (folderId == toolbarFolder) { rv = aOutput->Write(kToolbarFolderAttribute, sizeof(kToolbarFolderAttribute)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); } // ">" rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); // title rv = WriteTitle(aFolder, aOutput); NS_ENSURE_SUCCESS(rv, rv); // "

      \n" rv = aOutput->Write(kContainerClose, sizeof(kContainerClose)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); // description rv = WriteDescription(folderId, nsINavBookmarksService::TYPE_FOLDER, aOutput); NS_ENSURE_SUCCESS(rv, rv); return rv; } // nsPlacesImportExportService::WriteTitle // // Retrieves, escapes and writes the title to the stream. nsresult nsPlacesImportExportService::WriteTitle(nsINavHistoryResultNode* aItem, nsIOutputStream* aOutput) { // XXX Bug 381767 - support titles for separators PRUint32 type = 0; nsresult rv = aItem->GetType(&type); NS_ENSURE_SUCCESS(rv, rv); if (type == nsINavHistoryResultNode::RESULT_TYPE_SEPARATOR) return NS_ERROR_INVALID_ARG; nsCAutoString title; rv = aItem->GetTitle(title); NS_ENSURE_SUCCESS(rv, rv); char* escapedTitle = nsEscapeHTML(title.get()); if (escapedTitle) { PRUint32 dummy; rv = aOutput->Write(escapedTitle, strlen(escapedTitle), &dummy); nsMemory::Free(escapedTitle); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } // nsPlacesImportExportService::WriteDescription // // Write description out for all item types. nsresult nsPlacesImportExportService::WriteDescription(PRInt64 aItemId, PRInt32 aType, nsIOutputStream* aOutput) { PRBool hasDescription = PR_FALSE; nsresult rv = mAnnotationService->ItemHasAnnotation(aItemId, DESCRIPTION_ANNO, &hasDescription); if (NS_FAILED(rv) || !hasDescription) return rv; nsAutoString description; rv = mAnnotationService->GetItemAnnotationString(aItemId, DESCRIPTION_ANNO, description); NS_ENSURE_SUCCESS(rv, rv); char* escapedDesc = nsEscapeHTML(NS_ConvertUTF16toUTF8(description).get()); if (escapedDesc) { PRUint32 dummy; rv = aOutput->Write(kDescriptionIntro, sizeof(kDescriptionIntro)-1, &dummy); if (NS_FAILED(rv)) { nsMemory::Free(escapedDesc); return rv; } rv = aOutput->Write(escapedDesc, strlen(escapedDesc), &dummy); nsMemory::Free(escapedDesc); NS_ENSURE_SUCCESS(rv, rv); aOutput->Write(kDescriptionClose, sizeof(kDescriptionClose)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } // nsBookmarks::WriteItem // // "
      Name" nsresult nsPlacesImportExportService::WriteItem(nsINavHistoryResultNode* aItem, const nsACString& aIndent, nsIOutputStream* aOutput) { PRUint32 dummy; nsresult rv; // indent if (!aIndent.IsEmpty()) { rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); NS_ENSURE_SUCCESS(rv, rv); } // '
      Write(kItemOpen, sizeof(kItemOpen)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); // ' HREF="http://..."' - note that we need to call GetURI on the result // node because some nodes (eg queries) generate this lazily. rv = aOutput->Write(kHrefAttribute, sizeof(kHrefAttribute)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); nsCAutoString uri; rv = aItem->GetUri(uri); NS_ENSURE_SUCCESS(rv, rv); rv = WriteEscapedUrl(uri, aOutput); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); // write ADD_DATE PRTime dateAdded = 0; rv = aItem->GetDateAdded(&dateAdded); NS_ENSURE_SUCCESS(rv, rv); if (dateAdded) { rv = WriteDateAttribute(kDateAddedAttribute, sizeof(kDateAddedAttribute)-1, dateAdded, aOutput); NS_ENSURE_SUCCESS(rv, rv); } // write LAST_MODIFIED PRTime lastModified = 0; rv = aItem->GetLastModified(&lastModified); NS_ENSURE_SUCCESS(rv, rv); if (lastModified) { rv = WriteDateAttribute(kLastModifiedAttribute, sizeof(kLastModifiedAttribute)-1, lastModified, aOutput); NS_ENSURE_SUCCESS(rv, rv); } // ' ICON="..."' rv = WriteFaviconAttribute(uri, aOutput); NS_ENSURE_SUCCESS(rv, rv); // get item id PRInt64 itemId; rv = aItem->GetItemId(&itemId); NS_ENSURE_SUCCESS(rv, rv); // keyword (shortcuturl) nsAutoString keyword; rv = mBookmarksService->GetKeywordForBookmark(itemId, keyword); NS_ENSURE_SUCCESS(rv, rv); if (!keyword.IsEmpty()) { rv = aOutput->Write(kKeywordAttribute, sizeof(kKeywordAttribute)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); char* escapedKeyword = nsEscapeHTML(NS_ConvertUTF16toUTF8(keyword).get()); rv = aOutput->Write(escapedKeyword, strlen(escapedKeyword), &dummy); nsMemory::Free(escapedKeyword); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); } // post data nsCOMPtr pageURI; rv = NS_NewURI(getter_AddRefs(pageURI), uri, nsnull); NS_ENSURE_SUCCESS(rv, rv); PRBool hasPostData; rv = mAnnotationService->PageHasAnnotation(pageURI, POST_DATA_ANNO, &hasPostData); NS_ENSURE_SUCCESS(rv, rv); if (hasPostData) { nsAutoString postData; rv = mAnnotationService->GetPageAnnotationString(pageURI, POST_DATA_ANNO, postData); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kPostDataAttribute, sizeof(kPostDataAttribute)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); char* escapedPostData = nsEscapeHTML(NS_ConvertUTF16toUTF8(postData).get()); rv = aOutput->Write(escapedPostData, strlen(escapedPostData), &dummy); nsMemory::Free(escapedPostData); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); } // Write WEB_PANEL="true" if the load-in-sidebar annotation is set for the // item PRBool loadInSidebar = PR_FALSE; rv = mAnnotationService->ItemHasAnnotation(itemId, LOAD_IN_SIDEBAR_ANNO, &loadInSidebar); NS_ENSURE_SUCCESS(rv, rv); if (loadInSidebar) aOutput->Write(kWebPanelAttribute, sizeof(kWebPanelAttribute)-1, &dummy); // microsummary nsCOMPtr microsummary; rv = mMicrosummaryService->GetMicrosummary(itemId, getter_AddRefs(microsummary)); NS_ENSURE_SUCCESS(rv, rv); if (microsummary) { nsCOMPtr generator; rv = microsummary->GetGenerator(getter_AddRefs(generator)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr genURI; rv = generator->GetUri(getter_AddRefs(genURI)); NS_ENSURE_SUCCESS(rv, rv); nsCAutoString spec; rv = genURI->GetSpec(spec); NS_ENSURE_SUCCESS(rv, rv); // write out generator URI rv = aOutput->Write(kMicsumGenURIAttribute, sizeof(kMicsumGenURIAttribute)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); rv = WriteEscapedUrl(spec, aOutput); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); } // last charset PRBool hasLastCharset = PR_FALSE; rv = mAnnotationService->PageHasAnnotation(pageURI, LAST_CHARSET_ANNO, &hasLastCharset); NS_ENSURE_SUCCESS(rv, rv); if (hasLastCharset) { nsAutoString lastCharset; rv = mAnnotationService->GetPageAnnotationString(pageURI, LAST_CHARSET_ANNO, lastCharset); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kLastCharsetAttribute, sizeof(kLastCharsetAttribute)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); char* escapedLastCharset = nsEscapeHTML(NS_ConvertUTF16toUTF8(lastCharset).get()); rv = aOutput->Write(escapedLastCharset, strlen(escapedLastCharset), &dummy); nsMemory::Free(escapedLastCharset); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); } // '>' rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); // title nsCAutoString title; rv = aItem->GetTitle(title); NS_ENSURE_SUCCESS(rv, rv); char* escapedTitle = nsEscapeHTML(title.get()); if (escapedTitle) { rv = aOutput->Write(escapedTitle, strlen(escapedTitle), &dummy); nsMemory::Free(escapedTitle); NS_ENSURE_SUCCESS(rv, rv); } // '\n' rv = aOutput->Write(kItemClose, sizeof(kItemClose)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); // description rv = WriteDescription(itemId, nsINavBookmarksService::TYPE_BOOKMARK, aOutput); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } // WriteLivemark // // Similar to WriteItem, this has an additional FEEDURL attribute and // the HREF is optional and points to the source page. nsresult nsPlacesImportExportService::WriteLivemark(nsINavHistoryResultNode* aFolder, const nsACString& aIndent, nsIOutputStream* aOutput) { PRUint32 dummy; nsresult rv; // indent if (!aIndent.IsEmpty()) { rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); NS_ENSURE_SUCCESS(rv, rv); } // '
      Write(kItemOpen, sizeof(kItemOpen)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); // get folder id PRInt64 folderId; rv = aFolder->GetItemId(&folderId); NS_ENSURE_SUCCESS(rv, rv); // get feed URI nsCOMPtr feedURI; rv = mLivemarkService->GetFeedURI(folderId, getter_AddRefs(feedURI)); NS_ENSURE_SUCCESS(rv, rv); if (feedURI) { nsCString feedSpec; rv = feedURI->GetSpec(feedSpec); NS_ENSURE_SUCCESS(rv, rv); // write feed URI rv = aOutput->Write(kFeedURIAttribute, sizeof(kFeedURIAttribute)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); rv = WriteEscapedUrl(feedSpec, aOutput); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); } // get the optional site URI nsCOMPtr siteURI; rv = mLivemarkService->GetSiteURI(folderId, getter_AddRefs(siteURI)); NS_ENSURE_SUCCESS(rv, rv); if (siteURI) { nsCString siteSpec; rv = siteURI->GetSpec(siteSpec); NS_ENSURE_SUCCESS(rv, rv); // write site URI rv = aOutput->Write(kHrefAttribute, sizeof(kHrefAttribute)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); rv = WriteEscapedUrl(siteSpec, aOutput); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); } // '>' rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); // title rv = WriteTitle(aFolder, aOutput); NS_ENSURE_SUCCESS(rv, rv); // '\n' rv = aOutput->Write(kItemClose, sizeof(kItemClose)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); // description rv = WriteDescription(folderId, nsINavBookmarksService::TYPE_BOOKMARK, aOutput); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } // nsPlacesImportExportService::WriteSeparator // // "
      " nsresult nsPlacesImportExportService::WriteSeparator(nsINavHistoryResultNode* aItem, const nsACString& aIndent, nsIOutputStream* aOutput) { PRUint32 dummy; nsresult rv; // indent if (!aIndent.IsEmpty()) { rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); NS_ENSURE_SUCCESS(rv, rv); } rv = aOutput->Write(kSeparator, sizeof(kSeparator)-1, &dummy); // XXX: separator result nodes don't support the title getter yet PRInt64 itemId; rv = aItem->GetItemId(&itemId); NS_ENSURE_SUCCESS(rv, rv); // Note: we can't write the separator ID or anything else other than NAME // because it makes Firefox 2.x crash/hang - see bug #381129 nsAutoString title; rv = mBookmarksService->GetItemTitle(itemId, title); if (NS_SUCCEEDED(rv) && !title.IsEmpty()) { rv = aOutput->Write(kNameAttribute, strlen(kNameAttribute), &dummy); NS_ENSURE_SUCCESS(rv, rv); char* escapedTitle = nsEscapeHTML(NS_ConvertUTF16toUTF8(title).get()); if (escapedTitle) { PRUint32 dummy; rv = aOutput->Write(escapedTitle, strlen(escapedTitle), &dummy); nsMemory::Free(escapedTitle); NS_ENSURE_SUCCESS(rv, rv); rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); } } // '>' rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); // line break rv = aOutput->Write(NS_LINEBREAK, sizeof(NS_LINEBREAK)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); return rv; } // WriteEscapedUrl // // Writes the given string to the stream escaped as necessary for URLs. // // Unfortunately, the old bookmarks system uses a custom hardcoded and // braindead escaping scheme that we need to emulate. It just replaces // quotes with %22 and that's it. nsresult WriteEscapedUrl(const nsCString& aString, nsIOutputStream* aOutput) { nsCAutoString escaped(aString); PRInt32 offset; while ((offset = escaped.FindChar('\"')) >= 0) { escaped.Cut(offset, 1); escaped.Insert(NS_LITERAL_CSTRING("%22"), offset); } PRUint32 dummy; return aOutput->Write(escaped.get(), escaped.Length(), &dummy); } // nsPlacesImportExportService::WriteContainerContents // // The indent here is the indent of the parent. We will add an additional // indent before writing data. nsresult nsPlacesImportExportService::WriteContainerContents(nsINavHistoryResultNode* aFolder, const nsACString& aIndent, nsIOutputStream* aOutput) { nsCAutoString myIndent(aIndent); myIndent.Append(kIndent); PRInt64 folderId; nsresult rv = aFolder->GetItemId(&folderId); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr folderNode = do_QueryInterface(aFolder, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = folderNode->SetContainerOpen(PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); PRUint32 childCount = 0; folderNode->GetChildCount(&childCount); for (PRUint32 i = 0; i < childCount; ++i) { nsCOMPtr child; rv = folderNode->GetChild(i, getter_AddRefs(child)); NS_ENSURE_SUCCESS(rv, rv); PRUint32 type = 0; rv = child->GetType(&type); NS_ENSURE_SUCCESS(rv, rv); if (type == nsINavHistoryResultNode::RESULT_TYPE_FOLDER) { // bookmarks folder PRInt64 childFolderId; rv = child->GetItemId(&childFolderId); NS_ENSURE_SUCCESS(rv, rv); // it could be a regular folder or it could be a livemark PRBool isLivemark; rv = mLivemarkService->IsLivemark(childFolderId, &isLivemark); NS_ENSURE_SUCCESS(rv, rv); if (isLivemark) rv = WriteLivemark(child, myIndent, aOutput); else rv = WriteContainer(child, myIndent, aOutput); } else if (type == nsINavHistoryResultNode::RESULT_TYPE_SEPARATOR) { rv = WriteSeparator(child, myIndent, aOutput); } else { rv = WriteItem(child, myIndent, aOutput); } NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } // nsIPlacesImportExportService::ImportHTMLFromFile // NS_IMETHODIMP nsPlacesImportExportService::ImportHTMLFromFile(nsILocalFile* aFile, PRBool aIsInitialImport) { // this version is exposed on the interface and disallows changing of roots return ImportHTMLFromFileInternal(aFile, PR_FALSE, 0, aIsInitialImport); } // nsIPlacesImportExportService::ImportHTMLFromFileToFolder // NS_IMETHODIMP nsPlacesImportExportService::ImportHTMLFromFileToFolder(nsILocalFile* aFile, PRInt64 aFolderId, PRBool aIsInitialImport) { // this version is exposed on the interface and disallows changing of roots return ImportHTMLFromFileInternal(aFile, PR_FALSE, aFolderId, aIsInitialImport); } nsresult nsPlacesImportExportService::ImportHTMLFromFileInternal(nsILocalFile* aFile, PRBool aAllowRootChanges, PRInt64 aFolder, PRBool aIsImportDefaults) { nsresult rv = EnsureServiceState(); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr file(do_QueryInterface(aFile)); #ifdef DEBUG_IMPORT nsAutoString path; file->GetPath(path); printf("\nImporting %s\n", NS_ConvertUTF16toUTF8(path).get()); #endif // confirm file exists PRBool exists; rv = file->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); if (!exists) { return NS_ERROR_INVALID_ARG; } nsCOMPtr parser = do_CreateInstance(kParserCID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr sink = new BookmarkContentSink; NS_ENSURE_TRUE(sink, NS_ERROR_OUT_OF_MEMORY); rv = sink->Init(aAllowRootChanges, mBookmarksService, aFolder, aIsImportDefaults); NS_ENSURE_SUCCESS(rv, rv); parser->SetContentSink(sink); // channel: note we have to set the content type or the default "unknown" type // will confuse the parser nsCOMPtr ioservice = do_GetIOService(&rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr fileURI; rv = ioservice->NewFileURI(file, getter_AddRefs(fileURI)); NS_ENSURE_SUCCESS(rv, rv); rv = ioservice->NewChannelFromURI(fileURI, getter_AddRefs(mImportChannel)); NS_ENSURE_SUCCESS(rv, rv); rv = mImportChannel->SetContentType(NS_LITERAL_CSTRING("text/html")); NS_ENSURE_SUCCESS(rv, rv); // init parser rv = parser->Parse(fileURI, nsnull); NS_ENSURE_SUCCESS(rv, rv); // wrap the import in a transaction to make it faster mIsImportDefaults = aIsImportDefaults; mBookmarksService->RunInBatchMode(this, parser); mImportChannel = nsnull; return NS_OK; } NS_IMETHODIMP nsPlacesImportExportService::RunBatched(nsISupports* aUserData) { nsresult rv; if (mIsImportDefaults) { PRInt64 bookmarksMenuFolder; rv = mBookmarksService->GetBookmarksMenuFolder(&bookmarksMenuFolder); NS_ENSURE_SUCCESS(rv,rv); rv = mBookmarksService->RemoveFolderChildren(bookmarksMenuFolder); NS_ENSURE_SUCCESS(rv, rv); PRInt64 toolbarFolder; rv = mBookmarksService->GetToolbarFolder(&toolbarFolder); NS_ENSURE_SUCCESS(rv,rv); rv = mBookmarksService->RemoveFolderChildren(toolbarFolder); NS_ENSURE_SUCCESS(rv, rv); } // streams nsCOMPtr stream; rv = mImportChannel->Open(getter_AddRefs(stream)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr bufferedstream; rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedstream), stream, 4096); NS_ENSURE_SUCCESS(rv, rv); // feed the parser the data // Note: on error, we always need to set the channel's status to be the // same, and to always call OnStopRequest with the channel error. nsCOMPtr listener = do_QueryInterface(aUserData, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = listener->OnStartRequest(mImportChannel, nsnull); rv = SyncChannelStatus(mImportChannel, rv); while (NS_SUCCEEDED(rv)) { PRUint32 available; rv = bufferedstream->Available(&available); if (rv == NS_BASE_STREAM_CLOSED) { rv = NS_OK; available = 0; } if (NS_FAILED(rv)) { mImportChannel->Cancel(rv); break; } if (!available) break; // blocking input stream has none available when done rv = listener->OnDataAvailable(mImportChannel, nsnull, bufferedstream, 0, available); rv = SyncChannelStatus(mImportChannel, rv); if (NS_FAILED(rv)) break; } listener->OnStopRequest(mImportChannel, nsnull, rv); return NS_OK; } // nsIPlacesImportExportService::ExportHMTLToFile // NS_IMETHODIMP nsPlacesImportExportService::ExportHTMLToFile(nsILocalFile* aBookmarksFile) { if (!aBookmarksFile) return NS_ERROR_NULL_POINTER; #ifdef DEBUG_EXPORT nsAutoString path; aBookmarksFile->GetPath(path); printf("\nExporting %s\n", NS_ConvertUTF16toUTF8(path).get()); PRTime startTime = PR_Now(); printf("\nStart time: %lld\n", startTime); #endif nsresult rv = EnsureServiceState(); NS_ENSURE_SUCCESS(rv, rv); // get a safe output stream, so we don't clobber the bookmarks file unless // all the writes succeeded. nsCOMPtr out; rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(out), aBookmarksFile, PR_WRONLY | PR_CREATE_FILE, /*octal*/ 0600, 0); NS_ENSURE_SUCCESS(rv, rv); // We need a buffered output stream for performance. // See bug 202477. nsCOMPtr strm; rv = NS_NewBufferedOutputStream(getter_AddRefs(strm), out, 4096); NS_ENSURE_SUCCESS(rv, rv); // get bookmarks menu folder id PRInt64 bookmarksMenuFolder; rv = mBookmarksService->GetBookmarksMenuFolder(&bookmarksMenuFolder); NS_ENSURE_SUCCESS(rv,rv); PRInt64 toolbarFolder; rv = mBookmarksService->GetToolbarFolder(&toolbarFolder); NS_ENSURE_SUCCESS(rv,rv); // file header PRUint32 dummy; rv = strm->Write(kFileIntro, sizeof(kFileIntro)-1, &dummy); NS_ENSURE_SUCCESS(rv, rv); // get empty options nsCOMPtr options; rv = mHistoryService->GetNewQueryOptions(getter_AddRefs(options)); NS_ENSURE_SUCCESS(rv, rv); // get a new query object nsCOMPtr query; rv = mHistoryService->GetNewQuery(getter_AddRefs(query)); NS_ENSURE_SUCCESS(rv, rv); // query for just this folder rv = query->SetFolders(&bookmarksMenuFolder, 1); NS_ENSURE_SUCCESS(rv, rv); // execute query nsCOMPtr result; rv = mHistoryService->ExecuteQuery(query, options, getter_AddRefs(result)); NS_ENSURE_SUCCESS(rv, rv); // get root (folder) node nsCOMPtr rootNode; rv = result->GetRoot(getter_AddRefs(rootNode)); NS_ENSURE_SUCCESS(rv, rv); // 'Write(kRootIntro, sizeof(kRootIntro)-1, &dummy); //

      Bookmarks

      rv = strm->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); // > NS_ENSURE_SUCCESS(rv, rv); rv = WriteTitle(rootNode, strm); NS_ENSURE_SUCCESS(rv, rv); rv = strm->Write(kCloseRootH1, sizeof(kCloseRootH1)-1, &dummy); //

      NS_ENSURE_SUCCESS(rv, rv); // prologue rv = WriteContainerPrologue(EmptyCString(), strm); NS_ENSURE_SUCCESS(rv, rv); // indents nsCAutoString indent; indent.Assign(kIndent); // write out bookmarks menu contents rv = WriteContainerContents(rootNode, EmptyCString(), strm); NS_ENSURE_SUCCESS(rv, rv); // write out the toolbar folder contents as a folder under the bookmarks-menu // for backwards compatibility rv = query->SetFolders(&toolbarFolder, 1); NS_ENSURE_SUCCESS(rv, rv); rv = mHistoryService->ExecuteQuery(query, options, getter_AddRefs(result)); NS_ENSURE_SUCCESS(rv, rv); // get root (folder) node rv = result->GetRoot(getter_AddRefs(rootNode)); NS_ENSURE_SUCCESS(rv, rv); rv = WriteContainer(rootNode, nsDependentCString(kIndent), strm); NS_ENSURE_SUCCESS(rv, rv); // epilogue rv = WriteContainerEpilogue(EmptyCString(), strm); NS_ENSURE_SUCCESS(rv, rv); // commit the write nsCOMPtr safeStream = do_QueryInterface(strm, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = safeStream->Finish(); #ifdef DEBUG_EXPORT printf("\nTotal time in seconds: %lld\n", (PR_Now() - startTime)/1000000); #endif return rv; } #define BROWSER_BOOKMARKS_OVERWRITE_PREF "browser.bookmarks.overwrite" #define BROWSER_BOOKMARKS_MAX_BACKUPS_PREF "browser.bookmarks.max_backups" #define POSTPLACES_BOOKMARKS_FILE "bookmarks.postplaces.html" NS_IMETHODIMP nsPlacesImportExportService::BackupBookmarksFile() { nsresult rv = EnsureServiceState(); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); // get bookmarks file nsCOMPtr bookmarksFileDir; rv = NS_GetSpecialDirectory(NS_APP_BOOKMARKS_50_FILE, getter_AddRefs(bookmarksFileDir)); PRBool overwriteBookmarks; rv = prefs->GetBoolPref(BROWSER_BOOKMARKS_OVERWRITE_PREF, &overwriteBookmarks); NS_ENSURE_SUCCESS(rv, rv); if (!overwriteBookmarks) { rv = bookmarksFileDir->SetLeafName(NS_LITERAL_STRING(POSTPLACES_BOOKMARKS_FILE)); NS_ENSURE_SUCCESS(rv, rv); } NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr bookmarksFile(do_QueryInterface(bookmarksFileDir)); // create if it doesn't exist PRBool exists; rv = bookmarksFile->Exists(&exists); if (NS_FAILED(rv)) { rv = bookmarksFile->Create(nsIFile::NORMAL_FILE_TYPE, 0600); NS_ASSERTION(rv, "Unable to create bookmarks.html!"); return rv; } // export bookmarks.html rv = ExportHTMLToFile(bookmarksFile); NS_ENSURE_SUCCESS(rv, rv); // archive if needed PRInt32 numberOfBackups; rv = prefs->GetIntPref(BROWSER_BOOKMARKS_MAX_BACKUPS_PREF, &numberOfBackups); if (NS_FAILED(rv)) numberOfBackups = 5; if (numberOfBackups > 0) { rv = ArchiveBookmarksFile(numberOfBackups, PR_FALSE); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } /** * ArchiveBookmarksFile() * * Creates a dated backup once a day in /bookmarkbackups * * PRInt32 numberOfBackups - the maximum number of backups to keep * * PRBool forceArchive - forces creating an archive even if one was * already created that day (overwrites) */ nsresult nsPlacesImportExportService::ArchiveBookmarksFile(PRInt32 numberOfBackups, PRBool forceArchive) { nsCOMPtr bookmarksBackupDir; nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(bookmarksBackupDir)); NS_ENSURE_SUCCESS(rv, rv); nsDependentCString dirName("bookmarkbackups"); rv = bookmarksBackupDir->AppendNative(dirName); NS_ENSURE_SUCCESS(rv, rv); PRBool exists; rv = bookmarksBackupDir->Exists(&exists); if (NS_FAILED(rv) || !exists) { rv = bookmarksBackupDir->Create(nsIFile::DIRECTORY_TYPE, 0700); // if there's no backup folder, there's no backup, fail NS_ENSURE_SUCCESS(rv, rv); } // construct the new leafname PRTime now64 = PR_Now(); PRExplodedTime nowInfo; PR_ExplodeTime(now64, PR_LocalTimeParameters, &nowInfo); PR_NormalizeTime(&nowInfo, PR_LocalTimeParameters); char timeString[128]; // Use YYYY-MM-DD (ISO 8601) as it doesn't contain illegal characters // and makes the alphabetical order of multiple backup files more useful. PR_FormatTime(timeString, 128, "bookmarks-%Y-%m-%d.html", &nowInfo); //nsCAutoString backupFilenameCString(timeString); //nsAutoString backupFilenameString = NS_ConvertUTF8toUTF16(backupFilenameCString); nsAutoString backupFilenameString = NS_ConvertUTF8toUTF16((timeString)); nsCOMPtr backupFile; if (forceArchive) { // if we have a backup from today, nuke it nsCOMPtr currentBackup; rv = bookmarksBackupDir->Clone(getter_AddRefs(currentBackup)); NS_ENSURE_SUCCESS(rv, rv); rv = currentBackup->Append(backupFilenameString); NS_ENSURE_SUCCESS(rv, rv); rv = currentBackup->Exists(&exists); if (NS_SUCCEEDED(rv) && exists) { rv = currentBackup->Remove(PR_FALSE); NS_ENSURE_SUCCESS(rv, rv); } } else { nsCOMPtr existingBackups; rv = bookmarksBackupDir->GetDirectoryEntries(getter_AddRefs(existingBackups)); NS_ENSURE_SUCCESS(rv, rv); nsStringArray backupFileNames; PRBool hasMoreElements = PR_FALSE; PRBool hasCurrentBackup = PR_FALSE; while (NS_SUCCEEDED(existingBackups->HasMoreElements(&hasMoreElements)) && hasMoreElements) { rv = existingBackups->GetNext(getter_AddRefs(backupFile)); NS_ENSURE_SUCCESS(rv, rv); nsAutoString backupName; rv = backupFile->GetLeafName(backupName); NS_ENSURE_SUCCESS(rv, rv); // the backup for today exists, do not create later if (backupName == backupFilenameString) { hasCurrentBackup = PR_TRUE; continue; } // mark the rest for possible removal if (Substring(backupName, 0, 10) == NS_LITERAL_STRING("bookmarks-")) backupFileNames.AppendString(backupName); } if (numberOfBackups > 0 && backupFileNames.Count() >= numberOfBackups) { PRInt32 numberOfBackupsToDelete = backupFileNames.Count() - numberOfBackups + 1; backupFileNames.Sort(); while (numberOfBackupsToDelete--) { (void)bookmarksBackupDir->Clone(getter_AddRefs(backupFile)); (void)backupFile->Append(*backupFileNames[0]); (void)backupFile->Remove(PR_FALSE); backupFileNames.RemoveStringAt(0); } } if (hasCurrentBackup) return NS_OK; } nsCOMPtr bookmarksFile; rv = NS_GetSpecialDirectory(NS_APP_BOOKMARKS_50_FILE, getter_AddRefs(bookmarksFile)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); PRBool overwriteBookmarks; rv = prefs->GetBoolPref(BROWSER_BOOKMARKS_OVERWRITE_PREF, &overwriteBookmarks); NS_ENSURE_SUCCESS(rv, rv); if (!overwriteBookmarks) { rv = bookmarksFile->SetLeafName(NS_LITERAL_STRING(POSTPLACES_BOOKMARKS_FILE)); NS_ENSURE_SUCCESS(rv, rv); } rv = bookmarksFile->CopyTo(bookmarksBackupDir, backupFilenameString); // at least dump something out in case this fails in a debug build NS_ENSURE_SUCCESS(rv, rv); return rv; }