diff --git a/browser/components/places/src/nsPlacesImportExportService.cpp b/browser/components/places/src/nsPlacesImportExportService.cpp index 5856e4d11123..b9fee347d146 100644 --- a/browser/components/places/src/nsPlacesImportExportService.cpp +++ b/browser/components/places/src/nsPlacesImportExportService.cpp @@ -951,9 +951,21 @@ BookmarkContentSink::HandleLinkBegin(const nsIParserNode& node) // save the favicon, ignore errors if (!icon.IsEmpty() || !iconUri.IsEmpty()) { nsCOMPtr iconUriObject; - NS_NewURI(getter_AddRefs(iconUriObject), iconUri); - if (!icon.IsEmpty() || iconUriObject) { + rv = NS_NewURI(getter_AddRefs(iconUriObject), iconUri); + if (!icon.IsEmpty() || NS_SUCCEEDED(rv)) { rv = SetFaviconForURI(frame.mPreviousLink, iconUriObject, icon); + if (NS_FAILED(rv)) { + nsCAutoString warnMsg; + warnMsg.Append("Bookmarks Import: unable to set favicon '"); + warnMsg.Append(NS_ConvertUTF16toUTF8(iconUri)); + warnMsg.Append("' for page '"); + nsCAutoString spec; + rv = frame.mPreviousLink->GetSpec(spec); + if (NS_SUCCEEDED(rv)) + warnMsg.Append(spec); + warnMsg.Append("'"); + NS_WARNING(warnMsg.get()); + } } } @@ -1313,7 +1325,19 @@ BookmarkContentSink::SetFaviconForURI(nsIURI* aPageURI, nsIURI* aIconURI, PR_snprintf(buf, sizeof(buf), "%lld", PR_Now()); faviconSpec.Append(buf); rv = NS_NewURI(getter_AddRefs(faviconURI), faviconSpec); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_FAILED(rv)) { + nsCAutoString warnMsg; + warnMsg.Append("Bookmarks Import: Unable to make up new favicon '"); + warnMsg.Append(faviconSpec); + warnMsg.Append("' for page '"); + nsCAutoString spec; + rv = aPageURI->GetSpec(spec); + if (NS_SUCCEEDED(rv)) + warnMsg.Append(spec); + warnMsg.Append("'"); + NS_WARNING(warnMsg.get()); + return NS_OK; + } serialNumber++; } @@ -1471,9 +1495,18 @@ WriteFaviconAttribute(const nsACString& aURI, nsIOutputStream* aOutput) nsresult rv; PRUint32 dummy; + // if favicon uri is invalid we skip the attribute silently, to avoid + // creating a corrupt file. nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), aURI); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_FAILED(rv)) { + nsCAutoString warnMsg; + warnMsg.Append("Bookmarks Export: Found invalid favicon '"); + warnMsg.Append(aURI); + warnMsg.Append("'"); + NS_WARNING(warnMsg.get()); + return NS_OK; + } // get favicon nsCOMPtr faviconService = do_GetService(NS_FAVICONSERVICE_CONTRACTID, &rv); @@ -1739,6 +1772,23 @@ nsPlacesImportExportService::WriteItem(nsINavHistoryResultNode* aItem, PRUint32 dummy; nsresult rv; + // before doing any attempt to write the item check that uri is valid, if the + // item has a bad uri we skip it silently, otherwise we could stop while + // exporting, generating a corrupt file. + nsCAutoString uri; + rv = aItem->GetUri(uri); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr pageURI; + rv = NS_NewURI(getter_AddRefs(pageURI), uri, nsnull); + if (NS_FAILED(rv)) { + nsCAutoString warnMsg; + warnMsg.Append("Bookmarks Export: Found invalid item uri '"); + warnMsg.Append(uri); + warnMsg.Append("'"); + NS_WARNING(warnMsg.get()); + return NS_OK; + } + // indent if (!aIndent.IsEmpty()) { rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy); @@ -1753,9 +1803,6 @@ nsPlacesImportExportService::WriteItem(nsINavHistoryResultNode* aItem, // 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); @@ -1806,10 +1853,6 @@ nsPlacesImportExportService::WriteItem(nsINavHistoryResultNode* aItem, } // post data - nsCOMPtr pageURI; - rv = NS_NewURI(getter_AddRefs(pageURI), uri, nsnull); - NS_ENSURE_SUCCESS(rv, rv); - PRBool hasPostData; rv = mAnnotationService->ItemHasAnnotation(itemId, POST_DATA_ANNO, &hasPostData); diff --git a/browser/components/places/tests/unit/bookmarks.corrupt.html b/browser/components/places/tests/unit/bookmarks.corrupt.html new file mode 100644 index 000000000000..9fdcadfb2a95 --- /dev/null +++ b/browser/components/places/tests/unit/bookmarks.corrupt.html @@ -0,0 +1,38 @@ + + + +Bookmarks +

Bookmarks

+ +

+

Get Bookmark Add-ons +
+

Mozilla Firefox

+

+

Help and Tutorials +
Customize Firefox +
Get Involved +
About Us +
About Us +

+

test

+
folder test comment +

+

test post keyword +
item description +
+

Unsorted Bookmarks

+

+

Example.tld +

+

Bookmarks Toolbar Folder

+
Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar +

+

Getting Started +
Latest Headlines +
Getting Started +
Livemark test comment +

+

diff --git a/browser/components/places/tests/unit/head_bookmarks.js b/browser/components/places/tests/unit/head_bookmarks.js index e2bd0730557c..7d202e842fbf 100644 --- a/browser/components/places/tests/unit/head_bookmarks.js +++ b/browser/components/places/tests/unit/head_bookmarks.js @@ -134,3 +134,34 @@ Cc["@mozilla.org/feed-processor;1"].createInstance(Ci.nsIFeedProcessor); function uri(spec) { return iosvc.newURI(spec, null, null); } + +/* + * Removes all bookmarks and checks for correct cleanup + */ +function remove_all_bookmarks() { + var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. + getService(Ci.nsINavBookmarksService); + // Clear all bookmarks + bs.removeFolderChildren(bs.bookmarksMenuFolder); + bs.removeFolderChildren(bs.toolbarFolder); + bs.removeFolderChildren(bs.unfiledBookmarksFolder); + // Check for correct cleanup + check_no_bookmarks() +} + +/* + * Checks that we don't have any bookmark + */ +function check_no_bookmarks() { + var hs = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsINavHistoryService); + var query = hs.getNewQuery(); + query.setFolders([bs.toolbarFolder, bs.bookmarksMenuFolder, bs.unfiledBookmarksFolder], 3); + var options = hs.getNewQueryOptions(); + options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS; + var result = hs.executeQuery(query, options); + var root = result.root; + root.containerOpen = true; + do_check_eq(root.childCount, 0); + root.containerOpen = false; +} diff --git a/browser/components/places/tests/unit/test_457441-import-export-corrupt-bookmarks-html.js b/browser/components/places/tests/unit/test_457441-import-export-corrupt-bookmarks-html.js new file mode 100644 index 000000000000..9ffc049c9503 --- /dev/null +++ b/browser/components/places/tests/unit/test_457441-import-export-corrupt-bookmarks-html.js @@ -0,0 +1,218 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* ***** 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 Bug 457441 code. + * + * The Initial Developer of the Original Code is Mozilla Corp. + * Portions created by the Initial Developer are Copyright (C) 2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Marco Bonardo (Original Author) + * + * 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 ***** */ + +/* + * This test ensures that importing/exporting to HTML does not stop + * if a malformed uri is found. + */ + +// Get Services +var hs = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsINavHistoryService); +var dbConn = hs.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection; +var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. + getService(Ci.nsINavBookmarksService); +var as = Cc["@mozilla.org/browser/annotation-service;1"]. + getService(Ci.nsIAnnotationService); +var lms = Cc["@mozilla.org/browser/livemark-service;2"]. + getService(Ci.nsILivemarkService); +var icos = Cc["@mozilla.org/browser/favicon-service;1"]. + getService(Ci.nsIFaviconService); +var ps = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); +var ies = Cc["@mozilla.org/browser/places/import-export-service;1"]. + getService(Ci.nsIPlacesImportExportService); + +const DESCRIPTION_ANNO = "bookmarkProperties/description"; +const LOAD_IN_SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar"; +const POST_DATA_ANNO = "bookmarkProperties/POSTData"; + +const TEST_FAVICON_PAGE_URL = "http://en-US.www.mozilla.com/en-US/firefox/central/"; +const TEST_FAVICON_DATA_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="; + +// main +function run_test() { + // avoid creating the places smart folder during tests + ps.setIntPref("browser.places.smartBookmarksVersion", -1); + + // import bookmarks from corrupt file + var corruptBookmarksFile = do_get_file("browser/components/places/tests/unit/bookmarks.corrupt.html"); + try { + ies.importHTMLFromFile(corruptBookmarksFile, true); + } catch(ex) { do_throw("couldn't import corrupt bookmarks file: " + ex); } + + // Check that every bookmark is correct + // Corrupt bookmarks should not have been imported + database_check(); + + // Create corruption in database + var corruptItemId = bs.insertBookmark(bs.toolbarFolder, + uri("http://test.mozilla.org"), + bs.DEFAULT_INDEX, "We love belugas"); + var stmt = dbConn.createStatement("UPDATE moz_bookmarks SET fk = NULL WHERE id = :itemId"); + stmt.params.itemId = corruptItemId; + stmt.execute(); + + // Export bookmarks + var bookmarksFile = dirSvc.get("ProfD", Ci.nsILocalFile); + bookmarksFile.append("bookmarks.exported.html"); + if (bookmarksFile.exists()) + bookmarksFile.remove(false); + bookmarksFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, 0600); + if (!bookmarksFile.exists()) + do_throw("couldn't create file: bookmarks.exported.html"); + try { + ies.exportHTMLToFile(bookmarksFile); + } catch(ex) { do_throw("couldn't export to bookmarks.exported.html: " + ex); } + + // Clear all bookmarks + remove_all_bookmarks(); + + // Import bookmarks + try { + ies.importHTMLFromFile(bookmarksFile, true); + } catch(ex) { do_throw("couldn't import the exported file: " + ex); } + + // Check that every bookmark is correct + database_check(); +} + +/* + * Check for imported bookmarks correctness + */ +function database_check() { + // BOOKMARKS MENU + var query = hs.getNewQuery(); + query.setFolders([bs.bookmarksMenuFolder], 1); + var options = hs.getNewQueryOptions(); + options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS; + var result = hs.executeQuery(query, options); + var rootNode = result.root; + rootNode.containerOpen = true; + do_check_eq(rootNode.childCount, 4); + + // get test folder + var testFolder = rootNode.getChild(3); + do_check_eq(testFolder.type, testFolder.RESULT_TYPE_FOLDER); + do_check_eq(testFolder.title, "test"); + // add date + do_check_eq(bs.getItemDateAdded(testFolder.itemId)/1000000, 1177541020); + // last modified + do_check_eq(bs.getItemLastModified(testFolder.itemId)/1000000, 1177541050); + testFolder = testFolder.QueryInterface(Ci.nsINavHistoryQueryResultNode); + do_check_eq(testFolder.hasChildren, true); + // folder description + do_check_true(as.itemHasAnnotation(testFolder.itemId, + DESCRIPTION_ANNO)); + do_check_eq("folder test comment", + as.getItemAnnotation(testFolder.itemId, DESCRIPTION_ANNO)); + // open test folder, and test the children + testFolder.containerOpen = true; + var cc = testFolder.childCount; + do_check_eq(cc, 1); + + // test bookmark 1 + var testBookmark1 = testFolder.getChild(0); + // url + do_check_eq("http://test/post", testBookmark1.uri); + // title + do_check_eq("test post keyword", testBookmark1.title); + // keyword + do_check_eq("test", bs.getKeywordForBookmark(testBookmark1.itemId)); + // sidebar + do_check_true(as.itemHasAnnotation(testBookmark1.itemId, + LOAD_IN_SIDEBAR_ANNO)); + // add date + do_check_eq(testBookmark1.dateAdded/1000000, 1177375336); + // last modified + do_check_eq(testBookmark1.lastModified/1000000, 1177375423); + // post data + do_check_true(as.itemHasAnnotation(testBookmark1.itemId, + POST_DATA_ANNO)); + do_check_eq("hidden1%3Dbar&text1%3D%25s", + as.getItemAnnotation(testBookmark1.itemId, POST_DATA_ANNO)); + // last charset + var testURI = uri(testBookmark1.uri); + do_check_eq("ISO-8859-1", hs.getCharsetForURI(testURI)); + // description + do_check_true(as.itemHasAnnotation(testBookmark1.itemId, + DESCRIPTION_ANNO)); + do_check_eq("item description", + as.getItemAnnotation(testBookmark1.itemId, + DESCRIPTION_ANNO)); + + // clean up + testFolder.containerOpen = false; + rootNode.containerOpen = false; + + // BOOKMARKS TOOLBAR + query.setFolders([bs.toolbarFolder], 1); + result = hs.executeQuery(query, hs.getNewQueryOptions()); + var toolbar = result.root; + toolbar.containerOpen = true; + do_check_eq(toolbar.childCount, 3); + + // livemark + var livemark = toolbar.getChild(1); + // title + do_check_eq("Latest Headlines", livemark.title); + // livemark check + do_check_true(lms.isLivemark(livemark.itemId)); + // site url + do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/", + lms.getSiteURI(livemark.itemId).spec); + // feed url + do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml", + lms.getFeedURI(livemark.itemId).spec); + + // cleanup + toolbar.containerOpen = false; + + // UNFILED BOOKMARKS + query.setFolders([bs.unfiledBookmarksFolder], 1); + result = hs.executeQuery(query, hs.getNewQueryOptions()); + var unfiledBookmarks = result.root; + unfiledBookmarks.containerOpen = true; + do_check_eq(unfiledBookmarks.childCount, 1); + unfiledBookmarks.containerOpen = false; + + // favicons + var faviconURI = icos.getFaviconForPage(uri(TEST_FAVICON_PAGE_URL)); + var dataURL = icos.getFaviconDataAsDataURL(faviconURI); + do_check_eq(TEST_FAVICON_DATA_URL, dataURL); +}