Home | History | Annotate | Download | only in bookmarks
      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:
     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 
     93   virtual void Run() {
     94     RunImpl();
     95     NotifyOnFinish();
     96   }
     97 
     98   // Writing bookmarks and favicons data to file.
     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.
    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.
    163   void IncrementIndent() {
    164     indent_.resize(indent_.size() + kIndentSize, ' ');
    165   }
    166 
    167   // Decrements the indent.
    168   void DecrementIndent() {
    169     DCHECK(!indent_.empty());
    170     indent_.resize(indent_.size() - kIndentSize, ' ');
    171   }
    172 
    173   // Called at the end of the export process.
    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.
    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.
    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.
    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.
    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.
    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 
    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 
    380 BookmarkFaviconFetcher::~BookmarkFaviconFetcher() {
    381 }
    382 
    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 
    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 
    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 
    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 
    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 
    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 
    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