1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla History System
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Google Inc.
19 : * Portions created by the Initial Developer are Copyright (C) 2005
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Brett Wilson <brettw@gmail.com>
24 : * Dietrich Ayala <dietrich@mozilla.com>
25 : * Drew Willcoxon <adw@mozilla.com>
26 : * Marco Bonardo <mak77@bonardo.net>
27 : *
28 : * Alternatively, the contents of this file may be used under the terms of
29 : * either the GNU General Public License Version 2 or later (the "GPL"), or
30 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 : * in which case the provisions of the GPL or the LGPL are applicable instead
32 : * of those above. If you wish to allow use of your version of this file only
33 : * under the terms of either the GPL or the LGPL, and not to allow others to
34 : * use your version of this file under the terms of the MPL, indicate your
35 : * decision by deleting the provisions above and replace them with the notice
36 : * and other provisions required by the GPL or the LGPL. If you do not delete
37 : * the provisions above, a recipient may use your version of this file under
38 : * the terms of any one of the MPL, the GPL or the LGPL.
39 : *
40 : * ***** END LICENSE BLOCK ***** */
41 :
42 : /**
43 : * Importer/exporter between the mozStorage-based bookmarks and the old-style
44 : * "bookmarks.html"
45 : *
46 : * Format:
47 : *
48 : * Primary heading := h1
49 : * Old version used this to set attributes on the bookmarks RDF root, such
50 : * as the last modified date. We only use H1 to check for the attribute
51 : * PLACES_ROOT, which tells us that this hierarchy root is the places root.
52 : * For backwards compatibility, if we don't find this, we assume that the
53 : * hierarchy is rooted at the bookmarks menu.
54 : * Heading := any heading other than h1
55 : * Old version used this to set attributes on the current container. We only
56 : * care about the content of the heading container, which contains the title
57 : * of the bookmark container.
58 : * Bookmark := a
59 : * HREF is the destination of the bookmark
60 : * FEEDURL is the URI of the RSS feed if this is a livemark.
61 : * LAST_CHARSET is stored as an annotation so that the next time we go to
62 : * that page we remember the user's preference.
63 : * WEB_PANEL is set to "true" if the bookmark should be loaded in the sidebar.
64 : * ICON will be stored in the favicon service
65 : * ICON_URI is new for places bookmarks.html, it refers to the original
66 : * URI of the favicon so we don't have to make up favicon URLs.
67 : * Text of the <a> container is the name of the bookmark
68 : * Ignored: LAST_VISIT, ID (writing out non-RDF IDs can confuse Firefox 2)
69 : * Bookmark comment := dd
70 : * This affects the previosly added bookmark
71 : * Separator := hr
72 : * Insert a separator into the current container
73 : * The folder hierarchy is defined by <dl>/<ul>/<menu> (the old importing code
74 : * handles all these cases, when we write, use <dl>).
75 : *
76 : * Overall design
77 : * --------------
78 : *
79 : * We need to emulate a recursive parser. A "Bookmark import frame" is created
80 : * corresponding to each folder we encounter. These are arranged in a stack,
81 : * and contain all the state we need to keep track of.
82 : *
83 : * A frame is created when we find a heading, which defines a new container.
84 : * The frame also keeps track of the nesting of <DL>s, (in well-formed
85 : * bookmarks files, these will have a 1-1 correspondence with frames, but we
86 : * try to be a little more flexible here). When the nesting count decreases
87 : * to 0, then we know a frame is complete and to pop back to the previous
88 : * frame.
89 : *
90 : * Note that a lot of things happen when tags are CLOSED because we need to
91 : * get the text from the content of the tag. For example, link and heading tags
92 : * both require the content (= title) before actually creating it.
93 : */
94 :
95 : #include "nsPlacesImportExportService.h"
96 : #include "nsNetUtil.h"
97 : #include "nsParserCIID.h"
98 : #include "nsUnicharUtils.h"
99 : #include "nsAppDirectoryServiceDefs.h"
100 : #include "nsDirectoryServiceUtils.h"
101 : #include "nsToolkitCompsCID.h"
102 : #include "nsIHTMLContentSink.h"
103 : #include "nsIParser.h"
104 : #include "prprf.h"
105 : #include "nsIObserverService.h"
106 : #include "nsISupportsPrimitives.h"
107 : #include "nsPlacesMacros.h"
108 : #include "mozilla/Util.h"
109 : #include "Helpers.h"
110 :
111 : using namespace mozilla;
112 : using namespace mozilla::places;
113 :
114 : static NS_DEFINE_CID(kParserCID, NS_PARSER_CID);
115 :
116 : #define KEY_TOOLBARFOLDER_LOWER "personal_toolbar_folder"
117 : #define KEY_BOOKMARKSMENU_LOWER "bookmarks_menu"
118 : #define KEY_UNFILEDFOLDER_LOWER "unfiled_bookmarks_folder"
119 : #define KEY_PLACESROOT_LOWER "places_root"
120 : #define KEY_HREF_LOWER "href"
121 : #define KEY_FEEDURL_LOWER "feedurl"
122 : #define KEY_WEB_PANEL_LOWER "web_panel"
123 : #define KEY_LASTCHARSET_LOWER "last_charset"
124 : #define KEY_ICON_LOWER "icon"
125 : #define KEY_ICON_URI_LOWER "icon_uri"
126 : #define KEY_SHORTCUTURL_LOWER "shortcuturl"
127 : #define KEY_POST_DATA_LOWER "post_data"
128 : #define KEY_NAME_LOWER "name"
129 : #define KEY_MICSUM_GEN_URI_LOWER "micsum_gen_uri"
130 : #define KEY_DATE_ADDED_LOWER "add_date"
131 : #define KEY_LAST_MODIFIED_LOWER "last_modified"
132 : #define KEY_GENERATED_TITLE_LOWER "generated_title"
133 :
134 : #define LOAD_IN_SIDEBAR_ANNO NS_LITERAL_CSTRING("bookmarkProperties/loadInSidebar")
135 : #define DESCRIPTION_ANNO NS_LITERAL_CSTRING("bookmarkProperties/description")
136 : #define POST_DATA_ANNO NS_LITERAL_CSTRING("bookmarkProperties/POSTData")
137 :
138 : #define BOOKMARKS_MENU_ICON_URI "chrome://browser/skin/places/bookmarksMenu.png"
139 :
140 : // The RESTORE_*_NSIOBSERVER_TOPIC #defines should match the constants of the
141 : // same names in toolkit/components/places/src/utils.js
142 : #define RESTORE_BEGIN_NSIOBSERVER_TOPIC "bookmarks-restore-begin"
143 : #define RESTORE_SUCCESS_NSIOBSERVER_TOPIC "bookmarks-restore-success"
144 : #define RESTORE_FAILED_NSIOBSERVER_TOPIC "bookmarks-restore-failed"
145 : #define RESTORE_NSIOBSERVER_DATA NS_LITERAL_STRING("html")
146 : #define RESTORE_INITIAL_NSIOBSERVER_DATA NS_LITERAL_STRING("html-initial")
147 :
148 : #define LMANNO_FEEDURI "livemark/feedURI"
149 : #define LMANNO_SITEURI "livemark/siteURI"
150 :
151 : // define to get debugging messages on console about import/export
152 : //#define DEBUG_IMPORT
153 : //#define DEBUG_EXPORT
154 :
155 : #if defined(XP_WIN) || defined(XP_OS2)
156 : #define NS_LINEBREAK "\015\012"
157 : #else
158 : #define NS_LINEBREAK "\012"
159 : #endif
160 :
161 : class nsIOutputStream;
162 : static const char kWhitespace[] = " \r\n\t\b";
163 : static nsresult WriteEscapedUrl(const nsCString &aString, nsIOutputStream* aOutput);
164 :
165 : class BookmarkImportFrame
166 267 : {
167 : public:
168 89 : BookmarkImportFrame(PRInt64 aID) :
169 : mContainerID(aID),
170 : mContainerNesting(0),
171 : mLastContainerType(Container_Normal),
172 : mInDescription(false),
173 : mPreviousId(0),
174 : mPreviousDateAdded(0),
175 89 : mPreviousLastModifiedDate(0)
176 : {
177 89 : }
178 :
179 : enum ContainerType { Container_Normal,
180 : Container_Places,
181 : Container_Menu,
182 : Container_Toolbar,
183 : Container_Unfiled};
184 :
185 : PRInt64 mContainerID;
186 :
187 : // How many <dl>s have been nested. Each frame/container should start
188 : // with a heading, and is then followed by a <dl>, <ul>, or <menu>. When
189 : // that list is complete, then it is the end of this container and we need
190 : // to pop back up one level for new items. If we never get an open tag for
191 : // one of these things, we should assume that the container is empty and
192 : // that things we find should be siblings of it. Normally, these <dl>s won't
193 : // be nested so this will be 0 or 1.
194 : PRInt32 mContainerNesting;
195 :
196 : // when we find a heading tag, it actually affects the title of the NEXT
197 : // container in the list. This stores that heading tag and whether it was
198 : // special. 'ConsumeHeading' resets this.
199 : ContainerType mLastContainerType;
200 :
201 : // this contains the text from the last begin tag until now. It is reset
202 : // at every begin tag. We can check it when we see a </a>, or </h3>
203 : // to see what the text content of that node should be.
204 : nsString mPreviousText;
205 :
206 : // true when we hit a <dd>, which contains the description for the preceding
207 : // <a> tag. We can't just check for </dd> like we can for </a> or </h3>
208 : // because if there is a sub-folder, it is actually a child of the <dd>
209 : // because the tag is never explicitly closed. If this is true and we see a
210 : // new open tag, that means to commit the description to the previous
211 : // bookmark.
212 : //
213 : // Additional weirdness happens when the previous <dt> tag contains a <h3>:
214 : // this means there is a new folder with the given description, and whose
215 : // children are contained in the following <dl> list.
216 : //
217 : // This is handled in OpenContainer(), which commits previous text if
218 : // necessary.
219 : bool mInDescription;
220 :
221 : // contains the URL of the previous bookmark created. This is used so that
222 : // when we encounter a <dd>, we know what bookmark to associate the text with.
223 : // This is cleared whenever we hit a <h3>, so that we know NOT to save this
224 : // with a bookmark, but to keep it until
225 : nsCOMPtr<nsIURI> mPreviousLink;
226 :
227 : // contains the URL of the previous livemark, so that when the link ends,
228 : // and the livemark title is known, we can create it.
229 : nsCOMPtr<nsIURI> mPreviousFeed;
230 :
231 61 : void ConsumeHeading(nsAString* aHeading, ContainerType* aContainerType)
232 : {
233 61 : *aHeading = mPreviousText;
234 61 : *aContainerType = mLastContainerType;
235 61 : mPreviousText.Truncate();
236 61 : }
237 :
238 : // Contains the id of an imported, or newly created bookmark.
239 : PRInt64 mPreviousId;
240 :
241 : // Contains the date-added and last-modified-date of an imported item.
242 : // Used to override the values set by insertBookmark, createFolder, etc.
243 : PRTime mPreviousDateAdded;
244 : PRTime mPreviousLastModifiedDate;
245 : };
246 :
247 : /**
248 : * Copied from nsEscape.cpp, which requires internal string API.
249 : */
250 : char*
251 124 : nsEscapeHTML(const char* string)
252 : {
253 : /* XXX Hardcoded max entity len. The +1 is for the trailing null. */
254 124 : char* escaped = nsnull;
255 124 : PRUint32 len = strlen(string);
256 124 : if (len >= (PR_UINT32_MAX / 6))
257 0 : return nsnull;
258 :
259 124 : escaped = (char*)NS_Alloc((len * 6) + 1);
260 124 : if (escaped) {
261 124 : char* ptr = escaped;
262 2294 : for (; *string != '\0'; string++) {
263 2170 : switch(*string) {
264 : case '<':
265 0 : *ptr++ = '&';
266 0 : *ptr++ = 'l';
267 0 : *ptr++ = 't';
268 0 : *ptr++ = ';';
269 0 : break;
270 : case '>':
271 0 : *ptr++ = '&';
272 0 : *ptr++ = 'g';
273 0 : *ptr++ = 't';
274 0 : *ptr++ = ';';
275 0 : break;
276 : case '&':
277 5 : *ptr++ = '&';
278 5 : *ptr++ = 'a';
279 5 : *ptr++ = 'm';
280 5 : *ptr++ = 'p';
281 5 : *ptr++ = ';';
282 5 : break;
283 : case '"':
284 0 : *ptr++ = '&';
285 0 : *ptr++ = 'q';
286 0 : *ptr++ = 'u';
287 0 : *ptr++ = 'o';
288 0 : *ptr++ = 't';
289 0 : *ptr++ = ';';
290 0 : break;
291 : case '\'':
292 0 : *ptr++ = '&';
293 0 : *ptr++ = '#';
294 0 : *ptr++ = '3';
295 0 : *ptr++ = '9';
296 0 : *ptr++ = ';';
297 0 : break;
298 : default:
299 2165 : *ptr++ = *string;
300 : }
301 : }
302 124 : *ptr = '\0';
303 : }
304 124 : return escaped;
305 : }
306 :
307 22 : PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsPlacesImportExportService, gImportExportService)
308 :
309 285 : NS_IMPL_ISUPPORTS2(nsPlacesImportExportService, nsIPlacesImportExportService,
310 : nsINavHistoryBatchCallback)
311 :
312 :
313 11 : nsPlacesImportExportService::nsPlacesImportExportService()
314 : {
315 11 : NS_ASSERTION(!gImportExportService,
316 : "Attempting to create two instances of the service!");
317 11 : gImportExportService = this;
318 11 : }
319 :
320 33 : nsPlacesImportExportService::~nsPlacesImportExportService()
321 : {
322 11 : NS_ASSERTION(gImportExportService == this,
323 : "Deleting a non-singleton instance of the service");
324 11 : if (gImportExportService == this)
325 11 : gImportExportService = nsnull;
326 44 : }
327 :
328 : nsresult
329 11 : nsPlacesImportExportService::Init()
330 : {
331 : // Be sure to call EnsureServiceState() before using services.
332 11 : mHistoryService = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
333 11 : NS_ENSURE_TRUE(mHistoryService, NS_ERROR_OUT_OF_MEMORY);
334 11 : mFaviconService = do_GetService(NS_FAVICONSERVICE_CONTRACTID);
335 11 : NS_ENSURE_TRUE(mFaviconService, NS_ERROR_OUT_OF_MEMORY);
336 11 : mAnnotationService = do_GetService(NS_ANNOTATIONSERVICE_CONTRACTID);
337 11 : NS_ENSURE_TRUE(mAnnotationService, NS_ERROR_OUT_OF_MEMORY);
338 11 : mBookmarksService = do_GetService(NS_NAVBOOKMARKSSERVICE_CONTRACTID);
339 11 : NS_ENSURE_TRUE(mBookmarksService, NS_ERROR_OUT_OF_MEMORY);
340 11 : mLivemarkService = do_GetService(NS_LIVEMARKSERVICE_CONTRACTID);
341 11 : NS_ENSURE_TRUE(mLivemarkService, NS_ERROR_OUT_OF_MEMORY);
342 11 : return NS_OK;
343 : }
344 :
345 : /**
346 : * The content sink stuff is based loosely on nsIHTMLContentSink.
347 : */
348 : class BookmarkContentSink : public nsIHTMLContentSink
349 28 : {
350 : public:
351 : BookmarkContentSink();
352 :
353 : nsresult Init(bool aAllowRootChanges,
354 : PRInt64 aFolder,
355 : bool aIsImportDefaults);
356 :
357 : NS_DECL_ISUPPORTS
358 :
359 : // nsIContentSink (superclass of nsIHTMLContentSink)
360 53 : NS_IMETHOD WillParse() { return NS_OK; }
361 25 : NS_IMETHOD WillInterrupt() { return NS_OK; }
362 53 : NS_IMETHOD WillResume() { return NS_OK; }
363 28 : NS_IMETHOD SetParser(nsParserBase* aParser) { return NS_OK; }
364 0 : virtual void FlushPendingNotifications(mozFlushType aType) { }
365 25 : NS_IMETHOD SetDocumentCharset(nsACString& aCharset) { return NS_OK; }
366 0 : virtual nsISupports *GetTarget() { return nsnull; }
367 :
368 : // nsIHTMLContentSink
369 25 : NS_IMETHOD OpenHead() { return NS_OK; }
370 0 : NS_IMETHOD BeginContext(PRInt32 aPosition) { return NS_OK; }
371 0 : NS_IMETHOD EndContext(PRInt32 aPosition) { return NS_OK; }
372 112 : NS_IMETHOD IsEnabled(PRInt32 aTag, bool* aReturn)
373 112 : { *aReturn = true; return NS_OK; }
374 0 : NS_IMETHOD DidProcessTokens() { return NS_OK; }
375 0 : NS_IMETHOD WillProcessAToken() { return NS_OK; }
376 2390 : NS_IMETHOD DidProcessAToken() { return NS_OK; }
377 : NS_IMETHOD OpenContainer(const nsIParserNode& aNode);
378 : NS_IMETHOD CloseContainer(const nsHTMLTag aTag);
379 : NS_IMETHOD AddLeaf(const nsIParserNode& aNode);
380 0 : NS_IMETHOD NotifyTagObservers(nsIParserNode* aNode) { return NS_OK; }
381 :
382 : protected:
383 : nsCOMPtr<nsINavBookmarksService> mBookmarksService;
384 : nsCOMPtr<nsINavHistoryService> mHistoryService;
385 : nsCOMPtr<nsIAnnotationService> mAnnotationService;
386 : nsCOMPtr<mozIAsyncLivemarks> mLivemarkService;
387 :
388 : // If set, we will move root items to from their existing position
389 : // in the hierarchy, to where we find them in the bookmarks file
390 : // being imported. This should be set when we are loading
391 : // the default places html file, and should be unset when doing
392 : // normal imports so that root folders will not get moved when
393 : // importing bookmarks.html files.
394 : bool mAllowRootChanges;
395 :
396 : // If set, this is an import of initial bookmarks.html content,
397 : // so we don't want to kick off HTTP traffic
398 : // and we want the imported personal toolbar folder
399 : // to be set as the personal toolbar folder. (If not set
400 : // we will treat it as a normal folder.)
401 : bool mIsImportDefaults;
402 :
403 : // If a folder was specified to import into, then ignore flags to put
404 : // bookmarks in the bookmarks menu or toolbar and keep them inside
405 : // the folder.
406 : bool mFolderSpecified;
407 :
408 : void HandleContainerBegin(const nsIParserNode& node);
409 : void HandleContainerEnd();
410 : void HandleHead1Begin(const nsIParserNode& node);
411 : void HandleHeadBegin(const nsIParserNode& node);
412 : void HandleHeadEnd();
413 : void HandleLinkBegin(const nsIParserNode& node);
414 : void HandleLinkEnd();
415 : void HandleSeparator(const nsIParserNode& node);
416 :
417 : // This is a list of frames. We really want a recursive parser, but the HTML
418 : // parser gives us tags as a stream. This implements all the state on a stack
419 : // so we can get the recursive information we need. Use "CurFrame" to get the
420 : // top "stack frame" with the current state in it.
421 : nsTArray<BookmarkImportFrame> mFrames;
422 2167 : BookmarkImportFrame& CurFrame()
423 : {
424 2167 : NS_ASSERTION(mFrames.Length() > 0, "Asking for frame when there are none!");
425 2167 : return mFrames[mFrames.Length() - 1];
426 : }
427 94 : BookmarkImportFrame& PreviousFrame()
428 : {
429 94 : NS_ASSERTION(mFrames.Length() > 1, "Asking for frame when there are not enough!");
430 94 : return mFrames[mFrames.Length() - 2];
431 : }
432 : nsresult NewFrame();
433 : nsresult PopFrame();
434 :
435 : nsresult SetFaviconForURI(nsIURI* aPageURI, nsIURI* aFaviconURI,
436 : const nsString& aData);
437 :
438 : PRTime ConvertImportedDateToInternalDate(const nsACString& aDate);
439 :
440 : #ifdef DEBUG_IMPORT
441 : // prints spaces for indenting to the current frame depth
442 : void PrintNesting()
443 : {
444 : for (PRUint32 i = 0; i < mFrames.Length(); i ++)
445 : printf(" ");
446 : }
447 : #endif
448 : };
449 :
450 :
451 28 : BookmarkContentSink::BookmarkContentSink() : mFrames(16)
452 : {
453 28 : }
454 :
455 :
456 : nsresult
457 28 : BookmarkContentSink::Init(bool aAllowRootChanges,
458 : PRInt64 aFolder,
459 : bool aIsImportDefaults)
460 : {
461 28 : mBookmarksService = do_GetService(NS_NAVBOOKMARKSSERVICE_CONTRACTID);
462 28 : NS_ENSURE_TRUE(mBookmarksService, NS_ERROR_OUT_OF_MEMORY);
463 28 : mHistoryService = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
464 28 : NS_ENSURE_TRUE(mHistoryService, NS_ERROR_OUT_OF_MEMORY);
465 28 : mAnnotationService = do_GetService(NS_ANNOTATIONSERVICE_CONTRACTID);
466 28 : NS_ENSURE_TRUE(mAnnotationService, NS_ERROR_OUT_OF_MEMORY);
467 28 : mLivemarkService = do_GetService(NS_LIVEMARKSERVICE_CONTRACTID);
468 28 : NS_ENSURE_TRUE(mLivemarkService, NS_ERROR_OUT_OF_MEMORY);
469 :
470 28 : mAllowRootChanges = aAllowRootChanges;
471 28 : mIsImportDefaults = aIsImportDefaults;
472 :
473 : // initialize the root frame with the menu root
474 : PRInt64 menuRoot;
475 : nsresult rv;
476 28 : if (aFolder == 0) {
477 24 : rv = mBookmarksService->GetBookmarksMenuFolder(&menuRoot);
478 24 : NS_ENSURE_SUCCESS(rv, rv);
479 24 : mFolderSpecified = false;
480 : }
481 : else {
482 4 : menuRoot = aFolder;
483 4 : mFolderSpecified = true;
484 : }
485 28 : if (!mFrames.AppendElement(BookmarkImportFrame(menuRoot)))
486 0 : return NS_ERROR_OUT_OF_MEMORY;
487 :
488 28 : return NS_OK;
489 : }
490 :
491 :
492 728 : NS_IMPL_ISUPPORTS2(BookmarkContentSink,
493 : nsIContentSink,
494 : nsIHTMLContentSink)
495 :
496 :
497 : NS_IMETHODIMP
498 825 : BookmarkContentSink::OpenContainer(const nsIParserNode& aNode)
499 : {
500 825 : switch(aNode.GetNodeType()) {
501 : case eHTMLTag_h1:
502 25 : HandleHead1Begin(aNode);
503 25 : break;
504 : case eHTMLTag_h2:
505 : case eHTMLTag_h3:
506 : case eHTMLTag_h4:
507 : case eHTMLTag_h5:
508 : case eHTMLTag_h6:
509 61 : HandleHeadBegin(aNode);
510 61 : break;
511 : case eHTMLTag_a:
512 147 : HandleLinkBegin(aNode);
513 147 : break;
514 : case eHTMLTag_dl:
515 : case eHTMLTag_ul:
516 : case eHTMLTag_menu:
517 86 : HandleContainerBegin(aNode);
518 86 : break;
519 : case eHTMLTag_dd:
520 49 : CurFrame().mInDescription = true;
521 49 : break;
522 : }
523 825 : return NS_OK;
524 : }
525 :
526 :
527 : NS_IMETHODIMP
528 850 : BookmarkContentSink::CloseContainer(const nsHTMLTag aTag)
529 : {
530 850 : BookmarkImportFrame& frame = CurFrame();
531 :
532 : // see the comment for the definition of mInDescription. Basically, we commit
533 : // any text in mPreviousText to the description of the node/folder if there
534 : // is any.
535 850 : if (frame.mInDescription) {
536 49 : frame.mPreviousText.Trim(kWhitespace); // important!
537 49 : if (!frame.mPreviousText.IsEmpty()) {
538 :
539 49 : PRInt64 itemId = !frame.mPreviousLink ? frame.mContainerID
540 49 : : frame.mPreviousId;
541 :
542 49 : bool hasDescription = false;
543 49 : nsresult rv = mAnnotationService->ItemHasAnnotation(itemId,
544 49 : DESCRIPTION_ANNO,
545 49 : &hasDescription);
546 49 : if (NS_SUCCEEDED(rv) && !hasDescription) {
547 70 : mAnnotationService->SetItemAnnotationString(itemId, DESCRIPTION_ANNO,
548 : frame.mPreviousText, 0,
549 35 : nsIAnnotationService::EXPIRE_NEVER);
550 : }
551 49 : frame.mPreviousText.Truncate();
552 :
553 : // Set last-modified a 2nd time for all items with descriptions
554 : // we need to set last-modified as the *last* step in processing
555 : // any item type in the bookmarks.html file, so that we do
556 : // not overwrite the imported value. for items without descriptions,
557 : // setting this value after setting the item title is that
558 : // last point at which we can save this value before it gets reset.
559 : // for items with descriptions, it must set after that point.
560 : // however, at the point at which we set the title, there's no way
561 : // to determine if there will be a description following,
562 : // so we need to set the last-modified-date at both places.
563 :
564 : PRTime lastModified;
565 49 : if (!frame.mPreviousLink) {
566 33 : lastModified = PreviousFrame().mPreviousLastModifiedDate;
567 : } else {
568 16 : lastModified = frame.mPreviousLastModifiedDate;
569 : }
570 :
571 49 : if (itemId > 0 && lastModified > 0) {
572 40 : rv = mBookmarksService->SetItemLastModified(itemId, lastModified);
573 40 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemLastModified failed");
574 : }
575 : }
576 49 : frame.mInDescription = false;
577 : }
578 :
579 850 : switch (aTag) {
580 : case eHTMLTag_dl:
581 : case eHTMLTag_ul:
582 : case eHTMLTag_menu:
583 86 : HandleContainerEnd();
584 86 : break;
585 : case eHTMLTag_dt:
586 208 : break;
587 : case eHTMLTag_h1:
588 : // ignore
589 25 : break;
590 : case eHTMLTag_h2:
591 : case eHTMLTag_h3:
592 : case eHTMLTag_h4:
593 : case eHTMLTag_h5:
594 : case eHTMLTag_h6:
595 61 : HandleHeadEnd();
596 61 : break;
597 : case eHTMLTag_a:
598 147 : HandleLinkEnd();
599 147 : break;
600 : default:
601 323 : break;
602 : }
603 850 : return NS_OK;
604 : }
605 :
606 :
607 : // BookmarkContentSink::AddLeaf
608 : //
609 : // XXX on the branch, we should be calling CollectSkippedContent as in
610 : // nsHTMLFragmentContentSink.cpp:AddLeaf when we encounter title, script,
611 : // style, or server tags. Apparently if we don't, we'll leak the next DOM
612 : // node. However, this requires that we keep a reference to the parser we'll
613 : // introduce a circular reference because it has a reference to us.
614 : //
615 : // This is annoying to fix and these elements are not allowed in bookmarks
616 : // files anyway. So if somebody tries to import a crazy bookmarks file, it
617 : // will leak a little bit.
618 :
619 : NS_IMETHODIMP
620 1142 : BookmarkContentSink::AddLeaf(const nsIParserNode& aNode)
621 : {
622 1142 : switch (aNode.GetNodeType()) {
623 : case eHTMLTag_text:
624 : // save any text we find
625 306 : CurFrame().mPreviousText += aNode.GetText();
626 306 : break;
627 : case eHTMLTag_entity: {
628 0 : nsAutoString tmp;
629 0 : PRInt32 unicode = aNode.TranslateToUnicodeStr(tmp);
630 0 : if (unicode < 0) {
631 : // invalid entity - just use the text of it
632 0 : CurFrame().mPreviousText += aNode.GetText();
633 : } else {
634 0 : CurFrame().mPreviousText.Append(unicode);
635 : }
636 : break;
637 : }
638 : case eHTMLTag_whitespace:
639 281 : CurFrame().mPreviousText.Append(PRUnichar(' '));
640 281 : break;
641 : case eHTMLTag_hr:
642 0 : HandleSeparator(aNode);
643 0 : break;
644 : }
645 :
646 1142 : return NS_OK;
647 : }
648 :
649 :
650 : void
651 86 : BookmarkContentSink::HandleContainerBegin(const nsIParserNode& node)
652 : {
653 86 : CurFrame().mContainerNesting ++;
654 86 : }
655 :
656 :
657 : // BookmarkContentSink::HandleContainerEnd
658 : //
659 : // Our "indent" count has decreased, and when we hit 0 that means that this
660 : // container is complete and we need to pop back to the outer frame. Never
661 : // pop the toplevel frame
662 :
663 : void
664 86 : BookmarkContentSink::HandleContainerEnd()
665 : {
666 86 : BookmarkImportFrame& frame = CurFrame();
667 86 : if (frame.mContainerNesting > 0)
668 86 : frame.mContainerNesting --;
669 86 : if (mFrames.Length() > 1 && frame.mContainerNesting == 0) {
670 : // we also need to re-set the imported last-modified date here. Otherwise
671 : // the addition of items will override the imported field.
672 61 : BookmarkImportFrame& prevFrame = PreviousFrame();
673 61 : if (prevFrame.mPreviousLastModifiedDate > 0) {
674 45 : (void)mBookmarksService->SetItemLastModified(frame.mContainerID,
675 45 : prevFrame.mPreviousLastModifiedDate);
676 : }
677 61 : PopFrame();
678 : }
679 86 : }
680 :
681 :
682 : // BookmarkContentSink::HandleHead1Begin
683 : //
684 : // Handles <H1>. We check for the attribute PLACES_ROOT and reset the
685 : // container id if it's found. Otherwise, the default bookmark menu
686 : // root is assumed and imported things will go into the bookmarks menu.
687 :
688 : void
689 25 : BookmarkContentSink::HandleHead1Begin(const nsIParserNode& node)
690 : {
691 25 : PRInt32 attrCount = node.GetAttributeCount();
692 29 : for (PRInt32 i = 0; i < attrCount; i ++) {
693 4 : if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_PLACESROOT_LOWER)) {
694 0 : if (mFrames.Length() > 1) {
695 : NS_WARNING("Trying to set the places root from the middle of the hierarchy. "
696 0 : "This can only be set at the beginning.");
697 0 : return;
698 : }
699 :
700 : PRInt64 placesRoot;
701 0 : DebugOnly<nsresult> rv = mBookmarksService->GetPlacesRoot(&placesRoot);
702 0 : NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "could not get placesRoot");
703 0 : CurFrame().mContainerID = placesRoot;
704 : break;
705 : }
706 : }
707 : }
708 :
709 :
710 : // BookmarkContentSink::HandleHeadBegin
711 : //
712 : // Called for h2,h3,h4,h5,h6. This just stores the correct information in
713 : // the current frame; the actual new frame corresponding to the container
714 : // associated with the heading will be created when the tag has been closed
715 : // and we know the title (we don't know to create a new folder or to merge
716 : // with an existing one until we have the title).
717 :
718 : void
719 61 : BookmarkContentSink::HandleHeadBegin(const nsIParserNode& node)
720 : {
721 61 : BookmarkImportFrame& frame = CurFrame();
722 :
723 : // after a heading, a previous bookmark is not applicable (for example, for
724 : // the descriptions contained in a <dd>). Neither is any previous head type
725 61 : frame.mPreviousLink = nsnull;
726 61 : frame.mLastContainerType = BookmarkImportFrame::Container_Normal;
727 :
728 : // It is syntactically possible for a heading to appear after another heading
729 : // but before the <dl> that encloses that folder's contents. This should not
730 : // happen in practice, as the file will contain "<dl></dl>" sequence for
731 : // empty containers.
732 : //
733 : // Just to be on the safe side, if we encounter
734 : // <h3>FOO</h3>
735 : // <h3>BAR</h3>
736 : // <dl>...content 1...</dl>
737 : // <dl>...content 2...</dl>
738 : // we'll pop the stack when we find the h3 for BAR, treating that as an
739 : // implicit ending of the FOO container. The output will be FOO and BAR as
740 : // siblings. If there's another <dl> following (as in "content 2"), those
741 : // items will be treated as further siblings of FOO and BAR
742 61 : if (frame.mContainerNesting == 0)
743 0 : PopFrame();
744 :
745 : // We have to check for some attributes to see if this is a "special"
746 : // folder, which will have different creation rules when the end tag is
747 : // processed.
748 61 : PRInt32 attrCount = node.GetAttributeCount();
749 61 : frame.mLastContainerType = BookmarkImportFrame::Container_Normal;
750 158 : for (PRInt32 i = 0; i < attrCount; ++i) {
751 126 : if (!mFolderSpecified) {
752 108 : if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_TOOLBARFOLDER_LOWER)) {
753 20 : if (mIsImportDefaults)
754 20 : frame.mLastContainerType = BookmarkImportFrame::Container_Toolbar;
755 20 : break;
756 : }
757 88 : else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_BOOKMARKSMENU_LOWER)) {
758 0 : if (mIsImportDefaults)
759 0 : frame.mLastContainerType = BookmarkImportFrame::Container_Menu;
760 0 : break;
761 : }
762 88 : else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_UNFILEDFOLDER_LOWER)) {
763 9 : if (mIsImportDefaults)
764 9 : frame.mLastContainerType = BookmarkImportFrame::Container_Unfiled;
765 9 : break;
766 : }
767 79 : else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_PLACESROOT_LOWER)) {
768 0 : if (mIsImportDefaults)
769 0 : frame.mLastContainerType = BookmarkImportFrame::Container_Places;
770 0 : break;
771 : }
772 : }
773 :
774 97 : if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_DATE_ADDED_LOWER)) {
775 : frame.mPreviousDateAdded =
776 37 : ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(node.GetValueAt(i)));
777 : }
778 60 : else if (node.GetKeyAt(i).LowerCaseEqualsLiteral(KEY_LAST_MODIFIED_LOWER)) {
779 : frame.mPreviousLastModifiedDate =
780 41 : ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(node.GetValueAt(i)));
781 : }
782 : }
783 61 : CurFrame().mPreviousText.Truncate();
784 61 : }
785 :
786 :
787 : // BookmarkContentSink::HandleHeadEnd
788 : //
789 : // Creates the new frame for this heading now that we know the name of the
790 : // container (tokens since the heading open tag will have been placed in
791 : // mPreviousText).
792 :
793 : void
794 61 : BookmarkContentSink::HandleHeadEnd()
795 : {
796 61 : NewFrame();
797 61 : }
798 :
799 :
800 : // BookmarkContentSink::HandleLinkBegin
801 : //
802 : // Handles "<a" tags by creating a new bookmark. The title of the bookmark
803 : // will be the text content, which will be stuffed in mPreviousText for us
804 : // and which will be saved by HandleLinkEnd
805 :
806 : void
807 147 : BookmarkContentSink::HandleLinkBegin(const nsIParserNode& node)
808 : {
809 147 : BookmarkImportFrame& frame = CurFrame();
810 :
811 : // Make sure that the feed URIs from previous frames are emptied.
812 147 : frame.mPreviousFeed = nsnull;
813 : // Make sure that the bookmark id from previous frames are emptied.
814 147 : frame.mPreviousId = 0;
815 : // mPreviousText will hold link text, clear it.
816 147 : frame.mPreviousText.Truncate();
817 :
818 : // Get the attributes we care about.
819 294 : nsAutoString href;
820 294 : nsAutoString feedUrl;
821 294 : nsAutoString icon;
822 294 : nsAutoString iconUri;
823 294 : nsAutoString lastCharset;
824 294 : nsAutoString keyword;
825 294 : nsAutoString postData;
826 294 : nsAutoString webPanel;
827 294 : nsAutoString micsumGenURI;
828 294 : nsAutoString generatedTitle;
829 294 : nsAutoString dateAdded;
830 294 : nsAutoString lastModified;
831 :
832 147 : PRInt32 attrCount = node.GetAttributeCount();
833 690 : for (PRInt32 i = 0; i < attrCount; i++) {
834 543 : const nsAString& key = node.GetKeyAt(i);
835 1086 : nsAutoString value(node.GetValueAt(i));
836 543 : value.Trim(kWhitespace);
837 :
838 543 : if (key.LowerCaseEqualsLiteral(KEY_HREF_LOWER))
839 147 : href = value;
840 396 : else if (key.LowerCaseEqualsLiteral(KEY_FEEDURL_LOWER))
841 11 : feedUrl = value;
842 385 : else if (key.LowerCaseEqualsLiteral(KEY_ICON_LOWER))
843 81 : icon = value;
844 304 : else if (key.LowerCaseEqualsLiteral(KEY_ICON_URI_LOWER))
845 36 : iconUri = value;
846 268 : else if (key.LowerCaseEqualsLiteral(KEY_LASTCHARSET_LOWER))
847 11 : lastCharset = value;
848 257 : else if (key.LowerCaseEqualsLiteral(KEY_SHORTCUTURL_LOWER))
849 11 : keyword = value;
850 246 : else if (key.LowerCaseEqualsLiteral(KEY_POST_DATA_LOWER))
851 11 : postData = value;
852 235 : else if (key.LowerCaseEqualsLiteral(KEY_WEB_PANEL_LOWER))
853 11 : webPanel = value;
854 224 : else if (key.LowerCaseEqualsLiteral(KEY_MICSUM_GEN_URI_LOWER))
855 0 : micsumGenURI = value;
856 224 : else if (key.LowerCaseEqualsLiteral(KEY_GENERATED_TITLE_LOWER))
857 0 : generatedTitle = value;
858 224 : else if (key.LowerCaseEqualsLiteral(KEY_DATE_ADDED_LOWER))
859 80 : dateAdded = value;
860 144 : else if (key.LowerCaseEqualsLiteral(KEY_LAST_MODIFIED_LOWER))
861 84 : lastModified = value;
862 : }
863 :
864 : // For feeds, get the feed URL. If it is invalid, mPreviousFeed will be
865 : // NULL and we'll create it as a normal bookmark.
866 147 : if (!feedUrl.IsEmpty()) {
867 11 : NS_NewURI(getter_AddRefs(frame.mPreviousFeed),
868 22 : NS_ConvertUTF16toUTF8(feedUrl), nsnull);
869 : }
870 :
871 : // Ignore <a> tags that have no href.
872 147 : if (href.IsEmpty()) {
873 0 : frame.mPreviousLink = nsnull;
874 : // The exception is for feeds, where the href is an optional component
875 : // indicating the source web site.
876 0 : if (!frame.mPreviousFeed)
877 : return;
878 : }
879 : else {
880 : // Save the address if it's valid. Note that we ignore errors if this is a
881 : // feed since href is optional for them.
882 147 : nsresult rv = NS_NewURI(getter_AddRefs(frame.mPreviousLink), href, nsnull);
883 147 : if (NS_FAILED(rv) && !frame.mPreviousFeed) {
884 1 : frame.mPreviousLink = nsnull;
885 : return;
886 : }
887 : }
888 :
889 : // Save bookmark's last modified date.
890 146 : if (!lastModified.IsEmpty()) {
891 : frame.mPreviousLastModifiedDate =
892 84 : ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(lastModified));
893 : }
894 :
895 : // If this is a live bookmark, we will handle it in HandleLinkEnd(), so we
896 : // can skip bookmark creation.
897 146 : if (frame.mPreviousFeed)
898 : return;
899 :
900 : // Create the bookmark. The title is unknown for now, we will set it later.
901 135 : nsresult rv = mBookmarksService->InsertBookmark(frame.mContainerID,
902 : frame.mPreviousLink,
903 135 : mBookmarksService->DEFAULT_INDEX,
904 135 : EmptyCString(),
905 405 : &frame.mPreviousId);
906 135 : if (NS_FAILED(rv)) {
907 : // If inserting bookmark failed, there's nothing more we can do.
908 0 : NS_WARNING("InserBookmark failed");
909 : return;
910 : }
911 :
912 : // Set the date added value, if we have it.
913 135 : if (!dateAdded.IsEmpty()) {
914 : PRTime convertedDateAdded =
915 80 : ConvertImportedDateToInternalDate(NS_ConvertUTF16toUTF8(dateAdded));
916 80 : if (convertedDateAdded) {
917 80 : rv = mBookmarksService->SetItemDateAdded(frame.mPreviousId, convertedDateAdded);
918 80 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemDateAdded failed");
919 : }
920 : }
921 :
922 : // Save the favicon.
923 135 : if (!icon.IsEmpty() || !iconUri.IsEmpty()) {
924 162 : nsCOMPtr<nsIURI> iconUriObject;
925 81 : rv = NS_NewURI(getter_AddRefs(iconUriObject), iconUri);
926 81 : if (!icon.IsEmpty() || NS_SUCCEEDED(rv)) {
927 81 : rv = SetFaviconForURI(frame.mPreviousLink, iconUriObject, icon);
928 81 : if (NS_FAILED(rv)) {
929 0 : nsCAutoString warnMsg;
930 0 : warnMsg.Append("Bookmarks Import: unable to set favicon '");
931 0 : warnMsg.Append(NS_ConvertUTF16toUTF8(iconUri));
932 0 : warnMsg.Append("' for page '");
933 0 : nsCAutoString spec;
934 0 : rv = frame.mPreviousLink->GetSpec(spec);
935 0 : if (NS_SUCCEEDED(rv))
936 0 : warnMsg.Append(spec);
937 0 : warnMsg.Append("'");
938 0 : NS_WARNING(warnMsg.get());
939 : }
940 : }
941 : }
942 :
943 : // Save the keyword.
944 135 : if (!keyword.IsEmpty()) {
945 11 : rv = mBookmarksService->SetKeywordForBookmark(frame.mPreviousId, keyword);
946 11 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetKeywordForBookmark failed");
947 11 : if (NS_SUCCEEDED(rv) && !postData.IsEmpty()) {
948 11 : rv = mAnnotationService->SetItemAnnotationString(frame.mPreviousId,
949 11 : POST_DATA_ANNO,
950 : postData, 0,
951 11 : nsIAnnotationService::EXPIRE_NEVER);
952 11 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemAnnotationString failed");
953 : }
954 : }
955 :
956 : // Set load-in-sidebar annotation for the bookmark.
957 135 : if (webPanel.LowerCaseEqualsLiteral("true")) {
958 :
959 11 : rv = mAnnotationService->SetItemAnnotationInt32(frame.mPreviousId,
960 11 : LOAD_IN_SIDEBAR_ANNO,
961 : 1, 0,
962 11 : nsIAnnotationService::EXPIRE_NEVER);
963 11 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemAnnotationInt32 failed");
964 : }
965 :
966 : // Import last charset.
967 135 : if (!lastCharset.IsEmpty()) {
968 11 : rv = mHistoryService->SetCharsetForURI(frame.mPreviousLink,lastCharset);
969 11 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "setCharsetForURI failed");
970 : }
971 : }
972 :
973 :
974 : // BookmarkContentSink::HandleLinkEnd
975 : //
976 : // Saves the title for the given bookmark. This only writes the user title.
977 : // Any previous title will be untouched. If this is a new entry, it will have
978 : // an empty "official" title until you visit it.
979 :
980 : void
981 147 : BookmarkContentSink::HandleLinkEnd()
982 : {
983 : nsresult rv;
984 147 : BookmarkImportFrame& frame = CurFrame();
985 147 : frame.mPreviousText.Trim(kWhitespace);
986 :
987 147 : if (frame.mPreviousFeed) {
988 : // The is a live bookmark. We create it here since in HandleLinkBegin we
989 : // don't know the title.
990 : jsval livemark = livemarkInfoToJSVal(
991 11 : 0, EmptyCString(), frame.mPreviousText, frame.mContainerID,
992 11 : mBookmarksService->DEFAULT_INDEX, frame.mPreviousFeed, frame.mPreviousLink
993 33 : );
994 :
995 : // Create the live bookmark.
996 11 : rv = mLivemarkService->AddLivemark(livemark, nsnull);
997 11 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "AddLivemark failed!");
998 :
999 : #ifdef DEBUG_IMPORT
1000 : PrintNesting();
1001 : printf("Created livemark '%s'\n",
1002 : NS_ConvertUTF16toUTF8(frame.mPreviousText).get());
1003 : #endif
1004 : }
1005 136 : else if (frame.mPreviousLink) {
1006 : // This is a common bookmark.
1007 : #ifdef DEBUG_IMPORT
1008 : PrintNesting();
1009 : printf("Created bookmark '%s' %lld\n",
1010 : NS_ConvertUTF16toUTF8(frame.mPreviousText).get(), frame.mPreviousId);
1011 : #endif
1012 135 : rv = mBookmarksService->SetItemTitle(frame.mPreviousId,
1013 135 : NS_ConvertUTF16toUTF8(frame.mPreviousText));
1014 135 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemTitle failed");
1015 : }
1016 :
1017 : // Set last modified date as the last change.
1018 147 : if (frame.mPreviousId > 0 && frame.mPreviousLastModifiedDate > 0) {
1019 81 : rv = mBookmarksService->SetItemLastModified(frame.mPreviousId,
1020 81 : frame.mPreviousLastModifiedDate);
1021 81 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemLastModified failed");
1022 : // Note: don't clear mPreviousLastModifiedDate, because if this item has a
1023 : // description, we'll need to set it again.
1024 : }
1025 :
1026 147 : frame.mPreviousText.Truncate();
1027 147 : }
1028 :
1029 :
1030 : // BookmarkContentSink::HandleSeparator
1031 : //
1032 : // Inserts a separator into the current container
1033 : void
1034 0 : BookmarkContentSink::HandleSeparator(const nsIParserNode& aNode)
1035 : {
1036 0 : BookmarkImportFrame& frame = CurFrame();
1037 :
1038 : // create the separator
1039 :
1040 : #ifdef DEBUG_IMPORT
1041 : PrintNesting();
1042 : printf("--------\n");
1043 : #endif
1044 :
1045 0 : nsresult rv = mBookmarksService->InsertSeparator(frame.mContainerID,
1046 0 : mBookmarksService->DEFAULT_INDEX,
1047 0 : &frame.mPreviousId);
1048 0 : if (NS_FAILED(rv)) {
1049 0 : NS_WARNING("InsertSeparator failed");
1050 0 : return;
1051 : }
1052 : // Import separator title if set.
1053 : // Note that Places does not use separator titles, nor backup/restore them.
1054 0 : PRInt32 attrCount = aNode.GetAttributeCount();
1055 0 : for (PRInt32 i = 0; i < attrCount; i ++) {
1056 0 : const nsAString& key = aNode.GetKeyAt(i);
1057 :
1058 0 : if (key.LowerCaseEqualsLiteral(KEY_NAME_LOWER)) {
1059 0 : nsAutoString name;
1060 0 : name = aNode.GetValueAt(i);
1061 0 : name.Trim(kWhitespace);
1062 0 : if (!name.IsEmpty()) {
1063 0 : rv = mBookmarksService->SetItemTitle(frame.mPreviousId,
1064 0 : NS_ConvertUTF16toUTF8(name));
1065 0 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemTitle failed");
1066 : }
1067 : }
1068 : }
1069 :
1070 : // Note: we do not need to import ADD_DATE or LAST_MODIFIED for separators
1071 : // because pre-Places bookmarks does not support them.
1072 : // and we can't write them out because attributes other than NAME
1073 : // will make Firefox 2.x crash/hang due to bug #381129
1074 : }
1075 :
1076 :
1077 : // BookmarkContentSink::NewFrame
1078 : //
1079 : // This is called when there is a new folder found. The folder takes the
1080 : // name from the previous frame's heading.
1081 :
1082 : nsresult
1083 61 : BookmarkContentSink::NewFrame()
1084 : {
1085 : nsresult rv;
1086 :
1087 61 : PRInt64 ourID = 0;
1088 122 : nsString containerName;
1089 : BookmarkImportFrame::ContainerType containerType;
1090 61 : BookmarkImportFrame& frame = CurFrame();
1091 61 : frame.ConsumeHeading(&containerName, &containerType);
1092 :
1093 61 : bool updateFolder = false;
1094 :
1095 61 : switch (containerType) {
1096 : case BookmarkImportFrame::Container_Normal:
1097 : // append a new folder
1098 64 : rv = mBookmarksService->CreateFolder(CurFrame().mContainerID,
1099 32 : NS_ConvertUTF16toUTF8(containerName),
1100 32 : mBookmarksService->DEFAULT_INDEX,
1101 96 : &ourID);
1102 32 : NS_ENSURE_SUCCESS(rv, rv);
1103 32 : break;
1104 : case BookmarkImportFrame::Container_Places:
1105 : // places root, never reparent here, when we're building the initial
1106 : // hierarchy, it will only be defined at the top level
1107 0 : rv = mBookmarksService->GetPlacesRoot(&ourID);
1108 0 : NS_ENSURE_SUCCESS(rv, rv);
1109 0 : break;
1110 : case BookmarkImportFrame::Container_Menu:
1111 : // menu folder
1112 0 : rv = mBookmarksService->GetBookmarksMenuFolder(&ourID);
1113 0 : NS_ENSURE_SUCCESS(rv, rv);
1114 0 : if (mAllowRootChanges)
1115 0 : updateFolder = true;
1116 0 : break;
1117 : case BookmarkImportFrame::Container_Unfiled:
1118 : // unfiled bookmarks folder
1119 9 : rv = mBookmarksService->GetUnfiledBookmarksFolder(&ourID);
1120 9 : NS_ENSURE_SUCCESS(rv, rv);
1121 9 : if (mAllowRootChanges)
1122 0 : updateFolder = true;
1123 9 : break;
1124 : case BookmarkImportFrame::Container_Toolbar:
1125 : // get toolbar folder
1126 20 : rv = mBookmarksService->GetToolbarFolder(&ourID);
1127 20 : NS_ENSURE_SUCCESS(rv, rv);
1128 :
1129 20 : break;
1130 : default:
1131 0 : NS_NOTREACHED("Unknown container type");
1132 : }
1133 :
1134 : #ifdef DEBUG_IMPORT
1135 : PrintNesting();
1136 : printf("Folder %lld \'%s\'", ourID, NS_ConvertUTF16toUTF8(containerName).get());
1137 : #endif
1138 :
1139 61 : if (updateFolder) {
1140 : // move the menu folder to the current position
1141 0 : rv = mBookmarksService->MoveItem(ourID, CurFrame().mContainerID, -1);
1142 0 : NS_ENSURE_SUCCESS(rv, rv);
1143 0 : rv = mBookmarksService->SetItemTitle(ourID, NS_ConvertUTF16toUTF8(containerName));
1144 0 : NS_ENSURE_SUCCESS(rv, rv);
1145 : #ifdef DEBUG_IMPORT
1146 : printf(" [reparenting]");
1147 : #endif
1148 : }
1149 :
1150 : #ifdef DEBUG_IMPORT
1151 : printf("\n");
1152 : #endif
1153 :
1154 61 : if (frame.mPreviousDateAdded > 0) {
1155 37 : rv = mBookmarksService->SetItemDateAdded(ourID, frame.mPreviousDateAdded);
1156 37 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemDateAdded failed");
1157 37 : frame.mPreviousDateAdded = 0;
1158 : }
1159 61 : if (frame.mPreviousLastModifiedDate > 0) {
1160 45 : rv = mBookmarksService->SetItemLastModified(ourID, frame.mPreviousLastModifiedDate);
1161 45 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SetItemLastModified failed");
1162 : // don't clear last-modified, in case there's a description
1163 : }
1164 :
1165 61 : frame.mPreviousId = ourID;
1166 :
1167 61 : if (!mFrames.AppendElement(BookmarkImportFrame(ourID)))
1168 0 : return NS_ERROR_OUT_OF_MEMORY;
1169 :
1170 61 : return NS_OK;
1171 : }
1172 :
1173 :
1174 : nsresult
1175 61 : BookmarkContentSink::PopFrame()
1176 : {
1177 : // we must always have one frame
1178 61 : if (mFrames.Length() <= 1) {
1179 0 : NS_NOTREACHED("Trying to complete more bookmark folders than you started");
1180 0 : return NS_ERROR_FAILURE;
1181 : }
1182 61 : mFrames.RemoveElementAt(mFrames.Length() - 1);
1183 61 : return NS_OK;
1184 : }
1185 :
1186 :
1187 : // BookmarkContentSink::SetFaviconForURI
1188 : //
1189 : // aData is a string that is a data URI for the favicon. Our job is to
1190 : // decode it and store it in the favicon service.
1191 : //
1192 : // When aIconURI is non-null, we will use that as the URI of the favicon
1193 : // when storing in the favicon service.
1194 : //
1195 : // When aIconURI is null, we have to make up a URI for this favicon so that
1196 : // it can be stored in the service. The real one will be set the next time
1197 : // the user visits the page. Our made up one should get expired when the
1198 : // page no longer references it.
1199 : nsresult
1200 81 : BookmarkContentSink::SetFaviconForURI(nsIURI* aPageURI, nsIURI* aIconURI,
1201 : const nsString& aData)
1202 : {
1203 : nsresult rv;
1204 : static PRUint32 serialNumber = 0; // for made-up favicon URIs
1205 :
1206 : nsCOMPtr<nsIFaviconService> faviconService =
1207 162 : do_GetService(NS_FAVICONSERVICE_CONTRACTID);
1208 81 : NS_ENSURE_TRUE(faviconService, NS_ERROR_OUT_OF_MEMORY);
1209 :
1210 : // if the input favicon URI is a chrome: URI, then we just save it and don't
1211 : // worry about data
1212 81 : if (aIconURI) {
1213 72 : nsCString faviconScheme;
1214 36 : aIconURI->GetScheme(faviconScheme);
1215 36 : if (faviconScheme.EqualsLiteral("chrome")) {
1216 0 : return faviconService->SetFaviconUrlForPage(aPageURI, aIconURI);
1217 : }
1218 : }
1219 :
1220 : // some bookmarks have placeholder URIs that contain just "data:"
1221 : // ignore these
1222 81 : if (aData.Length() <= 5)
1223 1 : return NS_OK;
1224 :
1225 160 : nsCOMPtr<nsIURI> faviconURI;
1226 80 : if (aIconURI) {
1227 35 : faviconURI = aIconURI;
1228 : }
1229 : else {
1230 : // make up favicon URL
1231 90 : nsCAutoString faviconSpec;
1232 45 : faviconSpec.AssignLiteral("http://www.mozilla.org/2005/made-up-favicon/");
1233 45 : faviconSpec.AppendInt(serialNumber);
1234 45 : faviconSpec.AppendLiteral("-");
1235 : char buf[32];
1236 45 : PR_snprintf(buf, sizeof(buf), "%lld", PR_Now());
1237 45 : faviconSpec.Append(buf);
1238 45 : rv = NS_NewURI(getter_AddRefs(faviconURI), faviconSpec);
1239 45 : if (NS_FAILED(rv)) {
1240 0 : nsCAutoString warnMsg;
1241 0 : warnMsg.Append("Bookmarks Import: Unable to make up new favicon '");
1242 0 : warnMsg.Append(faviconSpec);
1243 0 : warnMsg.Append("' for page '");
1244 0 : nsCAutoString spec;
1245 0 : rv = aPageURI->GetSpec(spec);
1246 0 : if (NS_SUCCEEDED(rv))
1247 0 : warnMsg.Append(spec);
1248 0 : warnMsg.Append("'");
1249 0 : NS_WARNING(warnMsg.get());
1250 0 : return NS_OK;
1251 : }
1252 90 : serialNumber++;
1253 : }
1254 :
1255 : // save the favicon data
1256 : // This could fail if the favicon is bigger than defined limit, in such a
1257 : // case data will not be saved to the db but we will still continue.
1258 80 : (void) faviconService->SetFaviconDataFromDataURL(faviconURI, aData, 0);
1259 :
1260 80 : rv = faviconService->SetFaviconUrlForPage(aPageURI, faviconURI);
1261 80 : NS_ENSURE_SUCCESS(rv, rv);
1262 :
1263 80 : return NS_OK;
1264 : }
1265 :
1266 :
1267 : // Converts a string date in seconds to an int date in microseconds
1268 : PRTime
1269 242 : BookmarkContentSink::ConvertImportedDateToInternalDate(const nsACString& aDate) {
1270 242 : PRTime convertedDate = 0;
1271 242 : if (!aDate.IsEmpty()) {
1272 : nsresult rv;
1273 242 : convertedDate = PromiseFlatCString(aDate).ToInteger(&rv);
1274 242 : if (NS_SUCCEEDED(rv)) {
1275 242 : convertedDate *= 1000000; // in bookmarks.html this value is in seconds, not microseconds
1276 : }
1277 : else {
1278 0 : convertedDate = 0;
1279 : }
1280 : }
1281 242 : return convertedDate;
1282 : }
1283 :
1284 :
1285 : // SyncChannelStatus
1286 : //
1287 : // If a function returns an error, we need to set the channel status to be
1288 : // the same, but only if the channel doesn't have its own error. This returns
1289 : // the error code that should be sent to OnStopRequest.
1290 : static nsresult
1291 53 : SyncChannelStatus(nsIChannel* channel, nsresult status)
1292 : {
1293 : nsresult channelStatus;
1294 53 : channel->GetStatus(&channelStatus);
1295 53 : if (NS_FAILED(channelStatus))
1296 0 : return channelStatus;
1297 :
1298 53 : if (NS_SUCCEEDED(status))
1299 53 : return NS_OK; // caller and the channel are happy
1300 :
1301 : // channel was OK, but caller wasn't: set the channel state
1302 0 : channel->Cancel(status);
1303 0 : return status;
1304 : }
1305 :
1306 :
1307 : static char kFileIntro[] =
1308 : "<!DOCTYPE NETSCAPE-Bookmark-file-1>" NS_LINEBREAK
1309 : // Note: we write bookmarks in UTF-8
1310 : "<!-- This is an automatically generated file." NS_LINEBREAK
1311 : " It will be read and overwritten." NS_LINEBREAK
1312 : " DO NOT EDIT! -->" NS_LINEBREAK
1313 : "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">" NS_LINEBREAK
1314 : "<TITLE>Bookmarks</TITLE>" NS_LINEBREAK;
1315 : static const char kRootIntro[] = "<H1";
1316 : static const char kCloseRootH1[] = "</H1>" NS_LINEBREAK NS_LINEBREAK;
1317 :
1318 : static const char kBookmarkIntro[] = "<DL><p>" NS_LINEBREAK;
1319 : static const char kBookmarkClose[] = "</DL><p>" NS_LINEBREAK;
1320 : static const char kContainerIntro[] = "<DT><H3";
1321 : static const char kContainerClose[] = "</H3>" NS_LINEBREAK;
1322 : static const char kItemOpen[] = "<DT><A";
1323 : static const char kItemClose[] = "</A>" NS_LINEBREAK;
1324 : static const char kSeparator[] = "<HR";
1325 : static const char kQuoteStr[] = "\"";
1326 : static const char kCloseAngle[] = ">";
1327 : static const char kIndent[] = " ";
1328 : static const char kDescriptionIntro[] = "<DD>";
1329 : static const char kDescriptionClose[] = NS_LINEBREAK;
1330 :
1331 : static const char kPlacesRootAttribute[] = " PLACES_ROOT=\"true\"";
1332 : static const char kBookmarksRootAttribute[] = " BOOKMARKS_MENU=\"true\"";
1333 : static const char kToolbarFolderAttribute[] = " PERSONAL_TOOLBAR_FOLDER=\"true\"";
1334 : static const char kUnfiledBookmarksFolderAttribute[] = " UNFILED_BOOKMARKS_FOLDER=\"true\"";
1335 : static const char kIconAttribute[] = " ICON=\"";
1336 : static const char kIconURIAttribute[] = " ICON_URI=\"";
1337 : static const char kHrefAttribute[] = " HREF=\"";
1338 : static const char kFeedURIAttribute[] = " FEEDURL=\"";
1339 : static const char kWebPanelAttribute[] = " WEB_PANEL=\"true\"";
1340 : static const char kKeywordAttribute[] = " SHORTCUTURL=\"";
1341 : static const char kPostDataAttribute[] = " POST_DATA=\"";
1342 : static const char kNameAttribute[] = " NAME=\"";
1343 : static const char kMicsumGenURIAttribute[] = " MICSUM_GEN_URI=\"";
1344 : static const char kDateAddedAttribute[] = " ADD_DATE=\"";
1345 : static const char kLastModifiedAttribute[] = " LAST_MODIFIED=\"";
1346 : static const char kLastCharsetAttribute[] = " LAST_CHARSET=\"";
1347 :
1348 :
1349 : // WriteContainerPrologue
1350 : //
1351 : // <DL><p>
1352 : //
1353 : // Goes after the container header (<H3...) but before the contents
1354 : static nsresult
1355 32 : WriteContainerPrologue(const nsACString& aIndent, nsIOutputStream* aOutput)
1356 : {
1357 : PRUint32 dummy;
1358 32 : nsresult rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy);
1359 32 : NS_ENSURE_SUCCESS(rv, rv);
1360 32 : rv = aOutput->Write(kBookmarkIntro, sizeof(kBookmarkIntro)-1, &dummy);
1361 32 : NS_ENSURE_SUCCESS(rv, rv);
1362 32 : return NS_OK;
1363 : }
1364 :
1365 :
1366 : // WriteContainerEpilogue
1367 : //
1368 : // </DL><p>
1369 : //
1370 : // Goes after the container contents to close the container
1371 : static nsresult
1372 32 : WriteContainerEpilogue(const nsACString& aIndent, nsIOutputStream* aOutput)
1373 : {
1374 : PRUint32 dummy;
1375 32 : nsresult rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy);
1376 32 : NS_ENSURE_SUCCESS(rv, rv);
1377 32 : rv = aOutput->Write(kBookmarkClose, sizeof(kBookmarkClose)-1, &dummy);
1378 32 : NS_ENSURE_SUCCESS(rv, rv);
1379 32 : return NS_OK;
1380 : }
1381 :
1382 :
1383 : // WriteFaviconAttribute
1384 : //
1385 : // This writes the 'ICON="data:asdlfkjas;ldkfja;skdljfasdf"' attribute for
1386 : // an item. We special-case chrome favicon URIs by just writing the chrome:
1387 : // URI.
1388 : static nsresult
1389 56 : WriteFaviconAttribute(const nsACString& aURI, nsIOutputStream* aOutput)
1390 : {
1391 : PRUint32 dummy;
1392 :
1393 : // if favicon uri is invalid we skip the attribute silently, to avoid
1394 : // creating a corrupt file.
1395 112 : nsCOMPtr<nsIURI> uri;
1396 56 : nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI);
1397 56 : if (NS_FAILED(rv)) {
1398 0 : nsCAutoString warnMsg;
1399 0 : warnMsg.Append("Bookmarks Export: Found invalid favicon '");
1400 0 : warnMsg.Append(aURI);
1401 0 : warnMsg.Append("'");
1402 0 : NS_WARNING(warnMsg.get());
1403 0 : return NS_OK;
1404 : }
1405 :
1406 : // get favicon
1407 112 : nsCOMPtr<nsIFaviconService> faviconService = do_GetService(NS_FAVICONSERVICE_CONTRACTID, &rv);
1408 56 : NS_ENSURE_SUCCESS(rv, rv);
1409 112 : nsCOMPtr<nsIURI> faviconURI;
1410 56 : rv = faviconService->GetFaviconForPage(uri, getter_AddRefs(faviconURI));
1411 56 : if (rv == NS_ERROR_NOT_AVAILABLE)
1412 30 : return NS_OK; // no favicon
1413 26 : NS_ENSURE_SUCCESS(rv, rv); // anything else is error
1414 :
1415 52 : nsCAutoString faviconScheme;
1416 52 : nsCAutoString faviconSpec;
1417 26 : rv = faviconURI->GetSpec(faviconSpec);
1418 26 : NS_ENSURE_SUCCESS(rv, rv);
1419 26 : rv = faviconURI->GetScheme(faviconScheme);
1420 26 : NS_ENSURE_SUCCESS(rv, rv);
1421 :
1422 : // write favicon URI: 'ICON_URI="..."'
1423 26 : rv = aOutput->Write(kIconURIAttribute, sizeof(kIconURIAttribute)-1, &dummy);
1424 26 : NS_ENSURE_SUCCESS(rv, rv);
1425 26 : rv = WriteEscapedUrl(faviconSpec, aOutput);
1426 26 : NS_ENSURE_SUCCESS(rv, rv);
1427 26 : rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
1428 26 : NS_ENSURE_SUCCESS(rv, rv);
1429 :
1430 26 : if (!faviconScheme.EqualsLiteral("chrome")) {
1431 : // only store data for non-chrome URIs
1432 :
1433 52 : nsAutoString faviconContents;
1434 26 : rv = faviconService->GetFaviconDataAsDataURL(faviconURI, faviconContents);
1435 26 : NS_ENSURE_SUCCESS(rv, rv);
1436 26 : if (faviconContents.Length() > 0) {
1437 25 : rv = aOutput->Write(kIconAttribute, sizeof(kIconAttribute)-1, &dummy);
1438 25 : NS_ENSURE_SUCCESS(rv, rv);
1439 50 : NS_ConvertUTF16toUTF8 utf8Favicon(faviconContents);
1440 25 : rv = aOutput->Write(utf8Favicon.get(), utf8Favicon.Length(), &dummy);
1441 25 : NS_ENSURE_SUCCESS(rv, rv);
1442 25 : rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
1443 25 : NS_ENSURE_SUCCESS(rv, rv);
1444 : }
1445 : }
1446 26 : return NS_OK;
1447 : }
1448 :
1449 :
1450 : // WriteDateAttribute
1451 : //
1452 : // This writes the '{attr value=}"{time in seconds}"' attribute for
1453 : // an item.
1454 : static nsresult
1455 156 : WriteDateAttribute(const char aAttributeStart[], PRInt32 aLength, PRTime aAttributeValue, nsIOutputStream* aOutput)
1456 : {
1457 : // write attribute start
1458 : PRUint32 dummy;
1459 156 : nsresult rv = aOutput->Write(aAttributeStart, aLength, &dummy);
1460 156 : NS_ENSURE_SUCCESS(rv, rv);
1461 :
1462 : // in bookmarks.html this value is in seconds, not microseconds
1463 156 : aAttributeValue /= 1000000;
1464 :
1465 : // write attribute value
1466 : char dateInSeconds[32];
1467 156 : PR_snprintf(dateInSeconds, sizeof(dateInSeconds), "%lld", aAttributeValue);
1468 156 : rv = aOutput->Write(dateInSeconds, strlen(dateInSeconds), &dummy);
1469 156 : NS_ENSURE_SUCCESS(rv, rv);
1470 156 : rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
1471 156 : NS_ENSURE_SUCCESS(rv, rv);
1472 :
1473 156 : return NS_OK;
1474 : }
1475 :
1476 :
1477 : // nsPlacesImportExportService::WriteContainer
1478 : //
1479 : // Writes out all the necessary parts of a bookmarks folder.
1480 : nsresult
1481 22 : nsPlacesImportExportService::WriteContainer(nsINavHistoryResultNode* aFolder,
1482 : const nsACString& aIndent,
1483 : nsIOutputStream* aOutput)
1484 : {
1485 22 : nsresult rv = WriteContainerHeader(aFolder, aIndent, aOutput);
1486 22 : NS_ENSURE_SUCCESS(rv, rv);
1487 22 : rv = WriteContainerPrologue(aIndent, aOutput);
1488 22 : NS_ENSURE_SUCCESS(rv, rv);
1489 22 : rv = WriteContainerContents(aFolder, aIndent, aOutput);
1490 22 : NS_ENSURE_SUCCESS(rv, rv);
1491 22 : rv = WriteContainerEpilogue(aIndent, aOutput);
1492 22 : NS_ENSURE_SUCCESS(rv, rv);
1493 22 : return NS_OK;
1494 : }
1495 :
1496 :
1497 : // nsPlacesImportExportService::WriteContainerHeader
1498 : //
1499 : // This writes '<DL><H3>Title</H3>'
1500 : // Remember folders can also have favicons, which we put in the H3 tag
1501 : nsresult
1502 22 : nsPlacesImportExportService::WriteContainerHeader(nsINavHistoryResultNode* aFolder,
1503 : const nsACString& aIndent,
1504 : nsIOutputStream* aOutput)
1505 : {
1506 : PRUint32 dummy;
1507 : nsresult rv;
1508 :
1509 : // indent
1510 22 : if (!aIndent.IsEmpty()) {
1511 22 : rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy);
1512 22 : NS_ENSURE_SUCCESS(rv, rv);
1513 : }
1514 :
1515 : // "<DL H3"
1516 22 : rv = aOutput->Write(kContainerIntro, sizeof(kContainerIntro)-1, &dummy);
1517 22 : NS_ENSURE_SUCCESS(rv, rv);
1518 :
1519 : // get folder id
1520 : PRInt64 folderId;
1521 22 : rv = aFolder->GetItemId(&folderId);
1522 22 : NS_ENSURE_SUCCESS(rv, rv);
1523 :
1524 : // write ADD_DATE
1525 22 : PRTime dateAdded = 0;
1526 22 : rv = aFolder->GetDateAdded(&dateAdded);
1527 22 : NS_ENSURE_SUCCESS(rv, rv);
1528 :
1529 22 : if (dateAdded) {
1530 22 : rv = WriteDateAttribute(kDateAddedAttribute, sizeof(kDateAddedAttribute)-1, dateAdded, aOutput);
1531 22 : NS_ENSURE_SUCCESS(rv, rv);
1532 : }
1533 :
1534 : // write LAST_MODIFIED
1535 22 : PRTime lastModified = 0;
1536 22 : rv = aFolder->GetLastModified(&lastModified);
1537 22 : NS_ENSURE_SUCCESS(rv, rv);
1538 :
1539 22 : if (lastModified) {
1540 22 : rv = WriteDateAttribute(kLastModifiedAttribute, sizeof(kLastModifiedAttribute)-1, lastModified, aOutput);
1541 22 : NS_ENSURE_SUCCESS(rv, rv);
1542 : }
1543 :
1544 : PRInt64 placesRoot;
1545 22 : rv = mBookmarksService->GetPlacesRoot(&placesRoot);
1546 22 : NS_ENSURE_SUCCESS(rv, rv);
1547 :
1548 : PRInt64 bookmarksMenuFolder;
1549 22 : rv = mBookmarksService->GetBookmarksMenuFolder(&bookmarksMenuFolder);
1550 22 : NS_ENSURE_SUCCESS(rv, rv);
1551 :
1552 : PRInt64 toolbarFolder;
1553 22 : rv = mBookmarksService->GetToolbarFolder(&toolbarFolder);
1554 22 : NS_ENSURE_SUCCESS(rv, rv);
1555 :
1556 : PRInt64 unfiledBookmarksFolder;
1557 22 : rv = mBookmarksService->GetUnfiledBookmarksFolder(&unfiledBookmarksFolder);
1558 22 : NS_ENSURE_SUCCESS(rv, rv);
1559 :
1560 : // " PERSONAL_TOOLBAR_FOLDER="true"", etc.
1561 22 : if (folderId == placesRoot) {
1562 0 : rv = aOutput->Write(kPlacesRootAttribute, sizeof(kPlacesRootAttribute)-1, &dummy);
1563 0 : NS_ENSURE_SUCCESS(rv, rv);
1564 : }
1565 22 : else if (folderId == bookmarksMenuFolder) {
1566 0 : rv = aOutput->Write(kBookmarksRootAttribute, sizeof(kBookmarksRootAttribute)-1, &dummy);
1567 0 : NS_ENSURE_SUCCESS(rv, rv);
1568 : }
1569 22 : else if (folderId == unfiledBookmarksFolder) {
1570 5 : rv = aOutput->Write(kUnfiledBookmarksFolderAttribute, sizeof(kUnfiledBookmarksFolderAttribute)-1, &dummy);
1571 5 : NS_ENSURE_SUCCESS(rv, rv);
1572 : }
1573 17 : else if (folderId == toolbarFolder) {
1574 7 : rv = aOutput->Write(kToolbarFolderAttribute, sizeof(kToolbarFolderAttribute)-1, &dummy);
1575 7 : NS_ENSURE_SUCCESS(rv, rv);
1576 : }
1577 :
1578 : // ">"
1579 22 : rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy);
1580 22 : NS_ENSURE_SUCCESS(rv, rv);
1581 :
1582 : // title
1583 22 : rv = WriteTitle(aFolder, aOutput);
1584 22 : NS_ENSURE_SUCCESS(rv, rv);
1585 :
1586 : // "</H3>\n"
1587 22 : rv = aOutput->Write(kContainerClose, sizeof(kContainerClose)-1, &dummy);
1588 22 : NS_ENSURE_SUCCESS(rv, rv);
1589 :
1590 : // description
1591 22 : rv = WriteDescription(folderId, nsINavBookmarksService::TYPE_FOLDER, aOutput);
1592 22 : NS_ENSURE_SUCCESS(rv, rv);
1593 :
1594 22 : return rv;
1595 : }
1596 :
1597 :
1598 : // nsPlacesImportExportService::WriteTitle
1599 : //
1600 : // Retrieves, escapes and writes the title to the stream.
1601 : nsresult
1602 37 : nsPlacesImportExportService::WriteTitle(nsINavHistoryResultNode* aItem,
1603 : nsIOutputStream* aOutput)
1604 : {
1605 : // XXX Bug 381767 - support titles for separators
1606 37 : PRUint32 type = 0;
1607 37 : nsresult rv = aItem->GetType(&type);
1608 37 : NS_ENSURE_SUCCESS(rv, rv);
1609 37 : if (type == nsINavHistoryResultNode::RESULT_TYPE_SEPARATOR)
1610 0 : return NS_ERROR_INVALID_ARG;
1611 :
1612 74 : nsCAutoString title;
1613 37 : rv = aItem->GetTitle(title);
1614 37 : NS_ENSURE_SUCCESS(rv, rv);
1615 :
1616 37 : char* escapedTitle = nsEscapeHTML(title.get());
1617 37 : if (escapedTitle) {
1618 : PRUint32 dummy;
1619 37 : rv = aOutput->Write(escapedTitle, strlen(escapedTitle), &dummy);
1620 37 : nsMemory::Free(escapedTitle);
1621 37 : NS_ENSURE_SUCCESS(rv, rv);
1622 : }
1623 37 : return NS_OK;
1624 : }
1625 :
1626 :
1627 : // nsPlacesImportExportService::WriteDescription
1628 : //
1629 : // Write description out for all item types.
1630 : nsresult
1631 83 : nsPlacesImportExportService::WriteDescription(PRInt64 aItemId, PRInt32 aType,
1632 : nsIOutputStream* aOutput)
1633 : {
1634 83 : bool hasDescription = false;
1635 83 : nsresult rv = mAnnotationService->ItemHasAnnotation(aItemId,
1636 83 : DESCRIPTION_ANNO,
1637 83 : &hasDescription);
1638 83 : if (NS_FAILED(rv) || !hasDescription)
1639 67 : return rv;
1640 :
1641 32 : nsAutoString description;
1642 32 : rv = mAnnotationService->GetItemAnnotationString(aItemId, DESCRIPTION_ANNO,
1643 16 : description);
1644 16 : NS_ENSURE_SUCCESS(rv, rv);
1645 :
1646 16 : char* escapedDesc = nsEscapeHTML(NS_ConvertUTF16toUTF8(description).get());
1647 16 : if (escapedDesc) {
1648 : PRUint32 dummy;
1649 16 : rv = aOutput->Write(kDescriptionIntro, sizeof(kDescriptionIntro)-1, &dummy);
1650 16 : if (NS_FAILED(rv)) {
1651 0 : nsMemory::Free(escapedDesc);
1652 0 : return rv;
1653 : }
1654 16 : rv = aOutput->Write(escapedDesc, strlen(escapedDesc), &dummy);
1655 16 : nsMemory::Free(escapedDesc);
1656 16 : NS_ENSURE_SUCCESS(rv, rv);
1657 16 : rv = aOutput->Write(kDescriptionClose, sizeof(kDescriptionClose)-1, &dummy);
1658 16 : NS_ENSURE_SUCCESS(rv, rv);
1659 : }
1660 16 : return NS_OK;
1661 : }
1662 :
1663 : // nsBookmarks::WriteItem
1664 : //
1665 : // "<DT><A HREF="..." ICON="...">Name</A>"
1666 : nsresult
1667 57 : nsPlacesImportExportService::WriteItem(nsINavHistoryResultNode* aItem,
1668 : const nsACString& aIndent,
1669 : nsIOutputStream* aOutput)
1670 : {
1671 : // before doing any attempt to write the item check that uri is valid, if the
1672 : // item has a bad uri we skip it silently, otherwise we could stop while
1673 : // exporting, generating a corrupt file.
1674 114 : nsCAutoString uri;
1675 57 : nsresult rv = aItem->GetUri(uri);
1676 57 : NS_ENSURE_SUCCESS(rv, rv);
1677 114 : nsCOMPtr<nsIURI> pageURI;
1678 57 : rv = NS_NewURI(getter_AddRefs(pageURI), uri, nsnull);
1679 57 : if (NS_FAILED(rv)) {
1680 2 : nsCAutoString warnMsg;
1681 1 : warnMsg.Append("Bookmarks Export: Found invalid item uri '");
1682 1 : warnMsg.Append(uri);
1683 1 : warnMsg.Append("'");
1684 1 : NS_WARNING(warnMsg.get());
1685 1 : return NS_OK;
1686 : }
1687 :
1688 : // indent
1689 : PRUint32 dummy;
1690 56 : if (!aIndent.IsEmpty()) {
1691 56 : rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy);
1692 56 : NS_ENSURE_SUCCESS(rv, rv);
1693 : }
1694 :
1695 : // '<DT><A'
1696 56 : rv = aOutput->Write(kItemOpen, sizeof(kItemOpen)-1, &dummy);
1697 56 : NS_ENSURE_SUCCESS(rv, rv);
1698 :
1699 : // ' HREF="http://..."' - note that we need to call GetURI on the result
1700 : // node because some nodes (eg queries) generate this lazily.
1701 56 : rv = aOutput->Write(kHrefAttribute, sizeof(kHrefAttribute)-1, &dummy);
1702 56 : NS_ENSURE_SUCCESS(rv, rv);
1703 56 : rv = WriteEscapedUrl(uri, aOutput);
1704 56 : NS_ENSURE_SUCCESS(rv, rv);
1705 56 : rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
1706 56 : NS_ENSURE_SUCCESS(rv, rv);
1707 :
1708 : // write ADD_DATE
1709 56 : PRTime dateAdded = 0;
1710 56 : rv = aItem->GetDateAdded(&dateAdded);
1711 56 : NS_ENSURE_SUCCESS(rv, rv);
1712 :
1713 56 : if (dateAdded) {
1714 56 : rv = WriteDateAttribute(kDateAddedAttribute, sizeof(kDateAddedAttribute)-1, dateAdded, aOutput);
1715 56 : NS_ENSURE_SUCCESS(rv, rv);
1716 : }
1717 :
1718 : // write LAST_MODIFIED
1719 56 : PRTime lastModified = 0;
1720 56 : rv = aItem->GetLastModified(&lastModified);
1721 56 : NS_ENSURE_SUCCESS(rv, rv);
1722 :
1723 56 : if (lastModified) {
1724 56 : rv = WriteDateAttribute(kLastModifiedAttribute, sizeof(kLastModifiedAttribute)-1, lastModified, aOutput);
1725 56 : NS_ENSURE_SUCCESS(rv, rv);
1726 : }
1727 :
1728 : // ' ICON="..."'
1729 56 : rv = WriteFaviconAttribute(uri, aOutput);
1730 56 : NS_ENSURE_SUCCESS(rv, rv);
1731 :
1732 : // get item id
1733 : PRInt64 itemId;
1734 56 : rv = aItem->GetItemId(&itemId);
1735 56 : NS_ENSURE_SUCCESS(rv, rv);
1736 :
1737 : // keyword (shortcuturl)
1738 112 : nsAutoString keyword;
1739 56 : rv = mBookmarksService->GetKeywordForBookmark(itemId, keyword);
1740 56 : NS_ENSURE_SUCCESS(rv, rv);
1741 56 : if (!keyword.IsEmpty()) {
1742 5 : rv = aOutput->Write(kKeywordAttribute, sizeof(kKeywordAttribute)-1, &dummy);
1743 5 : NS_ENSURE_SUCCESS(rv, rv);
1744 5 : char* escapedKeyword = nsEscapeHTML(NS_ConvertUTF16toUTF8(keyword).get());
1745 5 : rv = aOutput->Write(escapedKeyword, strlen(escapedKeyword), &dummy);
1746 5 : nsMemory::Free(escapedKeyword);
1747 5 : NS_ENSURE_SUCCESS(rv, rv);
1748 5 : rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
1749 5 : NS_ENSURE_SUCCESS(rv, rv);
1750 : }
1751 :
1752 : // post data
1753 : bool hasPostData;
1754 112 : rv = mAnnotationService->ItemHasAnnotation(itemId, POST_DATA_ANNO,
1755 56 : &hasPostData);
1756 56 : NS_ENSURE_SUCCESS(rv, rv);
1757 56 : if (hasPostData) {
1758 10 : nsAutoString postData;
1759 10 : rv = mAnnotationService->GetItemAnnotationString(itemId, POST_DATA_ANNO,
1760 5 : postData);
1761 5 : NS_ENSURE_SUCCESS(rv, rv);
1762 5 : rv = aOutput->Write(kPostDataAttribute, sizeof(kPostDataAttribute)-1, &dummy);
1763 5 : NS_ENSURE_SUCCESS(rv, rv);
1764 5 : char* escapedPostData = nsEscapeHTML(NS_ConvertUTF16toUTF8(postData).get());
1765 5 : rv = aOutput->Write(escapedPostData, strlen(escapedPostData), &dummy);
1766 5 : nsMemory::Free(escapedPostData);
1767 5 : NS_ENSURE_SUCCESS(rv, rv);
1768 5 : rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
1769 5 : NS_ENSURE_SUCCESS(rv, rv);
1770 : }
1771 :
1772 : // Write WEB_PANEL="true" if the load-in-sidebar annotation is set for the
1773 : // item
1774 56 : bool loadInSidebar = false;
1775 112 : rv = mAnnotationService->ItemHasAnnotation(itemId, LOAD_IN_SIDEBAR_ANNO,
1776 56 : &loadInSidebar);
1777 56 : NS_ENSURE_SUCCESS(rv, rv);
1778 56 : if (loadInSidebar)
1779 5 : aOutput->Write(kWebPanelAttribute, sizeof(kWebPanelAttribute)-1, &dummy);
1780 :
1781 : // last charset
1782 112 : nsAutoString lastCharset;
1783 112 : if (NS_SUCCEEDED(mHistoryService->GetCharsetForURI(pageURI, lastCharset)) &&
1784 56 : !lastCharset.IsEmpty()) {
1785 5 : rv = aOutput->Write(kLastCharsetAttribute, sizeof(kLastCharsetAttribute)-1, &dummy);
1786 5 : NS_ENSURE_SUCCESS(rv, rv);
1787 5 : char* escapedLastCharset = nsEscapeHTML(NS_ConvertUTF16toUTF8(lastCharset).get());
1788 5 : rv = aOutput->Write(escapedLastCharset, strlen(escapedLastCharset), &dummy);
1789 5 : nsMemory::Free(escapedLastCharset);
1790 5 : NS_ENSURE_SUCCESS(rv, rv);
1791 5 : rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
1792 5 : NS_ENSURE_SUCCESS(rv, rv);
1793 : }
1794 :
1795 : // '>'
1796 56 : rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy);
1797 56 : NS_ENSURE_SUCCESS(rv, rv);
1798 :
1799 : // title
1800 112 : nsCAutoString title;
1801 56 : rv = aItem->GetTitle(title);
1802 56 : NS_ENSURE_SUCCESS(rv, rv);
1803 56 : char* escapedTitle = nsEscapeHTML(title.get());
1804 56 : if (escapedTitle) {
1805 56 : rv = aOutput->Write(escapedTitle, strlen(escapedTitle), &dummy);
1806 56 : nsMemory::Free(escapedTitle);
1807 56 : NS_ENSURE_SUCCESS(rv, rv);
1808 : }
1809 :
1810 : // '</A>\n'
1811 56 : rv = aOutput->Write(kItemClose, sizeof(kItemClose)-1, &dummy);
1812 56 : NS_ENSURE_SUCCESS(rv, rv);
1813 :
1814 : // description
1815 56 : rv = WriteDescription(itemId, nsINavBookmarksService::TYPE_BOOKMARK, aOutput);
1816 56 : NS_ENSURE_SUCCESS(rv, rv);
1817 :
1818 56 : return NS_OK;
1819 : }
1820 :
1821 :
1822 : // WriteLivemark
1823 : //
1824 : // Similar to WriteItem, this has an additional FEEDURL attribute and
1825 : // the HREF is optional and points to the source page.
1826 : nsresult
1827 5 : nsPlacesImportExportService::WriteLivemark(nsINavHistoryResultNode* aFolder, const nsACString& aIndent,
1828 : nsIOutputStream* aOutput)
1829 : {
1830 : PRUint32 dummy;
1831 : nsresult rv;
1832 :
1833 : // indent
1834 5 : if (!aIndent.IsEmpty()) {
1835 5 : rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(), &dummy);
1836 5 : NS_ENSURE_SUCCESS(rv, rv);
1837 : }
1838 :
1839 : // '<DT><A'
1840 5 : rv = aOutput->Write(kItemOpen, sizeof(kItemOpen)-1, &dummy);
1841 5 : NS_ENSURE_SUCCESS(rv, rv);
1842 :
1843 : // get folder id
1844 : PRInt64 folderId;
1845 5 : rv = aFolder->GetItemId(&folderId);
1846 5 : NS_ENSURE_SUCCESS(rv, rv);
1847 :
1848 : // get feed URI
1849 10 : nsString feedSpec;
1850 5 : rv = mAnnotationService->GetItemAnnotationString(folderId,
1851 5 : NS_LITERAL_CSTRING(LMANNO_FEEDURI),
1852 5 : feedSpec);
1853 :
1854 5 : NS_ENSURE_SUCCESS(rv, rv);
1855 :
1856 : // write feed URI
1857 5 : rv = aOutput->Write(kFeedURIAttribute, sizeof(kFeedURIAttribute)-1, &dummy);
1858 5 : NS_ENSURE_SUCCESS(rv, rv);
1859 5 : rv = WriteEscapedUrl(NS_ConvertUTF16toUTF8(feedSpec), aOutput);
1860 5 : NS_ENSURE_SUCCESS(rv, rv);
1861 5 : rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
1862 5 : NS_ENSURE_SUCCESS(rv, rv);
1863 :
1864 : // get the optional site URI
1865 10 : nsString siteSpec;
1866 5 : rv = mAnnotationService->GetItemAnnotationString(folderId,
1867 5 : NS_LITERAL_CSTRING(LMANNO_SITEURI),
1868 5 : siteSpec);
1869 5 : if (NS_SUCCEEDED(rv) && !siteSpec.IsEmpty()) {
1870 : // write site URI
1871 5 : rv = aOutput->Write(kHrefAttribute, sizeof(kHrefAttribute)-1, &dummy);
1872 5 : NS_ENSURE_SUCCESS(rv, rv);
1873 5 : rv = WriteEscapedUrl(NS_ConvertUTF16toUTF8(siteSpec), aOutput);
1874 5 : NS_ENSURE_SUCCESS(rv, rv);
1875 5 : rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
1876 5 : NS_ENSURE_SUCCESS(rv, rv);
1877 : }
1878 :
1879 : // '>'
1880 5 : rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy);
1881 5 : NS_ENSURE_SUCCESS(rv, rv);
1882 :
1883 : // title
1884 5 : rv = WriteTitle(aFolder, aOutput);
1885 5 : NS_ENSURE_SUCCESS(rv, rv);
1886 :
1887 : // '</A>\n'
1888 5 : rv = aOutput->Write(kItemClose, sizeof(kItemClose)-1, &dummy);
1889 5 : NS_ENSURE_SUCCESS(rv, rv);
1890 :
1891 : // description
1892 5 : rv = WriteDescription(folderId, nsINavBookmarksService::TYPE_BOOKMARK, aOutput);
1893 5 : NS_ENSURE_SUCCESS(rv, rv);
1894 :
1895 5 : return NS_OK;
1896 : }
1897 :
1898 :
1899 : // nsPlacesImportExportService::WriteSeparator
1900 : //
1901 : // "<HR NAME="...">"
1902 : nsresult
1903 0 : nsPlacesImportExportService::WriteSeparator(nsINavHistoryResultNode* aItem,
1904 : const nsACString& aIndent,
1905 : nsIOutputStream* aOutput)
1906 : {
1907 : PRUint32 dummy;
1908 : nsresult rv;
1909 :
1910 : // indent
1911 0 : if (!aIndent.IsEmpty()) {
1912 0 : rv = aOutput->Write(PromiseFlatCString(aIndent).get(), aIndent.Length(),
1913 0 : &dummy);
1914 0 : NS_ENSURE_SUCCESS(rv, rv);
1915 : }
1916 :
1917 0 : rv = aOutput->Write(kSeparator, sizeof(kSeparator)-1, &dummy);
1918 0 : NS_ENSURE_SUCCESS(rv, rv);
1919 :
1920 : // XXX: separator result nodes don't support the title getter yet
1921 : PRInt64 itemId;
1922 0 : rv = aItem->GetItemId(&itemId);
1923 0 : NS_ENSURE_SUCCESS(rv, rv);
1924 :
1925 : // Note: we can't write the separator ID or anything else other than NAME
1926 : // because it makes Firefox 2.x crash/hang - see bug #381129
1927 :
1928 0 : nsCAutoString title;
1929 0 : rv = mBookmarksService->GetItemTitle(itemId, title);
1930 0 : if (NS_SUCCEEDED(rv) && !title.IsEmpty()) {
1931 0 : rv = aOutput->Write(kNameAttribute, strlen(kNameAttribute), &dummy);
1932 0 : NS_ENSURE_SUCCESS(rv, rv);
1933 :
1934 0 : char* escapedTitle = nsEscapeHTML(title.get());
1935 0 : if (escapedTitle) {
1936 : PRUint32 dummy;
1937 0 : rv = aOutput->Write(escapedTitle, strlen(escapedTitle), &dummy);
1938 0 : nsMemory::Free(escapedTitle);
1939 0 : NS_ENSURE_SUCCESS(rv, rv);
1940 0 : rv = aOutput->Write(kQuoteStr, sizeof(kQuoteStr)-1, &dummy);
1941 0 : NS_ENSURE_SUCCESS(rv, rv);
1942 : }
1943 : }
1944 :
1945 : // '>'
1946 0 : rv = aOutput->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy);
1947 0 : NS_ENSURE_SUCCESS(rv, rv);
1948 :
1949 : // line break
1950 0 : rv = aOutput->Write(NS_LINEBREAK, sizeof(NS_LINEBREAK)-1, &dummy);
1951 0 : NS_ENSURE_SUCCESS(rv, rv);
1952 :
1953 0 : return rv;
1954 : }
1955 :
1956 :
1957 : // WriteEscapedUrl
1958 : //
1959 : // Writes the given string to the stream escaped as necessary for URLs.
1960 : //
1961 : // Unfortunately, the old bookmarks system uses a custom hardcoded and
1962 : // braindead escaping scheme that we need to emulate. It just replaces
1963 : // quotes with %22 and that's it.
1964 : nsresult
1965 92 : WriteEscapedUrl(const nsCString& aString, nsIOutputStream* aOutput)
1966 : {
1967 184 : nsCAutoString escaped(aString);
1968 : PRInt32 offset;
1969 184 : while ((offset = escaped.FindChar('\"')) >= 0) {
1970 0 : escaped.Cut(offset, 1);
1971 0 : escaped.Insert(NS_LITERAL_CSTRING("%22"), offset);
1972 : }
1973 : PRUint32 dummy;
1974 92 : return aOutput->Write(escaped.get(), escaped.Length(), &dummy);
1975 : }
1976 :
1977 :
1978 : // nsPlacesImportExportService::WriteContainerContents
1979 : //
1980 : // The indent here is the indent of the parent. We will add an additional
1981 : // indent before writing data.
1982 : nsresult
1983 32 : nsPlacesImportExportService::WriteContainerContents(nsINavHistoryResultNode* aFolder,
1984 : const nsACString& aIndent,
1985 : nsIOutputStream* aOutput)
1986 : {
1987 64 : nsCAutoString myIndent(aIndent);
1988 32 : myIndent.Append(kIndent);
1989 :
1990 : PRInt64 folderId;
1991 32 : nsresult rv = aFolder->GetItemId(&folderId);
1992 32 : NS_ENSURE_SUCCESS(rv, rv);
1993 :
1994 64 : nsCOMPtr<nsINavHistoryContainerResultNode> folderNode = do_QueryInterface(aFolder, &rv);
1995 32 : NS_ENSURE_SUCCESS(rv, rv);
1996 :
1997 32 : rv = folderNode->SetContainerOpen(true);
1998 32 : NS_ENSURE_SUCCESS(rv, rv);
1999 :
2000 32 : PRUint32 childCount = 0;
2001 32 : folderNode->GetChildCount(&childCount);
2002 104 : for (PRUint32 i = 0; i < childCount; ++i) {
2003 144 : nsCOMPtr<nsINavHistoryResultNode> child;
2004 72 : rv = folderNode->GetChild(i, getter_AddRefs(child));
2005 72 : NS_ENSURE_SUCCESS(rv, rv);
2006 72 : PRUint32 type = 0;
2007 72 : rv = child->GetType(&type);
2008 72 : NS_ENSURE_SUCCESS(rv, rv);
2009 72 : if (type == nsINavHistoryResultNode::RESULT_TYPE_FOLDER) {
2010 : // bookmarks folder
2011 : PRInt64 childFolderId;
2012 15 : rv = child->GetItemId(&childFolderId);
2013 15 : NS_ENSURE_SUCCESS(rv, rv);
2014 :
2015 : // it could be a regular folder or it could be a livemark.
2016 : // Livemarks service is async, for now just workaround using annotations
2017 : // service.
2018 : bool isLivemark;
2019 15 : nsresult rv = mAnnotationService->ItemHasAnnotation(childFolderId,
2020 15 : NS_LITERAL_CSTRING(LMANNO_FEEDURI),
2021 15 : &isLivemark);
2022 15 : NS_ENSURE_SUCCESS(rv, rv);
2023 :
2024 15 : if (isLivemark)
2025 5 : rv = WriteLivemark(child, myIndent, aOutput);
2026 : else
2027 10 : rv = WriteContainer(child, myIndent, aOutput);
2028 : }
2029 57 : else if (type == nsINavHistoryResultNode::RESULT_TYPE_SEPARATOR) {
2030 0 : rv = WriteSeparator(child, myIndent, aOutput);
2031 : }
2032 : else {
2033 57 : rv = WriteItem(child, myIndent, aOutput);
2034 : }
2035 72 : NS_ENSURE_SUCCESS(rv, rv);
2036 : }
2037 32 : return NS_OK;
2038 : }
2039 :
2040 :
2041 : // NotifyImportObservers
2042 : //
2043 : // Notifies bookmarks-restore observers using nsIObserverService. This
2044 : // function is void and we simply return on failure because we don't want
2045 : // the import itself to fail if notifying observers does.
2046 : static void
2047 62 : NotifyImportObservers(const char* aTopic,
2048 : PRInt64 aFolderId,
2049 : bool aIsInitialImport)
2050 : {
2051 124 : nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
2052 62 : if (!obs)
2053 : return;
2054 :
2055 124 : nsCOMPtr<nsISupports> folderIdSupp = nsnull;
2056 62 : if (aFolderId > 0) {
2057 : nsCOMPtr<nsISupportsPRInt64> folderIdInt =
2058 20 : do_CreateInstance(NS_SUPPORTS_PRINT64_CONTRACTID);
2059 10 : if (!folderIdInt)
2060 : return;
2061 :
2062 10 : if (NS_FAILED(folderIdInt->SetData(aFolderId)))
2063 : return;
2064 :
2065 20 : folderIdSupp = do_QueryInterface(folderIdInt);
2066 : }
2067 :
2068 62 : obs->NotifyObservers(folderIdSupp,
2069 : aTopic,
2070 46 : (aIsInitialImport ? RESTORE_INITIAL_NSIOBSERVER_DATA
2071 62 : : RESTORE_NSIOBSERVER_DATA).get());
2072 : }
2073 :
2074 :
2075 : NS_IMETHODIMP
2076 15 : nsPlacesImportExportService::ImportHTMLFromFile(nsILocalFile* aFile,
2077 : bool aIsInitialImport)
2078 : {
2079 15 : NotifyImportObservers(RESTORE_BEGIN_NSIOBSERVER_TOPIC, -1, aIsInitialImport);
2080 :
2081 : // this version is exposed on the interface and disallows changing of roots
2082 : nsresult rv = ImportHTMLFromFileInternal(aFile,
2083 : false,
2084 : 0,
2085 15 : aIsInitialImport);
2086 :
2087 15 : if (NS_FAILED(rv)) {
2088 : NotifyImportObservers(RESTORE_FAILED_NSIOBSERVER_TOPIC,
2089 : -1,
2090 2 : aIsInitialImport);
2091 : }
2092 : else {
2093 : NotifyImportObservers(RESTORE_SUCCESS_NSIOBSERVER_TOPIC,
2094 : -1,
2095 13 : aIsInitialImport);
2096 : }
2097 :
2098 15 : return rv;
2099 : }
2100 :
2101 :
2102 : NS_IMETHODIMP
2103 11 : nsPlacesImportExportService::ImportHTMLFromURI(nsIURI* aURI,
2104 : bool aIsInitialImport)
2105 : {
2106 11 : NotifyImportObservers(RESTORE_BEGIN_NSIOBSERVER_TOPIC, -1, aIsInitialImport);
2107 :
2108 : // this version is exposed on the interface and disallows changing of roots
2109 : nsresult rv = ImportHTMLFromURIInternal(aURI,
2110 : false,
2111 : 0,
2112 11 : aIsInitialImport);
2113 :
2114 11 : if (NS_FAILED(rv)) {
2115 : NotifyImportObservers(RESTORE_FAILED_NSIOBSERVER_TOPIC,
2116 : -1,
2117 0 : aIsInitialImport);
2118 : }
2119 : else {
2120 : NotifyImportObservers(RESTORE_SUCCESS_NSIOBSERVER_TOPIC,
2121 : -1,
2122 11 : aIsInitialImport);
2123 : }
2124 :
2125 11 : return rv;
2126 : }
2127 :
2128 :
2129 : NS_IMETHODIMP
2130 5 : nsPlacesImportExportService::ImportHTMLFromFileToFolder(nsILocalFile* aFile,
2131 : PRInt64 aFolderId,
2132 : bool aIsInitialImport)
2133 : {
2134 : NotifyImportObservers(RESTORE_BEGIN_NSIOBSERVER_TOPIC,
2135 : aFolderId,
2136 5 : aIsInitialImport);
2137 :
2138 : // this version is exposed on the interface and disallows changing of roots
2139 : nsresult rv = ImportHTMLFromFileInternal(aFile,
2140 : false,
2141 : aFolderId,
2142 5 : aIsInitialImport);
2143 :
2144 5 : if (NS_FAILED(rv)) {
2145 : NotifyImportObservers(RESTORE_FAILED_NSIOBSERVER_TOPIC,
2146 : aFolderId,
2147 1 : aIsInitialImport);
2148 : }
2149 : else {
2150 : NotifyImportObservers(RESTORE_SUCCESS_NSIOBSERVER_TOPIC,
2151 : aFolderId,
2152 4 : aIsInitialImport);
2153 : }
2154 :
2155 5 : return rv;
2156 : }
2157 :
2158 :
2159 : nsresult
2160 20 : nsPlacesImportExportService::ImportHTMLFromFileInternal(nsILocalFile* aFile,
2161 : bool aAllowRootChanges,
2162 : PRInt64 aFolder,
2163 : bool aIsImportDefaults)
2164 : {
2165 : nsresult rv;
2166 :
2167 40 : nsCOMPtr<nsIFile> file = do_QueryInterface(aFile);
2168 20 : NS_ENSURE_STATE(file);
2169 :
2170 : #ifdef DEBUG_IMPORT
2171 : nsAutoString path;
2172 : file->GetPath(path);
2173 : printf("\nImporting %s\n", NS_ConvertUTF16toUTF8(path).get());
2174 : #endif
2175 :
2176 : // Confirm file to be imported exists.
2177 : bool exists;
2178 20 : rv = file->Exists(&exists);
2179 20 : NS_ENSURE_SUCCESS(rv, rv);
2180 20 : if (!exists) {
2181 3 : return NS_ERROR_INVALID_ARG;
2182 : }
2183 :
2184 34 : nsCOMPtr<nsIIOService> ioservice = do_GetIOService(&rv);
2185 17 : NS_ENSURE_SUCCESS(rv, rv);
2186 34 : nsCOMPtr<nsIURI> fileURI;
2187 17 : rv = ioservice->NewFileURI(file, getter_AddRefs(fileURI));
2188 17 : NS_ENSURE_SUCCESS(rv, rv);
2189 :
2190 17 : return ImportHTMLFromURIInternal(fileURI, aAllowRootChanges, aFolder, aIsImportDefaults);
2191 : }
2192 :
2193 : nsresult
2194 28 : nsPlacesImportExportService::ImportHTMLFromURIInternal(nsIURI* aURI,
2195 : bool aAllowRootChanges,
2196 : PRInt64 aFolder,
2197 : bool aIsImportDefaults)
2198 : {
2199 28 : nsresult rv = EnsureServiceState();
2200 28 : NS_ENSURE_SUCCESS(rv, rv);
2201 :
2202 56 : nsCOMPtr<nsIParser> parser = do_CreateInstance(kParserCID);
2203 28 : NS_ENSURE_TRUE(parser, NS_ERROR_OUT_OF_MEMORY);
2204 :
2205 56 : nsCOMPtr<BookmarkContentSink> sink = new BookmarkContentSink();
2206 28 : NS_ENSURE_TRUE(sink, NS_ERROR_OUT_OF_MEMORY);
2207 28 : rv = sink->Init(aAllowRootChanges, aFolder, aIsImportDefaults);
2208 28 : NS_ENSURE_SUCCESS(rv, rv);
2209 28 : parser->SetContentSink(sink);
2210 :
2211 : // Set the content type on the channel, otherwise the default "unknown" type
2212 : // will confuse the parser.
2213 56 : nsCOMPtr<nsIIOService> ioservice = do_GetIOService(&rv);
2214 28 : NS_ENSURE_SUCCESS(rv, rv);
2215 28 : rv = ioservice->NewChannelFromURI(aURI, getter_AddRefs(mImportChannel));
2216 28 : NS_ENSURE_SUCCESS(rv, rv);
2217 28 : rv = mImportChannel->SetContentType(NS_LITERAL_CSTRING("text/html"));
2218 28 : NS_ENSURE_SUCCESS(rv, rv);
2219 :
2220 : // Init parser.
2221 28 : rv = parser->Parse(aURI, nsnull);
2222 28 : NS_ENSURE_SUCCESS(rv, rv);
2223 :
2224 : // Run the import in batch mode, so it will be executed in a transaction
2225 : // and will be faster.
2226 28 : mIsImportDefaults = aIsImportDefaults;
2227 28 : mBookmarksService->RunInBatchMode(this, parser);
2228 28 : mImportChannel = nsnull;
2229 :
2230 28 : return NS_OK;
2231 : }
2232 :
2233 :
2234 : NS_IMETHODIMP
2235 28 : nsPlacesImportExportService::RunBatched(nsISupports* aUserData)
2236 : {
2237 : nsresult rv;
2238 28 : if (mIsImportDefaults) {
2239 : PRInt64 bookmarksMenuFolder;
2240 22 : rv = mBookmarksService->GetBookmarksMenuFolder(&bookmarksMenuFolder);
2241 22 : NS_ENSURE_SUCCESS(rv, rv);
2242 :
2243 22 : rv = mBookmarksService->RemoveFolderChildren(bookmarksMenuFolder);
2244 22 : NS_ENSURE_SUCCESS(rv, rv);
2245 :
2246 : PRInt64 toolbarFolder;
2247 22 : rv = mBookmarksService->GetToolbarFolder(&toolbarFolder);
2248 22 : NS_ENSURE_SUCCESS(rv, rv);
2249 :
2250 22 : rv = mBookmarksService->RemoveFolderChildren(toolbarFolder);
2251 22 : NS_ENSURE_SUCCESS(rv, rv);
2252 :
2253 : PRInt64 unfiledBookmarksFolder;
2254 22 : rv = mBookmarksService->GetUnfiledBookmarksFolder(&unfiledBookmarksFolder);
2255 22 : NS_ENSURE_SUCCESS(rv, rv);
2256 :
2257 22 : rv = mBookmarksService->RemoveFolderChildren(unfiledBookmarksFolder);
2258 22 : NS_ENSURE_SUCCESS(rv, rv);
2259 : }
2260 :
2261 : // streams
2262 56 : nsCOMPtr<nsIInputStream> stream;
2263 28 : rv = mImportChannel->Open(getter_AddRefs(stream));
2264 28 : NS_ENSURE_SUCCESS(rv, rv);
2265 56 : nsCOMPtr<nsIInputStream> bufferedstream;
2266 28 : rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedstream), stream, 4096);
2267 28 : NS_ENSURE_SUCCESS(rv, rv);
2268 :
2269 : // feed the parser the data
2270 : // Note: on error, we always need to set the channel's status to be the
2271 : // same, and to always call OnStopRequest with the channel error.
2272 56 : nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(aUserData, &rv);
2273 28 : NS_ENSURE_SUCCESS(rv, rv);
2274 28 : rv = listener->OnStartRequest(mImportChannel, nsnull);
2275 28 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "OnStartRequest failed");
2276 28 : rv = SyncChannelStatus(mImportChannel, rv);
2277 28 : NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "SyncChannelStatus failed");
2278 :
2279 81 : while (NS_SUCCEEDED(rv))
2280 : {
2281 : PRUint32 available;
2282 53 : rv = bufferedstream->Available(&available);
2283 53 : if (rv == NS_BASE_STREAM_CLOSED) {
2284 0 : rv = NS_OK;
2285 0 : available = 0;
2286 : }
2287 53 : if (NS_FAILED(rv)) {
2288 0 : mImportChannel->Cancel(rv);
2289 0 : break;
2290 : }
2291 53 : if (!available)
2292 28 : break; // blocking input stream has none available when done
2293 :
2294 25 : rv = listener->OnDataAvailable(mImportChannel, nsnull, bufferedstream, 0,
2295 25 : available);
2296 25 : if (NS_FAILED(rv))
2297 0 : break;
2298 25 : rv = SyncChannelStatus(mImportChannel, rv);
2299 25 : if (NS_FAILED(rv))
2300 0 : break;
2301 : }
2302 :
2303 28 : rv = listener->OnStopRequest(mImportChannel, nsnull, rv);
2304 28 : NS_ENSURE_SUCCESS(rv, rv);
2305 :
2306 28 : return NS_OK;
2307 : }
2308 :
2309 :
2310 : NS_IMETHODIMP
2311 10 : nsPlacesImportExportService::ExportHTMLToFile(nsILocalFile* aBookmarksFile)
2312 : {
2313 10 : NS_ENSURE_ARG(aBookmarksFile);
2314 :
2315 : #ifdef DEBUG_EXPORT
2316 : nsAutoString path;
2317 : aBookmarksFile->GetPath(path);
2318 : printf("\nExporting %s\n", NS_ConvertUTF16toUTF8(path).get());
2319 :
2320 : PRTime startTime = PR_Now();
2321 : printf("\nStart time: %lld\n", startTime);
2322 : #endif
2323 :
2324 10 : nsresult rv = EnsureServiceState();
2325 10 : NS_ENSURE_SUCCESS(rv, rv);
2326 :
2327 : // get a safe output stream, so we don't clobber the bookmarks file unless
2328 : // all the writes succeeded.
2329 20 : nsCOMPtr<nsIOutputStream> out;
2330 10 : rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(out),
2331 : aBookmarksFile,
2332 : PR_WRONLY | PR_CREATE_FILE,
2333 10 : 0600, 0);
2334 10 : NS_ENSURE_SUCCESS(rv, rv);
2335 :
2336 : // We need a buffered output stream for performance.
2337 : // See bug 202477.
2338 20 : nsCOMPtr<nsIOutputStream> strm;
2339 10 : rv = NS_NewBufferedOutputStream(getter_AddRefs(strm), out, 4096);
2340 10 : NS_ENSURE_SUCCESS(rv, rv);
2341 :
2342 : // Get a new query object.
2343 20 : nsCOMPtr<nsINavHistoryQuery> query;
2344 10 : rv = mHistoryService->GetNewQuery(getter_AddRefs(query));
2345 10 : NS_ENSURE_SUCCESS(rv, rv);
2346 20 : nsCOMPtr<nsINavHistoryQueryOptions> options;
2347 10 : rv = mHistoryService->GetNewQueryOptions(getter_AddRefs(options));
2348 10 : NS_ENSURE_SUCCESS(rv, rv);
2349 20 : nsCOMPtr<nsINavHistoryResult> result;
2350 :
2351 : // We need the bookmarks menu root node to write out the title.
2352 : PRInt64 bookmarksMenuFolder;
2353 10 : rv = mBookmarksService->GetBookmarksMenuFolder(&bookmarksMenuFolder);
2354 10 : NS_ENSURE_SUCCESS(rv, rv);
2355 10 : rv = query->SetFolders(&bookmarksMenuFolder, 1);
2356 10 : NS_ENSURE_SUCCESS(rv, rv);
2357 10 : rv = mHistoryService->ExecuteQuery(query, options, getter_AddRefs(result));
2358 10 : NS_ENSURE_SUCCESS(rv, rv);
2359 20 : nsCOMPtr<nsINavHistoryContainerResultNode> rootNode;
2360 10 : rv = result->GetRoot(getter_AddRefs(rootNode));
2361 10 : NS_ENSURE_SUCCESS(rv, rv);
2362 :
2363 : // file header
2364 : PRUint32 dummy;
2365 10 : rv = strm->Write(kFileIntro, sizeof(kFileIntro)-1, &dummy);
2366 10 : NS_ENSURE_SUCCESS(rv, rv);
2367 :
2368 : // '<H1'
2369 10 : rv = strm->Write(kRootIntro, sizeof(kRootIntro)-1, &dummy); // <H1
2370 10 : NS_ENSURE_SUCCESS(rv, rv);
2371 :
2372 : // '>Bookmarks</H1>
2373 10 : rv = strm->Write(kCloseAngle, sizeof(kCloseAngle)-1, &dummy); // >
2374 10 : NS_ENSURE_SUCCESS(rv, rv);
2375 10 : rv = WriteTitle(rootNode, strm);
2376 10 : NS_ENSURE_SUCCESS(rv, rv);
2377 10 : rv = strm->Write(kCloseRootH1, sizeof(kCloseRootH1)-1, &dummy); // </H1>
2378 10 : NS_ENSURE_SUCCESS(rv, rv);
2379 :
2380 : // Container's prologue.
2381 10 : rv = WriteContainerPrologue(EmptyCString(), strm);
2382 10 : NS_ENSURE_SUCCESS(rv, rv);
2383 :
2384 : // indents
2385 20 : nsCAutoString indent;
2386 10 : indent.Assign(kIndent);
2387 :
2388 : // Bookmarks Menu.
2389 10 : rv = WriteContainerContents(rootNode, EmptyCString(), strm);
2390 10 : NS_ENSURE_SUCCESS(rv, rv);
2391 :
2392 : // Bookmarks Toolbar.
2393 : // We write this folder under the bookmarks-menu for backwards compatibility.
2394 : PRInt64 toolbarFolder;
2395 10 : rv = mBookmarksService->GetToolbarFolder(&toolbarFolder);
2396 10 : NS_ENSURE_SUCCESS(rv, rv);
2397 10 : rv = query->SetFolders(&toolbarFolder, 1);
2398 10 : NS_ENSURE_SUCCESS(rv, rv);
2399 10 : rv = mHistoryService->ExecuteQuery(query, options, getter_AddRefs(result));
2400 10 : NS_ENSURE_SUCCESS(rv, rv);
2401 10 : rv = result->GetRoot(getter_AddRefs(rootNode));
2402 10 : NS_ENSURE_SUCCESS(rv, rv);
2403 : // Write it out only if it's not empty.
2404 10 : rv = rootNode->SetContainerOpen(true);
2405 10 : NS_ENSURE_SUCCESS(rv, rv);
2406 10 : PRUint32 childCount = 0;
2407 10 : rv = rootNode->GetChildCount(&childCount);
2408 10 : NS_ENSURE_SUCCESS(rv, rv);
2409 10 : rv = rootNode->SetContainerOpen(false);
2410 10 : NS_ENSURE_SUCCESS(rv, rv);
2411 10 : if (childCount) {
2412 7 : rv = WriteContainer(rootNode, nsDependentCString(kIndent), strm);
2413 7 : NS_ENSURE_SUCCESS(rv, rv);
2414 : }
2415 :
2416 : // Unfiled Bookmarks.
2417 : // We write this folder under the bookmarks-menu for backwards compatibility.
2418 : PRInt64 unfiledBookmarksFolder;
2419 10 : rv = mBookmarksService->GetUnfiledBookmarksFolder(&unfiledBookmarksFolder);
2420 10 : NS_ENSURE_SUCCESS(rv, rv);
2421 10 : rv = query->SetFolders(&unfiledBookmarksFolder, 1);
2422 10 : NS_ENSURE_SUCCESS(rv, rv);
2423 10 : rv = mHistoryService->ExecuteQuery(query, options, getter_AddRefs(result));
2424 10 : NS_ENSURE_SUCCESS(rv, rv);
2425 10 : rv = result->GetRoot(getter_AddRefs(rootNode));
2426 10 : NS_ENSURE_SUCCESS(rv, rv);
2427 : // Write it out only if it's not empty.
2428 10 : rv = rootNode->SetContainerOpen(true);
2429 10 : NS_ENSURE_SUCCESS(rv, rv);
2430 10 : childCount = 0;
2431 10 : rootNode->GetChildCount(&childCount);
2432 10 : NS_ENSURE_SUCCESS(rv, rv);
2433 10 : rv = rootNode->SetContainerOpen(false);
2434 10 : NS_ENSURE_SUCCESS(rv, rv);
2435 10 : if (childCount) {
2436 5 : rv = WriteContainer(rootNode, nsDependentCString(kIndent), strm);
2437 5 : NS_ENSURE_SUCCESS(rv, rv);
2438 : }
2439 :
2440 : // Container's epilogue.
2441 10 : rv = WriteContainerEpilogue(EmptyCString(), strm);
2442 10 : NS_ENSURE_SUCCESS(rv, rv);
2443 :
2444 : // commit the write
2445 20 : nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(strm, &rv);
2446 10 : NS_ENSURE_SUCCESS(rv, rv);
2447 10 : rv = safeStream->Finish();
2448 10 : NS_ENSURE_SUCCESS(rv, rv);
2449 :
2450 : #ifdef DEBUG_EXPORT
2451 : printf("\nTotal time in seconds: %lld\n", (PR_Now() - startTime)/1000000);
2452 : #endif
2453 :
2454 10 : return NS_OK;
2455 : }
2456 :
2457 :
2458 : NS_IMETHODIMP
2459 2 : nsPlacesImportExportService::BackupBookmarksFile()
2460 : {
2461 2 : nsresult rv = EnsureServiceState();
2462 2 : NS_ENSURE_SUCCESS(rv, rv);
2463 :
2464 : // get bookmarks file
2465 4 : nsCOMPtr<nsIFile> bookmarksFileDir;
2466 : rv = NS_GetSpecialDirectory(NS_APP_BOOKMARKS_50_FILE,
2467 2 : getter_AddRefs(bookmarksFileDir));
2468 :
2469 2 : NS_ENSURE_SUCCESS(rv, rv);
2470 4 : nsCOMPtr<nsILocalFile> bookmarksFile = do_QueryInterface(bookmarksFileDir);
2471 2 : NS_ENSURE_STATE(bookmarksFile);
2472 :
2473 : // Create the file if it doesn't exist.
2474 : bool exists;
2475 2 : rv = bookmarksFile->Exists(&exists);
2476 2 : if (NS_FAILED(rv) || !exists) {
2477 1 : rv = bookmarksFile->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
2478 1 : if (NS_FAILED(rv)) {
2479 0 : NS_WARNING("Unable to create bookmarks.html!");
2480 0 : return rv;
2481 : }
2482 : }
2483 :
2484 : // export bookmarks.html
2485 2 : rv = ExportHTMLToFile(bookmarksFile);
2486 2 : NS_ENSURE_SUCCESS(rv, rv);
2487 :
2488 2 : return NS_OK;
2489 : }
|