/* -*- 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 "nsIServiceManager.h" #include "nsLocalFile.h" // includes platform-specific headers #include "nsString.h" #include "nsCOMPtr.h" #include "nsReadableUtils.h" #include "nsPrintfCString.h" #include "nsCRT.h" #include "nsNativeCharsetUtils.h" #include "nsUTF8Utils.h" #include "nsArray.h" #ifdef XP_WIN #include #endif #if !defined(MOZ_WIDGET_COCOA) && !defined(XP_WIN) NS_IMETHODIMP nsLocalFile::InitWithFile(nsIFile* aFile) { if (NS_WARN_IF(!aFile)) { return NS_ERROR_INVALID_ARG; } nsAutoCString path; aFile->GetNativePath(path); if (path.IsEmpty()) { return NS_ERROR_INVALID_ARG; } return InitWithNativePath(path); } #endif #define kMaxFilenameLength 255 #define kMaxExtensionLength 100 #define kMaxSequenceNumberLength 5 // "-9999" // requirement: kMaxExtensionLength < kMaxFilenameLength - kMaxSequenceNumberLength NS_IMETHODIMP nsLocalFile::CreateUnique(uint32_t aType, uint32_t aAttributes) { nsresult rv; bool longName; #ifdef XP_WIN nsAutoString pathName, leafName, rootName, suffix; rv = GetPath(pathName); #else nsAutoCString pathName, leafName, rootName, suffix; rv = GetNativePath(pathName); #endif if (NS_FAILED(rv)) { return rv; } auto FailedBecauseExists = [&] (nsresult aRv) { if (aRv == NS_ERROR_FILE_ACCESS_DENIED) { bool exists; return NS_SUCCEEDED(Exists(&exists)) && exists; } return aRv == NS_ERROR_FILE_ALREADY_EXISTS; }; longName = (pathName.Length() + kMaxSequenceNumberLength > kMaxFilenameLength); if (!longName) { rv = Create(aType, aAttributes); if (!FailedBecauseExists(rv)) { return rv; } } #ifdef XP_WIN rv = GetLeafName(leafName); if (NS_FAILED(rv)) { return rv; } const int32_t lastDot = leafName.RFindChar(char16_t('.')); #else rv = GetNativeLeafName(leafName); if (NS_FAILED(rv)) { return rv; } const int32_t lastDot = leafName.RFindChar('.'); #endif if (lastDot == kNotFound) { rootName = leafName; } else { suffix = Substring(leafName, lastDot); // include '.' rootName = Substring(leafName, 0, lastDot); // strip suffix and dot } if (longName) { int32_t maxRootLength = (kMaxFilenameLength - (pathName.Length() - leafName.Length()) - suffix.Length() - kMaxSequenceNumberLength); // We cannot create an item inside a directory whose name is too long. // Also, ensure that at least one character remains after we truncate // the root name, as we don't want to end up with an empty leaf name. if (maxRootLength < 2) { return NS_ERROR_FILE_UNRECOGNIZED_PATH; } #ifdef XP_WIN // ensure that we don't cut the name in mid-UTF16-character rootName.SetLength(NS_IS_LOW_SURROGATE(rootName[maxRootLength]) ? maxRootLength - 1 : maxRootLength); SetLeafName(rootName + suffix); #else if (NS_IsNativeUTF8()) { // ensure that we don't cut the name in mid-UTF8-character // (assume the name is valid UTF8 to begin with) while (UTF8traits::isInSeq(rootName[maxRootLength])) { --maxRootLength; } // Another check to avoid ending up with an empty leaf name. if (maxRootLength == 0 && suffix.IsEmpty()) { return NS_ERROR_FILE_UNRECOGNIZED_PATH; } } rootName.SetLength(maxRootLength); SetNativeLeafName(rootName + suffix); #endif nsresult rvCreate = Create(aType, aAttributes); if (!FailedBecauseExists(rvCreate)) { return rvCreate; } } for (int indx = 1; indx < 10000; ++indx) { // start with "Picture-1.jpg" after "Picture.jpg" exists #ifdef XP_WIN SetLeafName(rootName + NS_ConvertASCIItoUTF16(nsPrintfCString("-%d", indx)) + suffix); #else SetNativeLeafName(rootName + nsPrintfCString("-%d", indx) + suffix); #endif rv = Create(aType, aAttributes); if (NS_SUCCEEDED(rv) || !FailedBecauseExists(rv)) { return rv; } } // The disk is full, sort of return NS_ERROR_FILE_TOO_BIG; } #if defined(XP_WIN) static const char16_t kPathSeparatorChar = '\\'; #elif defined(XP_UNIX) static const char16_t kPathSeparatorChar = '/'; #else #error Need to define file path separator for your platform #endif static void SplitPath(char16_t* aPath, nsTArray& aNodeArray) { if (*aPath == 0) { return; } if (*aPath == kPathSeparatorChar) { aPath++; } aNodeArray.AppendElement(aPath); for (char16_t* cp = aPath; *cp != 0; ++cp) { if (*cp == kPathSeparatorChar) { *cp++ = 0; if (*cp == 0) { break; } aNodeArray.AppendElement(cp); } } } NS_IMETHODIMP nsLocalFile::GetRelativeDescriptor(nsIFile* aFromFile, nsACString& aResult) { if (NS_WARN_IF(!aFromFile)) { return NS_ERROR_INVALID_ARG; } // // aResult will be UTF-8 encoded // nsresult rv; aResult.Truncate(0); nsAutoString thisPath, fromPath; AutoTArray thisNodes; AutoTArray fromNodes; rv = GetPath(thisPath); if (NS_FAILED(rv)) { return rv; } rv = aFromFile->GetPath(fromPath); if (NS_FAILED(rv)) { return rv; } // get raw pointer to mutable string buffer char16_t* thisPathPtr; thisPath.BeginWriting(thisPathPtr); char16_t* fromPathPtr; fromPath.BeginWriting(fromPathPtr); SplitPath(thisPathPtr, thisNodes); SplitPath(fromPathPtr, fromNodes); size_t nodeIndex; for (nodeIndex = 0; nodeIndex < thisNodes.Length() && nodeIndex < fromNodes.Length(); ++nodeIndex) { #ifdef XP_WIN if (_wcsicmp(char16ptr_t(thisNodes[nodeIndex]), char16ptr_t(fromNodes[nodeIndex]))) { break; } #else if (nsCRT::strcmp(thisNodes[nodeIndex], fromNodes[nodeIndex])) { break; } #endif } size_t branchIndex = nodeIndex; for (nodeIndex = branchIndex; nodeIndex < fromNodes.Length(); ++nodeIndex) { aResult.AppendLiteral("../"); } for (nodeIndex = branchIndex; nodeIndex < thisNodes.Length(); ++nodeIndex) { NS_ConvertUTF16toUTF8 nodeStr(thisNodes[nodeIndex]); aResult.Append(nodeStr); if (nodeIndex + 1 < thisNodes.Length()) { aResult.Append('/'); } } return NS_OK; } NS_IMETHODIMP nsLocalFile::SetRelativeDescriptor(nsIFile* aFromFile, const nsACString& aRelativeDesc) { NS_NAMED_LITERAL_CSTRING(kParentDirStr, "../"); nsCOMPtr targetFile; nsresult rv = aFromFile->Clone(getter_AddRefs(targetFile)); if (NS_FAILED(rv)) { return rv; } // // aRelativeDesc is UTF-8 encoded // nsCString::const_iterator strBegin, strEnd; aRelativeDesc.BeginReading(strBegin); aRelativeDesc.EndReading(strEnd); nsCString::const_iterator nodeBegin(strBegin), nodeEnd(strEnd); nsCString::const_iterator pos(strBegin); nsCOMPtr parentDir; while (FindInReadable(kParentDirStr, nodeBegin, nodeEnd)) { rv = targetFile->GetParent(getter_AddRefs(parentDir)); if (NS_FAILED(rv)) { return rv; } if (!parentDir) { return NS_ERROR_FILE_UNRECOGNIZED_PATH; } targetFile = parentDir; nodeBegin = nodeEnd; pos = nodeEnd; nodeEnd = strEnd; } nodeBegin = nodeEnd = pos; while (nodeEnd != strEnd) { FindCharInReadable('/', nodeEnd, strEnd); targetFile->Append(NS_ConvertUTF8toUTF16(Substring(nodeBegin, nodeEnd))); if (nodeEnd != strEnd) { // If there's more left in the string, inc over the '/' nodeEnd is on. ++nodeEnd; } nodeBegin = nodeEnd; } return InitWithFile(targetFile); } NS_IMETHODIMP nsLocalFile::GetRelativePath(nsIFile* aFromFile, nsACString& aResult) { return GetRelativeDescriptor(aFromFile, aResult); } NS_IMETHODIMP nsLocalFile::SetRelativePath(nsIFile* aFromFile, const nsACString& aRelativePath) { return SetRelativeDescriptor(aFromFile, aRelativePath); }