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.
5 #include "chrome/browser/bookmarks/bookmark_html_writer.h"
7 #include "base/base64.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"
33 using content::BrowserThread
;
37 static BookmarkFaviconFetcher
* fetcher
= NULL
;
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"
52 const char kNewline
[] = "\r\n";
54 // The following are used for bookmarks.
56 // Start of a bookmark.
57 const char kBookmarkStart
[] = "<DT><A HREF=\"";
58 // After kBookmarkStart.
59 const char kAddDate
[] = "\" ADD_DATE=\"";
61 const char kIcon
[] = "\" ICON=\"";
63 const char kBookmarkAttributeEnd
[] = "\">";
65 const char kBookmarkEnd
[] = "</A>";
67 // The following are used when writing folders.
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
[] = "\">";
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>";
84 // Number of characters to indent by.
85 const size_t kIndentSize
= 4;
87 // Class responsible for the actual writing. Takes ownership of favicons_map.
88 class Writer
: public base::RefCountedThreadSafe
<Writer
> {
90 Writer(base::Value
* bookmarks
,
91 const base::FilePath
& path
,
92 BookmarkFaviconFetcher::URLFaviconMap
* favicons_map
,
93 BookmarksExportObserver
* observer
)
94 : bookmarks_(bookmarks
),
96 favicons_map_(favicons_map
),
100 // Writing bookmarks and favicons data to file.
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
) {
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
) {
130 return; // Invalid type for root folder and/or other folder.
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
)) {
146 Write(kFolderChildrenEnd
);
148 // File stream close is forced so that unit test could read it.
149 file_stream_
.reset();
155 friend class base::RefCountedThreadSafe
<Writer
>;
157 // Types of text being written out. The type dictates how the text is
160 // The text is the value of an html attribute, eg foo in
164 // Actual content, eg foo in <h1>foo</h2>.
170 // Opens the file, returning true on success.
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
);
177 // Increments the indent.
178 void IncrementIndent() {
179 indent_
.resize(indent_
.size() + kIndentSize
, ' ');
182 // Decrements the indent.
183 void DecrementIndent() {
184 DCHECK(!indent_
.empty());
185 indent_
.resize(indent_
.size() - kIndentSize
, ' ');
188 // Called at the end of the export process.
189 void NotifyOnFinish() {
190 if (observer_
!= NULL
) {
191 observer_
->OnExportFinished();
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.
201 size_t wrote
= file_stream_
->WriteSync(text
.c_str(), text
.length());
202 bool result
= (wrote
== text
.length());
207 // Writes out the text string (as UTF8). The text is escaped based on
209 bool Write(const std::string
& text
, TextType type
) {
210 DCHECK(IsStringUTF8(text
));
211 std::string utf8_string
;
214 case ATTRIBUTE_VALUE
:
215 // Convert " to "
217 ReplaceSubstringsAfterOffset(&utf8_string
, 0, "\"", """);
221 utf8_string
= net::EscapeForHTML(text
);
228 return Write(utf8_string
);
231 // Indents the current line.
233 return Write(indent_
);
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()));
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
)) {
258 if (type_string
== BookmarkCodec::kTypeURL
) {
259 std::string url_string
;
260 if (!value
.GetString(BookmarkCodec::kURLKey
, &url_string
)) {
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_data
;
271 favicon_data
.assign(reinterpret_cast<const char*>(data
->front()),
273 std::string favicon_base64_encoded
;
274 base::Base64Encode(favicon_data
, &favicon_base64_encoded
);
275 GURL
favicon_url("data:image/png;base64," + favicon_base64_encoded
);
276 favicon_string
= favicon_url
.spec();
279 if (!WriteIndent() ||
280 !Write(kBookmarkStart
) ||
281 !Write(url_string
, ATTRIBUTE_VALUE
) ||
283 !WriteTime(date_added_string
) ||
284 (!favicon_string
.empty() &&
286 !Write(favicon_string
, ATTRIBUTE_VALUE
))) ||
287 !Write(kBookmarkAttributeEnd
) ||
288 !Write(title
, CONTENT
) ||
289 !Write(kBookmarkEnd
) ||
297 std::string last_modified_date
;
298 const base::Value
* child_values
= NULL
;
299 if (!value
.GetString(BookmarkCodec::kDateModifiedKey
,
300 &last_modified_date
) ||
301 !value
.Get(BookmarkCodec::kChildrenKey
, &child_values
) ||
302 child_values
->GetType() != base::Value::TYPE_LIST
) {
306 if (folder_type
!= BookmarkNode::OTHER_NODE
&&
307 folder_type
!= BookmarkNode::MOBILE
) {
308 // The other/mobile folder name are not written out. This gives the effect
309 // of making the contents of the 'other folder' be a sibling to the
310 // bookmark bar folder.
311 if (!WriteIndent() ||
312 !Write(kFolderStart
) ||
313 !WriteTime(date_added_string
) ||
314 !Write(kLastModified
) ||
315 !WriteTime(last_modified_date
)) {
318 if (folder_type
== BookmarkNode::BOOKMARK_BAR
) {
319 if (!Write(kBookmarkBar
))
321 title
= l10n_util::GetStringUTF8(IDS_BOOKMARK_BAR_FOLDER_NAME
);
322 } else if (!Write(kFolderAttributeEnd
)) {
325 if (!Write(title
, CONTENT
) ||
326 !Write(kFolderEnd
) ||
329 !Write(kFolderChildren
) ||
336 // Write the children.
337 const base::ListValue
* children
=
338 static_cast<const base::ListValue
*>(child_values
);
339 for (size_t i
= 0; i
< children
->GetSize(); ++i
) {
340 const base::Value
* child_value
;
341 if (!children
->Get(i
, &child_value
) ||
342 child_value
->GetType() != base::Value::TYPE_DICTIONARY
) {
346 if (!WriteNode(*static_cast<const base::DictionaryValue
*>(child_value
),
347 BookmarkNode::FOLDER
)) {
351 if (folder_type
!= BookmarkNode::OTHER_NODE
&&
352 folder_type
!= BookmarkNode::MOBILE
) {
353 // Close out the folder.
355 if (!WriteIndent() ||
356 !Write(kFolderChildrenEnd
) ||
364 // The BookmarkModel as a base::Value. This value was generated from the
366 scoped_ptr
<base::Value
> bookmarks_
;
368 // Path we're writing to.
369 base::FilePath path_
;
371 // Map that stores favicon per URL.
372 scoped_ptr
<BookmarkFaviconFetcher::URLFaviconMap
> favicons_map_
;
374 // Observer to be notified on finish.
375 BookmarksExportObserver
* observer_
;
377 // File we're writing to.
378 scoped_ptr
<net::FileStream
> file_stream_
;
380 // How much we indent when writing a bookmark/folder. This is modified
381 // via IncrementIndent and DecrementIndent.
384 DISALLOW_COPY_AND_ASSIGN(Writer
);
389 BookmarkFaviconFetcher::BookmarkFaviconFetcher(
391 const base::FilePath
& path
,
392 BookmarksExportObserver
* observer
)
395 observer_(observer
) {
396 favicons_map_
.reset(new URLFaviconMap());
398 chrome::NOTIFICATION_PROFILE_DESTROYED
,
399 content::Source
<Profile
>(profile_
));
402 BookmarkFaviconFetcher::~BookmarkFaviconFetcher() {
405 void BookmarkFaviconFetcher::ExportBookmarks() {
406 ExtractUrls(BookmarkModelFactory::GetForProfile(
407 profile_
)->bookmark_bar_node());
408 ExtractUrls(BookmarkModelFactory::GetForProfile(profile_
)->other_node());
409 ExtractUrls(BookmarkModelFactory::GetForProfile(profile_
)->mobile_node());
410 if (!bookmark_urls_
.empty())
416 void BookmarkFaviconFetcher::Observe(
418 const content::NotificationSource
& source
,
419 const content::NotificationDetails
& details
) {
420 if (chrome::NOTIFICATION_PROFILE_DESTROYED
== type
&& fetcher
!= NULL
) {
421 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, fetcher
);
426 void BookmarkFaviconFetcher::ExtractUrls(const BookmarkNode
* node
) {
427 if (node
->is_url()) {
428 std::string url
= node
->url().spec();
430 bookmark_urls_
.push_back(url
);
432 for (int i
= 0; i
< node
->child_count(); ++i
)
433 ExtractUrls(node
->GetChild(i
));
437 void BookmarkFaviconFetcher::ExecuteWriter() {
438 // BookmarkModel isn't thread safe (nor would we want to lock it down
439 // for the duration of the write), as such we make a copy of the
440 // BookmarkModel using BookmarkCodec then write from that.
442 BrowserThread::PostTask(
443 BrowserThread::FILE, FROM_HERE
,
444 base::Bind(&Writer::DoWrite
,
445 new Writer(codec
.Encode(BookmarkModelFactory::GetForProfile(
447 path_
, favicons_map_
.release(), observer_
)));
448 if (fetcher
!= NULL
) {
449 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, fetcher
);
454 bool BookmarkFaviconFetcher::FetchNextFavicon() {
455 if (bookmark_urls_
.empty()) {
459 std::string url
= bookmark_urls_
.front();
460 // Filter out urls that we've already got favicon for.
461 URLFaviconMap::const_iterator iter
= favicons_map_
->find(url
);
462 if (favicons_map_
->end() == iter
) {
463 FaviconService
* favicon_service
= FaviconServiceFactory::GetForProfile(
464 profile_
, Profile::EXPLICIT_ACCESS
);
465 favicon_service
->GetRawFaviconForURL(
466 FaviconService::FaviconForURLParams(
467 GURL(url
), chrome::FAVICON
, gfx::kFaviconSize
),
468 ui::SCALE_FACTOR_100P
,
469 base::Bind(&BookmarkFaviconFetcher::OnFaviconDataAvailable
,
470 base::Unretained(this)),
471 &cancelable_task_tracker_
);
474 bookmark_urls_
.pop_front();
476 } while (!bookmark_urls_
.empty());
480 void BookmarkFaviconFetcher::OnFaviconDataAvailable(
481 const chrome::FaviconBitmapResult
& bitmap_result
) {
483 if (!bookmark_urls_
.empty()) {
484 url
= GURL(bookmark_urls_
.front());
485 bookmark_urls_
.pop_front();
487 if (bitmap_result
.is_valid() && !url
.is_empty()) {
488 favicons_map_
->insert(
489 make_pair(url
.spec(), bitmap_result
.bitmap_data
));
492 if (FetchNextFavicon()) {
498 namespace bookmark_html_writer
{
500 void WriteBookmarks(Profile
* profile
,
501 const base::FilePath
& path
,
502 BookmarksExportObserver
* observer
) {
503 // BookmarkModel isn't thread safe (nor would we want to lock it down
504 // for the duration of the write), as such we make a copy of the
505 // BookmarkModel using BookmarkCodec then write from that.
506 if (fetcher
== NULL
) {
507 fetcher
= new BookmarkFaviconFetcher(profile
, path
, observer
);
508 fetcher
->ExportBookmarks();
512 } // namespace bookmark_html_writer