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/common/importer/firefox_importer_utils.h"
11 #include "base/files/file_util.h"
12 #include "base/logging.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/values.h"
19 #include "chrome/common/ini_parser.h"
20 #include "chrome/grit/generated_resources.h"
21 #include "ui/base/l10n/l10n_util.h"
26 // Retrieves the file system path of the profile name.
27 base::FilePath
GetProfilePath(const base::DictionaryValue
& root
,
28 const std::string
& profile_name
) {
29 base::string16 path16
;
30 std::string is_relative
;
31 if (!root
.GetStringASCII(profile_name
+ ".IsRelative", &is_relative
) ||
32 !root
.GetString(profile_name
+ ".Path", &path16
))
33 return base::FilePath();
36 base::ReplaceSubstringsAfterOffset(
37 &path16
, 0, base::ASCIIToUTF16("/"), base::ASCIIToUTF16("\\"));
39 base::FilePath path
= base::FilePath::FromUTF16Unsafe(path16
);
41 // IsRelative=1 means the folder path would be relative to the
42 // path of profiles.ini. IsRelative=0 refers to a custom profile
44 if (is_relative
== "1")
45 path
= GetProfilesINI().DirName().Append(path
);
50 // Checks if the named profile is the default profile.
51 bool IsDefaultProfile(const base::DictionaryValue
& root
,
52 const std::string
& profile_name
) {
53 std::string is_default
;
54 root
.GetStringASCII(profile_name
+ ".Default", &is_default
);
55 return is_default
== "1";
60 base::FilePath
GetFirefoxProfilePath() {
61 base::FilePath ini_file
= GetProfilesINI();
63 base::ReadFileToString(ini_file
, &content
);
64 DictionaryValueINIParser ini_parser
;
65 ini_parser
.Parse(content
);
66 return GetFirefoxProfilePathFromDictionary(ini_parser
.root());
69 base::FilePath
GetFirefoxProfilePathFromDictionary(
70 const base::DictionaryValue
& root
) {
71 std::vector
<std::string
> profiles
;
72 for (int i
= 0; ; ++i
) {
73 std::string current_profile
= base::StringPrintf("Profile%d", i
);
74 if (root
.HasKey(current_profile
)) {
75 profiles
.push_back(current_profile
);
77 // Profiles are continuously numbered. So we exit when we can't
84 return base::FilePath();
86 // When multiple profiles exist, the path to the default profile is returned,
87 // since the other profiles are used mostly by developers for testing.
88 for (std::vector
<std::string
>::const_iterator it
= profiles
.begin();
89 it
!= profiles
.end(); ++it
)
90 if (IsDefaultProfile(root
, *it
))
91 return GetProfilePath(root
, *it
);
93 // If no default profile is found, the path to Profile0 will be returned.
94 return GetProfilePath(root
, profiles
.front());
97 #if defined(OS_MACOSX)
98 // Find the "*.app" component of the path and build up from there.
99 // The resulting path will be .../Firefox.app/Contents/MacOS.
100 // We do this because we don't trust LastAppDir to always be
101 // this particular path, without any subdirs, and we want to make
102 // our assumption about Firefox's root being in that path explicit.
103 bool ComposeMacAppPath(const std::string
& path_from_file
,
104 base::FilePath
* output
) {
105 base::FilePath
path(path_from_file
);
106 typedef std::vector
<base::FilePath::StringType
> ComponentVector
;
107 ComponentVector path_components
;
108 path
.GetComponents(&path_components
);
109 if (path_components
.empty())
111 // The first path component is special because it may be absolute. Calling
112 // Append with an absolute path component will trigger an assert, so we
113 // must handle it differently and initialize output with it.
114 *output
= base::FilePath(path_components
[0]);
115 // Append next path components untill we find the *.app component. When we do,
116 // append Contents/MacOS.
117 for (size_t i
= 1; i
< path_components
.size(); ++i
) {
118 *output
= output
->Append(path_components
[i
]);
119 if (base::EndsWith(path_components
[i
], ".app",
120 base::CompareCase::SENSITIVE
)) {
121 *output
= output
->Append("Contents");
122 *output
= output
->Append("MacOS");
126 LOG(ERROR
) << path_from_file
<< " doesn't look like a valid Firefox "
127 << "installation path: missing /*.app/ directory.";
132 bool GetFirefoxVersionAndPathFromProfile(const base::FilePath
& profile_path
,
134 base::FilePath
* app_path
) {
136 base::FilePath compatibility_file
=
137 profile_path
.AppendASCII("compatibility.ini");
139 base::ReadFileToString(compatibility_file
, &content
);
140 base::ReplaceSubstringsAfterOffset(&content
, 0, "\r\n", "\n");
142 for (const std::string
& line
: base::SplitString(
143 content
, "\n", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
)) {
144 if (line
.empty() || line
[0] == '#' || line
[0] == ';')
146 size_t equal
= line
.find('=');
147 if (equal
!= std::string::npos
) {
148 std::string key
= line
.substr(0, equal
);
149 if (key
== "LastVersion") {
150 base::StringToInt(line
.substr(equal
+ 1), version
);
152 } else if (key
== "LastAppDir") {
153 // TODO(evanm): If the path in question isn't convertible to
154 // UTF-8, what does Firefox do? If it puts raw bytes in the
155 // file, we could go straight from bytes -> filepath;
156 // otherwise, we're out of luck here.
157 #if defined(OS_MACOSX)
158 // Extract path from "LastAppDir=/actual/path"
159 size_t separator_pos
= line
.find_first_of('=');
160 const std::string
& path_from_ini
= line
.substr(separator_pos
+ 1);
161 if (!ComposeMacAppPath(path_from_ini
, app_path
))
164 *app_path
= base::FilePath::FromUTF8Unsafe(line
.substr(equal
+ 1));
172 bool ReadPrefFile(const base::FilePath
& path
, std::string
* content
) {
176 base::ReadFileToString(path
, content
);
178 if (content
->empty()) {
179 LOG(WARNING
) << "Firefox preference file " << path
.value() << " is empty.";
186 std::string
ReadBrowserConfigProp(const base::FilePath
& app_path
,
187 const std::string
& pref_key
) {
189 if (!ReadPrefFile(app_path
.AppendASCII("browserconfig.properties"), &content
))
190 return std::string();
192 // This file has the syntax: key=value.
193 size_t prop_index
= content
.find(pref_key
+ "=");
194 if (prop_index
== std::string::npos
)
195 return std::string();
197 size_t start
= prop_index
+ pref_key
.length();
198 size_t stop
= std::string::npos
;
199 if (start
!= std::string::npos
)
200 stop
= content
.find("\n", start
+ 1);
202 if (start
== std::string::npos
||
203 stop
== std::string::npos
|| (start
== stop
)) {
204 LOG(WARNING
) << "Firefox property " << pref_key
<< " could not be parsed.";
205 return std::string();
208 return content
.substr(start
+ 1, stop
- start
- 1);
211 std::string
ReadPrefsJsValue(const base::FilePath
& profile_path
,
212 const std::string
& pref_key
) {
214 if (!ReadPrefFile(profile_path
.AppendASCII("prefs.js"), &content
))
215 return std::string();
217 return GetPrefsJsValue(content
, pref_key
);
220 GURL
GetHomepage(const base::FilePath
& profile_path
) {
221 std::string home_page_list
=
222 ReadPrefsJsValue(profile_path
, "browser.startup.homepage");
224 size_t seperator
= home_page_list
.find_first_of('|');
225 if (seperator
== std::string::npos
)
226 return GURL(home_page_list
);
228 return GURL(home_page_list
.substr(0, seperator
));
231 bool IsDefaultHomepage(const GURL
& homepage
, const base::FilePath
& app_path
) {
232 if (!homepage
.is_valid())
235 std::string default_homepages
=
236 ReadBrowserConfigProp(app_path
, "browser.startup.homepage");
238 size_t seperator
= default_homepages
.find_first_of('|');
239 if (seperator
== std::string::npos
)
240 return homepage
.spec() == GURL(default_homepages
).spec();
242 // Crack the string into separate homepage urls.
243 for (const std::string
& url
: base::SplitString(
244 default_homepages
, "|",
245 base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
)) {
246 if (homepage
.spec() == GURL(url
).spec())
253 std::string
GetPrefsJsValue(const std::string
& content
,
254 const std::string
& pref_key
) {
255 // This file has the syntax: user_pref("key", value);
256 std::string search_for
= std::string("user_pref(\"") + pref_key
+
258 size_t prop_index
= content
.find(search_for
);
259 if (prop_index
== std::string::npos
)
260 return std::string();
262 size_t start
= prop_index
+ search_for
.length();
263 size_t stop
= std::string::npos
;
264 if (start
!= std::string::npos
) {
265 // Stop at the last ')' on this line.
266 stop
= content
.find("\n", start
+ 1);
267 stop
= content
.rfind(")", stop
);
270 if (start
== std::string::npos
|| stop
== std::string::npos
||
272 LOG(WARNING
) << "Firefox property " << pref_key
<< " could not be parsed.";
273 return std::string();
276 // String values have double quotes we don't need to return to the caller.
277 if (content
[start
] == '\"' && content
[stop
- 1] == '\"') {
282 return content
.substr(start
, stop
- start
);
285 // The branding name is obtained from the application.ini file from the Firefox
286 // application directory. A sample application.ini file is the following:
290 // Profile=mozilla/firefox
292 // BuildID=20120421070307
293 // Copyright=Copyright (c) 1998 - 2010 mozilla.org
294 // ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
295 // .........................................
296 // In this example the function returns "Iceweasel" (or a localized equivalent).
297 base::string16
GetFirefoxImporterName(const base::FilePath
& app_path
) {
298 const base::FilePath app_ini_file
= app_path
.AppendASCII("application.ini");
299 std::string branding_name
;
300 if (base::PathExists(app_ini_file
)) {
302 base::ReadFileToString(app_ini_file
, &content
);
304 const std::string
name_attr("Name=");
305 bool in_app_section
= false;
306 for (const base::StringPiece
& line
: base::SplitStringPiece(
307 content
, "\n", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
)) {
308 if (line
== "[App]") {
309 in_app_section
= true;
310 } else if (in_app_section
) {
311 if (line
.find(name_attr
) == 0) {
312 line
.substr(name_attr
.size()).CopyToString(&branding_name
);
314 } else if (line
.length() > 0 && line
[0] == '[') {
315 // No longer in the [App] section.
322 base::StringToLowerASCII(&branding_name
);
323 if (branding_name
.find("iceweasel") != std::string::npos
)
324 return l10n_util::GetStringUTF16(IDS_IMPORT_FROM_ICEWEASEL
);
325 return l10n_util::GetStringUTF16(IDS_IMPORT_FROM_FIREFOX
);