Fix memory leak in Sync FakeServerEntity
[chromium-blink-merge.git] / chrome / utility / importer / safari_importer.mm
blob480eedd5301015284cb6c9fd5222ed2a4c5fe50b
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 <Cocoa/Cocoa.h>
7 #include "chrome/utility/importer/safari_importer.h"
9 #include <map>
10 #include <vector>
12 #include "base/files/file_util.h"
13 #include "base/mac/mac_util.h"
14 #include "base/strings/string16.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/time/time.h"
18 #include "chrome/common/importer/imported_bookmark_entry.h"
19 #include "chrome/common/importer/imported_favicon_usage.h"
20 #include "chrome/common/importer/importer_bridge.h"
21 #include "chrome/common/url_constants.h"
22 #include "chrome/grit/generated_resources.h"
23 #include "chrome/utility/importer/favicon_reencode.h"
24 #include "components/strings/grit/components_strings.h"
25 #include "net/base/data_url.h"
26 #include "sql/statement.h"
27 #include "url/gurl.h"
29 namespace {
31 // A function like this is used by other importers in order to filter out
32 // URLS we don't want to import.
33 // For now it's pretty basic, but I've split it out so it's easy to slot
34 // in necessary logic for filtering URLS, should we need it.
35 bool CanImportSafariURL(const GURL& url) {
36   // The URL is not valid.
37   if (!url.is_valid())
38     return false;
40   return true;
43 }  // namespace
45 SafariImporter::SafariImporter(const base::FilePath& library_dir)
46     : library_dir_(library_dir) {
49 SafariImporter::~SafariImporter() {
52 void SafariImporter::StartImport(const importer::SourceProfile& source_profile,
53                                  uint16 items,
54                                  ImporterBridge* bridge) {
55   bridge_ = bridge;
56   // The order here is important!
57   bridge_->NotifyStarted();
59   // In keeping with import on other platforms (and for other browsers), we
60   // don't import the home page (since it may lead to a useless homepage); see
61   // crbug.com/25603.
62   if ((items & importer::HISTORY) && !cancelled()) {
63     bridge_->NotifyItemStarted(importer::HISTORY);
64     ImportHistory();
65     bridge_->NotifyItemEnded(importer::HISTORY);
66   }
67   if ((items & importer::FAVORITES) && !cancelled()) {
68     bridge_->NotifyItemStarted(importer::FAVORITES);
69     ImportBookmarks();
70     bridge_->NotifyItemEnded(importer::FAVORITES);
71   }
72   if ((items & importer::PASSWORDS) && !cancelled()) {
73     bridge_->NotifyItemStarted(importer::PASSWORDS);
74     ImportPasswords();
75     bridge_->NotifyItemEnded(importer::PASSWORDS);
76   }
77   bridge_->NotifyEnded();
80 void SafariImporter::ImportBookmarks() {
81   base::string16 toolbar_name =
82       bridge_->GetLocalizedString(IDS_BOOKMARK_BAR_FOLDER_NAME);
83   std::vector<ImportedBookmarkEntry> bookmarks;
84   ParseBookmarks(toolbar_name, &bookmarks);
86   // Write bookmarks into profile.
87   if (!bookmarks.empty() && !cancelled()) {
88     const base::string16& first_folder_name =
89         bridge_->GetLocalizedString(IDS_BOOKMARK_GROUP_FROM_SAFARI);
90     bridge_->AddBookmarks(bookmarks, first_folder_name);
91   }
93   // Import favicons.
94   sql::Connection db;
95   if (!OpenDatabase(&db))
96     return;
98   FaviconMap favicon_map;
99   ImportFaviconURLs(&db, &favicon_map);
100   // Write favicons into profile.
101   if (!favicon_map.empty() && !cancelled()) {
102     std::vector<ImportedFaviconUsage> favicons;
103     LoadFaviconData(&db, favicon_map, &favicons);
104     bridge_->SetFavicons(favicons);
105   }
108 bool SafariImporter::OpenDatabase(sql::Connection* db) {
109   // Construct ~/Library/Safari/WebIcons.db path.
110   NSString* library_dir = [NSString
111       stringWithUTF8String:library_dir_.value().c_str()];
112   NSString* safari_dir = [library_dir
113       stringByAppendingPathComponent:@"Safari"];
114   NSString* favicons_db_path = [safari_dir
115       stringByAppendingPathComponent:@"WebpageIcons.db"];
117   const char* db_path = [favicons_db_path fileSystemRepresentation];
118   return db->Open(base::FilePath(db_path));
121 void SafariImporter::ImportFaviconURLs(sql::Connection* db,
122                                        FaviconMap* favicon_map) {
123   const char* query = "SELECT iconID, url FROM PageURL;";
124   sql::Statement s(db->GetUniqueStatement(query));
126   while (s.Step() && !cancelled()) {
127     int64 icon_id = s.ColumnInt64(0);
128     GURL url = GURL(s.ColumnString(1));
129     (*favicon_map)[icon_id].insert(url);
130   }
133 void SafariImporter::LoadFaviconData(
134     sql::Connection* db,
135     const FaviconMap& favicon_map,
136     std::vector<ImportedFaviconUsage>* favicons) {
137   const char* query = "SELECT i.url, d.data "
138                       "FROM IconInfo i JOIN IconData d "
139                       "ON i.iconID = d.iconID "
140                       "WHERE i.iconID = ?;";
141   sql::Statement s(db->GetUniqueStatement(query));
143   for (FaviconMap::const_iterator i = favicon_map.begin();
144        i != favicon_map.end(); ++i) {
145     s.Reset(true);
146     s.BindInt64(0, i->first);
147     if (s.Step()) {
148       ImportedFaviconUsage usage;
150       usage.favicon_url = GURL(s.ColumnString(0));
151       if (!usage.favicon_url.is_valid())
152         continue;  // Don't bother importing favicons with invalid URLs.
154       std::vector<unsigned char> data;
155       s.ColumnBlobAsVector(1, &data);
156       if (data.empty())
157         continue;  // Data definitely invalid.
159       if (!importer::ReencodeFavicon(&data[0], data.size(), &usage.png_data))
160         continue;  // Unable to decode.
162       usage.urls = i->second;
163       favicons->push_back(usage);
164     }
165   }
168 void SafariImporter::RecursiveReadBookmarksFolder(
169     NSDictionary* bookmark_folder,
170     const std::vector<base::string16>& parent_path_elements,
171     bool is_in_toolbar,
172     const base::string16& toolbar_name,
173     std::vector<ImportedBookmarkEntry>* out_bookmarks) {
174   DCHECK(bookmark_folder);
176   NSString* type = [bookmark_folder objectForKey:@"WebBookmarkType"];
177   NSString* title = [bookmark_folder objectForKey:@"Title"];
179   // Are we the dictionary that contains all other bookmarks?
180   // We need to know this so we don't add it to the path.
181   bool is_top_level_bookmarks_container = [bookmark_folder
182       objectForKey:@"WebBookmarkFileVersion"] != nil;
184   // We're expecting a list of bookmarks here, if that isn't what we got, fail.
185   if (!is_top_level_bookmarks_container) {
186     // Top level containers sometimes don't have title attributes.
187     if (![type isEqualToString:@"WebBookmarkTypeList"] || !title) {
188       NOTREACHED() << "Type=("
189                    << (type ? base::SysNSStringToUTF8(type) : "Null type")
190                    << ") Title=("
191                    << (title ? base::SysNSStringToUTF8(title) : "Null title")
192                    << ")";
193       return;
194     }
195   }
197   NSArray* elements = [bookmark_folder objectForKey:@"Children"];
198   if (!elements &&
199       (!parent_path_elements.empty() || !is_in_toolbar) &&
200       ![title isEqualToString:@"BookmarksMenu"]) {
201     // This is an empty folder, so add it explicitly.  Note that the condition
202     // above prevents either the toolbar folder or the bookmarks menu from being
203     // added if either is empty.  Note also that all non-empty folders are added
204     // implicitly when their children are added.
205     ImportedBookmarkEntry entry;
206     // Safari doesn't specify a creation time for the folder.
207     entry.creation_time = base::Time::Now();
208     entry.title = base::SysNSStringToUTF16(title);
209     entry.path = parent_path_elements;
210     entry.in_toolbar = is_in_toolbar;
211     entry.is_folder = true;
213     out_bookmarks->push_back(entry);
214     return;
215   }
217   std::vector<base::string16> path_elements(parent_path_elements);
218   // Create a folder for the toolbar, but not for the bookmarks menu.
219   if (path_elements.empty() && [title isEqualToString:@"BookmarksBar"]) {
220     is_in_toolbar = true;
221     path_elements.push_back(toolbar_name);
222   } else if (!is_top_level_bookmarks_container &&
223              !(path_elements.empty() &&
224                [title isEqualToString:@"BookmarksMenu"])) {
225     if (title)
226       path_elements.push_back(base::SysNSStringToUTF16(title));
227   }
229   // Iterate over individual bookmarks.
230   for (NSDictionary* bookmark in elements) {
231     NSString* type = [bookmark objectForKey:@"WebBookmarkType"];
232     if (!type)
233       continue;
235     // If this is a folder, recurse.
236     if ([type isEqualToString:@"WebBookmarkTypeList"]) {
237       RecursiveReadBookmarksFolder(bookmark,
238                                    path_elements,
239                                    is_in_toolbar,
240                                    toolbar_name,
241                                    out_bookmarks);
242     }
244     // If we didn't see a bookmark folder, then we're expecting a bookmark
245     // item.  If that's not what we got then ignore it.
246     if (![type isEqualToString:@"WebBookmarkTypeLeaf"])
247       continue;
249     NSString* url = [bookmark objectForKey:@"URLString"];
250     NSString* title = [[bookmark objectForKey:@"URIDictionary"]
251         objectForKey:@"title"];
253     if (!url || !title)
254       continue;
256     // Output Bookmark.
257     ImportedBookmarkEntry entry;
258     // Safari doesn't specify a creation time for the bookmark.
259     entry.creation_time = base::Time::Now();
260     entry.title = base::SysNSStringToUTF16(title);
261     entry.url = GURL(base::SysNSStringToUTF8(url));
262     entry.path = path_elements;
263     entry.in_toolbar = is_in_toolbar;
265     out_bookmarks->push_back(entry);
266   }
269 void SafariImporter::ParseBookmarks(
270     const base::string16& toolbar_name,
271     std::vector<ImportedBookmarkEntry>* bookmarks) {
272   DCHECK(bookmarks);
274   // Construct ~/Library/Safari/Bookmarks.plist path
275   NSString* library_dir = [NSString
276       stringWithUTF8String:library_dir_.value().c_str()];
277   NSString* safari_dir = [library_dir
278       stringByAppendingPathComponent:@"Safari"];
279   NSString* bookmarks_plist = [safari_dir
280     stringByAppendingPathComponent:@"Bookmarks.plist"];
282   // Load the plist file.
283   NSDictionary* bookmarks_dict = [NSDictionary
284       dictionaryWithContentsOfFile:bookmarks_plist];
285   if (!bookmarks_dict)
286     return;
288   // Recursively read in bookmarks.
289   std::vector<base::string16> parent_path_elements;
290   RecursiveReadBookmarksFolder(bookmarks_dict, parent_path_elements, false,
291                                toolbar_name, bookmarks);
294 void SafariImporter::ImportPasswords() {
295   // Safari stores it's passwords in the Keychain, same as us so we don't need
296   // to import them.
297   // Note: that we don't automatically pick them up, there is some logic around
298   // the user needing to explicitly input his username in a page and blurring
299   // the field before we pick it up, but the details of that are beyond the
300   // scope of this comment.
303 void SafariImporter::ImportHistory() {
304   std::vector<ImporterURLRow> rows;
305   ParseHistoryItems(&rows);
307   if (!rows.empty() && !cancelled()) {
308     bridge_->SetHistoryItems(rows, importer::VISIT_SOURCE_SAFARI_IMPORTED);
309   }
312 double SafariImporter::HistoryTimeToEpochTime(NSString* history_time) {
313   DCHECK(history_time);
314   // Add Difference between Unix epoch and CFAbsoluteTime epoch in seconds.
315   // Unix epoch is 1970-01-01 00:00:00.0 UTC,
316   // CF epoch is 2001-01-01 00:00:00.0 UTC.
317   return CFStringGetDoubleValue(base::mac::NSToCFCast(history_time)) +
318       kCFAbsoluteTimeIntervalSince1970;
321 void SafariImporter::ParseHistoryItems(
322     std::vector<ImporterURLRow>* history_items) {
323   DCHECK(history_items);
325   // Construct ~/Library/Safari/History.plist path
326   NSString* library_dir = [NSString
327       stringWithUTF8String:library_dir_.value().c_str()];
328   NSString* safari_dir = [library_dir
329       stringByAppendingPathComponent:@"Safari"];
330   NSString* history_plist = [safari_dir
331       stringByAppendingPathComponent:@"History.plist"];
333   // Load the plist file.
334   NSDictionary* history_dict = [NSDictionary
335       dictionaryWithContentsOfFile:history_plist];
336   if (!history_dict)
337     return;
339   NSArray* safari_history_items = [history_dict
340       objectForKey:@"WebHistoryDates"];
342   for (NSDictionary* history_item in safari_history_items) {
343     NSString* url_ns = [history_item objectForKey:@""];
344     if (!url_ns)
345       continue;
347     GURL url(base::SysNSStringToUTF8(url_ns));
349     if (!CanImportSafariURL(url))
350       continue;
352     ImporterURLRow row(url);
353     NSString* title_ns = [history_item objectForKey:@"title"];
355     // Sometimes items don't have a title, in which case we just substitue
356     // the url.
357     if (!title_ns)
358       title_ns = url_ns;
360     row.title = base::SysNSStringToUTF16(title_ns);
361     int visit_count = [[history_item objectForKey:@"visitCount"]
362                           intValue];
363     row.visit_count = visit_count;
364     // Include imported URLs in autocompletion - don't hide them.
365     row.hidden = 0;
366     // Item was never typed before in the omnibox.
367     row.typed_count = 0;
369     NSString* last_visit_str = [history_item objectForKey:@"lastVisitedDate"];
370     // The last visit time should always be in the history item, but if not
371     /// just continue without this item.
372     DCHECK(last_visit_str);
373     if (!last_visit_str)
374       continue;
376     // Convert Safari's last visit time to Unix Epoch time.
377     double seconds_since_unix_epoch = HistoryTimeToEpochTime(last_visit_str);
378     row.last_visit = base::Time::FromDoubleT(seconds_since_unix_epoch);
380     history_items->push_back(row);
381   }