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.
4
5#include "extensions/common/extension_resource.h"
6
7#include "base/files/file_util.h"
8#include "base/logging.h"
9#include "base/threading/thread_restrictions.h"
10
11namespace extensions {
12
13ExtensionResource::ExtensionResource() : follow_symlinks_anywhere_(false) {
14}
15
16ExtensionResource::ExtensionResource(const std::string& extension_id,
17                                     const base::FilePath& extension_root,
18                                     const base::FilePath& relative_path)
19    : extension_id_(extension_id),
20      extension_root_(extension_root),
21      relative_path_(relative_path),
22      follow_symlinks_anywhere_(false) {
23}
24
25ExtensionResource::~ExtensionResource() {}
26
27void ExtensionResource::set_follow_symlinks_anywhere() {
28  follow_symlinks_anywhere_ = true;
29}
30
31const base::FilePath& ExtensionResource::GetFilePath() const {
32  if (extension_root_.empty() || relative_path_.empty()) {
33    DCHECK(full_resource_path_.empty());
34    return full_resource_path_;
35  }
36
37  // We've already checked, just return last value.
38  if (!full_resource_path_.empty())
39    return full_resource_path_;
40
41  full_resource_path_ = GetFilePath(
42      extension_root_, relative_path_,
43      follow_symlinks_anywhere_ ?
44          FOLLOW_SYMLINKS_ANYWHERE : SYMLINKS_MUST_RESOLVE_WITHIN_ROOT);
45  return full_resource_path_;
46}
47
48// static
49base::FilePath ExtensionResource::GetFilePath(
50    const base::FilePath& extension_root,
51    const base::FilePath& relative_path,
52    SymlinkPolicy symlink_policy) {
53  // We need to resolve the parent references in the extension_root
54  // path on its own because IsParent doesn't like parent references.
55  base::FilePath clean_extension_root(
56      base::MakeAbsoluteFilePath(extension_root));
57  if (clean_extension_root.empty())
58    return base::FilePath();
59
60  base::FilePath full_path = clean_extension_root.Append(relative_path);
61
62  // If we are allowing the file to be a symlink outside of the root, then the
63  // path before resolving the symlink must still be within it.
64  if (symlink_policy == FOLLOW_SYMLINKS_ANYWHERE) {
65    std::vector<base::FilePath::StringType> components;
66    relative_path.GetComponents(&components);
67    int depth = 0;
68
69    for (std::vector<base::FilePath::StringType>::const_iterator
70         i = components.begin(); i != components.end(); i++) {
71      if (*i == base::FilePath::kParentDirectory) {
72        depth--;
73      } else if (*i != base::FilePath::kCurrentDirectory) {
74        depth++;
75      }
76      if (depth < 0) {
77        return base::FilePath();
78      }
79    }
80  }
81
82  // We must resolve the absolute path of the combined path when
83  // the relative path contains references to a parent folder (i.e., '..').
84  // We also check if the path exists because the posix version of
85  // MakeAbsoluteFilePath will fail if the path doesn't exist, and we want the
86  // same behavior on Windows... So until the posix and Windows version of
87  // MakeAbsoluteFilePath are unified, we need an extra call to PathExists,
88  // unfortunately.
89  // TODO(mad): Fix this once MakeAbsoluteFilePath is unified.
90  full_path = base::MakeAbsoluteFilePath(full_path);
91  if (base::PathExists(full_path) &&
92      (symlink_policy == FOLLOW_SYMLINKS_ANYWHERE ||
93       clean_extension_root.IsParent(full_path))) {
94    return full_path;
95  }
96
97  return base::FilePath();
98}
99
100// Unit-testing helpers.
101base::FilePath::StringType ExtensionResource::NormalizeSeperators(
102    const base::FilePath::StringType& path) const {
103#if defined(FILE_PATH_USES_WIN_SEPARATORS)
104  base::FilePath::StringType win_path = path;
105  for (size_t i = 0; i < win_path.length(); i++) {
106    if (base::FilePath::IsSeparator(win_path[i]))
107      win_path[i] = base::FilePath::kSeparators[0];
108  }
109  return win_path;
110#else
111  return path;
112#endif  // FILE_PATH_USES_WIN_SEPARATORS
113}
114
115bool ExtensionResource::ComparePathWithDefault(
116    const base::FilePath& path) const {
117  // Make sure we have a cached value to test against...
118  if (full_resource_path_.empty())
119    GetFilePath();
120  if (NormalizeSeperators(path.value()) ==
121    NormalizeSeperators(full_resource_path_.value())) {
122    return true;
123  } else {
124    return false;
125  }
126}
127
128}  // namespace extensions
129