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 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 (EndsWith(path_components
[i
], ".app", true)) {
120 *output
= output
->Append("Contents");
121 *output
= output
->Append("MacOS");
125 LOG(ERROR
) << path_from_file
<< " doesn't look like a valid Firefox "
126 << "installation path: missing /*.app/ directory.";
131 bool GetFirefoxVersionAndPathFromProfile(const base::FilePath
& profile_path
,
133 base::FilePath
* app_path
) {
135 base::FilePath compatibility_file
=
136 profile_path
.AppendASCII("compatibility.ini");
138 base::ReadFileToString(compatibility_file
, &content
);
139 ReplaceSubstringsAfterOffset(&content
, 0, "\r\n", "\n");
140 std::vector
<std::string
> lines
;
141 base::SplitString(content
, '\n', &lines
);
143 for (size_t i
= 0; i
< lines
.size(); ++i
) {
144 const std::string
& line
= lines
[i
];
145 if (line
.empty() || line
[0] == '#' || line
[0] == ';')
147 size_t equal
= line
.find('=');
148 if (equal
!= std::string::npos
) {
149 std::string key
= line
.substr(0, equal
);
150 if (key
== "LastVersion") {
151 base::StringToInt(line
.substr(equal
+ 1), version
);
153 } else if (key
== "LastAppDir") {
154 // TODO(evanm): If the path in question isn't convertible to
155 // UTF-8, what does Firefox do? If it puts raw bytes in the
156 // file, we could go straight from bytes -> filepath;
157 // otherwise, we're out of luck here.
158 #if defined(OS_MACOSX)
159 // Extract path from "LastAppDir=/actual/path"
160 size_t separator_pos
= line
.find_first_of('=');
161 const std::string
& path_from_ini
= line
.substr(separator_pos
+ 1);
162 if (!ComposeMacAppPath(path_from_ini
, app_path
))
165 *app_path
= base::FilePath::FromUTF8Unsafe(line
.substr(equal
+ 1));
173 bool ReadPrefFile(const base::FilePath
& path
, std::string
* content
) {
177 base::ReadFileToString(path
, content
);
179 if (content
->empty()) {
180 LOG(WARNING
) << "Firefox preference file " << path
.value() << " is empty.";
187 std::string
ReadBrowserConfigProp(const base::FilePath
& app_path
,
188 const std::string
& pref_key
) {
190 if (!ReadPrefFile(app_path
.AppendASCII("browserconfig.properties"), &content
))
191 return std::string();
193 // This file has the syntax: key=value.
194 size_t prop_index
= content
.find(pref_key
+ "=");
195 if (prop_index
== std::string::npos
)
196 return std::string();
198 size_t start
= prop_index
+ pref_key
.length();
199 size_t stop
= std::string::npos
;
200 if (start
!= std::string::npos
)
201 stop
= content
.find("\n", start
+ 1);
203 if (start
== std::string::npos
||
204 stop
== std::string::npos
|| (start
== stop
)) {
205 LOG(WARNING
) << "Firefox property " << pref_key
<< " could not be parsed.";
206 return std::string();
209 return content
.substr(start
+ 1, stop
- start
- 1);
212 std::string
ReadPrefsJsValue(const base::FilePath
& profile_path
,
213 const std::string
& pref_key
) {
215 if (!ReadPrefFile(profile_path
.AppendASCII("prefs.js"), &content
))
216 return std::string();
218 return GetPrefsJsValue(content
, pref_key
);
221 GURL
GetHomepage(const base::FilePath
& profile_path
) {
222 std::string home_page_list
=
223 ReadPrefsJsValue(profile_path
, "browser.startup.homepage");
225 size_t seperator
= home_page_list
.find_first_of('|');
226 if (seperator
== std::string::npos
)
227 return GURL(home_page_list
);
229 return GURL(home_page_list
.substr(0, seperator
));
232 bool IsDefaultHomepage(const GURL
& homepage
, const base::FilePath
& app_path
) {
233 if (!homepage
.is_valid())
236 std::string default_homepages
=
237 ReadBrowserConfigProp(app_path
, "browser.startup.homepage");
239 size_t seperator
= default_homepages
.find_first_of('|');
240 if (seperator
== std::string::npos
)
241 return homepage
.spec() == GURL(default_homepages
).spec();
243 // Crack the string into separate homepage urls.
244 std::vector
<std::string
> urls
;
245 base::SplitString(default_homepages
, '|', &urls
);
247 for (size_t i
= 0; i
< urls
.size(); ++i
) {
248 if (homepage
.spec() == GURL(urls
[i
]).spec())
255 std::string
GetPrefsJsValue(const std::string
& content
,
256 const std::string
& pref_key
) {
257 // This file has the syntax: user_pref("key", value);
258 std::string search_for
= std::string("user_pref(\"") + pref_key
+
260 size_t prop_index
= content
.find(search_for
);
261 if (prop_index
== std::string::npos
)
262 return std::string();
264 size_t start
= prop_index
+ search_for
.length();
265 size_t stop
= std::string::npos
;
266 if (start
!= std::string::npos
) {
267 // Stop at the last ')' on this line.
268 stop
= content
.find("\n", start
+ 1);
269 stop
= content
.rfind(")", stop
);
272 if (start
== std::string::npos
|| stop
== std::string::npos
||
274 LOG(WARNING
) << "Firefox property " << pref_key
<< " could not be parsed.";
275 return std::string();
278 // String values have double quotes we don't need to return to the caller.
279 if (content
[start
] == '\"' && content
[stop
- 1] == '\"') {
284 return content
.substr(start
, stop
- start
);
287 // The branding name is obtained from the application.ini file from the Firefox
288 // application directory. A sample application.ini file is the following:
292 // Profile=mozilla/firefox
294 // BuildID=20120421070307
295 // Copyright=Copyright (c) 1998 - 2010 mozilla.org
296 // ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
297 // .........................................
298 // In this example the function returns "Iceweasel" (or a localized equivalent).
299 base::string16
GetFirefoxImporterName(const base::FilePath
& app_path
) {
300 const base::FilePath app_ini_file
= app_path
.AppendASCII("application.ini");
301 std::string branding_name
;
302 if (base::PathExists(app_ini_file
)) {
304 base::ReadFileToString(app_ini_file
, &content
);
305 std::vector
<std::string
> lines
;
306 base::SplitString(content
, '\n', &lines
);
307 const std::string
name_attr("Name=");
308 bool in_app_section
= false;
309 for (size_t i
= 0; i
< lines
.size(); ++i
) {
310 base::TrimWhitespace(lines
[i
], base::TRIM_ALL
, &lines
[i
]);
311 if (lines
[i
] == "[App]") {
312 in_app_section
= true;
313 } else if (in_app_section
) {
314 if (lines
[i
].find(name_attr
) == 0) {
315 branding_name
= lines
[i
].substr(name_attr
.size());
317 } else if (lines
[i
].length() > 0 && lines
[i
][0] == '[') {
318 // No longer in the [App] section.
325 base::StringToLowerASCII(&branding_name
);
326 if (branding_name
.find("iceweasel") != std::string::npos
)
327 return l10n_util::GetStringUTF16(IDS_IMPORT_FROM_ICEWEASEL
);
328 return l10n_util::GetStringUTF16(IDS_IMPORT_FROM_FIREFOX
);