Bug 1824490 - Use the end page value rather than the start page value of the previous...
[gecko.git] / xpcom / io / nsLocalFileCommon.cpp
blob08a10c5b4f231bf3a29b9c6e7d1cdf5a4509271b
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 /* static */
29 const char* const sExecutableExts[] = {
30 // clang-format off
31 ".accda", // MS Access database
32 ".accdb", // MS Access database
33 ".accde", // MS Access database
34 ".accdr", // MS Access database
35 ".ad",
36 ".ade", // access project extension
37 ".adp",
38 ".afploc", // Apple Filing Protocol Location
39 ".air", // Adobe AIR installer
40 ".app", // executable application
41 ".application", // from bug 348763
42 ".asp",
43 ".atloc", // Appletalk Location
44 ".bas",
45 ".bat",
46 ".cer", // Signed certificate file
47 ".chm",
48 ".cmd",
49 ".com",
50 ".cpl",
51 ".crt",
52 ".der",
53 ".exe",
54 ".fileloc", // Apple finder internet location data file
55 ".ftploc", // Apple FTP Location
56 ".fxp", // FoxPro compiled app
57 ".hlp",
58 ".hta",
59 ".inetloc", // Apple finder internet location data file
60 ".inf",
61 ".ins",
62 ".isp",
63 ".jar", // java application bundle
64 ".jnlp",
65 ".js",
66 ".jse",
67 ".lnk",
68 ".mad", // Access Module Shortcut
69 ".maf", // Access
70 ".mag", // Access Diagram Shortcut
71 ".mam", // Access Macro Shortcut
72 ".maq", // Access Query Shortcut
73 ".mar", // Access Report Shortcut
74 ".mas", // Access Stored Procedure
75 ".mat", // Access Table Shortcut
76 ".mau", // Media Attachment Unit
77 ".mav", // Access View Shortcut
78 ".maw", // Access Data Access Page
79 ".mda", // Access Add-in, MDA Access 2 Workgroup
80 ".mdb",
81 ".mde",
82 ".mdt", // Access Add-in Data
83 ".mdw", // Access Workgroup Information
84 ".mdz", // Access Wizard Template
85 ".msc",
86 ".msh", // Microsoft Shell
87 ".msh1", // Microsoft Shell
88 ".msh1xml", // Microsoft Shell
89 ".msh2", // Microsoft Shell
90 ".msh2xml", // Microsoft Shell
91 ".mshxml", // Microsoft Shell
92 ".msi",
93 ".msp",
94 ".mst",
95 ".ops", // Office Profile Settings
96 ".pcd",
97 ".pif",
98 ".plg", // Developer Studio Build Log
99 ".prf", // windows system file
100 ".prg",
101 ".pst",
102 ".reg",
103 ".scf", // Windows explorer command
104 ".scr",
105 ".sct",
106 ".settingcontent-ms",
107 ".shb",
108 ".shs",
109 ".url",
110 ".vb",
111 ".vbe",
112 ".vbs",
113 ".vdx",
114 ".vsd",
115 ".vsdm",
116 ".vsdx",
117 ".vsmacros", // Visual Studio .NET Binary-based Macro Project
118 ".vss",
119 ".vssm",
120 ".vssx",
121 ".vst",
122 ".vstm",
123 ".vstx",
124 ".vsw",
125 ".vsx",
126 ".vtx",
127 ".webloc", // MacOS website location file
128 ".ws",
129 ".wsc",
130 ".wsf",
131 ".wsh"
132 // clang-format on
135 #if !defined(MOZ_WIDGET_COCOA) && !defined(XP_WIN)
136 NS_IMETHODIMP
137 nsLocalFile::InitWithFile(nsIFile* aFile) {
138 if (NS_WARN_IF(!aFile)) {
139 return NS_ERROR_INVALID_ARG;
142 nsAutoCString path;
143 aFile->GetNativePath(path);
144 if (path.IsEmpty()) {
145 return NS_ERROR_INVALID_ARG;
147 return InitWithNativePath(path);
149 #endif
151 #define kMaxFilenameLength 255
152 #define kMaxExtensionLength 100
153 #define kMaxSequenceNumberLength 5 // "-9999"
154 // requirement: kMaxExtensionLength <
155 // kMaxFilenameLength - kMaxSequenceNumberLength
157 NS_IMETHODIMP
158 nsLocalFile::CreateUnique(uint32_t aType, uint32_t aAttributes) {
159 nsresult rv;
160 bool longName;
162 #ifdef XP_WIN
163 nsAutoString pathName, leafName, rootName, suffix;
164 rv = GetPath(pathName);
165 #else
166 nsAutoCString pathName, leafName, rootName, suffix;
167 rv = GetNativePath(pathName);
168 #endif
169 if (NS_FAILED(rv)) {
170 return rv;
173 auto FailedBecauseExists = [&](nsresult aRv) {
174 if (aRv == NS_ERROR_FILE_ACCESS_DENIED) {
175 bool exists;
176 return NS_SUCCEEDED(Exists(&exists)) && exists;
178 return aRv == NS_ERROR_FILE_ALREADY_EXISTS;
181 longName =
182 (pathName.Length() + kMaxSequenceNumberLength > kMaxFilenameLength);
183 if (!longName) {
184 rv = Create(aType, aAttributes);
185 if (!FailedBecauseExists(rv)) {
186 return rv;
190 #ifdef XP_WIN
191 rv = GetLeafName(leafName);
192 if (NS_FAILED(rv)) {
193 return rv;
196 const int32_t lastDot = leafName.RFindChar(char16_t('.'));
197 #else
198 rv = GetNativeLeafName(leafName);
199 if (NS_FAILED(rv)) {
200 return rv;
203 const int32_t lastDot = leafName.RFindChar('.');
204 #endif
206 if (lastDot == kNotFound) {
207 rootName = leafName;
208 } else {
209 suffix = Substring(leafName, lastDot); // include '.'
210 rootName = Substring(leafName, 0, lastDot); // strip suffix and dot
213 if (longName) {
214 int32_t maxRootLength =
215 (kMaxFilenameLength - (pathName.Length() - leafName.Length()) -
216 suffix.Length() - kMaxSequenceNumberLength);
218 // We cannot create an item inside a directory whose name is too long.
219 // Also, ensure that at least one character remains after we truncate
220 // the root name, as we don't want to end up with an empty leaf name.
221 if (maxRootLength < 2) {
222 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
225 #ifdef XP_WIN
226 // ensure that we don't cut the name in mid-UTF16-character
227 rootName.SetLength(NS_IS_LOW_SURROGATE(rootName[maxRootLength])
228 ? maxRootLength - 1
229 : maxRootLength);
230 SetLeafName(rootName + suffix);
231 #else
232 if (NS_IsNativeUTF8()) {
233 // ensure that we don't cut the name in mid-UTF8-character
234 // (assume the name is valid UTF8 to begin with)
235 while (UTF8traits::isInSeq(rootName[maxRootLength])) {
236 --maxRootLength;
239 // Another check to avoid ending up with an empty leaf name.
240 if (maxRootLength == 0 && suffix.IsEmpty()) {
241 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
245 rootName.SetLength(maxRootLength);
246 SetNativeLeafName(rootName + suffix);
247 #endif
248 nsresult rvCreate = Create(aType, aAttributes);
249 if (!FailedBecauseExists(rvCreate)) {
250 return rvCreate;
254 for (int indx = 1; indx < 10000; ++indx) {
255 // start with "Picture-1.jpg" after "Picture.jpg" exists
256 #ifdef XP_WIN
257 SetLeafName(rootName +
258 NS_ConvertASCIItoUTF16(nsPrintfCString("-%d", indx)) + suffix);
259 #else
260 SetNativeLeafName(rootName + nsPrintfCString("-%d", indx) + suffix);
261 #endif
262 rv = Create(aType, aAttributes);
263 if (NS_SUCCEEDED(rv) || !FailedBecauseExists(rv)) {
264 return rv;
268 // The disk is full, sort of
269 return NS_ERROR_FILE_TOO_BIG;
272 #if defined(XP_WIN)
273 static const char16_t kPathSeparatorChar = '\\';
274 #elif defined(XP_UNIX)
275 static const char16_t kPathSeparatorChar = '/';
276 #else
277 # error Need to define file path separator for your platform
278 #endif
280 static void SplitPath(char16_t* aPath, nsTArray<char16_t*>& aNodeArray) {
281 if (*aPath == 0) {
282 return;
285 if (*aPath == kPathSeparatorChar) {
286 aPath++;
288 aNodeArray.AppendElement(aPath);
290 for (char16_t* cp = aPath; *cp != 0; ++cp) {
291 if (*cp == kPathSeparatorChar) {
292 *cp++ = 0;
293 if (*cp == 0) {
294 break;
296 aNodeArray.AppendElement(cp);
301 NS_IMETHODIMP
302 nsLocalFile::GetRelativeDescriptor(nsIFile* aFromFile, nsACString& aResult) {
303 if (NS_WARN_IF(!aFromFile)) {
304 return NS_ERROR_INVALID_ARG;
308 // aResult will be UTF-8 encoded
311 nsresult rv;
312 aResult.Truncate(0);
314 nsAutoString thisPath, fromPath;
315 AutoTArray<char16_t*, 32> thisNodes;
316 AutoTArray<char16_t*, 32> fromNodes;
318 rv = GetPath(thisPath);
319 if (NS_FAILED(rv)) {
320 return rv;
322 rv = aFromFile->GetPath(fromPath);
323 if (NS_FAILED(rv)) {
324 return rv;
327 // get raw pointer to mutable string buffer
328 char16_t* thisPathPtr = thisPath.BeginWriting();
329 char16_t* fromPathPtr = fromPath.BeginWriting();
331 SplitPath(thisPathPtr, thisNodes);
332 SplitPath(fromPathPtr, fromNodes);
334 size_t nodeIndex;
335 for (nodeIndex = 0;
336 nodeIndex < thisNodes.Length() && nodeIndex < fromNodes.Length();
337 ++nodeIndex) {
338 #ifdef XP_WIN
339 if (_wcsicmp(char16ptr_t(thisNodes[nodeIndex]),
340 char16ptr_t(fromNodes[nodeIndex]))) {
341 break;
343 #else
344 if (nsCRT::strcmp(thisNodes[nodeIndex], fromNodes[nodeIndex])) {
345 break;
347 #endif
350 size_t branchIndex = nodeIndex;
351 for (nodeIndex = branchIndex; nodeIndex < fromNodes.Length(); ++nodeIndex) {
352 aResult.AppendLiteral("../");
354 StringJoinAppend(aResult, "/"_ns, mozilla::Span{thisNodes}.From(branchIndex),
355 [](nsACString& dest, const auto& thisNode) {
356 // XXX(Bug 1682869) We wouldn't need to reconstruct a
357 // nsDependentString here if SplitPath already returned
358 // nsDependentString. In fact, it seems SplitPath might be
359 // replaced by ParseString?
360 AppendUTF16toUTF8(nsDependentString{thisNode}, dest);
363 return NS_OK;
366 NS_IMETHODIMP
367 nsLocalFile::SetRelativeDescriptor(nsIFile* aFromFile,
368 const nsACString& aRelativeDesc) {
369 constexpr auto kParentDirStr = "../"_ns;
371 nsCOMPtr<nsIFile> targetFile;
372 nsresult rv = aFromFile->Clone(getter_AddRefs(targetFile));
373 if (NS_FAILED(rv)) {
374 return rv;
378 // aRelativeDesc is UTF-8 encoded
381 nsCString::const_iterator strBegin, strEnd;
382 aRelativeDesc.BeginReading(strBegin);
383 aRelativeDesc.EndReading(strEnd);
385 nsCString::const_iterator nodeBegin(strBegin), nodeEnd(strEnd);
386 nsCString::const_iterator pos(strBegin);
388 nsCOMPtr<nsIFile> parentDir;
389 while (FindInReadable(kParentDirStr, nodeBegin, nodeEnd)) {
390 rv = targetFile->GetParent(getter_AddRefs(parentDir));
391 if (NS_FAILED(rv)) {
392 return rv;
394 if (!parentDir) {
395 return NS_ERROR_FILE_UNRECOGNIZED_PATH;
397 targetFile = parentDir;
399 nodeBegin = nodeEnd;
400 pos = nodeEnd;
401 nodeEnd = strEnd;
404 nodeBegin = nodeEnd = pos;
405 while (nodeEnd != strEnd) {
406 FindCharInReadable('/', nodeEnd, strEnd);
407 targetFile->Append(NS_ConvertUTF8toUTF16(Substring(nodeBegin, nodeEnd)));
408 if (nodeEnd != strEnd) { // If there's more left in the string, inc over
409 // the '/' nodeEnd is on.
410 ++nodeEnd;
412 nodeBegin = nodeEnd;
415 return InitWithFile(targetFile);
418 NS_IMETHODIMP
419 nsLocalFile::GetRelativePath(nsIFile* aFromFile, nsACString& aResult) {
420 return GetRelativeDescriptor(aFromFile, aResult);
423 NS_IMETHODIMP
424 nsLocalFile::SetRelativePath(nsIFile* aFromFile,
425 const nsACString& aRelativePath) {
426 return SetRelativeDescriptor(aFromFile, aRelativePath);