Backed out changeset de3502ab8cb5 (bug 1847180) for causing gtest failures at AllExte...
[gecko.git] / xpcom / io / nsLocalFileCommon.cpp
blob32aca20a36fc1a4f6cbfe3023d086d6ca4d436fd
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsLocalFile.h" // includes platform-specific headers
9 #include "nsString.h"
10 #include "nsCOMPtr.h"
11 #include "nsReadableUtils.h"
12 #include "nsPrintfCString.h"
13 #include "nsCRT.h"
14 #include "nsNativeCharsetUtils.h"
15 #include "nsUTF8Utils.h"
16 #include "nsArray.h"
17 #include "nsLocalFileCommon.h"
19 #ifdef XP_WIN
20 # include <string.h>
21 #endif
23 // Extensions that should be considered 'executable', ie will not allow users
24 // to open immediately without first saving to disk, and potentially provoke
25 // other warnings. PLEASE read the longer comment in
26 // toolkit/components/reputationservice/ApplicationReputation.cpp
27 // before modifying this list!
28 // If you update this list, make sure to update the length of sExecutableExts
29 // in nsLocalFileCommmon.h.
30 /* static */
31 const char* const sExecutableExts[] = {
32 // clang-format off
33 ".accda", // MS Access database
34 ".accdb", // MS Access database
35 ".accde", // MS Access database
36 ".accdr", // MS Access database
37 ".ad",
38 ".ade", // access project extension
39 ".adp",
40 ".afploc", // Apple Filing Protocol Location
41 ".air", // Adobe AIR installer
42 ".app", // executable application
43 ".application", // from bug 348763
44 ".appref-ms", // ClickOnce link
45 ".asp",
46 ".atloc", // Appletalk Location
47 ".bas",
48 ".bat",
49 ".cer", // Signed certificate file
50 ".chm",
51 ".cmd",
52 ".com",
53 ".cpl",
54 ".crt",
55 ".der",
56 ".diagcab", // Windows archive
57 ".exe",
58 ".fileloc", // Apple finder internet location data file
59 ".ftploc", // Apple FTP Location
60 ".fxp", // FoxPro compiled app
61 ".hlp",
62 ".hta",
63 ".inetloc", // Apple finder internet location data file
64 ".inf",
65 ".ins",
66 ".isp",
67 ".jar", // java application bundle
68 #ifndef MOZ_ESR
69 ".jnlp",
70 #endif
71 ".js",
72 ".jse",
73 ".lnk",
74 ".mad", // Access Module Shortcut
75 ".maf", // Access
76 ".mag", // Access Diagram Shortcut
77 ".mam", // Access Macro Shortcut
78 ".maq", // Access Query Shortcut
79 ".mar", // Access Report Shortcut
80 ".mas", // Access Stored Procedure
81 ".mat", // Access Table Shortcut
82 ".mau", // Media Attachment Unit
83 ".mav", // Access View Shortcut
84 ".maw", // Access Data Access Page
85 ".mda", // Access Add-in, MDA Access 2 Workgroup
86 ".mdb",
87 ".mde",
88 ".mdt", // Access Add-in Data
89 ".mdw", // Access Workgroup Information
90 ".mdz", // Access Wizard Template
91 ".msc",
92 ".msh", // Microsoft Shell
93 ".msh1", // Microsoft Shell
94 ".msh1xml", // Microsoft Shell
95 ".msh2", // Microsoft Shell
96 ".msh2xml", // Microsoft Shell
97 ".mshxml", // Microsoft Shell
98 ".msi",
99 ".msp",
100 ".mst",
101 ".ops", // Office Profile Settings
102 ".pcd",
103 ".pif",
104 ".plg", // Developer Studio Build Log
105 ".prf", // windows system file
106 ".prg",
107 ".pst",
108 ".reg",
109 ".scf", // Windows explorer command
110 ".scr",
111 ".sct",
112 ".settingcontent-ms",
113 ".shb",
114 ".shs",
115 ".url",
116 ".vb",
117 ".vbe",
118 ".vbs",
119 ".vdx",
120 ".vsd",
121 ".vsdm",
122 ".vsdx",
123 ".vsmacros", // Visual Studio .NET Binary-based Macro Project
124 ".vss",
125 ".vssm",
126 ".vssx",
127 ".vst",
128 ".vstm",
129 ".vstx",
130 ".vsw",
131 ".vsx",
132 ".vtx",
133 ".webloc", // MacOS website location file
134 ".ws",
135 ".wsc",
136 ".wsf",
137 ".wsh",
138 ".xll" // MS Excel dynamic link library
139 // clang-format on
142 #if !defined(MOZ_WIDGET_COCOA) && !defined(XP_WIN)
143 NS_IMETHODIMP
144 nsLocalFile::InitWithFile(nsIFile* aFile) {
145 if (NS_WARN_IF(!aFile)) {
146 return NS_ERROR_INVALID_ARG;
149 nsAutoCString path;
150 aFile->GetNativePath(path);
151 if (path.IsEmpty()) {
152 return NS_ERROR_INVALID_ARG;
154 return InitWithNativePath(path);
156 #endif
158 #define kMaxFilenameLength 255
159 #define kMaxExtensionLength 100
160 #define kMaxSequenceNumberLength 5 // "-9999"
161 // requirement: kMaxExtensionLength <
162 // kMaxFilenameLength - kMaxSequenceNumberLength
164 NS_IMETHODIMP
165 nsLocalFile::CreateUnique(uint32_t aType, uint32_t aAttributes) {
166 nsresult rv;
167 bool longName;
169 #ifdef XP_WIN
170 nsAutoString pathName, leafName, rootName, suffix;
171 rv = GetPath(pathName);
172 #else
173 nsAutoCString pathName, leafName, rootName, suffix;
174 rv = GetNativePath(pathName);
175 #endif
176 if (NS_FAILED(rv)) {
177 return rv;
180 auto FailedBecauseExists = [&](nsresult aRv) {
181 if (aRv == NS_ERROR_FILE_ACCESS_DENIED) {
182 bool exists;
183 return NS_SUCCEEDED(Exists(&exists)) && exists;
185 return aRv == NS_ERROR_FILE_ALREADY_EXISTS;
188 longName =
189 (pathName.Length() + kMaxSequenceNumberLength > kMaxFilenameLength);
190 if (!longName) {
191 rv = Create(aType, aAttributes);
192 if (!FailedBecauseExists(rv)) {
193 return rv;
197 #ifdef XP_WIN
198 rv = GetLeafName(leafName);
199 if (NS_FAILED(rv)) {
200 return rv;
203 const int32_t lastDot = leafName.RFindChar(char16_t('.'));
204 #else
205 rv = GetNativeLeafName(leafName);
206 if (NS_FAILED(rv)) {
207 return rv;
210 const int32_t lastDot = leafName.RFindChar('.');
211 #endif
213 if (lastDot == kNotFound) {
214 rootName = leafName;
215 } else {
216 suffix = Substring(leafName, lastDot); // include '.'
217 rootName = Substring(leafName, 0, lastDot); // strip suffix and dot
220 if (longName) {
221 int32_t maxRootLength =
222 (kMaxFilenameLength - (pathName.Length() - leafName.Length()) -
223 suffix.Length() - kMaxSequenceNumberLength);
225 // We cannot create an item inside a directory whose name is too long.
226 // Also, ensure that at least one character remains after we truncate
227 // the root name, as we don't want to end up with an empty leaf name.
228 if (maxRootLength < 2) {
229 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
232 #ifdef XP_WIN
233 // ensure that we don't cut the name in mid-UTF16-character
234 rootName.SetLength(NS_IS_LOW_SURROGATE(rootName[maxRootLength])
235 ? maxRootLength - 1
236 : maxRootLength);
237 SetLeafName(rootName + suffix);
238 #else
239 if (NS_IsNativeUTF8()) {
240 // ensure that we don't cut the name in mid-UTF8-character
241 // (assume the name is valid UTF8 to begin with)
242 while (UTF8traits::isInSeq(rootName[maxRootLength])) {
243 --maxRootLength;
246 // Another check to avoid ending up with an empty leaf name.
247 if (maxRootLength == 0 && suffix.IsEmpty()) {
248 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
252 rootName.SetLength(maxRootLength);
253 SetNativeLeafName(rootName + suffix);
254 #endif
255 nsresult rvCreate = Create(aType, aAttributes);
256 if (!FailedBecauseExists(rvCreate)) {
257 return rvCreate;
261 for (int indx = 1; indx < 10000; ++indx) {
262 // start with "Picture-1.jpg" after "Picture.jpg" exists
263 #ifdef XP_WIN
264 SetLeafName(rootName +
265 NS_ConvertASCIItoUTF16(nsPrintfCString("-%d", indx)) + suffix);
266 #else
267 SetNativeLeafName(rootName + nsPrintfCString("-%d", indx) + suffix);
268 #endif
269 rv = Create(aType, aAttributes);
270 if (NS_SUCCEEDED(rv) || !FailedBecauseExists(rv)) {
271 return rv;
275 // The disk is full, sort of
276 return NS_ERROR_FILE_TOO_BIG;
279 #if defined(XP_WIN)
280 static const char16_t kPathSeparatorChar = '\\';
281 #elif defined(XP_UNIX)
282 static const char16_t kPathSeparatorChar = '/';
283 #else
284 # error Need to define file path separator for your platform
285 #endif
287 static void SplitPath(char16_t* aPath, nsTArray<char16_t*>& aNodeArray) {
288 if (*aPath == 0) {
289 return;
292 if (*aPath == kPathSeparatorChar) {
293 aPath++;
295 aNodeArray.AppendElement(aPath);
297 for (char16_t* cp = aPath; *cp != 0; ++cp) {
298 if (*cp == kPathSeparatorChar) {
299 *cp++ = 0;
300 if (*cp == 0) {
301 break;
303 aNodeArray.AppendElement(cp);
308 NS_IMETHODIMP
309 nsLocalFile::GetRelativeDescriptor(nsIFile* aFromFile, nsACString& aResult) {
310 if (NS_WARN_IF(!aFromFile)) {
311 return NS_ERROR_INVALID_ARG;
315 // aResult will be UTF-8 encoded
318 nsresult rv;
319 aResult.Truncate(0);
321 nsAutoString thisPath, fromPath;
322 AutoTArray<char16_t*, 32> thisNodes;
323 AutoTArray<char16_t*, 32> fromNodes;
325 rv = GetPath(thisPath);
326 if (NS_FAILED(rv)) {
327 return rv;
329 rv = aFromFile->GetPath(fromPath);
330 if (NS_FAILED(rv)) {
331 return rv;
334 // get raw pointer to mutable string buffer
335 char16_t* thisPathPtr = thisPath.BeginWriting();
336 char16_t* fromPathPtr = fromPath.BeginWriting();
338 SplitPath(thisPathPtr, thisNodes);
339 SplitPath(fromPathPtr, fromNodes);
341 size_t nodeIndex;
342 for (nodeIndex = 0;
343 nodeIndex < thisNodes.Length() && nodeIndex < fromNodes.Length();
344 ++nodeIndex) {
345 #ifdef XP_WIN
346 if (_wcsicmp(char16ptr_t(thisNodes[nodeIndex]),
347 char16ptr_t(fromNodes[nodeIndex]))) {
348 break;
350 #else
351 if (nsCRT::strcmp(thisNodes[nodeIndex], fromNodes[nodeIndex])) {
352 break;
354 #endif
357 size_t branchIndex = nodeIndex;
358 for (nodeIndex = branchIndex; nodeIndex < fromNodes.Length(); ++nodeIndex) {
359 aResult.AppendLiteral("../");
361 StringJoinAppend(aResult, "/"_ns, mozilla::Span{thisNodes}.From(branchIndex),
362 [](nsACString& dest, const auto& thisNode) {
363 // XXX(Bug 1682869) We wouldn't need to reconstruct a
364 // nsDependentString here if SplitPath already returned
365 // nsDependentString. In fact, it seems SplitPath might be
366 // replaced by ParseString?
367 AppendUTF16toUTF8(nsDependentString{thisNode}, dest);
370 return NS_OK;
373 NS_IMETHODIMP
374 nsLocalFile::SetRelativeDescriptor(nsIFile* aFromFile,
375 const nsACString& aRelativeDesc) {
376 constexpr auto kParentDirStr = "../"_ns;
378 nsCOMPtr<nsIFile> targetFile;
379 nsresult rv = aFromFile->Clone(getter_AddRefs(targetFile));
380 if (NS_FAILED(rv)) {
381 return rv;
385 // aRelativeDesc is UTF-8 encoded
388 nsCString::const_iterator strBegin, strEnd;
389 aRelativeDesc.BeginReading(strBegin);
390 aRelativeDesc.EndReading(strEnd);
392 nsCString::const_iterator nodeBegin(strBegin), nodeEnd(strEnd);
393 nsCString::const_iterator pos(strBegin);
395 nsCOMPtr<nsIFile> parentDir;
396 while (FindInReadable(kParentDirStr, nodeBegin, nodeEnd)) {
397 rv = targetFile->GetParent(getter_AddRefs(parentDir));
398 if (NS_FAILED(rv)) {
399 return rv;
401 if (!parentDir) {
402 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
404 targetFile = parentDir;
406 nodeBegin = nodeEnd;
407 pos = nodeEnd;
408 nodeEnd = strEnd;
411 nodeBegin = nodeEnd = pos;
412 while (nodeEnd != strEnd) {
413 FindCharInReadable('/', nodeEnd, strEnd);
414 targetFile->Append(NS_ConvertUTF8toUTF16(Substring(nodeBegin, nodeEnd)));
415 if (nodeEnd != strEnd) { // If there's more left in the string, inc over
416 // the '/' nodeEnd is on.
417 ++nodeEnd;
419 nodeBegin = nodeEnd;
422 return InitWithFile(targetFile);
425 NS_IMETHODIMP
426 nsLocalFile::GetRelativePath(nsIFile* aFromFile, nsACString& aResult) {
427 return GetRelativeDescriptor(aFromFile, aResult);
430 NS_IMETHODIMP
431 nsLocalFile::SetRelativePath(nsIFile* aFromFile,
432 const nsACString& aRelativePath) {
433 return SetRelativeDescriptor(aFromFile, aRelativePath);