• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/bookmarks/bookmark_html_writer.h"
6 
7 #include "base/base64.h"
8 #include "base/callback.h"
9 #include "base/file_path.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/message_loop.h"
12 #include "base/platform_file.h"
13 #include "base/string_number_conversions.h"
14 #include "base/time.h"
15 #include "base/values.h"
16 #include "chrome/browser/bookmarks/bookmark_codec.h"
17 #include "chrome/browser/bookmarks/bookmark_model.h"
18 #include "chrome/browser/history/history_types.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "content/browser/browser_thread.h"
21 #include "content/common/notification_source.h"
22 #include "grit/generated_resources.h"
23 #include "net/base/escape.h"
24 #include "net/base/file_stream.h"
25 #include "net/base/net_errors.h"
26 #include "ui/base/l10n/l10n_util.h"
27 
28 namespace {
29 
30 static BookmarkFaviconFetcher* fetcher = NULL;
31 
32 // File header.
33 const char kHeader[] =
34     "<!DOCTYPE NETSCAPE-Bookmark-file-1>\r\n"
35     "<!-- This is an automatically generated file.\r\n"
36     "     It will be read and overwritten.\r\n"
37     "     DO NOT EDIT! -->\r\n"
38     "<META HTTP-EQUIV=\"Content-Type\""
39     " CONTENT=\"text/html; charset=UTF-8\">\r\n"
40     "<TITLE>Bookmarks</TITLE>\r\n"
41     "<H1>Bookmarks</H1>\r\n"
42     "<DL><p>\r\n";
43 
44 // Newline separator.
45 const char kNewline[] = "\r\n";
46 
47 // The following are used for bookmarks.
48 
49 // Start of a bookmark.
50 const char kBookmarkStart[] = "<DT><A HREF=\"";
51 // After kBookmarkStart.
52 const char kAddDate[] = "\" ADD_DATE=\"";
53 // After kAddDate.
54 const char kIcon[] = "\" ICON=\"";
55 // After kIcon.
56 const char kBookmarkAttributeEnd[] = "\">";
57 // End of a bookmark.
58 const char kBookmarkEnd[] = "</A>";
59 
60 // The following are used when writing folders.
61 
62 // Start of a folder.
63 const char kFolderStart[] = "<DT><H3 ADD_DATE=\"";
64 // After kFolderStart.
65 const char kLastModified[] = "\" LAST_MODIFIED=\"";
66 // After kLastModified when writing the bookmark bar.
67 const char kBookmarkBar[] = "\" PERSONAL_TOOLBAR_FOLDER=\"true\">";
68 // After kLastModified when writing a user created folder.
69 const char kFolderAttributeEnd[] = "\">";
70 // End of the folder.
71 const char kFolderEnd[] = "</H3>";
72 // Start of the children of a folder.
73 const char kFolderChildren[] = "<DL><p>";
74 // End of the children for a folder.
75 const char kFolderChildrenEnd[] = "</DL><p>";
76 
77 // Number of characters to indent by.
78 const size_t kIndentSize = 4;
79 
80 // Class responsible for the actual writing. Takes ownership of favicons_map.
81 class Writer : public Task {
82  public:
Writer(Value * bookmarks,const FilePath & path,BookmarkFaviconFetcher::URLFaviconMap * favicons_map,BookmarksExportObserver * observer)83   Writer(Value* bookmarks,
84          const FilePath& path,
85          BookmarkFaviconFetcher::URLFaviconMap* favicons_map,
86          BookmarksExportObserver* observer)
87       : bookmarks_(bookmarks),
88         path_(path),
89         favicons_map_(favicons_map),
90         observer_(observer) {
91   }
92 
Run()93   virtual void Run() {
94     RunImpl();
95     NotifyOnFinish();
96   }
97 
98   // Writing bookmarks and favicons data to file.
RunImpl()99   virtual void RunImpl() {
100     if (!OpenFile()) {
101       return;
102     }
103 
104     Value* roots;
105     if (!Write(kHeader) ||
106         bookmarks_->GetType() != Value::TYPE_DICTIONARY ||
107         !static_cast<DictionaryValue*>(bookmarks_.get())->Get(
108             BookmarkCodec::kRootsKey, &roots) ||
109         roots->GetType() != Value::TYPE_DICTIONARY) {
110       NOTREACHED();
111       return;
112     }
113 
114     DictionaryValue* roots_d_value = static_cast<DictionaryValue*>(roots);
115     Value* root_folder_value;
116     Value* other_folder_value;
117     if (!roots_d_value->Get(BookmarkCodec::kRootFolderNameKey,
118                             &root_folder_value) ||
119         root_folder_value->GetType() != Value::TYPE_DICTIONARY ||
120         !roots_d_value->Get(BookmarkCodec::kOtherBookmarkFolderNameKey,
121                             &other_folder_value) ||
122         other_folder_value->GetType() != Value::TYPE_DICTIONARY) {
123       NOTREACHED();
124       return;  // Invalid type for root folder and/or other folder.
125     }
126 
127     IncrementIndent();
128 
129     if (!WriteNode(*static_cast<DictionaryValue*>(root_folder_value),
130                    BookmarkNode::BOOKMARK_BAR) ||
131         !WriteNode(*static_cast<DictionaryValue*>(other_folder_value),
132                    BookmarkNode::OTHER_NODE)) {
133       return;
134     }
135 
136     DecrementIndent();
137 
138     Write(kFolderChildrenEnd);
139     Write(kNewline);
140     // File stream close is forced so that unit test could read it.
141     file_stream_.Close();
142   }
143 
144  private:
145   // Types of text being written out. The type dictates how the text is
146   // escaped.
147   enum TextType {
148     // The text is the value of an html attribute, eg foo in
149     // <a href="foo">.
150     ATTRIBUTE_VALUE,
151 
152     // Actual content, eg foo in <h1>foo</h2>.
153     CONTENT
154   };
155 
156   // Opens the file, returning true on success.
OpenFile()157   bool OpenFile() {
158     int flags = base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE;
159     return (file_stream_.Open(path_, flags) == net::OK);
160   }
161 
162   // Increments the indent.
IncrementIndent()163   void IncrementIndent() {
164     indent_.resize(indent_.size() + kIndentSize, ' ');
165   }
166 
167   // Decrements the indent.
DecrementIndent()168   void DecrementIndent() {
169     DCHECK(!indent_.empty());
170     indent_.resize(indent_.size() - kIndentSize, ' ');
171   }
172 
173   // Called at the end of the export process.
NotifyOnFinish()174   void NotifyOnFinish() {
175     if (observer_ != NULL) {
176       observer_->OnExportFinished();
177     }
178   }
179 
180   // Writes raw text out returning true on success. This does not escape
181   // the text in anyway.
Write(const std::string & text)182   bool Write(const std::string& text) {
183     size_t wrote = file_stream_.Write(text.c_str(), text.length(), NULL);
184     bool result = (wrote == text.length());
185     DCHECK(result);
186     return result;
187   }
188 
189   // Writes out the text string (as UTF8). The text is escaped based on
190   // type.
Write(const std::string & text,TextType type)191   bool Write(const std::string& text, TextType type) {
192     DCHECK(IsStringUTF8(text));
193     std::string utf8_string;
194 
195     switch (type) {
196       case ATTRIBUTE_VALUE:
197         // Convert " to &quot;
198         utf8_string = text;
199         ReplaceSubstringsAfterOffset(&utf8_string, 0, "\"", "&quot;");
200         break;
201 
202       case CONTENT:
203         utf8_string = EscapeForHTML(text);
204         break;
205 
206       default:
207         NOTREACHED();
208     }
209 
210     return Write(utf8_string);
211   }
212 
213   // Indents the current line.
WriteIndent()214   bool WriteIndent() {
215     return Write(indent_);
216   }
217 
218   // Converts a time string written to the JSON codec into a time_t string
219   // (used by bookmarks.html) and writes it.
WriteTime(const std::string & time_string)220   bool WriteTime(const std::string& time_string) {
221     int64 internal_value;
222     base::StringToInt64(time_string, &internal_value);
223     return Write(base::Int64ToString(
224         base::Time::FromInternalValue(internal_value).ToTimeT()));
225   }
226 
227   // Writes the node and all its children, returning true on success.
WriteNode(const DictionaryValue & value,BookmarkNode::Type folder_type)228   bool WriteNode(const DictionaryValue& value,
229                 BookmarkNode::Type folder_type) {
230     std::string title, date_added_string, type_string;
231     if (!value.GetString(BookmarkCodec::kNameKey, &title) ||
232         !value.GetString(BookmarkCodec::kDateAddedKey, &date_added_string) ||
233         !value.GetString(BookmarkCodec::kTypeKey, &type_string) ||
234         (type_string != BookmarkCodec::kTypeURL &&
235          type_string != BookmarkCodec::kTypeFolder))  {
236       NOTREACHED();
237       return false;
238     }
239 
240     if (type_string == BookmarkCodec::kTypeURL) {
241       std::string url_string;
242       if (!value.GetString(BookmarkCodec::kURLKey, &url_string)) {
243         NOTREACHED();
244         return false;
245       }
246 
247       std::string favicon_string;
248       BookmarkFaviconFetcher::URLFaviconMap::iterator itr =
249           favicons_map_->find(url_string);
250       if (itr != favicons_map_->end()) {
251         scoped_refptr<RefCountedMemory> data(itr->second.get());
252         std::string favicon_data;
253         favicon_data.assign(reinterpret_cast<const char*>(data->front()),
254                             data->size());
255         std::string favicon_base64_encoded;
256         if (base::Base64Encode(favicon_data, &favicon_base64_encoded)) {
257           GURL favicon_url("data:image/png;base64," + favicon_base64_encoded);
258           favicon_string = favicon_url.spec();
259         }
260       }
261 
262       if (!WriteIndent() ||
263           !Write(kBookmarkStart) ||
264           !Write(url_string, ATTRIBUTE_VALUE) ||
265           !Write(kAddDate) ||
266           !WriteTime(date_added_string) ||
267           (!favicon_string.empty() &&
268               (!Write(kIcon) ||
269                !Write(favicon_string, ATTRIBUTE_VALUE))) ||
270           !Write(kBookmarkAttributeEnd) ||
271           !Write(title, CONTENT) ||
272           !Write(kBookmarkEnd) ||
273           !Write(kNewline)) {
274         return false;
275       }
276       return true;
277     }
278 
279     // Folder.
280     std::string last_modified_date;
281     Value* child_values;
282     if (!value.GetString(BookmarkCodec::kDateModifiedKey,
283                          &last_modified_date) ||
284         !value.Get(BookmarkCodec::kChildrenKey, &child_values) ||
285         child_values->GetType() != Value::TYPE_LIST) {
286       NOTREACHED();
287       return false;
288     }
289     if (folder_type != BookmarkNode::OTHER_NODE) {
290       // The other folder name is not written out. This gives the effect of
291       // making the contents of the 'other folder' be a sibling to the bookmark
292       // bar folder.
293       if (!WriteIndent() ||
294           !Write(kFolderStart) ||
295           !WriteTime(date_added_string) ||
296           !Write(kLastModified) ||
297           !WriteTime(last_modified_date)) {
298         return false;
299       }
300       if (folder_type == BookmarkNode::BOOKMARK_BAR) {
301         if (!Write(kBookmarkBar))
302           return false;
303         title = l10n_util::GetStringUTF8(IDS_BOOMARK_BAR_FOLDER_NAME);
304       } else if (!Write(kFolderAttributeEnd)) {
305         return false;
306       }
307       if (!Write(title, CONTENT) ||
308           !Write(kFolderEnd) ||
309           !Write(kNewline) ||
310           !WriteIndent() ||
311           !Write(kFolderChildren) ||
312           !Write(kNewline)) {
313         return false;
314       }
315       IncrementIndent();
316     }
317 
318     // Write the children.
319     ListValue* children = static_cast<ListValue*>(child_values);
320     for (size_t i = 0; i < children->GetSize(); ++i) {
321       Value* child_value;
322       if (!children->Get(i, &child_value) ||
323           child_value->GetType() != Value::TYPE_DICTIONARY) {
324         NOTREACHED();
325         return false;
326       }
327       if (!WriteNode(*static_cast<DictionaryValue*>(child_value),
328                      BookmarkNode::FOLDER)) {
329         return false;
330       }
331     }
332     if (folder_type != BookmarkNode::OTHER_NODE) {
333       // Close out the folder.
334       DecrementIndent();
335       if (!WriteIndent() ||
336           !Write(kFolderChildrenEnd) ||
337           !Write(kNewline)) {
338         return false;
339       }
340     }
341     return true;
342   }
343 
344   // The BookmarkModel as a Value. This value was generated from the
345   // BookmarkCodec.
346   scoped_ptr<Value> bookmarks_;
347 
348   // Path we're writing to.
349   FilePath path_;
350 
351   // Map that stores favicon per URL.
352   scoped_ptr<BookmarkFaviconFetcher::URLFaviconMap> favicons_map_;
353 
354   // Observer to be notified on finish.
355   BookmarksExportObserver* observer_;
356 
357   // File we're writing to.
358   net::FileStream file_stream_;
359 
360   // How much we indent when writing a bookmark/folder. This is modified
361   // via IncrementIndent and DecrementIndent.
362   std::string indent_;
363 };
364 
365 }  // namespace
366 
BookmarkFaviconFetcher(Profile * profile,const FilePath & path,BookmarksExportObserver * observer)367 BookmarkFaviconFetcher::BookmarkFaviconFetcher(
368     Profile* profile,
369     const FilePath& path,
370     BookmarksExportObserver* observer)
371     : profile_(profile),
372       path_(path),
373       observer_(observer) {
374   favicons_map_.reset(new URLFaviconMap());
375   registrar_.Add(this,
376                  NotificationType::PROFILE_DESTROYED,
377                  Source<Profile>(profile_));
378 }
379 
~BookmarkFaviconFetcher()380 BookmarkFaviconFetcher::~BookmarkFaviconFetcher() {
381 }
382 
ExportBookmarks()383 void BookmarkFaviconFetcher::ExportBookmarks() {
384   ExtractUrls(profile_->GetBookmarkModel()->GetBookmarkBarNode());
385   ExtractUrls(profile_->GetBookmarkModel()->other_node());
386   if (!bookmark_urls_.empty()) {
387     FetchNextFavicon();
388   } else {
389     ExecuteWriter();
390   }
391 }
392 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)393 void BookmarkFaviconFetcher::Observe(NotificationType type,
394                                      const NotificationSource& source,
395                                      const NotificationDetails& details) {
396   if (NotificationType::PROFILE_DESTROYED == type && fetcher != NULL) {
397     MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher);
398     fetcher = NULL;
399   }
400 }
401 
ExtractUrls(const BookmarkNode * node)402 void BookmarkFaviconFetcher::ExtractUrls(const BookmarkNode* node) {
403   if (BookmarkNode::URL == node->type()) {
404     std::string url = node->GetURL().spec();
405     if (!url.empty()) {
406       bookmark_urls_.push_back(url);
407     }
408   } else {
409     for (int i = 0; i < node->child_count(); ++i) {
410       ExtractUrls(node->GetChild(i));
411     }
412   }
413 }
414 
ExecuteWriter()415 void BookmarkFaviconFetcher::ExecuteWriter() {
416   // BookmarkModel isn't thread safe (nor would we want to lock it down
417   // for the duration of the write), as such we make a copy of the
418   // BookmarkModel using BookmarkCodec then write from that.
419   BookmarkCodec codec;
420   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
421       new Writer(codec.Encode(profile_->GetBookmarkModel()),
422                  path_,
423                  favicons_map_.release(),
424                  observer_));
425   if (fetcher != NULL) {
426     MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher);
427     fetcher = NULL;
428   }
429 }
430 
FetchNextFavicon()431 bool BookmarkFaviconFetcher::FetchNextFavicon() {
432   if (bookmark_urls_.empty()) {
433     return false;
434   }
435   do {
436     std::string url = bookmark_urls_.front();
437     // Filter out urls that we've already got favicon for.
438     URLFaviconMap::const_iterator iter = favicons_map_->find(url);
439     if (favicons_map_->end() == iter) {
440       FaviconService* favicon_service =
441           profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
442       favicon_service->GetFaviconForURL(GURL(url), history::FAVICON,
443           &favicon_consumer_,
444           NewCallback(this, &BookmarkFaviconFetcher::OnFaviconDataAvailable));
445       return true;
446     } else {
447       bookmark_urls_.pop_front();
448     }
449   } while (!bookmark_urls_.empty());
450   return false;
451 }
452 
OnFaviconDataAvailable(FaviconService::Handle handle,history::FaviconData favicon)453 void BookmarkFaviconFetcher::OnFaviconDataAvailable(
454     FaviconService::Handle handle,
455     history::FaviconData favicon) {
456   GURL url;
457   if (!bookmark_urls_.empty()) {
458     url = GURL(bookmark_urls_.front());
459     bookmark_urls_.pop_front();
460   }
461   if (favicon.is_valid() && !url.is_empty()) {
462     favicons_map_->insert(make_pair(url.spec(), favicon.image_data));
463   }
464 
465   if (FetchNextFavicon()) {
466     return;
467   }
468   ExecuteWriter();
469 }
470 
471 namespace bookmark_html_writer {
472 
WriteBookmarks(Profile * profile,const FilePath & path,BookmarksExportObserver * observer)473 void WriteBookmarks(Profile* profile,
474                     const FilePath& path,
475                     BookmarksExportObserver* observer) {
476   // BookmarkModel isn't thread safe (nor would we want to lock it down
477   // for the duration of the write), as such we make a copy of the
478   // BookmarkModel using BookmarkCodec then write from that.
479   if (fetcher == NULL) {
480     fetcher = new BookmarkFaviconFetcher(profile, path, observer);
481     fetcher->ExportBookmarks();
482   }
483 }
484 
485 }  // namespace bookmark_html_writer
486