summaryrefslogtreecommitdiffstats
path: root/xpcom/io/FilePreferences.cpp
blob: ef942beb21f740511ce382cf68fdee2d6b8958f9 (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
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "FilePreferences.h"

#include "mozilla/Preferences.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsString.h"

namespace mozilla {
namespace FilePreferences {

static bool sBlockUNCPaths = false;
typedef nsTArray<nsString> Paths;

static Paths& PathArray()
{
  static Paths sPaths;
  return sPaths;
}

static void AllowDirectory(char const* directory)
{
  nsCOMPtr<nsIFile> file;
  NS_GetSpecialDirectory(directory, getter_AddRefs(file));
  if (!file) {
    return;
  }

  nsString path;
  if (NS_FAILED(file->GetTarget(path))) {
    return;
  }

  // The whitelist makes sense only for UNC paths, because this code is used
  // to block only UNC paths, hence, no need to add non-UNC directories here
  // as those would never pass the check.
  if (!StringBeginsWith(path, NS_LITERAL_STRING("\\\\"))) {
    return;
  }

  if (!PathArray().Contains(path)) {
    PathArray().AppendElement(path);
  }
}

void InitPrefs()
{
  sBlockUNCPaths = Preferences::GetBool("network.file.disable_unc_paths", false);
}

void InitDirectoriesWhitelist()
{
  // NS_GRE_DIR is the installation path where the binary resides.
  AllowDirectory(NS_GRE_DIR);
  // NS_APP_USER_PROFILE_50_DIR and NS_APP_USER_PROFILE_LOCAL_50_DIR are the two
  // parts of the profile we store permanent and local-specific data.
  AllowDirectory(NS_APP_USER_PROFILE_50_DIR);
  AllowDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR);
}

namespace { // anon

class Normalizer
{
public:
  Normalizer(const nsAString& aFilePath, const char16_t aSeparator);
  bool Get(nsAString& aNormalizedFilePath);

private:
  bool ConsumeItem();
  bool ConsumeSeparator();
  bool IsEOF() { return mFilePathCursor == mFilePathEnd; }

  bool ConsumeName();
  bool CheckParentDir();
  bool CheckCurrentDir();

  nsString::const_char_iterator mFilePathCursor;
  nsString::const_char_iterator mFilePathEnd;

  nsDependentSubstring mItem;
  char16_t const mSeparator;
  nsTArray<nsDependentSubstring> mStack;
};

Normalizer::Normalizer(const nsAString& aFilePath, const char16_t aSeparator)
  : mFilePathCursor(aFilePath.BeginReading())
  , mFilePathEnd(aFilePath.EndReading())
  , mSeparator(aSeparator)
{
}

bool Normalizer::ConsumeItem()
{
  if (IsEOF()) {
    return false;
  }

  nsString::const_char_iterator nameBegin = mFilePathCursor;
  while (mFilePathCursor != mFilePathEnd) {
    if (*mFilePathCursor == mSeparator) {
      break; // don't include the separator
    }
    ++mFilePathCursor;
  }

  mItem.Rebind(nameBegin, mFilePathCursor);
  return true;
}

bool Normalizer::ConsumeSeparator()
{
  if (IsEOF()) {
    return false;
  }

  if (*mFilePathCursor != mSeparator) {
    return false;
  }

  ++mFilePathCursor;
  return true;
}

bool Normalizer::Get(nsAString& aNormalizedFilePath)
{
  aNormalizedFilePath.Truncate();

  if (IsEOF()) {
    return true;
  }
  if (ConsumeSeparator()) {
    aNormalizedFilePath.Append(mSeparator);
  }

  if (IsEOF()) {
    return true;
  }
  if (ConsumeSeparator()) {
    aNormalizedFilePath.Append(mSeparator);
  }

  while (!IsEOF()) {
    if (!ConsumeName()) {
      return false;
    }
  }

  for (auto const& name : mStack) {
    aNormalizedFilePath.Append(name);
  }

  return true;
}

bool Normalizer::ConsumeName()
{
  if (!ConsumeItem()) {
    return true;
  }

  if (CheckCurrentDir()) {
    return true;
  }

  if (CheckParentDir()) {
    if (!mStack.Length()) {
      // This means there are more \.. than valid names
      return false;
    }

    mStack.RemoveElementAt(mStack.Length() - 1);
    return true;
  }

  if (mItem.IsEmpty()) {
    // this means an empty name (a lone slash), which is illegal
    return false;
  }

  if (ConsumeSeparator()) {
    mItem.Rebind(mItem.BeginReading(), mFilePathCursor);
  }
  mStack.AppendElement(mItem);

  return true;
}

bool Normalizer::CheckCurrentDir()
{
  if (mItem == NS_LITERAL_STRING(".")) {
    ConsumeSeparator();
    // EOF is acceptable
    return true;
  }

  return false;
}

bool Normalizer::CheckParentDir()
{
  if (mItem == NS_LITERAL_STRING("..")) {
    ConsumeSeparator();
    // EOF is acceptable
    return true;
  }

  return false;
}

} // anon

bool IsBlockedUNCPath(const nsAString& aFilePath)
{
  if (!sBlockUNCPaths) {
    return false;
  }

  if (!StringBeginsWith(aFilePath, NS_LITERAL_STRING("\\\\"))) {
    return false;
  }

  nsAutoString normalized;
  if (!Normalizer(aFilePath, L'\\').Get(normalized)) {
    // Broken paths are considered invalid and thus inaccessible
    return true;
  }

  for (const auto& allowedPrefix : PathArray()) {
    if (StringBeginsWith(normalized, allowedPrefix)) {
      if (normalized.Length() == allowedPrefix.Length()) {
        return false;
      }
      if (normalized[allowedPrefix.Length()] == L'\\') {
        return false;
      }

      // When we are here, the path has a form "\\path\prefixevil"
      // while we have an allowed prefix of "\\path\prefix".
      // Note that we don't want to add a slash to the end of a prefix
      // so that opening the directory (no slash at the end) still works.
      break;
    }
  }

  return true;
}

void testing::SetBlockUNCPaths(bool aBlock)
{
  sBlockUNCPaths = aBlock;
}

void testing::AddDirectoryToWhitelist(nsAString const & aPath)
{
  PathArray().AppendElement(aPath);
}

bool testing::NormalizePath(nsAString const & aPath, nsAString & aNormalized)
{
  Normalizer normalizer(aPath, L'\\');
  return normalizer.Get(aNormalized);
}

} // ::FilePreferences
} // ::mozilla