summaryrefslogtreecommitdiffstats
path: root/chrome/common/importer/firefox_importer_utils.cc
blob: 46e120c304e4cfeace63f1663c7e0a201cfc8dec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/common/importer/firefox_importer_utils.h"

#include <algorithm>
#include <map>
#include <string>

#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/common/ini_parser.h"
#include "chrome/grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"

namespace {

// Retrieves the file system path of the profile name.
base::FilePath GetProfilePath(const base::DictionaryValue& root,
                              const std::string& profile_name) {
  base::string16 path16;
  std::string is_relative;
  if (!root.GetStringASCII(profile_name + ".IsRelative", &is_relative) ||
      !root.GetString(profile_name + ".Path", &path16))
    return base::FilePath();

#if defined(OS_WIN)
  base::ReplaceSubstringsAfterOffset(
      &path16, 0, base::ASCIIToUTF16("/"), base::ASCIIToUTF16("\\"));
#endif
  base::FilePath path = base::FilePath::FromUTF16Unsafe(path16);

  // IsRelative=1 means the folder path would be relative to the
  // path of profiles.ini. IsRelative=0 refers to a custom profile
  // location.
  if (is_relative == "1")
    path = GetProfilesINI().DirName().Append(path);

  return path;
}

// Checks if the named profile is the default profile.
bool IsDefaultProfile(const base::DictionaryValue& root,
                      const std::string& profile_name) {
  std::string is_default;
  root.GetStringASCII(profile_name + ".Default", &is_default);
  return is_default == "1";
}

} // namespace

base::FilePath GetFirefoxProfilePath() {
  base::FilePath ini_file = GetProfilesINI();
  std::string content;
  base::ReadFileToString(ini_file, &content);
  DictionaryValueINIParser ini_parser;
  ini_parser.Parse(content);
  return GetFirefoxProfilePathFromDictionary(ini_parser.root());
}

base::FilePath GetFirefoxProfilePathFromDictionary(
    const base::DictionaryValue& root) {
  std::vector<std::string> profiles;
  for (int i = 0; ; ++i) {
    std::string current_profile = base::StringPrintf("Profile%d", i);
    if (root.HasKey(current_profile)) {
      profiles.push_back(current_profile);
    } else {
      // Profiles are continuously numbered. So we exit when we can't
      // find the i-th one.
      break;
    }
  }

  if (profiles.empty())
    return base::FilePath();

  // When multiple profiles exist, the path to the default profile is returned,
  // since the other profiles are used mostly by developers for testing.
  for (std::vector<std::string>::const_iterator it = profiles.begin();
       it != profiles.end(); ++it)
    if (IsDefaultProfile(root, *it))
      return GetProfilePath(root, *it);

  // If no default profile is found, the path to Profile0 will be returned.
  return GetProfilePath(root, profiles.front());
}

#if defined(OS_MACOSX)
// Find the "*.app" component of the path and build up from there.
// The resulting path will be .../Firefox.app/Contents/MacOS.
// We do this because we don't trust LastAppDir to always be
// this particular path, without any subdirs, and we want to make
// our assumption about Firefox's root being in that path explicit.
bool ComposeMacAppPath(const std::string& path_from_file,
                       base::FilePath* output) {
  base::FilePath path(path_from_file);
  typedef std::vector<base::FilePath::StringType> ComponentVector;
  ComponentVector path_components;
  path.GetComponents(&path_components);
  if (path_components.empty())
    return false;
  // The first path component is special because it may be absolute. Calling
  // Append with an absolute path component will trigger an assert, so we
  // must handle it differently and initialize output with it.
  *output = base::FilePath(path_components[0]);
  // Append next path components untill we find the *.app component. When we do,
  // append Contents/MacOS.
  for (size_t i = 1; i < path_components.size(); ++i) {
    *output = output->Append(path_components[i]);
    if (base::EndsWith(path_components[i], ".app",
                       base::CompareCase::SENSITIVE)) {
      *output = output->Append("Contents");
      *output = output->Append("MacOS");
      return true;
    }
  }
  LOG(ERROR) << path_from_file << " doesn't look like a valid Firefox "
             << "installation path: missing /*.app/ directory.";
  return false;
}
#endif  // OS_MACOSX

bool GetFirefoxVersionAndPathFromProfile(const base::FilePath& profile_path,
                                         int* version,
                                         base::FilePath* app_path) {
  bool ret = false;
  base::FilePath compatibility_file =
      profile_path.AppendASCII("compatibility.ini");
  std::string content;
  base::ReadFileToString(compatibility_file, &content);
  base::ReplaceSubstringsAfterOffset(&content, 0, "\r\n", "\n");
  std::vector<std::string> lines;
  base::SplitString(content, '\n', &lines);

  for (size_t i = 0; i < lines.size(); ++i) {
    const std::string& line = lines[i];
    if (line.empty() || line[0] == '#' || line[0] == ';')
      continue;
    size_t equal = line.find('=');
    if (equal != std::string::npos) {
      std::string key = line.substr(0, equal);
      if (key == "LastVersion") {
        base::StringToInt(line.substr(equal + 1), version);
        ret = true;
      } else if (key == "LastAppDir") {
        // TODO(evanm): If the path in question isn't convertible to
        // UTF-8, what does Firefox do?  If it puts raw bytes in the
        // file, we could go straight from bytes -> filepath;
        // otherwise, we're out of luck here.
#if defined(OS_MACOSX)
        // Extract path from "LastAppDir=/actual/path"
        size_t separator_pos = line.find_first_of('=');
        const std::string& path_from_ini = line.substr(separator_pos + 1);
        if (!ComposeMacAppPath(path_from_ini, app_path))
          return false;
#else  // !OS_MACOSX
        *app_path = base::FilePath::FromUTF8Unsafe(line.substr(equal + 1));
#endif  // OS_MACOSX
      }
    }
  }
  return ret;
}

bool ReadPrefFile(const base::FilePath& path, std::string* content) {
  if (content == NULL)
    return false;

  base::ReadFileToString(path, content);

  if (content->empty()) {
    LOG(WARNING) << "Firefox preference file " << path.value() << " is empty.";
    return false;
  }

  return true;
}

std::string ReadBrowserConfigProp(const base::FilePath& app_path,
                                  const std::string& pref_key) {
  std::string content;
  if (!ReadPrefFile(app_path.AppendASCII("browserconfig.properties"), &content))
    return std::string();

  // This file has the syntax: key=value.
  size_t prop_index = content.find(pref_key + "=");
  if (prop_index == std::string::npos)
    return std::string();

  size_t start = prop_index + pref_key.length();
  size_t stop = std::string::npos;
  if (start != std::string::npos)
    stop = content.find("\n", start + 1);

  if (start == std::string::npos ||
      stop == std::string::npos || (start == stop)) {
    LOG(WARNING) << "Firefox property " << pref_key << " could not be parsed.";
    return std::string();
  }

  return content.substr(start + 1, stop - start - 1);
}

std::string ReadPrefsJsValue(const base::FilePath& profile_path,
                             const std::string& pref_key) {
  std::string content;
  if (!ReadPrefFile(profile_path.AppendASCII("prefs.js"), &content))
    return std::string();

  return GetPrefsJsValue(content, pref_key);
}

GURL GetHomepage(const base::FilePath& profile_path) {
  std::string home_page_list =
      ReadPrefsJsValue(profile_path, "browser.startup.homepage");

  size_t seperator = home_page_list.find_first_of('|');
  if (seperator == std::string::npos)
    return GURL(home_page_list);

  return GURL(home_page_list.substr(0, seperator));
}

bool IsDefaultHomepage(const GURL& homepage, const base::FilePath& app_path) {
  if (!homepage.is_valid())
    return false;

  std::string default_homepages =
      ReadBrowserConfigProp(app_path, "browser.startup.homepage");

  size_t seperator = default_homepages.find_first_of('|');
  if (seperator == std::string::npos)
    return homepage.spec() == GURL(default_homepages).spec();

  // Crack the string into separate homepage urls.
  std::vector<std::string> urls;
  base::SplitString(default_homepages, '|', &urls);

  for (size_t i = 0; i < urls.size(); ++i) {
    if (homepage.spec() == GURL(urls[i]).spec())
      return true;
  }

  return false;
}

std::string GetPrefsJsValue(const std::string& content,
                            const std::string& pref_key) {
  // This file has the syntax: user_pref("key", value);
  std::string search_for = std::string("user_pref(\"") + pref_key +
                           std::string("\", ");
  size_t prop_index = content.find(search_for);
  if (prop_index == std::string::npos)
    return std::string();

  size_t start = prop_index + search_for.length();
  size_t stop = std::string::npos;
  if (start != std::string::npos) {
    // Stop at the last ')' on this line.
    stop = content.find("\n", start + 1);
    stop = content.rfind(")", stop);
  }

  if (start == std::string::npos || stop == std::string::npos ||
      stop < start) {
    LOG(WARNING) << "Firefox property " << pref_key << " could not be parsed.";
    return std::string();
  }

  // String values have double quotes we don't need to return to the caller.
  if (content[start] == '\"' && content[stop - 1] == '\"') {
    ++start;
    --stop;
  }

  return content.substr(start, stop - start);
}

// The branding name is obtained from the application.ini file from the Firefox
// application directory. A sample application.ini file is the following:
//   [App]
//   Vendor=Mozilla
//   Name=Iceweasel
//   Profile=mozilla/firefox
//   Version=3.5.16
//   BuildID=20120421070307
//   Copyright=Copyright (c) 1998 - 2010 mozilla.org
//   ID={ec8030f7-c20a-464f-9b0e-13a3a9e97384}
//   .........................................
// In this example the function returns "Iceweasel" (or a localized equivalent).
base::string16 GetFirefoxImporterName(const base::FilePath& app_path) {
  const base::FilePath app_ini_file = app_path.AppendASCII("application.ini");
  std::string branding_name;
  if (base::PathExists(app_ini_file)) {
    std::string content;
    base::ReadFileToString(app_ini_file, &content);
    std::vector<std::string> lines;
    base::SplitString(content, '\n', &lines);
    const std::string name_attr("Name=");
    bool in_app_section = false;
    for (size_t i = 0; i < lines.size(); ++i) {
      base::TrimWhitespace(lines[i], base::TRIM_ALL, &lines[i]);
      if (lines[i] == "[App]") {
        in_app_section = true;
      } else if (in_app_section) {
        if (lines[i].find(name_attr) == 0) {
          branding_name = lines[i].substr(name_attr.size());
          break;
        } else if (lines[i].length() > 0 && lines[i][0] == '[') {
          // No longer in the [App] section.
          break;
        }
      }
    }
  }

  base::StringToLowerASCII(&branding_name);
  if (branding_name.find("iceweasel") != std::string::npos)
    return l10n_util::GetStringUTF16(IDS_IMPORT_FROM_ICEWEASEL);
  return l10n_util::GetStringUTF16(IDS_IMPORT_FROM_FIREFOX);
}