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