diff options
author | wolfbeast <mcwerewolf@gmail.com> | 2014-05-21 11:38:25 +0200 |
---|---|---|
committer | wolfbeast <mcwerewolf@gmail.com> | 2014-05-21 11:38:25 +0200 |
commit | d25ba7d760b017b038e5aa6c0a605b4a330eb68d (patch) | |
tree | 16ec27edc7d5f83986f16236d3a36a2682a0f37e /xpcom/io | |
parent | a942906574671868daf122284a9c4689e6924f74 (diff) | |
download | palemoon-gre-d25ba7d760b017b038e5aa6c0a605b4a330eb68d.tar.gz |
Recommit working copy to repo with proper line endings.
Diffstat (limited to 'xpcom/io')
90 files changed, 25459 insertions, 0 deletions
diff --git a/xpcom/io/Base64.cpp b/xpcom/io/Base64.cpp new file mode 100644 index 000000000..5d8dfc531 --- /dev/null +++ b/xpcom/io/Base64.cpp @@ -0,0 +1,346 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "Base64.h" + +#include "nsIInputStream.h" +#include "nsStringGlue.h" + +#include "plbase64.h" + +namespace { + +// BEGIN base64 encode code copied and modified from NSPR +const unsigned char *base = (unsigned char *)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +template <typename T> +static void +Encode3to4(const unsigned char *src, T *dest) +{ + uint32_t b32 = (uint32_t)0; + int i, j = 18; + + for( i = 0; i < 3; i++ ) + { + b32 <<= 8; + b32 |= (uint32_t)src[i]; + } + + for( i = 0; i < 4; i++ ) + { + dest[i] = base[ (uint32_t)((b32>>j) & 0x3F) ]; + j -= 6; + } +} + +template <typename T> +static void +Encode2to4(const unsigned char *src, T *dest) +{ + dest[0] = base[ (uint32_t)((src[0]>>2) & 0x3F) ]; + dest[1] = base[ (uint32_t)(((src[0] & 0x03) << 4) | ((src[1] >> 4) & 0x0F)) ]; + dest[2] = base[ (uint32_t)((src[1] & 0x0F) << 2) ]; + dest[3] = (unsigned char)'='; +} + +template <typename T> +static void +Encode1to4(const unsigned char *src, T *dest) +{ + dest[0] = base[ (uint32_t)((src[0]>>2) & 0x3F) ]; + dest[1] = base[ (uint32_t)((src[0] & 0x03) << 4) ]; + dest[2] = (unsigned char)'='; + dest[3] = (unsigned char)'='; +} + +template <typename T> +static void +Encode(const unsigned char *src, uint32_t srclen, T *dest) +{ + while( srclen >= 3 ) + { + Encode3to4(src, dest); + src += 3; + dest += 4; + srclen -= 3; + } + + switch( srclen ) + { + case 2: + Encode2to4(src, dest); + break; + case 1: + Encode1to4(src, dest); + break; + case 0: + break; + default: + NS_NOTREACHED("coding error"); + } +} + +// END base64 encode code copied and modified from NSPR. + +template <typename T> +struct EncodeInputStream_State { + unsigned char c[3]; + uint8_t charsOnStack; + typename T::char_type* buffer; +}; + +template <typename T> +NS_METHOD +EncodeInputStream_Encoder(nsIInputStream *aStream, + void *aClosure, + const char *aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t *aWriteCount) +{ + NS_ASSERTION(aCount > 0, "Er, what?"); + + EncodeInputStream_State<T>* state = + static_cast<EncodeInputStream_State<T>*>(aClosure); + + // If we have any data left from last time, encode it now. + uint32_t countRemaining = aCount; + const unsigned char *src = (const unsigned char*)aFromSegment; + if (state->charsOnStack) { + unsigned char firstSet[4]; + if (state->charsOnStack == 1) { + firstSet[0] = state->c[0]; + firstSet[1] = src[0]; + firstSet[2] = (countRemaining > 1) ? src[1] : '\0'; + firstSet[3] = '\0'; + } else /* state->charsOnStack == 2 */ { + firstSet[0] = state->c[0]; + firstSet[1] = state->c[1]; + firstSet[2] = src[0]; + firstSet[3] = '\0'; + } + Encode(firstSet, 3, state->buffer); + state->buffer += 4; + countRemaining -= (3 - state->charsOnStack); + src += (3 - state->charsOnStack); + state->charsOnStack = 0; + } + + // Encode the bulk of the + uint32_t encodeLength = countRemaining - countRemaining % 3; + NS_ABORT_IF_FALSE(encodeLength % 3 == 0, + "Should have an exact number of triplets!"); + Encode(src, encodeLength, state->buffer); + state->buffer += (encodeLength / 3) * 4; + src += encodeLength; + countRemaining -= encodeLength; + + // We must consume all data, so if there's some data left stash it + *aWriteCount = aCount; + + if (countRemaining) { + // We should never have a full triplet left at this point. + NS_ABORT_IF_FALSE(countRemaining < 3, "We should have encoded more!"); + state->c[0] = src[0]; + state->c[1] = (countRemaining == 2) ? src[1] : '\0'; + state->charsOnStack = countRemaining; + } + + return NS_OK; +} + +template <typename T> +nsresult +EncodeInputStream(nsIInputStream *aInputStream, + T &aDest, + uint32_t aCount, + uint32_t aOffset) +{ + nsresult rv; + uint64_t count64 = aCount; + + if (!aCount) { + rv = aInputStream->Available(&count64); + NS_ENSURE_SUCCESS(rv, rv); + // if count64 is over 4GB, it will be failed at the below condition, + // then will return NS_ERROR_OUT_OF_MEMORY + aCount = (uint32_t)count64; + } + + uint64_t countlong = + (count64 + 2) / 3 * 4; // +2 due to integer math. + if (countlong + aOffset > UINT32_MAX) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t count = uint32_t(countlong); + + aDest.SetLength(count + aOffset); + if (aDest.Length() != count + aOffset) + return NS_ERROR_OUT_OF_MEMORY; + + EncodeInputStream_State<T> state; + state.charsOnStack = 0; + state.c[2] = '\0'; + state.buffer = aOffset + aDest.BeginWriting(); + + while (1) { + uint32_t read = 0; + + rv = aInputStream->ReadSegments(&EncodeInputStream_Encoder<T>, + (void*)&state, + aCount, + &read); + if (NS_FAILED(rv)) { + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + NS_RUNTIMEABORT("Not implemented for async streams!"); + if (rv == NS_ERROR_NOT_IMPLEMENTED) + NS_RUNTIMEABORT("Requires a stream that implements ReadSegments!"); + return rv; + } + + if (!read) + break; + } + + // Finish encoding if anything is left + if (state.charsOnStack) + Encode(state.c, state.charsOnStack, state.buffer); + + *aDest.EndWriting() = '\0'; + + return NS_OK; +} + +} // namespace (anonymous) + +namespace mozilla { + +nsresult +Base64EncodeInputStream(nsIInputStream *aInputStream, + nsACString &aDest, + uint32_t aCount, + uint32_t aOffset) +{ + return EncodeInputStream<nsACString>(aInputStream, aDest, aCount, aOffset); +} + +nsresult +Base64EncodeInputStream(nsIInputStream *aInputStream, + nsAString &aDest, + uint32_t aCount, + uint32_t aOffset) +{ + return EncodeInputStream<nsAString>(aInputStream, aDest, aCount, aOffset); +} + +nsresult +Base64Encode(const nsACString &aBinaryData, nsACString &aString) +{ + // Check for overflow. + if (aBinaryData.Length() > (UINT32_MAX / 4) * 3) { + return NS_ERROR_FAILURE; + } + + // Don't ask PR_Base64Encode to encode empty strings + if (aBinaryData.IsEmpty()) { + aString.Truncate(); + return NS_OK; + } + + uint32_t stringLen = ((aBinaryData.Length() + 2) / 3) * 4; + + char *buffer; + + // Add one byte for null termination. + if (aString.SetCapacity(stringLen + 1, fallible_t()) && + (buffer = aString.BeginWriting()) && + PL_Base64Encode(aBinaryData.BeginReading(), aBinaryData.Length(), buffer)) { + // PL_Base64Encode doesn't null terminate the buffer for us when we pass + // the buffer in. Do that manually. + buffer[stringLen] = '\0'; + + aString.SetLength(stringLen); + return NS_OK; + } + + aString.Truncate(); + return NS_ERROR_INVALID_ARG; +} + +nsresult +Base64Encode(const nsAString &aString, nsAString &aBinaryData) +{ + NS_LossyConvertUTF16toASCII string(aString); + nsAutoCString binaryData; + + nsresult rv = Base64Encode(string, binaryData); + if (NS_SUCCEEDED(rv)) { + CopyASCIItoUTF16(binaryData, aBinaryData); + } else { + aBinaryData.Truncate(); + } + + return rv; +} + +nsresult +Base64Decode(const nsACString &aString, nsACString &aBinaryData) +{ + // Check for overflow. + if (aString.Length() > UINT32_MAX / 3) { + return NS_ERROR_FAILURE; + } + + // Don't ask PR_Base64Decode to decode the empty string + if (aString.IsEmpty()) { + aBinaryData.Truncate(); + return NS_OK; + } + + uint32_t binaryDataLen = ((aString.Length() * 3) / 4); + + char *buffer; + + // Add one byte for null termination. + if (aBinaryData.SetCapacity(binaryDataLen + 1, fallible_t()) && + (buffer = aBinaryData.BeginWriting()) && + PL_Base64Decode(aString.BeginReading(), aString.Length(), buffer)) { + // PL_Base64Decode doesn't null terminate the buffer for us when we pass + // the buffer in. Do that manually, taking into account the number of '=' + // characters we were passed. + if (!aString.IsEmpty() && aString[aString.Length() - 1] == '=') { + if (aString.Length() > 1 && aString[aString.Length() - 2] == '=') { + binaryDataLen -= 2; + } else { + binaryDataLen -= 1; + } + } + buffer[binaryDataLen] = '\0'; + + aBinaryData.SetLength(binaryDataLen); + return NS_OK; + } + + aBinaryData.Truncate(); + return NS_ERROR_INVALID_ARG; +} + +nsresult +Base64Decode(const nsAString &aBinaryData, nsAString &aString) +{ + NS_LossyConvertUTF16toASCII binaryData(aBinaryData); + nsAutoCString string; + + nsresult rv = Base64Decode(binaryData, string); + if (NS_SUCCEEDED(rv)) { + CopyASCIItoUTF16(string, aString); + } else { + aString.Truncate(); + } + + return rv; +} + +} // namespace mozilla diff --git a/xpcom/io/Base64.h b/xpcom/io/Base64.h new file mode 100644 index 000000000..39fe0766a --- /dev/null +++ b/xpcom/io/Base64.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_Base64_h__ +#define mozilla_Base64_h__ + +#include "nsString.h" + +class nsIInputStream; + +namespace mozilla { + +nsresult +Base64EncodeInputStream(nsIInputStream *aInputStream, + nsACString &aDest, + uint32_t aCount, + uint32_t aOffset = 0); +nsresult +Base64EncodeInputStream(nsIInputStream *aInputStream, + nsAString &aDest, + uint32_t aCount, + uint32_t aOffset = 0); + +nsresult +Base64Encode(const nsACString &aString, nsACString &aBinary); +nsresult +Base64Encode(const nsAString &aString, nsAString &aBinaryData); + +nsresult +Base64Decode(const nsACString &aBinaryData, nsACString &aString); +nsresult +Base64Decode(const nsAString &aBinaryData, nsAString &aString); + +} // namespace mozilla + +#endif diff --git a/xpcom/io/CocoaFileUtils.h b/xpcom/io/CocoaFileUtils.h new file mode 100644 index 000000000..e007b5875 --- /dev/null +++ b/xpcom/io/CocoaFileUtils.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* 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/. */ + +// This namespace contains methods with Obj-C/Cocoa implementations. The header +// is C/C++ for inclusion in C/C++-only files. + +#ifndef CocoaFileUtils_h_ +#define CocoaFileUtils_h_ + +#include "nscore.h" +#include <CoreFoundation/CoreFoundation.h> + +namespace CocoaFileUtils { + +nsresult RevealFileInFinder(CFURLRef url); +nsresult OpenURL(CFURLRef url); +nsresult GetFileCreatorCode(CFURLRef url, OSType *creatorCode); +nsresult SetFileCreatorCode(CFURLRef url, OSType creatorCode); +nsresult GetFileTypeCode(CFURLRef url, OSType *typeCode); +nsresult SetFileTypeCode(CFURLRef url, OSType typeCode); + +} // namespace CocoaFileUtils + +#endif diff --git a/xpcom/io/CocoaFileUtils.mm b/xpcom/io/CocoaFileUtils.mm new file mode 100644 index 000000000..793183655 --- /dev/null +++ b/xpcom/io/CocoaFileUtils.mm @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:set ts=2 sts=2 sw=2 et cin: +/* 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 "CocoaFileUtils.h" +#include <Cocoa/Cocoa.h> +#include "nsObjCExceptions.h" +#include "nsDebug.h" + +namespace CocoaFileUtils { + +nsresult RevealFileInFinder(CFURLRef url) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + NS_ENSURE_ARG_POINTER(url); + + NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; + BOOL success = [[NSWorkspace sharedWorkspace] selectFile:[(NSURL*)url path] inFileViewerRootedAtPath:@""]; + [ap release]; + + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult OpenURL(CFURLRef url) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + NS_ENSURE_ARG_POINTER(url); + + NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; + BOOL success = [[NSWorkspace sharedWorkspace] openURL:(NSURL*)url]; + [ap release]; + + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult GetFileCreatorCode(CFURLRef url, OSType *creatorCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + NS_ENSURE_ARG_POINTER(url); + NS_ENSURE_ARG_POINTER(creatorCode); + + nsresult rv = NS_ERROR_FAILURE; + + NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; + NSDictionary* dict = [[NSFileManager defaultManager] fileAttributesAtPath:[(NSURL*)url path] traverseLink:YES]; + NSNumber* creatorNum = (NSNumber*)[dict objectForKey:NSFileHFSCreatorCode]; + if (creatorNum) { + *creatorCode = [creatorNum unsignedLongValue]; + rv = NS_OK; + } + [ap release]; + + return rv; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult SetFileCreatorCode(CFURLRef url, OSType creatorCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + NS_ENSURE_ARG_POINTER(url); + + NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; + NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:creatorCode] forKey:NSFileHFSCreatorCode]; + BOOL success = [[NSFileManager defaultManager] setAttributes:dict ofItemAtPath:[(NSURL*)url path] error:nil]; + [ap release]; + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult GetFileTypeCode(CFURLRef url, OSType *typeCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + NS_ENSURE_ARG_POINTER(url); + NS_ENSURE_ARG_POINTER(typeCode); + + nsresult rv = NS_ERROR_FAILURE; + + NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; + NSDictionary* dict = [[NSFileManager defaultManager] fileAttributesAtPath:[(NSURL*)url path] traverseLink:YES]; + NSNumber* typeNum = (NSNumber*)[dict objectForKey:NSFileHFSTypeCode]; + if (typeNum) { + *typeCode = [typeNum unsignedLongValue]; + rv = NS_OK; + } + [ap release]; + + return rv; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult SetFileTypeCode(CFURLRef url, OSType typeCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + NS_ENSURE_ARG_POINTER(url); + + NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init]; + NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:typeCode] forKey:NSFileHFSTypeCode]; + BOOL success = [[NSFileManager defaultManager] setAttributes:dict ofItemAtPath:[(NSURL*)url path] error:nil]; + [ap release]; + return (success ? NS_OK : NS_ERROR_FAILURE); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +} // namespace CocoaFileUtils diff --git a/xpcom/io/Makefile.in b/xpcom/io/Makefile.in new file mode 100644 index 000000000..e9a965bdd --- /dev/null +++ b/xpcom/io/Makefile.in @@ -0,0 +1,62 @@ +# +# 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/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +LIBRARY_NAME = xpcomio_s +MSVC_ENABLE_PGO := 1 +MOZILLA_INTERNAL_API = 1 +LIBXUL_LIBRARY = 1 + +# work around bug 408258 +ifdef GNU_CC +ifneq ($(OS_ARCH), Darwin) +MODULE_OPTIMIZE_FLAGS = $(MOZ_OPTIMIZE_FLAGS) -fno-strict-aliasing +endif +endif + +ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) +DISABLED_CMMSRCS += \ + CocoaFileUtils.mm \ + $(NULL) +endif + +SDK_HEADERS = \ + nsDirectoryServiceDefs.h \ + nsDirectoryServiceUtils.h \ + $(NULL) + +# we don't want the shared lib, but we want to force the creation of a static lib. +FORCE_STATIC_LIB = 1 + + +include $(topsrcdir)/config/rules.mk +include $(topsrcdir)/ipc/chromium/chromium-config.mk + +DEFINES += -D_IMPL_NS_COM + +ifeq ($(OS_ARCH),Linux) +ifneq (,$(findstring lib64,$(libdir))) +DEFINES += -DHAVE_USR_LIB64_DIR +endif +endif + +LOCAL_INCLUDES += -I.. + +ifeq ($(MOZ_PLATFORM_MAEMO),5) +CFLAGS += $(MOZ_DBUS_CFLAGS) +CXXFLAGS += $(MOZ_DBUS_CFLAGS) +endif + +ifdef MOZ_PLATFORM_MAEMO +CFLAGS += $(MOZ_PLATFORM_MAEMO_CFLAGS) $(MOZ_QT_CFLAGS) +CXXFLAGS += $(MOZ_PLATFORM_MAEMO_CFLAGS) $(MOZ_QT_CFLAGS) +endif + diff --git a/xpcom/io/SpecialSystemDirectory.cpp b/xpcom/io/SpecialSystemDirectory.cpp new file mode 100644 index 000000000..38834a69f --- /dev/null +++ b/xpcom/io/SpecialSystemDirectory.cpp @@ -0,0 +1,958 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "SpecialSystemDirectory.h" +#include "nsString.h" +#include "nsDependentString.h" +#include "nsAutoPtr.h" + +#if defined(XP_WIN) + +#include <windows.h> +#include <shlobj.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <direct.h> +#include <shlobj.h> +#include <knownfolders.h> +#include <guiddef.h> + +#elif defined(XP_OS2) + +#define MAX_PATH _MAX_PATH +#define INCL_WINWORKPLACE +#define INCL_DOSMISC +#define INCL_DOSMODULEMGR +#define INCL_DOSPROCESS +#define INCL_WINSHELLDATA +#include <os2.h> +#include <stdlib.h> +#include <stdio.h> +#include "prenv.h" + +#elif defined(XP_UNIX) + +#include <limits.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/param.h> +#include "prenv.h" + +#endif + +#if defined(VMS) +#include <unixlib.h> +#endif + +#ifndef MAXPATHLEN +#ifdef PATH_MAX +#define MAXPATHLEN PATH_MAX +#elif defined(MAX_PATH) +#define MAXPATHLEN MAX_PATH +#elif defined(_MAX_PATH) +#define MAXPATHLEN _MAX_PATH +#elif defined(CCHMAXPATH) +#define MAXPATHLEN CCHMAXPATH +#else +#define MAXPATHLEN 1024 +#endif +#endif + +#ifdef XP_WIN +typedef HRESULT (WINAPI* nsGetKnownFolderPath)(GUID& rfid, + DWORD dwFlags, + HANDLE hToken, + PWSTR *ppszPath); + +static nsGetKnownFolderPath gGetKnownFolderPath = NULL; +#endif + +void StartupSpecialSystemDirectory() +{ +#if defined (XP_WIN) + // SHGetKnownFolderPath is only available on Windows Vista + // so that we need to use GetProcAddress to get the pointer. + HMODULE hShell32DLLInst = GetModuleHandleW(L"shell32.dll"); + if(hShell32DLLInst) + { + gGetKnownFolderPath = (nsGetKnownFolderPath) + GetProcAddress(hShell32DLLInst, "SHGetKnownFolderPath"); + } +#endif +} + +#if defined (XP_WIN) + +static nsresult GetKnownFolder(GUID* guid, nsIFile** aFile) +{ + if (!guid || !gGetKnownFolderPath) + return NS_ERROR_FAILURE; + + PWSTR path = NULL; + gGetKnownFolderPath(*guid, 0, NULL, &path); + + if (!path) + return NS_ERROR_FAILURE; + + nsresult rv = NS_NewLocalFile(nsDependentString(path), + true, + aFile); + + CoTaskMemFree(path); + return rv; +} + +static nsresult +GetWindowsFolder(int folder, nsIFile** aFile) +{ + WCHAR path_orig[MAX_PATH + 3]; + WCHAR *path = path_orig+1; + HRESULT result = SHGetSpecialFolderPathW(NULL, path, folder, true); + + if (!SUCCEEDED(result)) + return NS_ERROR_FAILURE; + + // Append the trailing slash + int len = wcslen(path); + if (len > 1 && path[len - 1] != L'\\') + { + path[len] = L'\\'; + path[++len] = L'\0'; + } + + return NS_NewLocalFile(nsDependentString(path, len), true, aFile); +} + +__inline HRESULT +SHLoadLibraryFromKnownFolder(REFKNOWNFOLDERID aFolderId, DWORD aMode, + REFIID riid, void **ppv) +{ + *ppv = NULL; + IShellLibrary *plib; + HRESULT hr = CoCreateInstance(CLSID_ShellLibrary, NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&plib)); + if (SUCCEEDED(hr)) { + hr = plib->LoadLibraryFromKnownFolder(aFolderId, aMode); + if (SUCCEEDED(hr)) { + hr = plib->QueryInterface(riid, ppv); + } + plib->Release(); + } + return hr; +} + +/* + * Check to see if we're on Win7 and up, and if so, returns the default + * save-to location for the Windows Library passed in through aFolderId. + * Otherwise falls back on pre-win7 GetWindowsFolder. + */ +static nsresult +GetLibrarySaveToPath(int aFallbackFolderId, REFKNOWNFOLDERID aFolderId, + nsIFile** aFile) +{ + // Skip off checking for library support if the os is Vista or lower. + DWORD dwVersion = GetVersion(); + if ((DWORD)(LOBYTE(LOWORD(dwVersion))) < 6 || + ((DWORD)(LOBYTE(LOWORD(dwVersion))) == 6 && + (DWORD)(HIBYTE(LOWORD(dwVersion))) == 0)) + return GetWindowsFolder(aFallbackFolderId, aFile); + + nsRefPtr<IShellLibrary> shellLib; + nsRefPtr<IShellItem> savePath; + HRESULT hr = + SHLoadLibraryFromKnownFolder(aFolderId, STGM_READ, + IID_IShellLibrary, getter_AddRefs(shellLib)); + + if (shellLib && + SUCCEEDED(shellLib->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem, + getter_AddRefs(savePath)))) { + PRUnichar* str = nullptr; + if (SUCCEEDED(savePath->GetDisplayName(SIGDN_FILESYSPATH, &str))) { + nsAutoString path; + path.Assign(str); + path.AppendLiteral("\\"); + nsresult rv = + NS_NewLocalFile(path, false, aFile); + CoTaskMemFree(str); + return rv; + } + } + + return GetWindowsFolder(aFallbackFolderId, aFile); +} + +/** + * Provides a fallback for getting the path to APPDATA or LOCALAPPDATA by + * querying the registry when the call to SHGetSpecialFolderPathW is unable to + * provide these paths (Bug 513958). + */ +static nsresult GetRegWindowsAppDataFolder(bool aLocal, nsIFile** aFile) +{ + HKEY key; + NS_NAMED_LITERAL_STRING(keyName, + "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"); + DWORD res = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName.get(), 0, KEY_READ, + &key); + if (res != ERROR_SUCCESS) + return NS_ERROR_FAILURE; + + WCHAR path[MAX_PATH + 2]; + DWORD type, size; + res = RegQueryValueExW(key, (aLocal ? L"Local AppData" : L"AppData"), NULL, + &type, (LPBYTE)&path, &size); + ::RegCloseKey(key); + // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the + // buffer size must not equal 0, and the buffer size be a multiple of 2. + if (res != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0) + return NS_ERROR_FAILURE; + + // Append the trailing slash + int len = wcslen(path); + if (len > 1 && path[len - 1] != L'\\') + { + path[len] = L'\\'; + path[++len] = L'\0'; + } + + return NS_NewLocalFile(nsDependentString(path, len), true, aFile); +} + +#endif // XP_WIN + +#if defined(XP_UNIX) +static nsresult +GetUnixHomeDir(nsIFile** aFile) +{ +#ifdef VMS + char *pHome; + pHome = getenv("HOME"); + if (*pHome == '/') { + return NS_NewNativeLocalFile(nsDependentCString(pHome), + true, + aFile); + } else { + return NS_NewNativeLocalFile(nsDependentCString(decc$translate_vms(pHome)), + true, + aFile); + } +#elif defined(ANDROID) + // XXX no home dir on android; maybe we should return the sdcard if present? + return NS_ERROR_FAILURE; +#else + return NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")), + true, aFile); +#endif +} + +/* + The following license applies to the xdg_user_dir_lookup function: + + Copyright (c) 2007 Red Hat, Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +static char * +xdg_user_dir_lookup (const char *type) +{ + FILE *file; + char *home_dir, *config_home, *config_file; + char buffer[512]; + char *user_dir; + char *p, *d; + int len; + int relative; + + home_dir = getenv ("HOME"); + + if (home_dir == NULL) + goto error; + + config_home = getenv ("XDG_CONFIG_HOME"); + if (config_home == NULL || config_home[0] == 0) + { + config_file = (char*) malloc (strlen (home_dir) + strlen ("/.config/user-dirs.dirs") + 1); + if (config_file == NULL) + goto error; + + strcpy (config_file, home_dir); + strcat (config_file, "/.config/user-dirs.dirs"); + } + else + { + config_file = (char*) malloc (strlen (config_home) + strlen ("/user-dirs.dirs") + 1); + if (config_file == NULL) + goto error; + + strcpy (config_file, config_home); + strcat (config_file, "/user-dirs.dirs"); + } + + file = fopen (config_file, "r"); + free (config_file); + if (file == NULL) + goto error; + + user_dir = NULL; + while (fgets (buffer, sizeof (buffer), file)) + { + /* Remove newline at end */ + len = strlen (buffer); + if (len > 0 && buffer[len-1] == '\n') + buffer[len-1] = 0; + + p = buffer; + while (*p == ' ' || *p == '\t') + p++; + + if (strncmp (p, "XDG_", 4) != 0) + continue; + p += 4; + if (strncmp (p, type, strlen (type)) != 0) + continue; + p += strlen (type); + if (strncmp (p, "_DIR", 4) != 0) + continue; + p += 4; + + while (*p == ' ' || *p == '\t') + p++; + + if (*p != '=') + continue; + p++; + + while (*p == ' ' || *p == '\t') + p++; + + if (*p != '"') + continue; + p++; + + relative = 0; + if (strncmp (p, "$HOME/", 6) == 0) + { + p += 6; + relative = 1; + } + else if (*p != '/') + continue; + + if (relative) + { + user_dir = (char*) malloc (strlen (home_dir) + 1 + strlen (p) + 1); + if (user_dir == NULL) + goto error2; + + strcpy (user_dir, home_dir); + strcat (user_dir, "/"); + } + else + { + user_dir = (char*) malloc (strlen (p) + 1); + if (user_dir == NULL) + goto error2; + + *user_dir = 0; + } + + d = user_dir + strlen (user_dir); + while (*p && *p != '"') + { + if ((*p == '\\') && (*(p+1) != 0)) + p++; + *d++ = *p++; + } + *d = 0; + } +error2: + fclose (file); + + if (user_dir) + return user_dir; + + error: + return NULL; +} + +static const char xdg_user_dirs[] = + "DESKTOP\0" + "DOCUMENTS\0" + "DOWNLOAD\0" + "MUSIC\0" + "PICTURES\0" + "PUBLICSHARE\0" + "TEMPLATES\0" + "VIDEOS"; + +static const uint8_t xdg_user_dir_offsets[] = { + 0, + 8, + 18, + 27, + 33, + 42, + 54, + 64 +}; + +static nsresult +GetUnixXDGUserDirectory(SystemDirectories aSystemDirectory, + nsIFile** aFile) +{ + char *dir = xdg_user_dir_lookup + (xdg_user_dirs + xdg_user_dir_offsets[aSystemDirectory - + Unix_XDG_Desktop]); + + nsresult rv; + nsCOMPtr<nsIFile> file; + if (dir) { + rv = NS_NewNativeLocalFile(nsDependentCString(dir), true, + getter_AddRefs(file)); + free(dir); + } else if (Unix_XDG_Desktop == aSystemDirectory) { + // for the XDG desktop dir, fall back to HOME/Desktop + // (for historical compatibility) + rv = GetUnixHomeDir(getter_AddRefs(file)); + if (NS_FAILED(rv)) + return rv; + + rv = file->AppendNative(NS_LITERAL_CSTRING("Desktop")); + } +#if defined(MOZ_PLATFORM_MAEMO) + // "MYDOCSDIR" is exported to point to "/home/user/MyDocs" in maemo. + else if (Unix_XDG_Documents == aSystemDirectory) { + + char *myDocs = PR_GetEnv("MYDOCSDIR"); + if (!myDocs || !*myDocs) + return NS_ERROR_FAILURE; + + rv = NS_NewNativeLocalFile(nsDependentCString(myDocs), true, + getter_AddRefs(file)); + if (NS_FAILED(rv)) + return rv; + + rv = file->AppendNative(NS_LITERAL_CSTRING(".documents")); + } +#endif + else { + // no fallback for the other XDG dirs + rv = NS_ERROR_FAILURE; + } + + if (NS_FAILED(rv)) + return rv; + + bool exists; + rv = file->Exists(&exists); + if (NS_FAILED(rv)) + return rv; + if (!exists) { + rv = file->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (NS_FAILED(rv)) + return rv; + } + + *aFile = nullptr; + file.swap(*aFile); + + return NS_OK; +} +#endif + +nsresult +GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory, + nsIFile** aFile) +{ +#if defined(XP_WIN) + WCHAR path[MAX_PATH]; +#else + char path[MAXPATHLEN]; +#endif + + switch (aSystemSystemDirectory) + { + case OS_CurrentWorkingDirectory: +#if defined(XP_WIN) + if (!_wgetcwd(path, MAX_PATH)) + return NS_ERROR_FAILURE; + return NS_NewLocalFile(nsDependentString(path), + true, + aFile); +#elif defined(XP_OS2) + if (DosQueryPathInfo( ".", FIL_QUERYFULLNAME, path, MAXPATHLEN)) + return NS_ERROR_FAILURE; +#else + if(!getcwd(path, MAXPATHLEN)) + return NS_ERROR_FAILURE; +#endif + +#if !defined(XP_WIN) + return NS_NewNativeLocalFile(nsDependentCString(path), + true, + aFile); +#endif + + case OS_DriveDirectory: +#if defined (XP_WIN) + { + int32_t len = ::GetWindowsDirectoryW(path, MAX_PATH); + if (len == 0) + break; + if (path[1] == PRUnichar(':') && path[2] == PRUnichar('\\')) + path[3] = 0; + + return NS_NewLocalFile(nsDependentString(path), + true, + aFile); + } +#elif defined(XP_OS2) + { + ULONG ulBootDrive = 0; + char buffer[] = " :\\OS2\\"; + DosQuerySysInfo( QSV_BOOT_DRIVE, QSV_BOOT_DRIVE, + &ulBootDrive, sizeof ulBootDrive); + buffer[0] = 'A' - 1 + ulBootDrive; // duh, 1-based index... + + return NS_NewNativeLocalFile(nsDependentCString(buffer), + true, + aFile); + } +#else + return NS_NewNativeLocalFile(nsDependentCString("/"), + true, + aFile); + +#endif + + case OS_TemporaryDirectory: +#if defined (XP_WIN) + { + DWORD len = ::GetTempPathW(MAX_PATH, path); + if (len == 0) + break; + return NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + } +#elif defined(XP_OS2) + { + char *tPath = PR_GetEnv("TMP"); + if (!tPath || !*tPath) { + tPath = PR_GetEnv("TEMP"); + if (!tPath || !*tPath) { + // if an OS/2 system has neither TMP nor TEMP defined + // then it is severely broken, so this will never happen. + return NS_ERROR_UNEXPECTED; + } + } + nsCString tString = nsDependentCString(tPath); + if (tString.Find("/", false, 0, -1)) { + tString.ReplaceChar('/','\\'); + } + return NS_NewNativeLocalFile(tString, true, aFile); + } +#elif defined(MOZ_WIDGET_COCOA) + { + return GetOSXFolderType(kUserDomain, kTemporaryFolderType, aFile); + } + +#elif defined(XP_UNIX) + { + static const char *tPath = nullptr; + if (!tPath) { + tPath = PR_GetEnv("TMPDIR"); + if (!tPath || !*tPath) { + tPath = PR_GetEnv("TMP"); + if (!tPath || !*tPath) { + tPath = PR_GetEnv("TEMP"); + if (!tPath || !*tPath) { + tPath = "/tmp/"; + } + } + } + } + return NS_NewNativeLocalFile(nsDependentCString(tPath), + true, + aFile); + } +#else + break; +#endif +#if defined (XP_WIN) + case Win_SystemDirectory: + { + int32_t len = ::GetSystemDirectoryW(path, MAX_PATH); + + // Need enough space to add the trailing backslash + if (!len || len > MAX_PATH - 2) + break; + path[len] = L'\\'; + path[++len] = L'\0'; + + return NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + } + + case Win_WindowsDirectory: + { + int32_t len = ::GetWindowsDirectoryW(path, MAX_PATH); + + // Need enough space to add the trailing backslash + if (!len || len > MAX_PATH - 2) + break; + + path[len] = L'\\'; + path[++len] = L'\0'; + + return NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + } + + case Win_ProgramFiles: + { + return GetWindowsFolder(CSIDL_PROGRAM_FILES, aFile); + } + + case Win_HomeDirectory: + { + nsresult rv = GetWindowsFolder(CSIDL_PROFILE, aFile); + if (NS_SUCCEEDED(rv)) + return rv; + + int32_t len; + if ((len = ::GetEnvironmentVariableW(L"HOME", path, MAX_PATH)) > 0) + { + // Need enough space to add the trailing backslash + if (len > MAX_PATH - 2) + break; + + path[len] = L'\\'; + path[++len] = L'\0'; + + rv = NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + if (NS_SUCCEEDED(rv)) + return rv; + } + + len = ::GetEnvironmentVariableW(L"HOMEDRIVE", path, MAX_PATH); + if (0 < len && len < MAX_PATH) + { + WCHAR temp[MAX_PATH]; + DWORD len2 = ::GetEnvironmentVariableW(L"HOMEPATH", temp, MAX_PATH); + if (0 < len2 && len + len2 < MAX_PATH) + wcsncat(path, temp, len2); + + len = wcslen(path); + + // Need enough space to add the trailing backslash + if (len > MAX_PATH - 2) + break; + + path[len] = L'\\'; + path[++len] = L'\0'; + + return NS_NewLocalFile(nsDependentString(path, len), + true, + aFile); + } + } + case Win_Desktop: + { + return GetWindowsFolder(CSIDL_DESKTOP, aFile); + } + case Win_Programs: + { + return GetWindowsFolder(CSIDL_PROGRAMS, aFile); + } + + case Win_Downloads: + { + // Defined in KnownFolders.h. + GUID folderid_downloads = {0x374de290, 0x123f, 0x4565, {0x91, 0x64, + 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b}}; + nsresult rv = GetKnownFolder(&folderid_downloads, aFile); + // On WinXP, there is no downloads folder, default + // to 'Desktop'. + if(NS_ERROR_FAILURE == rv) + { + rv = GetWindowsFolder(CSIDL_DESKTOP, aFile); + } + return rv; + } + + case Win_Controls: + { + return GetWindowsFolder(CSIDL_CONTROLS, aFile); + } + case Win_Printers: + { + return GetWindowsFolder(CSIDL_PRINTERS, aFile); + } + case Win_Personal: + { + return GetWindowsFolder(CSIDL_PERSONAL, aFile); + } + case Win_Favorites: + { + return GetWindowsFolder(CSIDL_FAVORITES, aFile); + } + case Win_Startup: + { + return GetWindowsFolder(CSIDL_STARTUP, aFile); + } + case Win_Recent: + { + return GetWindowsFolder(CSIDL_RECENT, aFile); + } + case Win_Sendto: + { + return GetWindowsFolder(CSIDL_SENDTO, aFile); + } + case Win_Bitbucket: + { + return GetWindowsFolder(CSIDL_BITBUCKET, aFile); + } + case Win_Startmenu: + { + return GetWindowsFolder(CSIDL_STARTMENU, aFile); + } + case Win_Desktopdirectory: + { + return GetWindowsFolder(CSIDL_DESKTOPDIRECTORY, aFile); + } + case Win_Drives: + { + return GetWindowsFolder(CSIDL_DRIVES, aFile); + } + case Win_Network: + { + return GetWindowsFolder(CSIDL_NETWORK, aFile); + } + case Win_Nethood: + { + return GetWindowsFolder(CSIDL_NETHOOD, aFile); + } + case Win_Fonts: + { + return GetWindowsFolder(CSIDL_FONTS, aFile); + } + case Win_Templates: + { + return GetWindowsFolder(CSIDL_TEMPLATES, aFile); + } + case Win_Common_Startmenu: + { + return GetWindowsFolder(CSIDL_COMMON_STARTMENU, aFile); + } + case Win_Common_Programs: + { + return GetWindowsFolder(CSIDL_COMMON_PROGRAMS, aFile); + } + case Win_Common_Startup: + { + return GetWindowsFolder(CSIDL_COMMON_STARTUP, aFile); + } + case Win_Common_Desktopdirectory: + { + return GetWindowsFolder(CSIDL_COMMON_DESKTOPDIRECTORY, aFile); + } + case Win_Common_AppData: + { + return GetWindowsFolder(CSIDL_COMMON_APPDATA, aFile); + } + case Win_Printhood: + { + return GetWindowsFolder(CSIDL_PRINTHOOD, aFile); + } + case Win_Cookies: + { + return GetWindowsFolder(CSIDL_COOKIES, aFile); + } + case Win_Appdata: + { + nsresult rv = GetWindowsFolder(CSIDL_APPDATA, aFile); + if (NS_FAILED(rv)) + rv = GetRegWindowsAppDataFolder(false, aFile); + return rv; + } + case Win_LocalAppdata: + { + nsresult rv = GetWindowsFolder(CSIDL_LOCAL_APPDATA, aFile); + if (NS_FAILED(rv)) + rv = GetRegWindowsAppDataFolder(true, aFile); + return rv; + } + case Win_Documents: + { + return GetLibrarySaveToPath(CSIDL_MYDOCUMENTS, + FOLDERID_DocumentsLibrary, + aFile); + } + case Win_Pictures: + { + return GetLibrarySaveToPath(CSIDL_MYPICTURES, + FOLDERID_PicturesLibrary, + aFile); + } + case Win_Music: + { + return GetLibrarySaveToPath(CSIDL_MYMUSIC, + FOLDERID_MusicLibrary, + aFile); + } + case Win_Videos: + { + return GetLibrarySaveToPath(CSIDL_MYVIDEO, + FOLDERID_VideosLibrary, + aFile); + } +#endif // XP_WIN + +#if defined(XP_UNIX) + case Unix_LocalDirectory: + return NS_NewNativeLocalFile(nsDependentCString("/usr/local/netscape/"), + true, + aFile); + case Unix_LibDirectory: + return NS_NewNativeLocalFile(nsDependentCString("/usr/local/lib/netscape/"), + true, + aFile); + + case Unix_HomeDirectory: + return GetUnixHomeDir(aFile); + + case Unix_XDG_Desktop: + case Unix_XDG_Documents: + case Unix_XDG_Download: + case Unix_XDG_Music: + case Unix_XDG_Pictures: + case Unix_XDG_PublicShare: + case Unix_XDG_Templates: + case Unix_XDG_Videos: + return GetUnixXDGUserDirectory(aSystemSystemDirectory, aFile); +#endif + +#ifdef XP_OS2 + case OS2_SystemDirectory: + { + ULONG ulBootDrive = 0; + char buffer[] = " :\\OS2\\System\\"; + DosQuerySysInfo( QSV_BOOT_DRIVE, QSV_BOOT_DRIVE, + &ulBootDrive, sizeof ulBootDrive); + buffer[0] = 'A' - 1 + ulBootDrive; // duh, 1-based index... + + return NS_NewNativeLocalFile(nsDependentCString(buffer), + true, + aFile); + } + + case OS2_OS2Directory: + { + ULONG ulBootDrive = 0; + char buffer[] = " :\\OS2\\"; + DosQuerySysInfo( QSV_BOOT_DRIVE, QSV_BOOT_DRIVE, + &ulBootDrive, sizeof ulBootDrive); + buffer[0] = 'A' - 1 + ulBootDrive; // duh, 1-based index... + + return NS_NewNativeLocalFile(nsDependentCString(buffer), + true, + aFile); + } + + case OS2_HomeDirectory: + { + nsresult rv; + char *tPath = PR_GetEnv("MOZILLA_HOME"); + char buffer[CCHMAXPATH]; + /* If MOZILLA_HOME is not set, use GetCurrentProcessDirectory */ + /* To ensure we get a long filename system */ + if (!tPath || !*tPath) { + PPIB ppib; + PTIB ptib; + DosGetInfoBlocks( &ptib, &ppib); + DosQueryModuleName( ppib->pib_hmte, CCHMAXPATH, buffer); + *strrchr( buffer, '\\') = '\0'; // XXX DBCS misery + tPath = buffer; + } + rv = NS_NewNativeLocalFile(nsDependentCString(tPath), + true, + aFile); + + PrfWriteProfileString(HINI_USERPROFILE, "Mozilla", "Home", tPath); + return rv; + } + + case OS2_DesktopDirectory: + { + char szPath[CCHMAXPATH + 1]; + BOOL fSuccess; + fSuccess = WinQueryActiveDesktopPathname (szPath, sizeof(szPath)); + if (!fSuccess) { + // this could happen if we are running without the WPS, return + // the Home directory instead + return GetSpecialSystemDirectory(OS2_HomeDirectory, aFile); + } + int len = strlen (szPath); + if (len > CCHMAXPATH -1) + break; + szPath[len] = '\\'; + szPath[len + 1] = '\0'; + + return NS_NewNativeLocalFile(nsDependentCString(szPath), + true, + aFile); + } +#endif + default: + break; + } + return NS_ERROR_NOT_AVAILABLE; +} + +#if defined (MOZ_WIDGET_COCOA) +nsresult +GetOSXFolderType(short aDomain, OSType aFolderType, nsIFile **localFile) +{ + OSErr err; + FSRef fsRef; + nsresult rv = NS_ERROR_FAILURE; + + err = ::FSFindFolder(aDomain, aFolderType, kCreateFolder, &fsRef); + if (err == noErr) + { + NS_NewLocalFile(EmptyString(), true, localFile); + nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*localFile)); + if (localMacFile) + rv = localMacFile->InitWithFSRef(&fsRef); + } + return rv; +} +#endif + diff --git a/xpcom/io/SpecialSystemDirectory.h b/xpcom/io/SpecialSystemDirectory.h new file mode 100644 index 000000000..c78d4b233 --- /dev/null +++ b/xpcom/io/SpecialSystemDirectory.h @@ -0,0 +1,108 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef _SPECIALSYSTEMDIRECTORY_H_ +#define _SPECIALSYSTEMDIRECTORY_H_ + +#include "nscore.h" +#include "nsIFile.h" + +#ifdef MOZ_WIDGET_COCOA +#include <Carbon/Carbon.h> +#include "nsILocalFileMac.h" +#include "prenv.h" +#endif + +extern void StartupSpecialSystemDirectory(); + + +enum SystemDirectories { + OS_DriveDirectory = 1, + OS_TemporaryDirectory = 2, + OS_CurrentProcessDirectory= 3, + OS_CurrentWorkingDirectory= 4, + XPCOM_CurrentProcessComponentDirectory= 5, + XPCOM_CurrentProcessComponentRegistry= 6, + + Moz_BinDirectory = 100 , + Mac_SystemDirectory = 101, + Mac_DesktopDirectory = 102, + Mac_TrashDirectory = 103, + Mac_StartupDirectory = 104, + Mac_ShutdownDirectory = 105, + Mac_AppleMenuDirectory = 106, + Mac_ControlPanelDirectory = 107, + Mac_ExtensionDirectory = 108, + Mac_FontsDirectory = 109, + Mac_ClassicPreferencesDirectory = 110, + Mac_DocumentsDirectory = 111, + Mac_InternetSearchDirectory = 112, + Mac_DefaultDownloadDirectory = 113, + Mac_UserLibDirectory = 114, + Mac_PreferencesDirectory = 115, + + Win_SystemDirectory = 201, + Win_WindowsDirectory = 202, + Win_HomeDirectory = 203, + Win_Desktop = 204, + Win_Programs = 205, + Win_Controls = 206, + Win_Printers = 207, + Win_Personal = 208, + Win_Favorites = 209, + Win_Startup = 210, + Win_Recent = 211, + Win_Sendto = 212, + Win_Bitbucket = 213, + Win_Startmenu = 214, + Win_Desktopdirectory = 215, + Win_Drives = 216, + Win_Network = 217, + Win_Nethood = 218, + Win_Fonts = 219, + Win_Templates = 220, + Win_Common_Startmenu = 221, + Win_Common_Programs = 222, + Win_Common_Startup = 223, + Win_Common_Desktopdirectory = 224, + Win_Appdata = 225, + Win_Printhood = 226, + Win_Cookies = 227, + Win_LocalAppdata = 228, + Win_ProgramFiles = 229, + Win_Downloads = 230, + Win_Common_AppData = 231, + Win_Documents = 232, + Win_Pictures = 233, + Win_Music = 234, + Win_Videos = 235, + + Unix_LocalDirectory = 301, + Unix_LibDirectory = 302, + Unix_HomeDirectory = 303, + Unix_XDG_Desktop = 304, + Unix_XDG_Documents = 305, + Unix_XDG_Download = 306, + Unix_XDG_Music = 307, + Unix_XDG_Pictures = 308, + Unix_XDG_PublicShare = 309, + Unix_XDG_Templates = 310, + Unix_XDG_Videos = 311, + + OS2_SystemDirectory = 401, + OS2_OS2Directory = 402, + OS2_DesktopDirectory = 403, + OS2_HomeDirectory = 404 +}; + +nsresult +GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory, + nsIFile** aFile); +#ifdef MOZ_WIDGET_COCOA +nsresult +GetOSXFolderType(short aDomain, OSType aFolderType, nsIFile **localFile); +#endif + +#endif diff --git a/xpcom/io/moz.build b/xpcom/io/moz.build new file mode 100644 index 000000000..b17bca770 --- /dev/null +++ b/xpcom/io/moz.build @@ -0,0 +1,122 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPIDL_SOURCES += [ + 'nsIAsyncInputStream.idl', + 'nsIAsyncOutputStream.idl', + 'nsIBinaryInputStream.idl', + 'nsIBinaryOutputStream.idl', + 'nsIConverterInputStream.idl', + 'nsIConverterOutputStream.idl', + 'nsIDirectoryEnumerator.idl', + 'nsIDirectoryService.idl', + 'nsIFile.idl', + 'nsIIOUtil.idl', + 'nsIInputStream.idl', + 'nsIInputStreamTee.idl', + 'nsILineInputStream.idl', + 'nsILocalFile.idl', + 'nsILocalFileWin.idl', + 'nsIMultiplexInputStream.idl', + 'nsIObjectInputStream.idl', + 'nsIObjectOutputStream.idl', + 'nsIOutputStream.idl', + 'nsIPipe.idl', + 'nsISafeOutputStream.idl', + 'nsIScriptableBase64Encoder.idl', + 'nsIScriptableInputStream.idl', + 'nsISeekableStream.idl', + 'nsISimpleUnicharStreamFactory.idl', + 'nsIStorageStream.idl', + 'nsIStreamBufferAccess.idl', + 'nsIStringStream.idl', + 'nsIUnicharInputStream.idl', + 'nsIUnicharLineInputStream.idl', + 'nsIUnicharOutputStream.idl', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'os2': + XPIDL_SOURCES += [ + 'nsILocalFileOS2.idl', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + XPIDL_SOURCES += [ + 'nsILocalFileMac.idl', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'os2': + EXPORTS += ['nsLocalFileOS2.h'] + CPP_SOURCES += [ + 'nsLocalFileOS2.cpp', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + EXPORTS += ['nsLocalFileWin.h'] + CPP_SOURCES += [ + 'nsLocalFileWin.cpp', + ] +else: + EXPORTS += ['nsLocalFileUnix.h'] + CPP_SOURCES += [ + 'nsLocalFileUnix.cpp', + ] + +XPIDL_MODULE = 'xpcom_io' + +MODULE = 'xpcom' + +EXPORTS += [ + 'SpecialSystemDirectory.h', + 'nsAnonymousTemporaryFile.h', + 'nsAppDirectoryServiceDefs.h', + 'nsDirectoryService.h', + 'nsDirectoryServiceAtomList.h', + 'nsEscape.h', + 'nsLinebreakConverter.h', + 'nsLocalFile.h', + 'nsMultiplexInputStream.h', + 'nsNativeCharsetUtils.h', + 'nsScriptableInputStream.h', + 'nsStorageStream.h', + 'nsStreamUtils.h', + 'nsStringStream.h', + 'nsUnicharInputStream.h', + 'nsWildCard.h', +] + +EXPORTS.mozilla += [ + 'Base64.h', +] + +CPP_SOURCES += [ + 'Base64.cpp', + 'SpecialSystemDirectory.cpp', + 'nsAnonymousTemporaryFile.cpp', + 'nsAppFileLocationProvider.cpp', + 'nsBinaryStream.cpp', + 'nsDirectoryService.cpp', + 'nsEscape.cpp', + 'nsIOUtil.cpp', + 'nsInputStreamTee.cpp', + 'nsLinebreakConverter.cpp', + 'nsLocalFileCommon.cpp', + 'nsMultiplexInputStream.cpp', + 'nsNativeCharsetUtils.cpp', + 'nsPipe3.cpp', + 'nsScriptableBase64Encoder.cpp', + 'nsScriptableInputStream.cpp', + 'nsSegmentedBuffer.cpp', + 'nsStorageStream.cpp', + 'nsStreamUtils.cpp', + 'nsStringStream.cpp', + 'nsUnicharInputStream.cpp', + 'nsWildCard.cpp', +] + + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + CMMSRCS += [ + 'CocoaFileUtils.mm', + ] diff --git a/xpcom/io/nsAnonymousTemporaryFile.cpp b/xpcom/io/nsAnonymousTemporaryFile.cpp new file mode 100644 index 000000000..aaac18958 --- /dev/null +++ b/xpcom/io/nsAnonymousTemporaryFile.cpp @@ -0,0 +1,240 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsAnonymousTemporaryFile.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsXULAppAPI.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsAppDirectoryServiceDefs.h" + +#ifdef XP_WIN +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "mozilla/Services.h" +#include "nsIIdleService.h" +#include "nsISimpleEnumerator.h" +#include "nsIFile.h" +#include "nsAutoPtr.h" +#include "nsITimer.h" +#include "nsCRT.h" + +using namespace mozilla; +#endif + + +// We store the temp files in the system temp dir. +// +// On Windows systems in particular we use a sub-directory of the temp +// directory, because: +// 1. DELETE_ON_CLOSE is unreliable on Windows, in particular if we power +// cycle (and perhaps if we crash) the files are not deleted. We store +// the temporary files in a known sub-dir so that we can find and delete +// them easily and quickly. +// 2. On Windows NT the system temp dir is in the user's $HomeDir/AppData, +// so we can be sure the user always has write privileges to that directory; +// if the sub-dir for our temp files was in some shared location and +// was created by a privileged user, it's possible that other users +// wouldn't have write access to that sub-dir. (Non-Windows systems +// don't store their temp files in a sub-dir, so this isn't an issue on +// those platforms). +// 3. Content processes can access the system temp dir +// (NS_GetSpecialDirectory fails on NS_APP_USER_PROFILE_LOCAL_50_DIR +// for content process for example, which is where we previously stored +// temp files on Windows). This argument applies to all platforms, not +// just Windows. +static nsresult +GetTempDir(nsIFile** aTempDir) +{ + NS_ENSURE_ARG(aTempDir); + nsCOMPtr<nsIFile> tmpFile; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)); + NS_ENSURE_SUCCESS(rv,rv); + +#ifdef XP_WIN + // On windows DELETE_ON_CLOSE is unreliable, so we store temporary files + // in a subdir of the temp dir and delete that in an idle service observer + // to ensure it's been cleared. + rv = tmpFile->AppendNative(nsDependentCString("mozilla-temp-files")); + NS_ENSURE_SUCCESS(rv,rv); + rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700); + NS_ENSURE_TRUE(rv == NS_ERROR_FILE_ALREADY_EXISTS || NS_SUCCEEDED(rv), rv); +#endif + + tmpFile.forget(aTempDir); + + return NS_OK; +} + +nsresult +NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc) +{ + NS_ENSURE_ARG(aOutFileDesc); + nsresult rv; + + nsCOMPtr<nsIFile> tmpFile; + rv = GetTempDir(getter_AddRefs(tmpFile)); + NS_ENSURE_SUCCESS(rv,rv); + + // Give the temp file a name with a random element. CreateUnique will also + // append a counter to the name if it encounters a name collision. Adding + // a random element to the name reduces the likelihood of a name collision, + // so that CreateUnique() doesn't end up trying a lot of name variants in + // its "try appending an incrementing counter" loop, as file IO can be + // expensive on some mobile flash drives. + nsAutoCString name("mozilla-temp-"); + name.AppendInt(rand()); + + rv = tmpFile->AppendNative(name); + NS_ENSURE_SUCCESS(rv,rv); + + rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700); + NS_ENSURE_SUCCESS(rv,rv); + + rv = tmpFile->OpenNSPRFileDesc(PR_RDWR | nsIFile::DELETE_ON_CLOSE, + PR_IRWXU, aOutFileDesc); + + return rv; +} + +#ifdef XP_WIN + +// On Windows we have an idle service observer that runs some time after +// startup and deletes any stray anonymous temporary files... + +// Duration of idle time before we'll get a callback whereupon we attempt to +// remove any stray and unused anonymous temp files. +#define TEMP_FILE_IDLE_TIME_S 30 + +// The nsAnonTempFileRemover is created in a timer, which sets an idle observer. +// This is expiration time (in ms) which initial timer is set for (3 minutes). +#define SCHEDULE_TIMEOUT_MS 3 * 60 * 1000 + +#define XPCOM_SHUTDOWN_TOPIC "xpcom-shutdown" + +// This class adds itself as an idle observer. When the application has +// been idle for about 30 seconds we'll get a notification, whereupon we'll +// attempt to delete ${TempDir}/mozilla-temp-files/. This is to ensure all +// temp files that were supposed to be deleted on application exit were actually +// deleted, as they may not be if we previously crashed. See bugs 572579 and +// 785662. This is only needed on some versions of Windows, +// nsIFile::DELETE_ON_CLOSE works on other platforms. +// This class adds itself as a shutdown observer so that it can cancel the +// idle observer and its timer on shutdown. Note: the observer and idle +// services hold references to instances of this object, and those references +// are what keep this object alive. +class nsAnonTempFileRemover MOZ_FINAL : public nsIObserver { +public: + NS_DECL_ISUPPORTS + + nsAnonTempFileRemover() { + MOZ_COUNT_CTOR(nsAnonTempFileRemover); + } + + ~nsAnonTempFileRemover() { + MOZ_COUNT_DTOR(nsAnonTempFileRemover); + } + + nsresult Init() { + // We add the idle observer in a timer, so that the app has enough + // time to start up before we add the idle observer. If we register the + // idle observer too early, it will be registered before the fake idle + // service is installed when running in xpcshell, and this interferes with + // the fake idle service, causing xpcshell-test failures. + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + NS_ENSURE_TRUE(mTimer != nullptr, NS_ERROR_FAILURE); + nsresult rv = mTimer->Init(this, + SCHEDULE_TIMEOUT_MS, + nsITimer::TYPE_ONE_SHOT); + NS_ENSURE_SUCCESS(rv, rv); + + // Register shutdown observer so we can cancel the timer if we shutdown before + // the timer runs. + nsCOMPtr<nsIObserverService> obsSrv = services::GetObserverService(); + NS_ENSURE_TRUE(obsSrv != nullptr, NS_ERROR_FAILURE); + return obsSrv->AddObserver(this, XPCOM_SHUTDOWN_TOPIC, false); + } + + void Cleanup() { + // Cancel timer. + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + // Remove idle service observer. + nsCOMPtr<nsIIdleService> idleSvc = + do_GetService("@mozilla.org/widget/idleservice;1"); + if (idleSvc) { + idleSvc->RemoveIdleObserver(this, TEMP_FILE_IDLE_TIME_S); + } + // Remove shutdown observer. + nsCOMPtr<nsIObserverService> obsSrv = services::GetObserverService(); + if (obsSrv) { + obsSrv->RemoveObserver(this, XPCOM_SHUTDOWN_TOPIC); + } + } + + NS_IMETHODIMP Observe(nsISupports *aSubject, + const char *aTopic, + const PRUnichar *aData) + { + if (nsCRT::strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0 && + NS_FAILED(RegisterIdleObserver())) { + Cleanup(); + } else if (nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0) { + // The user has been idle for a while, clean up the temp files. + // The idle service will drop its reference to this object after + // we exit, destroying this object. + RemoveAnonTempFileFiles(); + Cleanup(); + } else if (nsCRT::strcmp(aTopic, XPCOM_SHUTDOWN_TOPIC) == 0) { + Cleanup(); + } + return NS_OK; + } + + nsresult RegisterIdleObserver() { + // Add this as an idle observer. When we've been idle for + // TEMP_FILE_IDLE_TIME_S seconds, we'll get a notification, and we'll then + // try to delete any stray temp files. + nsCOMPtr<nsIIdleService> idleSvc = + do_GetService("@mozilla.org/widget/idleservice;1"); + if (!idleSvc) + return NS_ERROR_FAILURE; + return idleSvc->AddIdleObserver(this, TEMP_FILE_IDLE_TIME_S); + } + + void RemoveAnonTempFileFiles() { + nsCOMPtr<nsIFile> tmpDir; + nsresult rv = GetTempDir(getter_AddRefs(tmpDir)); + NS_ENSURE_SUCCESS_VOID(rv); + + // Remove the directory recursively. + tmpDir->Remove(true); + } + +private: + nsCOMPtr<nsITimer> mTimer; +}; + +NS_IMPL_ISUPPORTS1(nsAnonTempFileRemover, nsIObserver) + +nsresult CreateAnonTempFileRemover() { + // Create a temp file remover. If Init() succeeds, the temp file remover is kept + // alive by a reference held by the observer service, since the temp file remover + // is a shutdown observer. We only create the temp file remover if we're running + // in the main process; there's no point in doing the temp file removal multiple + // times per startup. + if (XRE_GetProcessType() != GeckoProcessType_Default) { + return NS_OK; + } + nsRefPtr<nsAnonTempFileRemover> tempRemover = new nsAnonTempFileRemover(); + return tempRemover->Init(); +} + +#endif + diff --git a/xpcom/io/nsAnonymousTemporaryFile.h b/xpcom/io/nsAnonymousTemporaryFile.h new file mode 100644 index 000000000..2a0968a41 --- /dev/null +++ b/xpcom/io/nsAnonymousTemporaryFile.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#pragma once + +#include "prio.h" +#include "nscore.h" + +/** + * OpenAnonymousTemporaryFile + * + * Creates and opens a temporary file which has a random name. Callers have no + * control over the file name, and the file is opened in a temporary location + * which is appropriate for the platform. + * + * Upon success, aOutFileDesc contains an opened handle to the temporary file. + * The caller is responsible for closing the file when they're finished with it. + * + * The file will be deleted when the file handle is closed. On non-Windows + * platforms the file will be unlinked before this function returns. On Windows + * the OS supplied delete-on-close mechanism is unreliable if the application + * crashes or the computer power cycles unexpectedly, so unopened temporary + * files are purged at some time after application startup. + * + */ +nsresult +NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc); + diff --git a/xpcom/io/nsAppDirectoryServiceDefs.h b/xpcom/io/nsAppDirectoryServiceDefs.h new file mode 100644 index 000000000..542662aa8 --- /dev/null +++ b/xpcom/io/nsAppDirectoryServiceDefs.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsAppDirectoryServiceDefs_h___ +#define nsAppDirectoryServiceDefs_h___ + +//======================================================================================== +// +// Defines property names for directories available from standard nsIDirectoryServiceProviders. +// These keys are not guaranteed to exist because the nsIDirectoryServiceProviders which +// provide them are optional. +// +// Keys whose definition ends in "DIR" or "FILE" return a single nsIFile (or subclass). +// Keys whose definition ends in "LIST" return an nsISimpleEnumerator which enumerates a +// list of file objects. +// +// System and XPCOM level properties are defined in nsDirectoryServiceDefs.h. +// +//======================================================================================== + + +// -------------------------------------------------------------------------------------- +// Files and directories which exist on a per-product basis +// -------------------------------------------------------------------------------------- + +#define NS_APP_APPLICATION_REGISTRY_FILE "AppRegF" +#define NS_APP_APPLICATION_REGISTRY_DIR "AppRegD" + +#define NS_APP_DEFAULTS_50_DIR "DefRt" // The root dir of all defaults dirs +#define NS_APP_PREF_DEFAULTS_50_DIR "PrfDef" +#define NS_APP_PROFILE_DEFAULTS_50_DIR "profDef" // The profile defaults of the "current" + // locale. Should be first choice. +#define NS_APP_PROFILE_DEFAULTS_NLOC_50_DIR "ProfDefNoLoc" // The profile defaults of the "default" + // installed locale. Second choice + // when above is not available. + +#define NS_APP_USER_PROFILES_ROOT_DIR "DefProfRt" // The dir where user profile dirs live. +#define NS_APP_USER_PROFILES_LOCAL_ROOT_DIR "DefProfLRt" // The dir where user profile temp dirs live. + +#define NS_APP_RES_DIR "ARes" +#define NS_APP_CHROME_DIR "AChrom" +#define NS_APP_PLUGINS_DIR "APlugns" // Deprecated - use NS_APP_PLUGINS_DIR_LIST +#define NS_APP_SEARCH_DIR "SrchPlugns" + +#define NS_APP_CHROME_DIR_LIST "AChromDL" +#define NS_APP_PLUGINS_DIR_LIST "APluginsDL" +#define NS_APP_SEARCH_DIR_LIST "SrchPluginsDL" + +// -------------------------------------------------------------------------------------- +// Files and directories which exist on a per-profile basis +// These locations are typically provided by the profile mgr +// -------------------------------------------------------------------------------------- + +// In a shared profile environment, prefixing a profile-relative +// key with NS_SHARED returns a location that is shared by +// other users of the profile. Without this prefix, the consumer +// has exclusive access to this location. + +#define NS_SHARED "SHARED" + +#define NS_APP_PREFS_50_DIR "PrefD" // Directory which contains user prefs +#define NS_APP_PREFS_50_FILE "PrefF" +#define NS_APP_PREFS_DEFAULTS_DIR_LIST "PrefDL" +#define NS_EXT_PREFS_DEFAULTS_DIR_LIST "ExtPrefDL" +#define NS_APP_PREFS_OVERRIDE_DIR "PrefDOverride" // Directory for per-profile defaults + +#define NS_APP_USER_PROFILE_50_DIR "ProfD" +#define NS_APP_USER_PROFILE_LOCAL_50_DIR "ProfLD" + +#define NS_APP_USER_CHROME_DIR "UChrm" +#define NS_APP_USER_SEARCH_DIR "UsrSrchPlugns" + +#define NS_APP_LOCALSTORE_50_FILE "LclSt" +#define NS_APP_USER_PANELS_50_FILE "UPnls" +#define NS_APP_USER_MIMETYPES_50_FILE "UMimTyp" +#define NS_APP_CACHE_PARENT_DIR "cachePDir" + +#define NS_APP_BOOKMARKS_50_FILE "BMarks" + +#define NS_APP_DOWNLOADS_50_FILE "DLoads" + +#define NS_APP_SEARCH_50_FILE "SrchF" + +#define NS_APP_INSTALL_CLEANUP_DIR "XPIClnupD" //location of xpicleanup.dat xpicleanup.exe + +#define NS_APP_STORAGE_50_FILE "UStor" // sqlite database used as mozStorage profile db + +#define NS_APP_INDEXEDDB_PARENT_DIR "indexedDBPDir" + +#define NS_APP_PERMISSION_PARENT_DIR "permissionDBPDir" +#endif diff --git a/xpcom/io/nsAppFileLocationProvider.cpp b/xpcom/io/nsAppFileLocationProvider.cpp new file mode 100644 index 000000000..ba497572a --- /dev/null +++ b/xpcom/io/nsAppFileLocationProvider.cpp @@ -0,0 +1,578 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsAppFileLocationProvider.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIAtom.h" +#include "nsIFile.h" +#include "nsString.h" +#include "nsXPIDLString.h" +#include "nsISimpleEnumerator.h" +#include "prenv.h" +#include "nsCRT.h" + +#if defined(MOZ_WIDGET_COCOA) +#include <Carbon/Carbon.h> +#include "nsILocalFileMac.h" +#elif defined(XP_OS2) +#define INCL_DOSPROCESS +#define INCL_DOSMODULEMGR +#include <os2.h> +#elif defined(XP_WIN) +#include <windows.h> +#include <shlobj.h> +#elif defined(XP_UNIX) +#include <unistd.h> +#include <stdlib.h> +#include <sys/param.h> +#endif + + +// WARNING: These hard coded names need to go away. They need to +// come from localizable resources + +#if defined(MOZ_WIDGET_COCOA) +#define APP_REGISTRY_NAME NS_LITERAL_CSTRING("Application Registry") +#define ESSENTIAL_FILES NS_LITERAL_CSTRING("Essential Files") +#elif defined(XP_WIN) || defined(XP_OS2) +#define APP_REGISTRY_NAME NS_LITERAL_CSTRING("registry.dat") +#else +#define APP_REGISTRY_NAME NS_LITERAL_CSTRING("appreg") +#endif + +// define default product directory +#define DEFAULT_PRODUCT_DIR NS_LITERAL_CSTRING(MOZ_USER_DIR) + +// Locally defined keys used by nsAppDirectoryEnumerator +#define NS_ENV_PLUGINS_DIR "EnvPlugins" // env var MOZ_PLUGIN_PATH +#define NS_USER_PLUGINS_DIR "UserPlugins" + +#ifdef MOZ_WIDGET_COCOA +#define NS_MACOSX_USER_PLUGIN_DIR "OSXUserPlugins" +#define NS_MACOSX_LOCAL_PLUGIN_DIR "OSXLocalPlugins" +#define NS_MACOSX_JAVA2_PLUGIN_DIR "OSXJavaPlugins" +#elif XP_UNIX +#define NS_SYSTEM_PLUGINS_DIR "SysPlugins" +#endif + +#define DEFAULTS_DIR_NAME NS_LITERAL_CSTRING("defaults") +#define DEFAULTS_PREF_DIR_NAME NS_LITERAL_CSTRING("pref") +#define DEFAULTS_PROFILE_DIR_NAME NS_LITERAL_CSTRING("profile") +#define RES_DIR_NAME NS_LITERAL_CSTRING("res") +#define CHROME_DIR_NAME NS_LITERAL_CSTRING("chrome") +#define PLUGINS_DIR_NAME NS_LITERAL_CSTRING("plugins") +#define SEARCH_DIR_NAME NS_LITERAL_CSTRING("searchplugins") + +//***************************************************************************** +// nsAppFileLocationProvider::Constructor/Destructor +//***************************************************************************** + +nsAppFileLocationProvider::nsAppFileLocationProvider() +{ +} + +//***************************************************************************** +// nsAppFileLocationProvider::nsISupports +//***************************************************************************** + +NS_IMPL_THREADSAFE_ISUPPORTS2(nsAppFileLocationProvider, nsIDirectoryServiceProvider, nsIDirectoryServiceProvider2) + +//***************************************************************************** +// nsAppFileLocationProvider::nsIDirectoryServiceProvider +//***************************************************************************** + +NS_IMETHODIMP +nsAppFileLocationProvider::GetFile(const char *prop, bool *persistent, nsIFile **_retval) +{ + nsCOMPtr<nsIFile> localFile; + nsresult rv = NS_ERROR_FAILURE; + + NS_ENSURE_ARG(prop); + *_retval = nullptr; + *persistent = true; + +#ifdef MOZ_WIDGET_COCOA + FSRef fileRef; + nsCOMPtr<nsILocalFileMac> macFile; +#endif + + if (nsCRT::strcmp(prop, NS_APP_APPLICATION_REGISTRY_DIR) == 0) + { + rv = GetProductDirectory(getter_AddRefs(localFile)); + } + else if (nsCRT::strcmp(prop, NS_APP_APPLICATION_REGISTRY_FILE) == 0) + { + rv = GetProductDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) + rv = localFile->AppendNative(APP_REGISTRY_NAME); + } + else if (nsCRT::strcmp(prop, NS_APP_DEFAULTS_50_DIR) == 0) + { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) + rv = localFile->AppendRelativeNativePath(DEFAULTS_DIR_NAME); + } + else if (nsCRT::strcmp(prop, NS_APP_PREF_DEFAULTS_50_DIR) == 0) + { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(DEFAULTS_DIR_NAME); + if (NS_SUCCEEDED(rv)) + rv = localFile->AppendRelativeNativePath(DEFAULTS_PREF_DIR_NAME); + } + } + else if (nsCRT::strcmp(prop, NS_APP_PROFILE_DEFAULTS_50_DIR) == 0 || + nsCRT::strcmp(prop, NS_APP_PROFILE_DEFAULTS_NLOC_50_DIR) == 0) + { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(DEFAULTS_DIR_NAME); + if (NS_SUCCEEDED(rv)) + rv = localFile->AppendRelativeNativePath(DEFAULTS_PROFILE_DIR_NAME); + } + } + else if (nsCRT::strcmp(prop, NS_APP_USER_PROFILES_ROOT_DIR) == 0) + { + rv = GetDefaultUserProfileRoot(getter_AddRefs(localFile)); + } + else if (nsCRT::strcmp(prop, NS_APP_USER_PROFILES_LOCAL_ROOT_DIR) == 0) + { + rv = GetDefaultUserProfileRoot(getter_AddRefs(localFile), true); + } + else if (nsCRT::strcmp(prop, NS_APP_RES_DIR) == 0) + { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) + rv = localFile->AppendRelativeNativePath(RES_DIR_NAME); + } + else if (nsCRT::strcmp(prop, NS_APP_CHROME_DIR) == 0) + { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) + rv = localFile->AppendRelativeNativePath(CHROME_DIR_NAME); + } + else if (nsCRT::strcmp(prop, NS_APP_PLUGINS_DIR) == 0) + { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) + rv = localFile->AppendRelativeNativePath(PLUGINS_DIR_NAME); + } +#ifdef MOZ_WIDGET_COCOA + else if (nsCRT::strcmp(prop, NS_MACOSX_USER_PLUGIN_DIR) == 0) + { + if (::FSFindFolder(kUserDomain, kInternetPlugInFolderType, false, &fileRef) == noErr) { + rv = NS_NewLocalFileWithFSRef(&fileRef, true, getter_AddRefs(macFile)); + if (NS_SUCCEEDED(rv)) + localFile = macFile; + } + } + else if (nsCRT::strcmp(prop, NS_MACOSX_LOCAL_PLUGIN_DIR) == 0) + { + if (::FSFindFolder(kLocalDomain, kInternetPlugInFolderType, false, &fileRef) == noErr) { + rv = NS_NewLocalFileWithFSRef(&fileRef, true, getter_AddRefs(macFile)); + if (NS_SUCCEEDED(rv)) + localFile = macFile; + } + } + else if (nsCRT::strcmp(prop, NS_MACOSX_JAVA2_PLUGIN_DIR) == 0) + { + static const char *const java2PluginDirPath = + "/System/Library/Java/Support/Deploy.bundle/Contents/Resources/"; + rv = NS_NewNativeLocalFile(nsDependentCString(java2PluginDirPath), true, getter_AddRefs(localFile)); + } +#else + else if (nsCRT::strcmp(prop, NS_ENV_PLUGINS_DIR) == 0) + { + NS_ERROR("Don't use nsAppFileLocationProvider::GetFile(NS_ENV_PLUGINS_DIR, ...). " + "Use nsAppFileLocationProvider::GetFiles(...)."); + const char *pathVar = PR_GetEnv("MOZ_PLUGIN_PATH"); + if (pathVar && *pathVar) + rv = NS_NewNativeLocalFile(nsDependentCString(pathVar), true, + getter_AddRefs(localFile)); + } + else if (nsCRT::strcmp(prop, NS_USER_PLUGINS_DIR) == 0) + { +#ifdef ENABLE_SYSTEM_EXTENSION_DIRS + rv = GetProductDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) + rv = localFile->AppendRelativeNativePath(PLUGINS_DIR_NAME); +#else + rv = NS_ERROR_FAILURE; +#endif + } +#ifdef XP_UNIX + else if (nsCRT::strcmp(prop, NS_SYSTEM_PLUGINS_DIR) == 0) { +#ifdef ENABLE_SYSTEM_EXTENSION_DIRS + static const char *const sysLPlgDir = +#if defined(HAVE_USR_LIB64_DIR) && defined(__LP64__) + "/usr/lib64/mozilla/plugins"; +#elif defined(__OpenBSD__) || defined (__FreeBSD__) + "/usr/local/lib/mozilla/plugins"; +#else + "/usr/lib/mozilla/plugins"; +#endif + rv = NS_NewNativeLocalFile(nsDependentCString(sysLPlgDir), + false, getter_AddRefs(localFile)); +#else + rv = NS_ERROR_FAILURE; +#endif + } +#endif +#endif + else if (nsCRT::strcmp(prop, NS_APP_SEARCH_DIR) == 0) + { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) + rv = localFile->AppendRelativeNativePath(SEARCH_DIR_NAME); + } + else if (nsCRT::strcmp(prop, NS_APP_USER_SEARCH_DIR) == 0) + { + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, _retval); + if (NS_SUCCEEDED(rv)) + rv = (*_retval)->AppendNative(SEARCH_DIR_NAME); + } + else if (nsCRT::strcmp(prop, NS_APP_INSTALL_CLEANUP_DIR) == 0) + { + // This is cloned so that embeddors will have a hook to override + // with their own cleanup dir. See bugzilla bug #105087 + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + } + + if (localFile && NS_SUCCEEDED(rv)) + return localFile->QueryInterface(NS_GET_IID(nsIFile), (void**)_retval); + + return rv; +} + + +NS_METHOD nsAppFileLocationProvider::CloneMozBinDirectory(nsIFile **aLocalFile) +{ + NS_ENSURE_ARG_POINTER(aLocalFile); + nsresult rv; + + if (!mMozBinDirectory) + { + // Get the mozilla bin directory + // 1. Check the directory service first for NS_XPCOM_CURRENT_PROCESS_DIR + // This will be set if a directory was passed to NS_InitXPCOM + // 2. If that doesn't work, set it to be the current process directory + nsCOMPtr<nsIProperties> + directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + rv = directoryService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(mMozBinDirectory)); + if (NS_FAILED(rv)) { + rv = directoryService->Get(NS_OS_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(mMozBinDirectory)); + if (NS_FAILED(rv)) + return rv; + } + } + + nsCOMPtr<nsIFile> aFile; + rv = mMozBinDirectory->Clone(getter_AddRefs(aFile)); + if (NS_FAILED(rv)) + return rv; + + NS_IF_ADDREF(*aLocalFile = aFile); + return NS_OK; +} + + +//---------------------------------------------------------------------------------------- +// GetProductDirectory - Gets the directory which contains the application data folder +// +// UNIX : ~/.mozilla/ +// WIN : <Application Data folder on user's machine>\Mozilla +// Mac : :Documents:Mozilla: +//---------------------------------------------------------------------------------------- +NS_METHOD nsAppFileLocationProvider::GetProductDirectory(nsIFile **aLocalFile, bool aLocal) +{ + NS_ENSURE_ARG_POINTER(aLocalFile); + + nsresult rv; + bool exists; + nsCOMPtr<nsIFile> localDir; + +#if defined(MOZ_WIDGET_COCOA) + FSRef fsRef; + OSType folderType = aLocal ? (OSType) kCachedDataFolderType : (OSType) kDomainLibraryFolderType; + OSErr err = ::FSFindFolder(kUserDomain, folderType, kCreateFolder, &fsRef); + if (err) return NS_ERROR_FAILURE; + NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localDir)); + if (!localDir) return NS_ERROR_FAILURE; + nsCOMPtr<nsILocalFileMac> localDirMac(do_QueryInterface(localDir)); + rv = localDirMac->InitWithFSRef(&fsRef); + if (NS_FAILED(rv)) return rv; +#elif defined(XP_OS2) + nsCOMPtr<nsIProperties> directoryService = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + rv = directoryService->Get(NS_OS2_HOME_DIR, NS_GET_IID(nsIFile), getter_AddRefs(localDir)); + if (NS_FAILED(rv)) return rv; +#elif defined(XP_WIN) + nsCOMPtr<nsIProperties> directoryService = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + const char* prop = aLocal ? NS_WIN_LOCAL_APPDATA_DIR : NS_WIN_APPDATA_DIR; + rv = directoryService->Get(prop, NS_GET_IID(nsIFile), getter_AddRefs(localDir)); + if (NS_FAILED(rv)) return rv; +#elif defined(XP_UNIX) + rv = NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")), true, getter_AddRefs(localDir)); + if (NS_FAILED(rv)) return rv; +#else +#error dont_know_how_to_get_product_dir_on_your_platform +#endif + + rv = localDir->AppendRelativeNativePath(DEFAULT_PRODUCT_DIR); + if (NS_FAILED(rv)) return rv; + rv = localDir->Exists(&exists); + + if (NS_SUCCEEDED(rv) && !exists) + rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700); + + if (NS_FAILED(rv)) return rv; + + *aLocalFile = localDir; + NS_ADDREF(*aLocalFile); + + return rv; +} + + +//---------------------------------------------------------------------------------------- +// GetDefaultUserProfileRoot - Gets the directory which contains each user profile dir +// +// UNIX : ~/.mozilla/ +// WIN : <Application Data folder on user's machine>\Mozilla\Profiles +// Mac : :Documents:Mozilla:Profiles: +//---------------------------------------------------------------------------------------- +NS_METHOD nsAppFileLocationProvider::GetDefaultUserProfileRoot(nsIFile **aLocalFile, bool aLocal) +{ + NS_ENSURE_ARG_POINTER(aLocalFile); + + nsresult rv; + nsCOMPtr<nsIFile> localDir; + + rv = GetProductDirectory(getter_AddRefs(localDir), aLocal); + if (NS_FAILED(rv)) return rv; + +#if defined(MOZ_WIDGET_COCOA) || defined(XP_OS2) || defined(XP_WIN) + // These 3 platforms share this part of the path - do them as one + rv = localDir->AppendRelativeNativePath(NS_LITERAL_CSTRING("Profiles")); + if (NS_FAILED(rv)) return rv; + + bool exists; + rv = localDir->Exists(&exists); + if (NS_SUCCEEDED(rv) && !exists) + rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0775); + if (NS_FAILED(rv)) return rv; +#endif + + *aLocalFile = localDir; + NS_ADDREF(*aLocalFile); + + return rv; +} + +//***************************************************************************** +// nsAppFileLocationProvider::nsIDirectoryServiceProvider2 +//***************************************************************************** + +class nsAppDirectoryEnumerator : public nsISimpleEnumerator +{ + public: + NS_DECL_ISUPPORTS + + /** + * aKeyList is a null-terminated list of properties which are provided by aProvider + * They do not need to be publicly defined keys. + */ + nsAppDirectoryEnumerator(nsIDirectoryServiceProvider *aProvider, + const char* aKeyList[]) : + mProvider(aProvider), + mCurrentKey(aKeyList) + { + } + + NS_IMETHOD HasMoreElements(bool *result) + { + while (!mNext && *mCurrentKey) + { + bool dontCare; + nsCOMPtr<nsIFile> testFile; + (void)mProvider->GetFile(*mCurrentKey++, &dontCare, getter_AddRefs(testFile)); + // Don't return a file which does not exist. + bool exists; + if (testFile && NS_SUCCEEDED(testFile->Exists(&exists)) && exists) + mNext = testFile; + } + *result = mNext != nullptr; + return NS_OK; + } + + NS_IMETHOD GetNext(nsISupports **result) + { + NS_ENSURE_ARG_POINTER(result); + *result = nullptr; + + bool hasMore; + HasMoreElements(&hasMore); + if (!hasMore) + return NS_ERROR_FAILURE; + + *result = mNext; + NS_IF_ADDREF(*result); + mNext = nullptr; + + return *result ? NS_OK : NS_ERROR_FAILURE; + } + + // Virtual destructor since subclass nsPathsDirectoryEnumerator + // does not re-implement Release() + + virtual ~nsAppDirectoryEnumerator() + { + } + + protected: + nsIDirectoryServiceProvider *mProvider; + const char** mCurrentKey; + nsCOMPtr<nsIFile> mNext; +}; + +NS_IMPL_ISUPPORTS1(nsAppDirectoryEnumerator, nsISimpleEnumerator) + +/* nsPathsDirectoryEnumerator and PATH_SEPARATOR + * are not used on MacOS/X. */ + +#if defined(XP_WIN) || defined(XP_OS2) /* Win32 and OS/2 */ +#define PATH_SEPARATOR ';' +#else +#define PATH_SEPARATOR ':' +#endif + +class nsPathsDirectoryEnumerator : public nsAppDirectoryEnumerator +{ + public: + /** + * aKeyList is a null-terminated list. + * The first element is a path list. + * The remainder are properties provided by aProvider. + * They do not need to be publicly defined keys. + */ + nsPathsDirectoryEnumerator(nsIDirectoryServiceProvider *aProvider, + const char* aKeyList[]) : + nsAppDirectoryEnumerator(aProvider, aKeyList+1), + mEndPath(aKeyList[0]) + { + } + + NS_IMETHOD HasMoreElements(bool *result) + { + if (mEndPath) + while (!mNext && *mEndPath) + { + const char *pathVar = mEndPath; + + // skip PATH_SEPARATORs at the begining of the mEndPath + while (*pathVar == PATH_SEPARATOR) pathVar++; + + do { ++mEndPath; } while (*mEndPath && *mEndPath != PATH_SEPARATOR); + + nsCOMPtr<nsIFile> localFile; + NS_NewNativeLocalFile(Substring(pathVar, mEndPath), + true, + getter_AddRefs(localFile)); + if (*mEndPath == PATH_SEPARATOR) + ++mEndPath; + // Don't return a "file" (directory) which does not exist. + bool exists; + if (localFile && + NS_SUCCEEDED(localFile->Exists(&exists)) && + exists) + mNext = localFile; + } + if (mNext) + *result = true; + else + nsAppDirectoryEnumerator::HasMoreElements(result); + + return NS_OK; + } + + protected: + const char *mEndPath; +}; + +NS_IMETHODIMP +nsAppFileLocationProvider::GetFiles(const char *prop, nsISimpleEnumerator **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nullptr; + nsresult rv = NS_ERROR_FAILURE; + + if (!nsCRT::strcmp(prop, NS_APP_PLUGINS_DIR_LIST)) + { +#ifdef MOZ_WIDGET_COCOA + // As of Java for Mac OS X 10.5 Update 10, Apple has (in effect) deprecated Java Plugin2 on + // on OS X 10.5, and removed the soft link to it from /Library/Internet Plug-Ins/. Java + // Plugin2 is still present and usable, but there are no longer any links to it in the + // "normal" locations. So we won't be able to find it unless we look in the "non-normal" + // location where it actually is. Safari can use the WebKit-specific JavaPluginCocoa.bundle, + // which (of course) is still fully supported on OS X 10.5. But we have no alternative to + // using Java Plugin2. For more information see bug 668639. + static const char* keys[] = { NS_APP_PLUGINS_DIR, NS_MACOSX_USER_PLUGIN_DIR, + NS_MACOSX_LOCAL_PLUGIN_DIR, + IsOSXLeopard() ? NS_MACOSX_JAVA2_PLUGIN_DIR : nullptr, nullptr }; + *_retval = new nsAppDirectoryEnumerator(this, keys); +#else +#ifdef XP_UNIX + static const char* keys[] = { nullptr, NS_USER_PLUGINS_DIR, NS_APP_PLUGINS_DIR, NS_SYSTEM_PLUGINS_DIR, nullptr }; +#else + static const char* keys[] = { nullptr, NS_USER_PLUGINS_DIR, NS_APP_PLUGINS_DIR, nullptr }; +#endif + if (!keys[0] && !(keys[0] = PR_GetEnv("MOZ_PLUGIN_PATH"))) { + static const char nullstr = 0; + keys[0] = &nullstr; + } + *_retval = new nsPathsDirectoryEnumerator(this, keys); +#endif + NS_IF_ADDREF(*_retval); + rv = *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + if (!nsCRT::strcmp(prop, NS_APP_SEARCH_DIR_LIST)) + { + static const char* keys[] = { nullptr, NS_APP_SEARCH_DIR, NS_APP_USER_SEARCH_DIR, nullptr }; + if (!keys[0] && !(keys[0] = PR_GetEnv("MOZ_SEARCH_ENGINE_PATH"))) { + static const char nullstr = 0; + keys[0] = &nullstr; + } + *_retval = new nsPathsDirectoryEnumerator(this, keys); + NS_IF_ADDREF(*_retval); + rv = *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + return rv; +} + +#if defined(MOZ_WIDGET_COCOA) +bool +nsAppFileLocationProvider::IsOSXLeopard() +{ + static SInt32 version = 0; + + if (!version) { + OSErr err = ::Gestalt(gestaltSystemVersion, &version); + if (err != noErr) { + version = 0; + } else { + version &= 0xFFFF; // The system version is in the low order word + } + } + + return ((version >= 0x1050) && (version < 0x1060)); +} +#endif diff --git a/xpcom/io/nsAppFileLocationProvider.h b/xpcom/io/nsAppFileLocationProvider.h new file mode 100644 index 000000000..8ecf78d53 --- /dev/null +++ b/xpcom/io/nsAppFileLocationProvider.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIDirectoryService.h" +#include "nsIFile.h" +#include "mozilla/Attributes.h" + +class nsIFile; + +//***************************************************************************** +// class nsAppFileLocationProvider +//***************************************************************************** + +class nsAppFileLocationProvider MOZ_FINAL : public nsIDirectoryServiceProvider2 +{ +public: + nsAppFileLocationProvider(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 + +private: + ~nsAppFileLocationProvider() {} + +protected: + NS_METHOD CloneMozBinDirectory(nsIFile **aLocalFile); + /** + * Get the product directory. This is a user-specific directory for storing + * application settings (e.g. the Application Data directory on windows + * systems). + * @param aLocal If true, should try to get a directory that is only stored + * locally (ie not transferred with roaming profiles) + */ + NS_METHOD GetProductDirectory(nsIFile **aLocalFile, + bool aLocal = false); + NS_METHOD GetDefaultUserProfileRoot(nsIFile **aLocalFile, + bool aLocal = false); + +#if defined(MOZ_WIDGET_COCOA) + static bool IsOSXLeopard(); +#endif + + nsCOMPtr<nsIFile> mMozBinDirectory; +}; diff --git a/xpcom/io/nsBinaryStream.cpp b/xpcom/io/nsBinaryStream.cpp new file mode 100644 index 000000000..8c2cc2397 --- /dev/null +++ b/xpcom/io/nsBinaryStream.cpp @@ -0,0 +1,823 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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/. */ + +/** + * This file contains implementations of the nsIBinaryInputStream and + * nsIBinaryOutputStream interfaces. Together, these interfaces allows reading + * and writing of primitive data types (integers, floating-point values, + * booleans, etc.) to a stream in a binary, untagged, fixed-endianness format. + * This might be used, for example, to implement network protocols or to + * produce architecture-neutral binary disk files, i.e. ones that can be read + * and written by both big-endian and little-endian platforms. Output is + * written in big-endian order (high-order byte first), as this is traditional + * network order. + * + * @See nsIBinaryInputStream + * @See nsIBinaryOutputStream + */ +#include <string.h> +#include "nsBinaryStream.h" +#include "nsCRT.h" +#include "prlong.h" +#include "nsString.h" +#include "nsISerializable.h" +#include "nsIClassInfo.h" +#include "nsComponentManagerUtils.h" +#include "nsIURI.h" // for NS_IURI_IID +#include "mozilla/Endian.h" + +#include "jsapi.h" +#include "jsfriendapi.h" + +NS_IMPL_ISUPPORTS3(nsBinaryOutputStream, nsIObjectOutputStream, nsIBinaryOutputStream, nsIOutputStream) + +NS_IMETHODIMP +nsBinaryOutputStream::Flush() +{ + NS_ENSURE_STATE(mOutputStream); + return mOutputStream->Flush(); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Close() +{ + NS_ENSURE_STATE(mOutputStream); + return mOutputStream->Close(); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write(const char *aBuf, uint32_t aCount, uint32_t *aActualBytes) +{ + NS_ENSURE_STATE(mOutputStream); + return mOutputStream->Write(aBuf, aCount, aActualBytes); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval) +{ + NS_NOTREACHED("WriteFrom"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval) +{ + NS_NOTREACHED("WriteSegments"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBinaryOutputStream::IsNonBlocking(bool *aNonBlocking) +{ + NS_ENSURE_STATE(mOutputStream); + return mOutputStream->IsNonBlocking(aNonBlocking); +} + +nsresult +nsBinaryOutputStream::WriteFully(const char *aBuf, uint32_t aCount) +{ + NS_ENSURE_STATE(mOutputStream); + + nsresult rv; + uint32_t bytesWritten; + + rv = mOutputStream->Write(aBuf, aCount, &bytesWritten); + if (NS_FAILED(rv)) return rv; + if (bytesWritten != aCount) + return NS_ERROR_FAILURE; + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryOutputStream::SetOutputStream(nsIOutputStream *aOutputStream) +{ + NS_ENSURE_ARG_POINTER(aOutputStream); + mOutputStream = aOutputStream; + mBufferAccess = do_QueryInterface(aOutputStream); + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteBoolean(bool aBoolean) +{ + return Write8(aBoolean); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write8(uint8_t aByte) +{ + return WriteFully((const char*)&aByte, sizeof aByte); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write16(uint16_t a16) +{ + a16 = mozilla::NativeEndian::swapToBigEndian(a16); + return WriteFully((const char*)&a16, sizeof a16); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write32(uint32_t a32) +{ + a32 = mozilla::NativeEndian::swapToBigEndian(a32); + return WriteFully((const char*)&a32, sizeof a32); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write64(uint64_t a64) +{ + nsresult rv; + uint32_t bytesWritten; + + a64 = mozilla::NativeEndian::swapToBigEndian(a64); + rv = Write(reinterpret_cast<char*>(&a64), sizeof a64, &bytesWritten); + if (NS_FAILED(rv)) return rv; + if (bytesWritten != sizeof a64) + return NS_ERROR_FAILURE; + return rv; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteFloat(float aFloat) +{ + NS_ASSERTION(sizeof(float) == sizeof (uint32_t), + "False assumption about sizeof(float)"); + return Write32(*reinterpret_cast<uint32_t*>(&aFloat)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteDouble(double aDouble) +{ + NS_ASSERTION(sizeof(double) == sizeof(uint64_t), + "False assumption about sizeof(double)"); + return Write64(*reinterpret_cast<uint64_t*>(&aDouble)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteStringZ(const char *aString) +{ + uint32_t length; + nsresult rv; + + length = strlen(aString); + rv = Write32(length); + if (NS_FAILED(rv)) return rv; + return WriteFully(aString, length); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteWStringZ(const PRUnichar* aString) +{ + uint32_t length, byteCount; + nsresult rv; + + length = NS_strlen(aString); + rv = Write32(length); + if (NS_FAILED(rv)) return rv; + + if (length == 0) + return NS_OK; + byteCount = length * sizeof(PRUnichar); + +#ifdef IS_BIG_ENDIAN + rv = WriteBytes(reinterpret_cast<const char*>(aString), byteCount); +#else + // XXX use WriteSegments here to avoid copy! + PRUnichar *copy, temp[64]; + if (length <= 64) { + copy = temp; + } else { + copy = reinterpret_cast<PRUnichar*>(moz_malloc(byteCount)); + if (!copy) + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ASSERTION((uintptr_t(aString) & 0x1) == 0, "aString not properly aligned"); + mozilla::NativeEndian::copyAndSwapToBigEndian(copy, aString, length); + rv = WriteBytes(reinterpret_cast<const char*>(copy), byteCount); + if (copy != temp) + moz_free(copy); +#endif + + return rv; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteUtf8Z(const PRUnichar* aString) +{ + return WriteStringZ(NS_ConvertUTF16toUTF8(aString).get()); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteBytes(const char *aString, uint32_t aLength) +{ + nsresult rv; + uint32_t bytesWritten; + + rv = Write(aString, aLength, &bytesWritten); + if (NS_FAILED(rv)) return rv; + if (bytesWritten != aLength) + return NS_ERROR_FAILURE; + return rv; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteByteArray(uint8_t *aBytes, uint32_t aLength) +{ + return WriteBytes(reinterpret_cast<char *>(aBytes), aLength); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteObject(nsISupports* aObject, bool aIsStrongRef) +{ + return WriteCompoundObject(aObject, NS_GET_IID(nsISupports), + aIsStrongRef); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteSingleRefObject(nsISupports* aObject) +{ + return WriteCompoundObject(aObject, NS_GET_IID(nsISupports), + true); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteCompoundObject(nsISupports* aObject, + const nsIID& aIID, + bool aIsStrongRef) +{ + // Can't deal with weak refs + NS_ENSURE_TRUE(aIsStrongRef, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(aObject); + NS_ENSURE_TRUE(classInfo, NS_ERROR_NOT_AVAILABLE); + + nsCOMPtr<nsISerializable> serializable = do_QueryInterface(aObject); + NS_ENSURE_TRUE(serializable, NS_ERROR_NOT_AVAILABLE); + + nsCID cid; + classInfo->GetClassIDNoAlloc(&cid); + + nsresult rv = WriteID(cid); + NS_ENSURE_SUCCESS(rv, rv); + + rv = WriteID(aIID); + NS_ENSURE_SUCCESS(rv, rv); + + return serializable->Write(this); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteID(const nsIID& aIID) +{ + nsresult rv = Write32(aIID.m0); + NS_ENSURE_SUCCESS(rv, rv); + + rv = Write16(aIID.m1); + NS_ENSURE_SUCCESS(rv, rv); + + rv = Write16(aIID.m2); + NS_ENSURE_SUCCESS(rv, rv); + + for (int i = 0; i < 8; ++i) { + rv = Write8(aIID.m3[i]); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP_(char*) +nsBinaryOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) +{ + if (mBufferAccess) + return mBufferAccess->GetBuffer(aLength, aAlignMask); + return nullptr; +} + +NS_IMETHODIMP_(void) +nsBinaryOutputStream::PutBuffer(char* aBuffer, uint32_t aLength) +{ + if (mBufferAccess) + mBufferAccess->PutBuffer(aBuffer, aLength); +} + +NS_IMPL_ISUPPORTS3(nsBinaryInputStream, nsIObjectInputStream, nsIBinaryInputStream, nsIInputStream) + +NS_IMETHODIMP +nsBinaryInputStream::Available(uint64_t* aResult) +{ + NS_ENSURE_STATE(mInputStream); + return mInputStream->Available(aResult); +} + +NS_IMETHODIMP +nsBinaryInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aNumRead) +{ + NS_ENSURE_STATE(mInputStream); + + // mInputStream might give us short reads, so deal with that. + uint32_t totalRead = 0; + + uint32_t bytesRead; + do { + nsresult rv = mInputStream->Read(aBuffer, aCount, &bytesRead); + if (rv == NS_BASE_STREAM_WOULD_BLOCK && totalRead != 0) { + // We already read some data. Return it. + break; + } + + if (NS_FAILED(rv)) { + return rv; + } + + totalRead += bytesRead; + aBuffer += bytesRead; + aCount -= bytesRead; + } while (aCount != 0 && bytesRead != 0); + + *aNumRead = totalRead; + + return NS_OK; +} + + +// when forwarding ReadSegments to mInputStream, we need to make sure +// 'this' is being passed to the writer each time. To do this, we need +// a thunking function which keeps the real input stream around. + +// the closure wrapper +struct ReadSegmentsClosure { + nsIInputStream* mRealInputStream; + void* mRealClosure; + nsWriteSegmentFun mRealWriter; + nsresult mRealResult; + uint32_t mBytesRead; // to properly implement aToOffset +}; + +// the thunking function +static NS_METHOD +ReadSegmentForwardingThunk(nsIInputStream* aStream, + void *aClosure, + const char* aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t *aWriteCount) +{ + ReadSegmentsClosure* thunkClosure = + reinterpret_cast<ReadSegmentsClosure*>(aClosure); + + NS_ASSERTION(NS_SUCCEEDED(thunkClosure->mRealResult), + "How did this get to be a failure status?"); + + thunkClosure->mRealResult = + thunkClosure->mRealWriter(thunkClosure->mRealInputStream, + thunkClosure->mRealClosure, + aFromSegment, + thunkClosure->mBytesRead + aToOffset, + aCount, aWriteCount); + + return thunkClosure->mRealResult; +} + + +NS_IMETHODIMP +nsBinaryInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t count, uint32_t *_retval) +{ + NS_ENSURE_STATE(mInputStream); + + ReadSegmentsClosure thunkClosure = { this, closure, writer, NS_OK, 0 }; + + // mInputStream might give us short reads, so deal with that. + uint32_t bytesRead; + do { + nsresult rv = mInputStream->ReadSegments(ReadSegmentForwardingThunk, + &thunkClosure, + count, &bytesRead); + + if (rv == NS_BASE_STREAM_WOULD_BLOCK && thunkClosure.mBytesRead != 0) { + // We already read some data. Return it. + break; + } + + if (NS_FAILED(rv)) { + return rv; + } + + thunkClosure.mBytesRead += bytesRead; + count -= bytesRead; + } while (count != 0 && bytesRead != 0 && + NS_SUCCEEDED(thunkClosure.mRealResult)); + + *_retval = thunkClosure.mBytesRead; + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::IsNonBlocking(bool *aNonBlocking) +{ + NS_ENSURE_STATE(mInputStream); + return mInputStream->IsNonBlocking(aNonBlocking); +} + +NS_IMETHODIMP +nsBinaryInputStream::Close() +{ + NS_ENSURE_STATE(mInputStream); + return mInputStream->Close(); +} + +NS_IMETHODIMP +nsBinaryInputStream::SetInputStream(nsIInputStream *aInputStream) +{ + NS_ENSURE_ARG_POINTER(aInputStream); + mInputStream = aInputStream; + mBufferAccess = do_QueryInterface(aInputStream); + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadBoolean(bool* aBoolean) +{ + uint8_t byteResult; + nsresult rv = Read8(&byteResult); + if (NS_FAILED(rv)) return rv; + *aBoolean = !!byteResult; + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::Read8(uint8_t* aByte) +{ + nsresult rv; + uint32_t bytesRead; + + rv = Read(reinterpret_cast<char*>(aByte), sizeof(*aByte), &bytesRead); + if (NS_FAILED(rv)) return rv; + if (bytesRead != 1) + return NS_ERROR_FAILURE; + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::Read16(uint16_t* a16) +{ + nsresult rv; + uint32_t bytesRead; + + rv = Read(reinterpret_cast<char*>(a16), sizeof *a16, &bytesRead); + if (NS_FAILED(rv)) return rv; + if (bytesRead != sizeof *a16) + return NS_ERROR_FAILURE; + *a16 = mozilla::NativeEndian::swapFromBigEndian(*a16); + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::Read32(uint32_t* a32) +{ + nsresult rv; + uint32_t bytesRead; + + rv = Read(reinterpret_cast<char*>(a32), sizeof *a32, &bytesRead); + if (NS_FAILED(rv)) return rv; + if (bytesRead != sizeof *a32) + return NS_ERROR_FAILURE; + *a32 = mozilla::NativeEndian::swapFromBigEndian(*a32); + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::Read64(uint64_t* a64) +{ + nsresult rv; + uint32_t bytesRead; + + rv = Read(reinterpret_cast<char*>(a64), sizeof *a64, &bytesRead); + if (NS_FAILED(rv)) return rv; + if (bytesRead != sizeof *a64) + return NS_ERROR_FAILURE; + *a64 = mozilla::NativeEndian::swapFromBigEndian(*a64); + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadFloat(float* aFloat) +{ + NS_ASSERTION(sizeof(float) == sizeof (uint32_t), + "False assumption about sizeof(float)"); + return Read32(reinterpret_cast<uint32_t*>(aFloat)); +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadDouble(double* aDouble) +{ + NS_ASSERTION(sizeof(double) == sizeof(uint64_t), + "False assumption about sizeof(double)"); + return Read64(reinterpret_cast<uint64_t*>(aDouble)); +} + +static NS_METHOD +WriteSegmentToCString(nsIInputStream* aStream, + void *aClosure, + const char* aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t *aWriteCount) +{ + nsACString* outString = static_cast<nsACString*>(aClosure); + + outString->Append(aFromSegment, aCount); + + *aWriteCount = aCount; + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadCString(nsACString& aString) +{ + nsresult rv; + uint32_t length, bytesRead; + + rv = Read32(&length); + if (NS_FAILED(rv)) return rv; + + aString.Truncate(); + rv = ReadSegments(WriteSegmentToCString, &aString, length, &bytesRead); + if (NS_FAILED(rv)) return rv; + + if (bytesRead != length) + return NS_ERROR_FAILURE; + + return NS_OK; +} + + +// sometimes, WriteSegmentToString will be handed an odd-number of +// bytes, which means we only have half of the last PRUnichar +struct WriteStringClosure { + PRUnichar *mWriteCursor; + bool mHasCarryoverByte; + char mCarryoverByte; +}; + +// there are a few cases we have to account for here: +// * even length buffer, no carryover - easy, just append +// * odd length buffer, no carryover - the last byte needs to be saved +// for carryover +// * odd length buffer, with carryover - first byte needs to be used +// with the carryover byte, and +// the rest of the even length +// buffer is appended as normal +// * even length buffer, with carryover - the first byte needs to be +// used with the previous carryover byte. +// this gives you an odd length buffer, +// so you have to save the last byte for +// the next carryover + + +// same version of the above, but with correct casting and endian swapping +static NS_METHOD +WriteSegmentToString(nsIInputStream* aStream, + void *aClosure, + const char* aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t *aWriteCount) +{ + NS_PRECONDITION(aCount > 0, "Why are we being told to write 0 bytes?"); + NS_PRECONDITION(sizeof(PRUnichar) == 2, "We can't handle other sizes!"); + + WriteStringClosure* closure = static_cast<WriteStringClosure*>(aClosure); + PRUnichar *cursor = closure->mWriteCursor; + + // we're always going to consume the whole buffer no matter what + // happens, so take care of that right now.. that allows us to + // tweak aCount later. Do NOT move this! + *aWriteCount = aCount; + + // if the last Write had an odd-number of bytes read, then + if (closure->mHasCarryoverByte) { + // re-create the two-byte sequence we want to work with + char bytes[2] = { closure->mCarryoverByte, *aFromSegment }; + *cursor = *(PRUnichar*)bytes; + // Now the little endianness dance + mozilla::NativeEndian::swapToBigEndianInPlace(cursor, 1); + ++cursor; + + // now skip past the first byte of the buffer.. code from here + // can assume normal operations, but should not assume aCount + // is relative to the ORIGINAL buffer + ++aFromSegment; + --aCount; + + closure->mHasCarryoverByte = false; + } + + // this array is possibly unaligned... be careful how we access it! + const PRUnichar *unicodeSegment = + reinterpret_cast<const PRUnichar*>(aFromSegment); + + // calculate number of full characters in segment (aCount could be odd!) + uint32_t segmentLength = aCount / sizeof(PRUnichar); + + // copy all data into our aligned buffer. byte swap if necessary. + // cursor may be unaligned, so we cannot use copyAndSwapToBigEndian directly + memcpy(cursor, unicodeSegment, segmentLength * sizeof(PRUnichar)); + PRUnichar *end = cursor + segmentLength; + mozilla::NativeEndian::swapToBigEndianInPlace(cursor, segmentLength); + closure->mWriteCursor = end; + + // remember this is the modifed aCount and aFromSegment, + // so that will take into account the fact that we might have + // skipped the first byte in the buffer + if (aCount % sizeof(PRUnichar) != 0) { + // we must have had a carryover byte, that we'll need the next + // time around + closure->mCarryoverByte = aFromSegment[aCount - 1]; + closure->mHasCarryoverByte = true; + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsBinaryInputStream::ReadString(nsAString& aString) +{ + nsresult rv; + uint32_t length, bytesRead; + + rv = Read32(&length); + if (NS_FAILED(rv)) return rv; + + if (length == 0) { + aString.Truncate(); + return NS_OK; + } + + // pre-allocate output buffer, and get direct access to buffer... + if (!aString.SetLength(length, mozilla::fallible_t())) + return NS_ERROR_OUT_OF_MEMORY; + + nsAString::iterator start; + aString.BeginWriting(start); + + WriteStringClosure closure; + closure.mWriteCursor = start.get(); + closure.mHasCarryoverByte = false; + + rv = ReadSegments(WriteSegmentToString, &closure, + length*sizeof(PRUnichar), &bytesRead); + if (NS_FAILED(rv)) return rv; + + NS_ASSERTION(!closure.mHasCarryoverByte, "some strange stream corruption!"); + + if (bytesRead != length*sizeof(PRUnichar)) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadBytes(uint32_t aLength, char* *_rval) +{ + nsresult rv; + uint32_t bytesRead; + char* s; + + s = reinterpret_cast<char*>(moz_malloc(aLength)); + if (!s) + return NS_ERROR_OUT_OF_MEMORY; + + rv = Read(s, aLength, &bytesRead); + if (NS_FAILED(rv)) { + moz_free(s); + return rv; + } + if (bytesRead != aLength) { + moz_free(s); + return NS_ERROR_FAILURE; + } + + *_rval = s; + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadByteArray(uint32_t aLength, uint8_t* *_rval) +{ + return ReadBytes(aLength, reinterpret_cast<char **>(_rval)); +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadArrayBuffer(uint32_t aLength, const JS::Value& aBuffer, JSContext* cx) +{ + if (!aBuffer.isObject()) { + return NS_ERROR_FAILURE; + } + JS::RootedObject buffer(cx, &aBuffer.toObject()); + if (!JS_IsArrayBufferObject(buffer) || + JS_GetArrayBufferByteLength(buffer) < aLength) { + return NS_ERROR_FAILURE; + } + uint8_t* data = JS_GetArrayBufferData(&aBuffer.toObject()); + if (!data) { + return NS_ERROR_FAILURE; + } + + uint32_t bytesRead; + nsresult rv = Read(reinterpret_cast<char*>(data), aLength, &bytesRead); + NS_ENSURE_SUCCESS(rv, rv); + if (bytesRead != aLength) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadObject(bool aIsStrongRef, nsISupports* *aObject) +{ + nsCID cid; + nsIID iid; + nsresult rv = ReadID(&cid); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ReadID(&iid); + NS_ENSURE_SUCCESS(rv, rv); + + // HACK: Intercept old (pre-gecko6) nsIURI IID, and replace with + // the updated IID, so that we're QI'ing to an actual interface. + // (As soon as we drop support for upgrading from pre-gecko6, we can + // remove this chunk.) + static const nsIID oldURIiid = + { 0x7a22cc0, 0xce5, 0x11d3, + { 0x93, 0x31, 0x0, 0x10, 0x4b, 0xa0, 0xfd, 0x40 }}; + + // hackaround for bug 670542 + static const nsIID oldURIiid2 = + { 0xd6d04c36, 0x0fa4, 0x4db3, + { 0xbe, 0x05, 0x4a, 0x18, 0x39, 0x71, 0x03, 0xe2 }}; + + // hackaround for bug 682031 + static const nsIID oldURIiid3 = + { 0x12120b20, 0x0929, 0x40e9, + { 0x88, 0xcf, 0x6e, 0x08, 0x76, 0x6e, 0x8b, 0x23 }}; + + if (iid.Equals(oldURIiid) || + iid.Equals(oldURIiid2) || + iid.Equals(oldURIiid3)) { + const nsIID newURIiid = NS_IURI_IID; + iid = newURIiid; + } + // END HACK + + nsCOMPtr<nsISupports> object = do_CreateInstance(cid, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISerializable> serializable = do_QueryInterface(object); + NS_ENSURE_TRUE(serializable, NS_ERROR_UNEXPECTED); + + rv = serializable->Read(this); + NS_ENSURE_SUCCESS(rv, rv); + + return object->QueryInterface(iid, reinterpret_cast<void**>(aObject)); +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadID(nsID *aResult) +{ + nsresult rv = Read32(&aResult->m0); + NS_ENSURE_SUCCESS(rv, rv); + + rv = Read16(&aResult->m1); + NS_ENSURE_SUCCESS(rv, rv); + + rv = Read16(&aResult->m2); + NS_ENSURE_SUCCESS(rv, rv); + + for (int i = 0; i < 8; ++i) { + rv = Read8(&aResult->m3[i]); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP_(char*) +nsBinaryInputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask) +{ + if (mBufferAccess) + return mBufferAccess->GetBuffer(aLength, aAlignMask); + return nullptr; +} + +NS_IMETHODIMP_(void) +nsBinaryInputStream::PutBuffer(char* aBuffer, uint32_t aLength) +{ + if (mBufferAccess) + mBufferAccess->PutBuffer(aBuffer, aLength); +} diff --git a/xpcom/io/nsBinaryStream.h b/xpcom/io/nsBinaryStream.h new file mode 100644 index 000000000..3748baf8f --- /dev/null +++ b/xpcom/io/nsBinaryStream.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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/. */ + +#ifndef nsBinaryStream_h___ +#define nsBinaryStream_h___ + +#include "nsCOMPtr.h" +#include "nsAString.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIStreamBufferAccess.h" + +#define NS_BINARYOUTPUTSTREAM_CID \ +{ /* 86c37b9a-74e7-4672-844e-6e7dd83ba484 */ \ + 0x86c37b9a, \ + 0x74e7, \ + 0x4672, \ + {0x84, 0x4e, 0x6e, 0x7d, 0xd8, 0x3b, 0xa4, 0x84} \ +} + +#define NS_BINARYOUTPUTSTREAM_CONTRACTID "@mozilla.org/binaryoutputstream;1" + +// Derive from nsIObjectOutputStream so this class can be used as a superclass +// by nsObjectOutputStream. +class nsBinaryOutputStream : public nsIObjectOutputStream +{ +public: + nsBinaryOutputStream() {} + // virtual dtor since subclasses call our Release() + virtual ~nsBinaryOutputStream() {} + +protected: + // nsISupports methods + NS_DECL_ISUPPORTS + + // nsIOutputStream methods + NS_DECL_NSIOUTPUTSTREAM + + // nsIBinaryOutputStream methods + NS_DECL_NSIBINARYOUTPUTSTREAM + + // nsIObjectOutputStream methods + NS_DECL_NSIOBJECTOUTPUTSTREAM + + // Call Write(), ensuring that all proffered data is written + nsresult WriteFully(const char *aBuf, uint32_t aCount); + + nsCOMPtr<nsIOutputStream> mOutputStream; + nsCOMPtr<nsIStreamBufferAccess> mBufferAccess; +}; + +#define NS_BINARYINPUTSTREAM_CID \ +{ /* c521a612-2aad-46db-b6ab-3b821fb150b1 */ \ + 0xc521a612, \ + 0x2aad, \ + 0x46db, \ + {0xb6, 0xab, 0x3b, 0x82, 0x1f, 0xb1, 0x50, 0xb1} \ +} + +#define NS_BINARYINPUTSTREAM_CONTRACTID "@mozilla.org/binaryinputstream;1" + +// Derive from nsIObjectInputStream so this class can be used as a superclass +// by nsObjectInputStream. +class nsBinaryInputStream : public nsIObjectInputStream +{ +public: + nsBinaryInputStream() {} + // virtual dtor since subclasses call our Release() + virtual ~nsBinaryInputStream() {} + +protected: + // nsISupports methods + NS_DECL_ISUPPORTS + + // nsIInputStream methods + NS_DECL_NSIINPUTSTREAM + + // nsIBinaryInputStream methods + NS_DECL_NSIBINARYINPUTSTREAM + + // nsIObjectInputStream methods + NS_DECL_NSIOBJECTINPUTSTREAM + + nsCOMPtr<nsIInputStream> mInputStream; + nsCOMPtr<nsIStreamBufferAccess> mBufferAccess; +}; + +#endif // nsBinaryStream_h___ diff --git a/xpcom/io/nsDirectoryService.cpp b/xpcom/io/nsDirectoryService.cpp new file mode 100644 index 000000000..f013a4f11 --- /dev/null +++ b/xpcom/io/nsDirectoryService.cpp @@ -0,0 +1,933 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=4 ts=4 sts=4 et + * 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 "mozilla/Util.h" + +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsLocalFile.h" +#include "nsDebug.h" +#include "nsStaticAtom.h" +#include "nsEnumeratorUtils.h" + +#include "nsICategoryManager.h" +#include "nsISimpleEnumerator.h" +#include "nsIStringEnumerator.h" + +#if defined(XP_WIN) +#include <windows.h> +#include <shlobj.h> +#include <stdlib.h> +#include <stdio.h> +#elif defined(XP_UNIX) +#include <unistd.h> +#include <stdlib.h> +#include <sys/param.h> +#include "prenv.h" +#ifdef MOZ_WIDGET_COCOA +#include <CoreServices/CoreServices.h> +#include <Carbon/Carbon.h> +#endif +#elif defined(XP_OS2) +#define MAX_PATH _MAX_PATH +#endif + +#include "SpecialSystemDirectory.h" +#include "nsAppFileLocationProvider.h" + +using namespace mozilla; + +#define COMPONENT_DIRECTORY NS_LITERAL_CSTRING("components") + +// define home directory +// For Windows platform, We are choosing Appdata folder as HOME +#if defined (XP_WIN) +#define HOME_DIR NS_WIN_APPDATA_DIR +#elif defined (MOZ_WIDGET_COCOA) +#define HOME_DIR NS_OSX_HOME_DIR +#elif defined (XP_UNIX) +#define HOME_DIR NS_UNIX_HOME_DIR +#elif defined (XP_OS2) +#define HOME_DIR NS_OS2_HOME_DIR +#endif + +//---------------------------------------------------------------------------------------- +nsresult +nsDirectoryService::GetCurrentProcessDirectory(nsIFile** aFile) +//---------------------------------------------------------------------------------------- +{ + NS_ENSURE_ARG_POINTER(aFile); + *aFile = nullptr; + + // Set the component registry location: + if (!gService) + return NS_ERROR_FAILURE; + + nsresult rv; + + nsCOMPtr<nsIProperties> dirService; + rv = nsDirectoryService::Create(nullptr, + NS_GET_IID(nsIProperties), + getter_AddRefs(dirService)); // needs to be around for life of product + if (NS_FAILED(rv)) + return rv; + + if (dirService) + { + nsCOMPtr <nsIFile> aLocalFile; + dirService->Get(NS_XPCOM_INIT_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(aLocalFile)); + if (aLocalFile) + { + *aFile = aLocalFile; + NS_ADDREF(*aFile); + return NS_OK; + } + } + + nsLocalFile* localFile = new nsLocalFile; + + if (localFile == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(localFile); + + + +#ifdef XP_WIN + PRUnichar buf[MAX_PATH + 1]; + SetLastError(ERROR_SUCCESS); + if (GetModuleFileNameW(0, buf, mozilla::ArrayLength(buf)) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + // chop off the executable name by finding the rightmost backslash + PRUnichar* lastSlash = wcsrchr(buf, L'\\'); + if (lastSlash) + *(lastSlash + 1) = L'\0'; + + localFile->InitWithPath(nsDependentString(buf)); + *aFile = localFile; + return NS_OK; + } + +#elif defined(MOZ_WIDGET_COCOA) + // Works even if we're not bundled. + CFBundleRef appBundle = CFBundleGetMainBundle(); + if (appBundle != nullptr) + { + CFURLRef bundleURL = CFBundleCopyExecutableURL(appBundle); + if (bundleURL != nullptr) + { + CFURLRef parentURL = CFURLCreateCopyDeletingLastPathComponent(kCFAllocatorDefault, bundleURL); + if (parentURL) + { + // Pass true for the "resolveAgainstBase" arg to CFURLGetFileSystemRepresentation. + // This will resolve the relative portion of the CFURL against it base, giving a full + // path, which CFURLCopyFileSystemPath doesn't do. + char buffer[PATH_MAX]; + if (CFURLGetFileSystemRepresentation(parentURL, true, (UInt8 *)buffer, sizeof(buffer))) + { +#ifdef DEBUG_conrad + printf("nsDirectoryService - CurrentProcessDir is: %s\n", buffer); +#endif + rv = localFile->InitWithNativePath(nsDependentCString(buffer)); + if (NS_SUCCEEDED(rv)) + *aFile = localFile; + } + CFRelease(parentURL); + } + CFRelease(bundleURL); + } + } + + NS_ASSERTION(*aFile, "nsDirectoryService - Could not determine CurrentProcessDir.\n"); + if (*aFile) + return NS_OK; + +#elif defined(XP_UNIX) + + // In the absence of a good way to get the executable directory let + // us try this for unix: + // - if MOZILLA_FIVE_HOME is defined, that is it + // - else give the current directory + char buf[MAXPATHLEN]; + + // The MOZ_DEFAULT_MOZILLA_FIVE_HOME variable can be set at configure time with + // a --with-default-mozilla-five-home=foo autoconf flag. + // + // The idea here is to allow for builds that have a default MOZILLA_FIVE_HOME + // regardless of the environment. This makes it easier to write apps that + // embed mozilla without having to worry about setting up the environment + // + // We do this by putenv()ing the default value into the environment. Note that + // we only do this if it is not already set. +#ifdef MOZ_DEFAULT_MOZILLA_FIVE_HOME + const char *home = PR_GetEnv("MOZILLA_FIVE_HOME"); + if (!home || !*home) + { + putenv("MOZILLA_FIVE_HOME=" MOZ_DEFAULT_MOZILLA_FIVE_HOME); + } +#endif + + char *moz5 = PR_GetEnv("MOZILLA_FIVE_HOME"); + if (moz5 && *moz5) + { + if (realpath(moz5, buf)) { + localFile->InitWithNativePath(nsDependentCString(buf)); + *aFile = localFile; + return NS_OK; + } + } +#if defined(DEBUG) + static bool firstWarning = true; + + if((!moz5 || !*moz5) && firstWarning) { + // Warn that MOZILLA_FIVE_HOME not set, once. + printf("Warning: MOZILLA_FIVE_HOME not set.\n"); + firstWarning = false; + } +#endif /* DEBUG */ + + // Fall back to current directory. + if (getcwd(buf, sizeof(buf))) + { + localFile->InitWithNativePath(nsDependentCString(buf)); + *aFile = localFile; + return NS_OK; + } + +#elif defined(XP_OS2) + PPIB ppib; + PTIB ptib; + char buffer[CCHMAXPATH]; + DosGetInfoBlocks( &ptib, &ppib); + DosQueryModuleName( ppib->pib_hmte, CCHMAXPATH, buffer); + *strrchr( buffer, '\\') = '\0'; // XXX DBCS misery + localFile->InitWithNativePath(nsDependentCString(buffer)); + *aFile = localFile; + return NS_OK; + +#endif + + NS_RELEASE(localFile); + + NS_ERROR("unable to get current process directory"); + return NS_ERROR_FAILURE; +} // GetCurrentProcessDirectory() + +nsDirectoryService* nsDirectoryService::gService = nullptr; + +nsDirectoryService::nsDirectoryService() : + mHashtable(256, true) +{ +} + +nsresult +nsDirectoryService::Create(nsISupports *outer, REFNSIID aIID, void **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_NO_AGGREGATION(outer); + + if (!gService) + { + return NS_ERROR_NOT_INITIALIZED; + } + + return gService->QueryInterface(aIID, aResult); +} + +#define DIR_ATOM(name_, value_) nsIAtom* nsDirectoryService::name_ = nullptr; +#include "nsDirectoryServiceAtomList.h" +#undef DIR_ATOM + +#define DIR_ATOM(name_, value_) NS_STATIC_ATOM_BUFFER(name_##_buffer, value_) +#include "nsDirectoryServiceAtomList.h" +#undef DIR_ATOM + +static const nsStaticAtom directory_atoms[] = { +#define DIR_ATOM(name_, value_) NS_STATIC_ATOM(name_##_buffer, &nsDirectoryService::name_), +#include "nsDirectoryServiceAtomList.h" +#undef DIR_ATOM +}; + +NS_IMETHODIMP +nsDirectoryService::Init() +{ + NS_NOTREACHED("nsDirectoryService::Init() for internal use only!"); + return NS_OK; +} + +void +nsDirectoryService::RealInit() +{ + NS_ASSERTION(!gService, + "nsDirectoryService::RealInit Mustn't initialize twice!"); + + nsRefPtr<nsDirectoryService> self = new nsDirectoryService(); + + NS_RegisterStaticAtoms(directory_atoms); + + // Let the list hold the only reference to the provider. + nsAppFileLocationProvider *defaultProvider = new nsAppFileLocationProvider; + self->mProviders.AppendElement(defaultProvider); + + self.swap(gService); +} + +bool +nsDirectoryService::ReleaseValues(nsHashKey* key, void* data, void* closure) +{ + nsISupports* value = (nsISupports*)data; + NS_IF_RELEASE(value); + return true; +} + +nsDirectoryService::~nsDirectoryService() +{ +} + +NS_IMPL_THREADSAFE_ISUPPORTS4(nsDirectoryService, nsIProperties, nsIDirectoryService, nsIDirectoryServiceProvider, nsIDirectoryServiceProvider2) + + +NS_IMETHODIMP +nsDirectoryService::Undefine(const char* prop) +{ + NS_ENSURE_ARG(prop); + + nsCStringKey key(prop); + if (!mHashtable.Exists(&key)) + return NS_ERROR_FAILURE; + + mHashtable.Remove (&key); + return NS_OK; + } + +NS_IMETHODIMP +nsDirectoryService::GetKeys(uint32_t *count, char ***keys) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +struct FileData +{ + FileData(const char* aProperty, + const nsIID& aUUID) : + property(aProperty), + data(nullptr), + persistent(true), + uuid(aUUID) {} + + const char* property; + nsISupports* data; + bool persistent; + const nsIID& uuid; +}; + +static bool FindProviderFile(nsIDirectoryServiceProvider* aElement, + FileData* aData) +{ + nsresult rv; + if (aData->uuid.Equals(NS_GET_IID(nsISimpleEnumerator))) { + // Not all providers implement this iface + nsCOMPtr<nsIDirectoryServiceProvider2> prov2 = do_QueryInterface(aElement); + if (prov2) + { + nsCOMPtr<nsISimpleEnumerator> newFiles; + rv = prov2->GetFiles(aData->property, getter_AddRefs(newFiles)); + if (NS_SUCCEEDED(rv) && newFiles) { + if (aData->data) { + nsCOMPtr<nsISimpleEnumerator> unionFiles; + + NS_NewUnionEnumerator(getter_AddRefs(unionFiles), + (nsISimpleEnumerator*) aData->data, newFiles); + + if (unionFiles) + unionFiles.swap(* (nsISimpleEnumerator**) &aData->data); + } + else + { + NS_ADDREF(aData->data = newFiles); + } + + aData->persistent = false; // Enumerators can never be persistent + return rv == NS_SUCCESS_AGGREGATE_RESULT; + } + } + } + else + { + rv = aElement->GetFile(aData->property, &aData->persistent, + (nsIFile **)&aData->data); + if (NS_SUCCEEDED(rv) && aData->data) + return false; + } + + return true; +} + +NS_IMETHODIMP +nsDirectoryService::Get(const char* prop, const nsIID & uuid, void* *result) +{ + NS_ENSURE_ARG(prop); + + nsCStringKey key(prop); + + nsCOMPtr<nsISupports> value = dont_AddRef(mHashtable.Get(&key)); + + if (value) + { + nsCOMPtr<nsIFile> cloneFile; + nsCOMPtr<nsIFile> cachedFile = do_QueryInterface(value); + NS_ASSERTION(cachedFile, + "nsDirectoryService::Get nsIFile expected"); + + cachedFile->Clone(getter_AddRefs(cloneFile)); + return cloneFile->QueryInterface(uuid, result); + } + + // it is not one of our defaults, lets check any providers + FileData fileData(prop, uuid); + + for (int32_t i = mProviders.Length() - 1; i >= 0; i--) { + if (!FindProviderFile(mProviders[i], &fileData)) { + break; + } + } + if (fileData.data) + { + if (fileData.persistent) + { + Set(prop, static_cast<nsIFile*>(fileData.data)); + } + nsresult rv = (fileData.data)->QueryInterface(uuid, result); + NS_RELEASE(fileData.data); // addref occurs in FindProviderFile() + return rv; + } + + FindProviderFile(static_cast<nsIDirectoryServiceProvider*>(this), &fileData); + if (fileData.data) + { + if (fileData.persistent) + { + Set(prop, static_cast<nsIFile*>(fileData.data)); + } + nsresult rv = (fileData.data)->QueryInterface(uuid, result); + NS_RELEASE(fileData.data); // addref occurs in FindProviderFile() + return rv; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDirectoryService::Set(const char* prop, nsISupports* value) +{ + NS_ENSURE_ARG(prop); + + nsCStringKey key(prop); + if (mHashtable.Exists(&key) || value == nullptr) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIFile> ourFile; + value->QueryInterface(NS_GET_IID(nsIFile), getter_AddRefs(ourFile)); + if (ourFile) + { + nsCOMPtr<nsIFile> cloneFile; + ourFile->Clone (getter_AddRefs (cloneFile)); + mHashtable.Put(&key, cloneFile); + + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDirectoryService::Has(const char *prop, bool *_retval) +{ + NS_ENSURE_ARG(prop); + + *_retval = false; + nsCOMPtr<nsIFile> value; + nsresult rv = Get(prop, NS_GET_IID(nsIFile), getter_AddRefs(value)); + if (NS_FAILED(rv)) + return NS_OK; + + if (value) + { + *_retval = true; + } + + return rv; +} + +NS_IMETHODIMP +nsDirectoryService::RegisterProvider(nsIDirectoryServiceProvider *prov) +{ + if (!prov) + return NS_ERROR_FAILURE; + + mProviders.AppendElement(prov); + return NS_OK; +} + +void +nsDirectoryService::RegisterCategoryProviders() +{ + nsCOMPtr<nsICategoryManager> catman + (do_GetService(NS_CATEGORYMANAGER_CONTRACTID)); + if (!catman) + return; + + nsCOMPtr<nsISimpleEnumerator> entries; + catman->EnumerateCategory(XPCOM_DIRECTORY_PROVIDER_CATEGORY, + getter_AddRefs(entries)); + + nsCOMPtr<nsIUTF8StringEnumerator> strings(do_QueryInterface(entries)); + if (!strings) + return; + + bool more; + while (NS_SUCCEEDED(strings->HasMore(&more)) && more) { + nsAutoCString entry; + strings->GetNext(entry); + + nsXPIDLCString contractID; + catman->GetCategoryEntry(XPCOM_DIRECTORY_PROVIDER_CATEGORY, entry.get(), getter_Copies(contractID)); + + if (contractID) { + nsCOMPtr<nsIDirectoryServiceProvider> provider = do_GetService(contractID.get()); + if (provider) + RegisterProvider(provider); + } + } +} + +NS_IMETHODIMP +nsDirectoryService::UnregisterProvider(nsIDirectoryServiceProvider *prov) +{ + if (!prov) + return NS_ERROR_FAILURE; + + mProviders.RemoveElement(prov); + return NS_OK; +} + +// DO NOT ADD ANY LOCATIONS TO THIS FUNCTION UNTIL YOU TALK TO: dougt@netscape.com. +// This is meant to be a place of xpcom or system specific file locations, not +// application specific locations. If you need the later, register a callback for +// your application. + +NS_IMETHODIMP +nsDirectoryService::GetFile(const char *prop, bool *persistent, nsIFile **_retval) +{ + nsCOMPtr<nsIFile> localFile; + nsresult rv = NS_ERROR_FAILURE; + + *_retval = nullptr; + *persistent = true; + + nsCOMPtr<nsIAtom> inAtom = do_GetAtom(prop); + + // check to see if it is one of our defaults + + if (inAtom == nsDirectoryService::sCurrentProcess || + inAtom == nsDirectoryService::sOS_CurrentProcessDirectory ) + { + rv = GetCurrentProcessDirectory(getter_AddRefs(localFile)); + } + + // Unless otherwise set, the core pieces of the GRE exist + // in the current process directory. + else if (inAtom == nsDirectoryService::sGRE_Directory) + { + rv = GetCurrentProcessDirectory(getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sOS_DriveDirectory) + { + rv = GetSpecialSystemDirectory(OS_DriveDirectory, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sOS_TemporaryDirectory) + { + rv = GetSpecialSystemDirectory(OS_TemporaryDirectory, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sOS_CurrentProcessDirectory) + { + rv = GetSpecialSystemDirectory(OS_CurrentProcessDirectory, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sOS_CurrentWorkingDirectory) + { + rv = GetSpecialSystemDirectory(OS_CurrentWorkingDirectory, getter_AddRefs(localFile)); + } + +#if defined(MOZ_WIDGET_COCOA) + else if (inAtom == nsDirectoryService::sDirectory) + { + rv = GetOSXFolderType(kClassicDomain, kSystemFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sTrashDirectory) + { + rv = GetOSXFolderType(kClassicDomain, kTrashFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sStartupDirectory) + { + rv = GetOSXFolderType(kClassicDomain, kStartupFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sShutdownDirectory) + { + rv = GetOSXFolderType(kClassicDomain, kShutdownFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sAppleMenuDirectory) + { + rv = GetOSXFolderType(kClassicDomain, kAppleMenuFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sControlPanelDirectory) + { + rv = GetOSXFolderType(kClassicDomain, kControlPanelFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sExtensionDirectory) + { + rv = GetOSXFolderType(kClassicDomain, kExtensionFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sFontsDirectory) + { + rv = GetOSXFolderType(kClassicDomain, kFontsFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sPreferencesDirectory) + { + rv = GetOSXFolderType(kClassicDomain, kPreferencesFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sDocumentsDirectory) + { + rv = GetOSXFolderType(kClassicDomain, kDocumentsFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sInternetSearchDirectory) + { + rv = GetOSXFolderType(kClassicDomain, kInternetSearchSitesFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sUserLibDirectory) + { + rv = GetOSXFolderType(kUserDomain, kDomainLibraryFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sOS_HomeDirectory) + { + rv = GetOSXFolderType(kUserDomain, kDomainTopLevelFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sDefaultDownloadDirectory) + { + // 10.5 and later, we can use kDownloadsFolderType which is defined in + // Folders.h as "down". However, in order to support 10.4 still, we + // cannot use the named constant. We'll use it's value, and if it + // fails, fall back to the desktop. +#ifndef kDownloadsFolderType +#define kDownloadsFolderType 'down' +#endif + + rv = GetOSXFolderType(kUserDomain, kDownloadsFolderType, + getter_AddRefs(localFile)); + if (NS_FAILED(rv)) { + rv = GetOSXFolderType(kUserDomain, kDesktopFolderType, + getter_AddRefs(localFile)); + } + } + else if (inAtom == nsDirectoryService::sUserDesktopDirectory || + inAtom == nsDirectoryService::sOS_DesktopDirectory) + { + rv = GetOSXFolderType(kUserDomain, kDesktopFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sLocalDesktopDirectory) + { + rv = GetOSXFolderType(kLocalDomain, kDesktopFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sUserApplicationsDirectory) + { + rv = GetOSXFolderType(kUserDomain, kApplicationsFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sLocalApplicationsDirectory) + { + rv = GetOSXFolderType(kLocalDomain, kApplicationsFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sUserDocumentsDirectory) + { + rv = GetOSXFolderType(kUserDomain, kDocumentsFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sLocalDocumentsDirectory) + { + rv = GetOSXFolderType(kLocalDomain, kDocumentsFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sUserInternetPlugInDirectory) + { + rv = GetOSXFolderType(kUserDomain, kInternetPlugInFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sLocalInternetPlugInDirectory) + { + rv = GetOSXFolderType(kLocalDomain, kInternetPlugInFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sUserFrameworksDirectory) + { + rv = GetOSXFolderType(kUserDomain, kFrameworksFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sLocalFrameworksDirectory) + { + rv = GetOSXFolderType(kLocalDomain, kFrameworksFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sUserPreferencesDirectory) + { + rv = GetOSXFolderType(kUserDomain, kPreferencesFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sLocalPreferencesDirectory) + { + rv = GetOSXFolderType(kLocalDomain, kPreferencesFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sPictureDocumentsDirectory) + { + rv = GetOSXFolderType(kUserDomain, kPictureDocumentsFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sMovieDocumentsDirectory) + { + rv = GetOSXFolderType(kUserDomain, kMovieDocumentsFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sMusicDocumentsDirectory) + { + rv = GetOSXFolderType(kUserDomain, kMusicDocumentsFolderType, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sInternetSitesDirectory) + { + rv = GetOSXFolderType(kUserDomain, kInternetSitesFolderType, getter_AddRefs(localFile)); + } +#elif defined (XP_WIN) + else if (inAtom == nsDirectoryService::sSystemDirectory) + { + rv = GetSpecialSystemDirectory(Win_SystemDirectory, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sWindowsDirectory) + { + rv = GetSpecialSystemDirectory(Win_WindowsDirectory, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sWindowsProgramFiles) + { + rv = GetSpecialSystemDirectory(Win_ProgramFiles, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sOS_HomeDirectory) + { + rv = GetSpecialSystemDirectory(Win_HomeDirectory, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sDesktop) + { + rv = GetSpecialSystemDirectory(Win_Desktop, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sPrograms) + { + rv = GetSpecialSystemDirectory(Win_Programs, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sControls) + { + rv = GetSpecialSystemDirectory(Win_Controls, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sPrinters) + { + rv = GetSpecialSystemDirectory(Win_Printers, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sPersonal) + { + rv = GetSpecialSystemDirectory(Win_Personal, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sFavorites) + { + rv = GetSpecialSystemDirectory(Win_Favorites, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sStartup) + { + rv = GetSpecialSystemDirectory(Win_Startup, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sRecent) + { + rv = GetSpecialSystemDirectory(Win_Recent, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sSendto) + { + rv = GetSpecialSystemDirectory(Win_Sendto, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sBitbucket) + { + rv = GetSpecialSystemDirectory(Win_Bitbucket, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sStartmenu) + { + rv = GetSpecialSystemDirectory(Win_Startmenu, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sDesktopdirectory || + inAtom == nsDirectoryService::sOS_DesktopDirectory) + { + rv = GetSpecialSystemDirectory(Win_Desktopdirectory, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sDrives) + { + rv = GetSpecialSystemDirectory(Win_Drives, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sNetwork) + { + rv = GetSpecialSystemDirectory(Win_Network, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sNethood) + { + rv = GetSpecialSystemDirectory(Win_Nethood, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sFonts) + { + rv = GetSpecialSystemDirectory(Win_Fonts, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sTemplates) + { + rv = GetSpecialSystemDirectory(Win_Templates, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sCommon_Startmenu) + { + rv = GetSpecialSystemDirectory(Win_Common_Startmenu, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sCommon_Programs) + { + rv = GetSpecialSystemDirectory(Win_Common_Programs, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sCommon_Startup) + { + rv = GetSpecialSystemDirectory(Win_Common_Startup, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sCommon_Desktopdirectory) + { + rv = GetSpecialSystemDirectory(Win_Common_Desktopdirectory, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sCommon_AppData) + { + rv = GetSpecialSystemDirectory(Win_Common_AppData, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sAppdata) + { + rv = GetSpecialSystemDirectory(Win_Appdata, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sLocalAppdata) + { + rv = GetSpecialSystemDirectory(Win_LocalAppdata, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sPrinthood) + { + rv = GetSpecialSystemDirectory(Win_Printhood, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sWinCookiesDirectory) + { + rv = GetSpecialSystemDirectory(Win_Cookies, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sDefaultDownloadDirectory) + { + rv = GetSpecialSystemDirectory(Win_Downloads, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sDocs) + { + rv = GetSpecialSystemDirectory(Win_Documents, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sPictures) + { + rv = GetSpecialSystemDirectory(Win_Pictures, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sMusic) + { + rv = GetSpecialSystemDirectory(Win_Music, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sVideos) + { + rv = GetSpecialSystemDirectory(Win_Videos, getter_AddRefs(localFile)); + } +#elif defined (XP_UNIX) + + else if (inAtom == nsDirectoryService::sLocalDirectory) + { + rv = GetSpecialSystemDirectory(Unix_LocalDirectory, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sLibDirectory) + { + rv = GetSpecialSystemDirectory(Unix_LibDirectory, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sOS_HomeDirectory) + { + rv = GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sXDGDesktop || + inAtom == nsDirectoryService::sOS_DesktopDirectory) + { + rv = GetSpecialSystemDirectory(Unix_XDG_Desktop, getter_AddRefs(localFile)); + *persistent = false; + } + else if (inAtom == nsDirectoryService::sXDGDocuments) + { + rv = GetSpecialSystemDirectory(Unix_XDG_Documents, getter_AddRefs(localFile)); + *persistent = false; + } + else if (inAtom == nsDirectoryService::sXDGDownload || + inAtom == nsDirectoryService::sDefaultDownloadDirectory) + { + rv = GetSpecialSystemDirectory(Unix_XDG_Download, getter_AddRefs(localFile)); + *persistent = false; + } + else if (inAtom == nsDirectoryService::sXDGMusic) + { + rv = GetSpecialSystemDirectory(Unix_XDG_Music, getter_AddRefs(localFile)); + *persistent = false; + } + else if (inAtom == nsDirectoryService::sXDGPictures) + { + rv = GetSpecialSystemDirectory(Unix_XDG_Pictures, getter_AddRefs(localFile)); + *persistent = false; + } + else if (inAtom == nsDirectoryService::sXDGPublicShare) + { + rv = GetSpecialSystemDirectory(Unix_XDG_PublicShare, getter_AddRefs(localFile)); + *persistent = false; + } + else if (inAtom == nsDirectoryService::sXDGTemplates) + { + rv = GetSpecialSystemDirectory(Unix_XDG_Templates, getter_AddRefs(localFile)); + *persistent = false; + } + else if (inAtom == nsDirectoryService::sXDGVideos) + { + rv = GetSpecialSystemDirectory(Unix_XDG_Videos, getter_AddRefs(localFile)); + *persistent = false; + } +#elif defined (XP_OS2) + else if (inAtom == nsDirectoryService::sSystemDirectory) + { + rv = GetSpecialSystemDirectory(OS2_SystemDirectory, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sOS2Directory) + { + rv = GetSpecialSystemDirectory(OS2_OS2Directory, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sOS_HomeDirectory) + { + rv = GetSpecialSystemDirectory(OS2_HomeDirectory, getter_AddRefs(localFile)); + } + else if (inAtom == nsDirectoryService::sOS_DesktopDirectory) + { + rv = GetSpecialSystemDirectory(OS2_DesktopDirectory, getter_AddRefs(localFile)); + } +#endif + + if (NS_FAILED(rv)) + return rv; + + if (!localFile) + return NS_ERROR_FAILURE; + + return CallQueryInterface(localFile, _retval); +} + +NS_IMETHODIMP +nsDirectoryService::GetFiles(const char *prop, nsISimpleEnumerator **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nullptr; + + return NS_ERROR_FAILURE; +} diff --git a/xpcom/io/nsDirectoryService.h b/xpcom/io/nsDirectoryService.h new file mode 100644 index 000000000..ed9408b82 --- /dev/null +++ b/xpcom/io/nsDirectoryService.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsDirectoryService_h___ +#define nsDirectoryService_h___ + +#include "nsIDirectoryService.h" +#include "nsHashtable.h" +#include "nsIFile.h" +#include "nsIAtom.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" + +#define NS_XPCOM_INIT_CURRENT_PROCESS_DIR "MozBinD" // Can be used to set NS_XPCOM_CURRENT_PROCESS_DIR + // CANNOT be used to GET a location +#define NS_DIRECTORY_SERVICE_CID {0xf00152d0,0xb40b,0x11d3,{0x8c, 0x9c, 0x00, 0x00, 0x64, 0x65, 0x73, 0x74}} + +class nsDirectoryService MOZ_FINAL : public nsIDirectoryService, + public nsIProperties, + public nsIDirectoryServiceProvider2 +{ + public: + + // nsISupports interface + NS_DECL_ISUPPORTS + + NS_DECL_NSIPROPERTIES + + NS_DECL_NSIDIRECTORYSERVICE + + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + + NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 + + nsDirectoryService(); + ~nsDirectoryService(); + + static void RealInit(); + void RegisterCategoryProviders(); + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + + static nsDirectoryService* gService; + +private: + nsresult GetCurrentProcessDirectory(nsIFile** aFile); + + static bool ReleaseValues(nsHashKey* key, void* data, void* closure); + nsSupportsHashtable mHashtable; + nsTArray<nsCOMPtr<nsIDirectoryServiceProvider> > mProviders; + +public: + +#define DIR_ATOM(name_, value_) static nsIAtom* name_; +#include "nsDirectoryServiceAtomList.h" +#undef DIR_ATOM + +}; + + +#endif + diff --git a/xpcom/io/nsDirectoryServiceAtomList.h b/xpcom/io/nsDirectoryServiceAtomList.h new file mode 100644 index 000000000..d46e57343 --- /dev/null +++ b/xpcom/io/nsDirectoryServiceAtomList.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +DIR_ATOM(sCurrentProcess, NS_XPCOM_CURRENT_PROCESS_DIR) +DIR_ATOM(sGRE_Directory, NS_GRE_DIR) +DIR_ATOM(sOS_DriveDirectory, NS_OS_DRIVE_DIR) +DIR_ATOM(sOS_TemporaryDirectory, NS_OS_TEMP_DIR) +DIR_ATOM(sOS_CurrentProcessDirectory, NS_OS_CURRENT_PROCESS_DIR) +DIR_ATOM(sOS_CurrentWorkingDirectory, NS_OS_CURRENT_WORKING_DIR) +DIR_ATOM(sOS_HomeDirectory, NS_OS_HOME_DIR) +DIR_ATOM(sOS_DesktopDirectory, NS_OS_DESKTOP_DIR) +DIR_ATOM(sInitCurrentProcess_dummy, NS_XPCOM_INIT_CURRENT_PROCESS_DIR) +#if defined (MOZ_WIDGET_COCOA) +DIR_ATOM(sDirectory, NS_OS_SYSTEM_DIR) +DIR_ATOM(sTrashDirectory, NS_MAC_TRASH_DIR) +DIR_ATOM(sStartupDirectory, NS_MAC_STARTUP_DIR) +DIR_ATOM(sShutdownDirectory, NS_MAC_SHUTDOWN_DIR) +DIR_ATOM(sAppleMenuDirectory, NS_MAC_APPLE_MENU_DIR) +DIR_ATOM(sControlPanelDirectory, NS_MAC_CONTROL_PANELS_DIR) +DIR_ATOM(sExtensionDirectory, NS_MAC_EXTENSIONS_DIR) +DIR_ATOM(sFontsDirectory, NS_MAC_FONTS_DIR) +DIR_ATOM(sPreferencesDirectory, NS_MAC_PREFS_DIR) +DIR_ATOM(sDocumentsDirectory, NS_MAC_DOCUMENTS_DIR) +DIR_ATOM(sInternetSearchDirectory, NS_MAC_INTERNET_SEARCH_DIR) +DIR_ATOM(sUserLibDirectory, NS_MAC_USER_LIB_DIR) +DIR_ATOM(sDefaultDownloadDirectory, NS_OSX_DEFAULT_DOWNLOAD_DIR) +DIR_ATOM(sUserDesktopDirectory, NS_OSX_USER_DESKTOP_DIR) +DIR_ATOM(sLocalDesktopDirectory, NS_OSX_LOCAL_DESKTOP_DIR) +DIR_ATOM(sUserApplicationsDirectory, NS_OSX_USER_APPLICATIONS_DIR) +DIR_ATOM(sLocalApplicationsDirectory, NS_OSX_LOCAL_APPLICATIONS_DIR) +DIR_ATOM(sUserDocumentsDirectory, NS_OSX_USER_DOCUMENTS_DIR) +DIR_ATOM(sLocalDocumentsDirectory, NS_OSX_LOCAL_DOCUMENTS_DIR) +DIR_ATOM(sUserInternetPlugInDirectory, NS_OSX_USER_INTERNET_PLUGIN_DIR) +DIR_ATOM(sLocalInternetPlugInDirectory, NS_OSX_LOCAL_INTERNET_PLUGIN_DIR) +DIR_ATOM(sUserFrameworksDirectory, NS_OSX_USER_FRAMEWORKS_DIR) +DIR_ATOM(sLocalFrameworksDirectory, NS_OSX_LOCAL_FRAMEWORKS_DIR) +DIR_ATOM(sUserPreferencesDirectory, NS_OSX_USER_PREFERENCES_DIR) +DIR_ATOM(sLocalPreferencesDirectory, NS_OSX_LOCAL_PREFERENCES_DIR) +DIR_ATOM(sPictureDocumentsDirectory, NS_OSX_PICTURE_DOCUMENTS_DIR) +DIR_ATOM(sMovieDocumentsDirectory, NS_OSX_MOVIE_DOCUMENTS_DIR) +DIR_ATOM(sMusicDocumentsDirectory, NS_OSX_MUSIC_DOCUMENTS_DIR) +DIR_ATOM(sInternetSitesDirectory, NS_OSX_INTERNET_SITES_DIR) +#elif defined (XP_WIN) +DIR_ATOM(sSystemDirectory, NS_OS_SYSTEM_DIR) +DIR_ATOM(sWindowsDirectory, NS_WIN_WINDOWS_DIR) +DIR_ATOM(sWindowsProgramFiles, NS_WIN_PROGRAM_FILES_DIR) +DIR_ATOM(sDesktop, NS_WIN_DESKTOP_DIR) +DIR_ATOM(sPrograms, NS_WIN_PROGRAMS_DIR) +DIR_ATOM(sControls, NS_WIN_CONTROLS_DIR) +DIR_ATOM(sPrinters, NS_WIN_PRINTERS_DIR) +DIR_ATOM(sPersonal, NS_WIN_PERSONAL_DIR) +DIR_ATOM(sFavorites, NS_WIN_FAVORITES_DIR) +DIR_ATOM(sStartup, NS_WIN_STARTUP_DIR) +DIR_ATOM(sRecent, NS_WIN_RECENT_DIR) +DIR_ATOM(sSendto, NS_WIN_SEND_TO_DIR) +DIR_ATOM(sBitbucket, NS_WIN_BITBUCKET_DIR) +DIR_ATOM(sStartmenu, NS_WIN_STARTMENU_DIR) +DIR_ATOM(sDesktopdirectory, NS_WIN_DESKTOP_DIRECTORY) +DIR_ATOM(sDrives, NS_WIN_DRIVES_DIR) +DIR_ATOM(sNetwork, NS_WIN_NETWORK_DIR) +DIR_ATOM(sNethood, NS_WIN_NETHOOD_DIR) +DIR_ATOM(sFonts, NS_WIN_FONTS_DIR) +DIR_ATOM(sTemplates, NS_WIN_TEMPLATES_DIR) +DIR_ATOM(sCommon_Startmenu, NS_WIN_COMMON_STARTMENU_DIR) +DIR_ATOM(sCommon_Programs, NS_WIN_COMMON_PROGRAMS_DIR) +DIR_ATOM(sCommon_Startup, NS_WIN_COMMON_STARTUP_DIR) +DIR_ATOM(sCommon_Desktopdirectory, NS_WIN_COMMON_DESKTOP_DIRECTORY) +DIR_ATOM(sCommon_AppData, NS_WIN_COMMON_APPDATA_DIR) +DIR_ATOM(sAppdata, NS_WIN_APPDATA_DIR) +DIR_ATOM(sLocalAppdata, NS_WIN_LOCAL_APPDATA_DIR) +DIR_ATOM(sPrinthood, NS_WIN_PRINTHOOD) +DIR_ATOM(sWinCookiesDirectory, NS_WIN_COOKIES_DIR) +DIR_ATOM(sDefaultDownloadDirectory, NS_WIN_DEFAULT_DOWNLOAD_DIR) +DIR_ATOM(sDocs, NS_WIN_DOCUMENTS_DIR) +DIR_ATOM(sPictures, NS_WIN_PICTURES_DIR) +DIR_ATOM(sMusic, NS_WIN_MUSIC_DIR) +DIR_ATOM(sVideos, NS_WIN_VIDEOS_DIR) +#elif defined (XP_UNIX) +DIR_ATOM(sLocalDirectory, NS_UNIX_LOCAL_DIR) +DIR_ATOM(sLibDirectory, NS_UNIX_LIB_DIR) +DIR_ATOM(sDefaultDownloadDirectory, NS_UNIX_DEFAULT_DOWNLOAD_DIR) +DIR_ATOM(sXDGDesktop, NS_UNIX_XDG_DESKTOP_DIR) +DIR_ATOM(sXDGDocuments, NS_UNIX_XDG_DOCUMENTS_DIR) +DIR_ATOM(sXDGDownload, NS_UNIX_XDG_DOWNLOAD_DIR) +DIR_ATOM(sXDGMusic, NS_UNIX_XDG_MUSIC_DIR) +DIR_ATOM(sXDGPictures, NS_UNIX_XDG_PICTURES_DIR) +DIR_ATOM(sXDGPublicShare, NS_UNIX_XDG_PUBLIC_SHARE_DIR) +DIR_ATOM(sXDGTemplates, NS_UNIX_XDG_TEMPLATES_DIR) +DIR_ATOM(sXDGVideos, NS_UNIX_XDG_VIDEOS_DIR) +#elif defined (XP_OS2) +DIR_ATOM(sSystemDirectory, NS_OS_SYSTEM_DIR) +DIR_ATOM(sOS2Directory, NS_OS2_DIR) +#endif diff --git a/xpcom/io/nsDirectoryServiceDefs.h b/xpcom/io/nsDirectoryServiceDefs.h new file mode 100644 index 000000000..418c428a5 --- /dev/null +++ b/xpcom/io/nsDirectoryServiceDefs.h @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/** + * Defines the property names for directories available from + * nsIDirectoryService. These dirs are always available even if no + * nsIDirectoryServiceProviders have been registered with the service. + * Application level keys are defined in nsAppDirectoryServiceDefs.h. + * + * Keys whose definition ends in "DIR" or "FILE" return a single nsIFile (or + * subclass). Keys whose definition ends in "LIST" return an nsISimpleEnumerator + * which enumerates a list of file objects. + * + * Defines listed in this file are FROZEN. This list may grow. + */ + +#ifndef nsDirectoryServiceDefs_h___ +#define nsDirectoryServiceDefs_h___ + +/* General OS specific locations */ + +#define NS_OS_HOME_DIR "Home" +#define NS_OS_TEMP_DIR "TmpD" +#define NS_OS_CURRENT_WORKING_DIR "CurWorkD" +/* Files stored in this directory will appear on the user's desktop, + * if there is one, otherwise it's just the same as "Home" + */ +#define NS_OS_DESKTOP_DIR "Desk" + +/* Property returns the directory in which the procces was started from. + * On Unix this will be the path in the MOZILLA_FIVE_HOME env var and if + * unset will be the current working directory. + */ +#define NS_OS_CURRENT_PROCESS_DIR "CurProcD" + +/* This location is similar to NS_OS_CURRENT_PROCESS_DIR, however, + * NS_XPCOM_CURRENT_PROCESS_DIR can be overriden by passing a "bin + * directory" to NS_InitXPCOM2(). + */ +#define NS_XPCOM_CURRENT_PROCESS_DIR "XCurProcD" + +/* Property will return the location of the the XPCOM Shared Library. + */ +#define NS_XPCOM_LIBRARY_FILE "XpcomLib" + +/* Property will return the current location of the the GRE directory. + * If no GRE is used, this propery will behave like + * NS_XPCOM_CURRENT_PROCESS_DIR. + */ +#define NS_GRE_DIR "GreD" + +/* Platform Specific Locations */ + +#if !defined (XP_UNIX) || defined(MOZ_WIDGET_COCOA) + #define NS_OS_SYSTEM_DIR "SysD" +#endif + +#if defined (MOZ_WIDGET_COCOA) + #define NS_MAC_DESKTOP_DIR NS_OS_DESKTOP_DIR + #define NS_MAC_TRASH_DIR "Trsh" + #define NS_MAC_STARTUP_DIR "Strt" + #define NS_MAC_SHUTDOWN_DIR "Shdwn" + #define NS_MAC_APPLE_MENU_DIR "ApplMenu" + #define NS_MAC_CONTROL_PANELS_DIR "CntlPnl" + #define NS_MAC_EXTENSIONS_DIR "Exts" + #define NS_MAC_FONTS_DIR "Fnts" + #define NS_MAC_PREFS_DIR "Prfs" + #define NS_MAC_DOCUMENTS_DIR "Docs" + #define NS_MAC_INTERNET_SEARCH_DIR "ISrch" + #define NS_OSX_HOME_DIR NS_OS_HOME_DIR + #define NS_MAC_HOME_DIR NS_OS_HOME_DIR + #define NS_MAC_DEFAULT_DOWNLOAD_DIR "DfltDwnld" + #define NS_MAC_USER_LIB_DIR "ULibDir" // Only available under OS X + #define NS_OSX_DEFAULT_DOWNLOAD_DIR NS_MAC_DEFAULT_DOWNLOAD_DIR + #define NS_OSX_USER_DESKTOP_DIR "UsrDsk" + #define NS_OSX_LOCAL_DESKTOP_DIR "LocDsk" + #define NS_OSX_USER_APPLICATIONS_DIR "UsrApp" + #define NS_OSX_LOCAL_APPLICATIONS_DIR "LocApp" + #define NS_OSX_USER_DOCUMENTS_DIR "UsrDocs" + #define NS_OSX_LOCAL_DOCUMENTS_DIR "LocDocs" + #define NS_OSX_USER_INTERNET_PLUGIN_DIR "UsrIntrntPlgn" + #define NS_OSX_LOCAL_INTERNET_PLUGIN_DIR "LoclIntrntPlgn" + #define NS_OSX_USER_FRAMEWORKS_DIR "UsrFrmwrks" + #define NS_OSX_LOCAL_FRAMEWORKS_DIR "LocFrmwrks" + #define NS_OSX_USER_PREFERENCES_DIR "UsrPrfs" + #define NS_OSX_LOCAL_PREFERENCES_DIR "LocPrfs" + #define NS_OSX_PICTURE_DOCUMENTS_DIR "Pct" + #define NS_OSX_MOVIE_DOCUMENTS_DIR "Mov" + #define NS_OSX_MUSIC_DOCUMENTS_DIR "Music" + #define NS_OSX_INTERNET_SITES_DIR "IntrntSts" +#elif defined (XP_WIN) + #define NS_WIN_WINDOWS_DIR "WinD" + #define NS_WIN_PROGRAM_FILES_DIR "ProgF" + #define NS_WIN_HOME_DIR NS_OS_HOME_DIR + #define NS_WIN_DESKTOP_DIR "DeskV" // virtual folder at the root of the namespace + #define NS_WIN_PROGRAMS_DIR "Progs" // User start menu programs directory! + #define NS_WIN_CONTROLS_DIR "Cntls" + #define NS_WIN_PRINTERS_DIR "Prnts" + #define NS_WIN_PERSONAL_DIR "Pers" + #define NS_WIN_FAVORITES_DIR "Favs" + #define NS_WIN_STARTUP_DIR "Strt" + #define NS_WIN_RECENT_DIR "Rcnt" + #define NS_WIN_SEND_TO_DIR "SndTo" + #define NS_WIN_BITBUCKET_DIR "Buckt" + #define NS_WIN_STARTMENU_DIR "Strt" +// This gives the same thing as NS_OS_DESKTOP_DIR + #define NS_WIN_DESKTOP_DIRECTORY "DeskP" // file sys dir which physically stores objects on desktop + #define NS_WIN_DRIVES_DIR "Drivs" + #define NS_WIN_NETWORK_DIR "NetW" + #define NS_WIN_NETHOOD_DIR "netH" + #define NS_WIN_FONTS_DIR "Fnts" + #define NS_WIN_TEMPLATES_DIR "Tmpls" + #define NS_WIN_COMMON_STARTMENU_DIR "CmStrt" + #define NS_WIN_COMMON_PROGRAMS_DIR "CmPrgs" + #define NS_WIN_COMMON_STARTUP_DIR "CmStrt" + #define NS_WIN_COMMON_DESKTOP_DIRECTORY "CmDeskP" + #define NS_WIN_COMMON_APPDATA_DIR "CmAppData" + #define NS_WIN_APPDATA_DIR "AppData" + #define NS_WIN_LOCAL_APPDATA_DIR "LocalAppData" + #define NS_WIN_PRINTHOOD "PrntHd" + #define NS_WIN_COOKIES_DIR "CookD" + #define NS_WIN_DEFAULT_DOWNLOAD_DIR "DfltDwnld" + // On Win7 and up these ids will return the default save-to location for + // Windows Libraries associated with the specific content type. For other + // os they return the local user folder. Note these can return network file + // paths which can jank the ui thread so be careful how you access them. + #define NS_WIN_DOCUMENTS_DIR "Docs" + #define NS_WIN_PICTURES_DIR "Pict" + #define NS_WIN_MUSIC_DIR "Music" + #define NS_WIN_VIDEOS_DIR "Vids" +#elif defined (XP_UNIX) + #define NS_UNIX_LOCAL_DIR "Locl" + #define NS_UNIX_LIB_DIR "LibD" + #define NS_UNIX_HOME_DIR NS_OS_HOME_DIR + #define NS_UNIX_XDG_DESKTOP_DIR "XDGDesk" + #define NS_UNIX_XDG_DOCUMENTS_DIR "XDGDocs" + #define NS_UNIX_XDG_DOWNLOAD_DIR "XDGDwnld" + #define NS_UNIX_XDG_MUSIC_DIR "XDGMusic" + #define NS_UNIX_XDG_PICTURES_DIR "XDGPict" + #define NS_UNIX_XDG_PUBLIC_SHARE_DIR "XDGPubSh" + #define NS_UNIX_XDG_TEMPLATES_DIR "XDGTempl" + #define NS_UNIX_XDG_VIDEOS_DIR "XDGVids" + #define NS_UNIX_DEFAULT_DOWNLOAD_DIR "DfltDwnld" +#elif defined (XP_OS2) + #define NS_OS2_DIR "OS2Dir" + #define NS_OS2_HOME_DIR NS_OS_HOME_DIR + #define NS_OS2_DESKTOP_DIR NS_OS_DESKTOP_DIR +#endif + +/* Deprecated */ + +#define NS_OS_DRIVE_DIR "DrvD" + + + +#endif diff --git a/xpcom/io/nsDirectoryServiceUtils.h b/xpcom/io/nsDirectoryServiceUtils.h new file mode 100644 index 000000000..ff8e85c98 --- /dev/null +++ b/xpcom/io/nsDirectoryServiceUtils.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsDirectoryServiceUtils_h___ +#define nsDirectoryServiceUtils_h___ + +#include "nsIServiceManager.h" +#include "nsIProperties.h" +#include "nsServiceManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsXPCOMCID.h" +#include "nsIFile.h" + +inline nsresult +NS_GetSpecialDirectory(const char* specialDirName, nsIFile* *result) +{ + nsresult rv; + nsCOMPtr<nsIProperties> serv(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + return serv->Get(specialDirName, NS_GET_IID(nsIFile), + reinterpret_cast<void**>(result)); +} + +#endif diff --git a/xpcom/io/nsEscape.cpp b/xpcom/io/nsEscape.cpp new file mode 100644 index 000000000..8453e6a06 --- /dev/null +++ b/xpcom/io/nsEscape.cpp @@ -0,0 +1,497 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +// First checked in on 98/12/03 by John R. McMullen, derived from net.h/mkparse.c. + +#include "nsEscape.h" +#include "nsMemory.h" +#include "nsCRT.h" +#include "nsReadableUtils.h" + +const int netCharType[256] = +/* Bit 0 xalpha -- the alphas +** Bit 1 xpalpha -- as xalpha but +** converts spaces to plus and plus to %2B +** Bit 3 ... path -- as xalphas but doesn't escape '/' +*/ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1x */ + 0,0,0,0,0,0,0,0,0,0,7,4,0,7,7,4, /* 2x !"#$%&'()*+,-./ */ + 7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0, /* 3x 0123456789:;<=>? */ + 0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 4x @ABCDEFGHIJKLMNO */ + /* bits for '@' changed from 7 to 0 so '@' can be escaped */ + /* in usernames and passwords in publishing. */ + 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7, /* 5X PQRSTUVWXYZ[\]^_ */ + 0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 6x `abcdefghijklmno */ + 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0, /* 7X pqrstuvwxyz{\}~ DEL */ + 0, }; + +/* decode % escaped hex codes into character values + */ +#define UNHEX(C) \ + ((C >= '0' && C <= '9') ? C - '0' : \ + ((C >= 'A' && C <= 'F') ? C - 'A' + 10 : \ + ((C >= 'a' && C <= 'f') ? C - 'a' + 10 : 0))) + + +#define IS_OK(C) (netCharType[((unsigned int) (C))] & (flags)) +#define HEX_ESCAPE '%' + +//---------------------------------------------------------------------------------------- +static char* nsEscapeCount( + const char * str, + nsEscapeMask flags, + size_t* out_len) +//---------------------------------------------------------------------------------------- +{ + if (!str) + return 0; + + size_t i, len = 0, charsToEscape = 0; + static const char hexChars[] = "0123456789ABCDEF"; + + register const unsigned char* src = (const unsigned char *) str; + while (*src) + { + len++; + if (!IS_OK(*src++)) + charsToEscape++; + } + + // calculate how much memory should be allocated + // original length + 2 bytes for each escaped character + terminating '\0' + // do the sum in steps to check for overflow + size_t dstSize = len + 1 + charsToEscape; + if (dstSize <= len) + return 0; + dstSize += charsToEscape; + if (dstSize < len) + return 0; + + // fail if we need more than 4GB + // size_t is likely to be long unsigned int but nsMemory::Alloc(size_t) + // calls NS_Alloc_P(size_t) which calls PR_Malloc(uint32_t), so there is + // no chance to allocate more than 4GB using nsMemory::Alloc() + if (dstSize > UINT32_MAX) + return 0; + + char* result = (char *)nsMemory::Alloc(dstSize); + if (!result) + return 0; + + register unsigned char* dst = (unsigned char *) result; + src = (const unsigned char *) str; + if (flags == url_XPAlphas) + { + for (i = 0; i < len; i++) + { + unsigned char c = *src++; + if (IS_OK(c)) + *dst++ = c; + else if (c == ' ') + *dst++ = '+'; /* convert spaces to pluses */ + else + { + *dst++ = HEX_ESCAPE; + *dst++ = hexChars[c >> 4]; /* high nibble */ + *dst++ = hexChars[c & 0x0f]; /* low nibble */ + } + } + } + else + { + for (i = 0; i < len; i++) + { + unsigned char c = *src++; + if (IS_OK(c)) + *dst++ = c; + else + { + *dst++ = HEX_ESCAPE; + *dst++ = hexChars[c >> 4]; /* high nibble */ + *dst++ = hexChars[c & 0x0f]; /* low nibble */ + } + } + } + + *dst = '\0'; /* tack on eos */ + if(out_len) + *out_len = dst - (unsigned char *) result; + return result; +} + +//---------------------------------------------------------------------------------------- +char* nsEscape(const char * str, nsEscapeMask flags) +//---------------------------------------------------------------------------------------- +{ + if(!str) + return NULL; + return nsEscapeCount(str, flags, NULL); +} + +//---------------------------------------------------------------------------------------- +char* nsUnescape(char * str) +//---------------------------------------------------------------------------------------- +{ + nsUnescapeCount(str); + return str; +} + +//---------------------------------------------------------------------------------------- +int32_t nsUnescapeCount(char * str) +//---------------------------------------------------------------------------------------- +{ + register char *src = str; + register char *dst = str; + static const char hexChars[] = "0123456789ABCDEFabcdef"; + + char c1[] = " "; + char c2[] = " "; + char* const pc1 = c1; + char* const pc2 = c2; + + while (*src) + { + c1[0] = *(src+1); + if (*(src+1) == '\0') + c2[0] = '\0'; + else + c2[0] = *(src+2); + + if (*src != HEX_ESCAPE || PL_strpbrk(pc1, hexChars) == 0 || + PL_strpbrk(pc2, hexChars) == 0 ) + *dst++ = *src++; + else + { + src++; /* walk over escape */ + if (*src) + { + *dst = UNHEX(*src) << 4; + src++; + } + if (*src) + { + *dst = (*dst + UNHEX(*src)); + src++; + } + dst++; + } + } + + *dst = 0; + return (int)(dst - str); + +} /* NET_UnEscapeCnt */ + + +char * +nsEscapeHTML(const char * string) +{ + char *rv = nullptr; + /* XXX Hardcoded max entity len. The +1 is for the trailing null. */ + uint32_t len = strlen(string); + if (len >= (UINT32_MAX / 6)) + return nullptr; + + rv = (char *)NS_Alloc( (6 * len) + 1 ); + char *ptr = rv; + + if(rv) + { + for(; *string != '\0'; string++) + { + if(*string == '<') + { + *ptr++ = '&'; + *ptr++ = 'l'; + *ptr++ = 't'; + *ptr++ = ';'; + } + else if(*string == '>') + { + *ptr++ = '&'; + *ptr++ = 'g'; + *ptr++ = 't'; + *ptr++ = ';'; + } + else if(*string == '&') + { + *ptr++ = '&'; + *ptr++ = 'a'; + *ptr++ = 'm'; + *ptr++ = 'p'; + *ptr++ = ';'; + } + else if (*string == '"') + { + *ptr++ = '&'; + *ptr++ = 'q'; + *ptr++ = 'u'; + *ptr++ = 'o'; + *ptr++ = 't'; + *ptr++ = ';'; + } + else if (*string == '\'') + { + *ptr++ = '&'; + *ptr++ = '#'; + *ptr++ = '3'; + *ptr++ = '9'; + *ptr++ = ';'; + } + else + { + *ptr++ = *string; + } + } + *ptr = '\0'; + } + + return(rv); +} + +PRUnichar * +nsEscapeHTML2(const PRUnichar *aSourceBuffer, int32_t aSourceBufferLen) +{ + // Calculate the length, if the caller didn't. + if (aSourceBufferLen < 0) { + aSourceBufferLen = NS_strlen(aSourceBuffer); + } + + /* XXX Hardcoded max entity len. */ + if (uint32_t(aSourceBufferLen) >= + ((UINT32_MAX - sizeof(PRUnichar)) / (6 * sizeof(PRUnichar))) ) + return nullptr; + + PRUnichar *resultBuffer = (PRUnichar *)nsMemory::Alloc(aSourceBufferLen * + 6 * sizeof(PRUnichar) + sizeof(PRUnichar('\0'))); + PRUnichar *ptr = resultBuffer; + + if (resultBuffer) { + int32_t i; + + for(i = 0; i < aSourceBufferLen; i++) { + if(aSourceBuffer[i] == '<') { + *ptr++ = '&'; + *ptr++ = 'l'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if(aSourceBuffer[i] == '>') { + *ptr++ = '&'; + *ptr++ = 'g'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if(aSourceBuffer[i] == '&') { + *ptr++ = '&'; + *ptr++ = 'a'; + *ptr++ = 'm'; + *ptr++ = 'p'; + *ptr++ = ';'; + } else if (aSourceBuffer[i] == '"') { + *ptr++ = '&'; + *ptr++ = 'q'; + *ptr++ = 'u'; + *ptr++ = 'o'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if (aSourceBuffer[i] == '\'') { + *ptr++ = '&'; + *ptr++ = '#'; + *ptr++ = '3'; + *ptr++ = '9'; + *ptr++ = ';'; + } else { + *ptr++ = aSourceBuffer[i]; + } + } + *ptr = 0; + } + + return resultBuffer; +} + +//---------------------------------------------------------------------------------------- + +const int EscapeChars[256] = +/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */ + 0,1023, 0, 512,1023, 0,1023, 0,1023,1023,1023,1023,1023,1023, 953, 784, /* 2x !"#$%&'()*+,-./ */ + 1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1008,1008, 0,1008, 0, 768, /* 3x 0123456789:;<=>? */ + 1008,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023, /* 4x @ABCDEFGHIJKLMNO */ + 1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023, 896, 896, 896, 896,1023, /* 5x PQRSTUVWXYZ[\]^_ */ + 0,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023, /* 6x `abcdefghijklmno */ + 1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023, 896,1012, 896,1023, 0, /* 7x pqrstuvwxyz{|}~ */ + 0 /* 8x DEL */ +}; + +#define NO_NEED_ESC(C) (EscapeChars[((unsigned int) (C))] & (flags)) + +//---------------------------------------------------------------------------------------- + +/* returns an escaped string */ + +/* use the following flags to specify which + part of an URL you want to escape: + + esc_Scheme = 1 + esc_Username = 2 + esc_Password = 4 + esc_Host = 8 + esc_Directory = 16 + esc_FileBaseName = 32 + esc_FileExtension = 64 + esc_Param = 128 + esc_Query = 256 + esc_Ref = 512 +*/ + +/* by default this function will not escape parts of a string + that already look escaped, which means it already includes + a valid hexcode. This is done to avoid multiple escapes of + a string. Use the following flags to force escaping of a + string: + + esc_Forced = 1024 +*/ + +bool NS_EscapeURL(const char *part, + int32_t partLen, + uint32_t flags, + nsACString &result) +{ + if (!part) { + NS_NOTREACHED("null pointer"); + return false; + } + + int i = 0; + static const char hexChars[] = "0123456789ABCDEF"; + if (partLen < 0) + partLen = strlen(part); + bool forced = !!(flags & esc_Forced); + bool ignoreNonAscii = !!(flags & esc_OnlyASCII); + bool ignoreAscii = !!(flags & esc_OnlyNonASCII); + bool writing = !!(flags & esc_AlwaysCopy); + bool colon = !!(flags & esc_Colon); + + register const unsigned char* src = (const unsigned char *) part; + + char tempBuffer[100]; + unsigned int tempBufferPos = 0; + + bool previousIsNonASCII = false; + for (i = 0; i < partLen; i++) + { + unsigned char c = *src++; + + // if the char has not to be escaped or whatever follows % is + // a valid escaped string, just copy the char. + // + // Also the % will not be escaped until forced + // See bugzilla bug 61269 for details why we changed this + // + // And, we will not escape non-ascii characters if requested. + // On special request we will also escape the colon even when + // not covered by the matrix. + // ignoreAscii is not honored for control characters (C0 and DEL) + // + // And, we should escape the '|' character when it occurs after any + // non-ASCII character as it may be part of a multi-byte character. + // + // 0x20..0x7e are the valid ASCII characters. We also escape spaces + // (0x20) since they are not legal in URLs. + if ((NO_NEED_ESC(c) || (c == HEX_ESCAPE && !forced) + || (c > 0x7f && ignoreNonAscii) + || (c > 0x20 && c < 0x7f && ignoreAscii)) + && !(c == ':' && colon) + && !(previousIsNonASCII && c == '|' && !ignoreNonAscii)) + { + if (writing) + tempBuffer[tempBufferPos++] = c; + } + else /* do the escape magic */ + { + if (!writing) + { + result.Append(part, i); + writing = true; + } + tempBuffer[tempBufferPos++] = HEX_ESCAPE; + tempBuffer[tempBufferPos++] = hexChars[c >> 4]; /* high nibble */ + tempBuffer[tempBufferPos++] = hexChars[c & 0x0f]; /* low nibble */ + } + + if (tempBufferPos >= sizeof(tempBuffer) - 4) + { + NS_ASSERTION(writing, "should be writing"); + tempBuffer[tempBufferPos] = '\0'; + result += tempBuffer; + tempBufferPos = 0; + } + + previousIsNonASCII = (c > 0x7f); + } + if (writing) { + tempBuffer[tempBufferPos] = '\0'; + result += tempBuffer; + } + return writing; +} + +#define ISHEX(c) memchr(hexChars, c, sizeof(hexChars)-1) + +bool NS_UnescapeURL(const char *str, int32_t len, uint32_t flags, nsACString &result) +{ + if (!str) { + NS_NOTREACHED("null pointer"); + return false; + } + + if (len < 0) + len = strlen(str); + + bool ignoreNonAscii = !!(flags & esc_OnlyASCII); + bool ignoreAscii = !!(flags & esc_OnlyNonASCII); + bool writing = !!(flags & esc_AlwaysCopy); + bool skipControl = !!(flags & esc_SkipControl); + + static const char hexChars[] = "0123456789ABCDEFabcdef"; + + const char *last = str; + const char *p = str; + + for (int i=0; i<len; ++i, ++p) { + //printf("%c [i=%d of len=%d]\n", *p, i, len); + if (*p == HEX_ESCAPE && i < len-2) { + unsigned char *p1 = ((unsigned char *) p) + 1; + unsigned char *p2 = ((unsigned char *) p) + 2; + if (ISHEX(*p1) && ISHEX(*p2) && + ((*p1 < '8' && !ignoreAscii) || (*p1 >= '8' && !ignoreNonAscii)) && + !(skipControl && + (*p1 < '2' || (*p1 == '7' && (*p2 == 'f' || *p2 == 'F'))))) { + //printf("- p1=%c p2=%c\n", *p1, *p2); + writing = true; + if (p > last) { + //printf("- p=%p, last=%p\n", p, last); + result.Append(last, p - last); + last = p; + } + char u = (UNHEX(*p1) << 4) + UNHEX(*p2); + //printf("- u=%c\n", u); + result.Append(u); + i += 2; + p += 2; + last += 3; + } + } + } + if (writing && last < str + len) + result.Append(last, str + len - last); + + return writing; +} diff --git a/xpcom/io/nsEscape.h b/xpcom/io/nsEscape.h new file mode 100644 index 000000000..e66ac28f6 --- /dev/null +++ b/xpcom/io/nsEscape.h @@ -0,0 +1,180 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* First checked in on 98/12/03 by John R. McMullen, derived from net.h/mkparse.c. */ + +#ifndef _ESCAPE_H_ +#define _ESCAPE_H_ + +#include "prtypes.h" +#include "nscore.h" +#include "nsError.h" +#include "nsString.h" + +/** + * Valid mask values for nsEscape + * Note: these values are copied in nsINetUtil.idl. Any changes should be kept + * in sync. + */ +typedef enum { + url_All = 0 /**< %-escape every byte unconditionally */ +, url_XAlphas = 1u << 0 /**< Normal escape - leave alphas intact, escape the rest */ +, url_XPAlphas = 1u << 1 /**< As url_XAlphas, but convert spaces (0x20) to '+' and plus to %2B */ +, url_Path = 1u << 2 /**< As url_XAlphas, but don't escape slash ('/') */ +} nsEscapeMask; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Escape the given string according to mask + * @param str The string to escape + * @param mask How to escape the string + * @return A newly allocated escaped string that must be free'd with + * nsCRT::free, or null on failure + */ +char * nsEscape(const char * str, nsEscapeMask mask); + +char * nsUnescape(char * str); + /* decode % escaped hex codes into character values, + * modifies the parameter, returns the same buffer + */ + +int32_t nsUnescapeCount (char * str); + /* decode % escaped hex codes into character values, + * modifies the parameter buffer, returns the length of the result + * (result may contain \0's). + */ + +char * +nsEscapeHTML(const char * string); + +PRUnichar * +nsEscapeHTML2(const PRUnichar *aSourceBuffer, + int32_t aSourceBufferLen = -1); + /* + * Escape problem char's for HTML display + */ + + +#ifdef __cplusplus +} +#endif + + +/** + * NS_EscapeURL/NS_UnescapeURL constants for |flags| parameter: + * + * Note: These values are copied to nsINetUtil.idl + * Any changes should be kept in sync + */ +enum EscapeMask { + /** url components **/ + esc_Scheme = 1u << 0, + esc_Username = 1u << 1, + esc_Password = 1u << 2, + esc_Host = 1u << 3, + esc_Directory = 1u << 4, + esc_FileBaseName = 1u << 5, + esc_FileExtension = 1u << 6, + esc_FilePath = esc_Directory | esc_FileBaseName | esc_FileExtension, + esc_Param = 1u << 7, + esc_Query = 1u << 8, + esc_Ref = 1u << 9, + /** special flags **/ + esc_Minimal = esc_Scheme | esc_Username | esc_Password | esc_Host | esc_FilePath | esc_Param | esc_Query | esc_Ref, + esc_Forced = 1u << 10, /* forces escaping of existing escape sequences */ + esc_OnlyASCII = 1u << 11, /* causes non-ascii octets to be skipped */ + esc_OnlyNonASCII = 1u << 12, /* causes _graphic_ ascii octets (0x20-0x7E) + * to be skipped when escaping. causes all + * ascii octets (<= 0x7F) to be skipped when unescaping */ + esc_AlwaysCopy = 1u << 13, /* copy input to result buf even if escaping is unnecessary */ + esc_Colon = 1u << 14, /* forces escape of colon */ + esc_SkipControl = 1u << 15 /* skips C0 and DEL from unescaping */ +}; + +/** + * NS_EscapeURL + * + * Escapes invalid char's in an URL segment. Has no side-effect if the URL + * segment is already escaped. Otherwise, the escaped URL segment is appended + * to |result|. + * + * @param str url segment string + * @param len url segment string length (-1 if unknown) + * @param flags url segment type flag + * @param result result buffer, untouched if part is already escaped + * + * @return TRUE if escaping was performed, FALSE otherwise. + */ +bool NS_EscapeURL(const char *str, + int32_t len, + uint32_t flags, + nsACString &result); + +/** + * Expands URL escape sequences... beware embedded null bytes! + * + * @param str url string to unescape + * @param len length of |str| + * @param flags only esc_OnlyNonASCII, esc_SkipControl and esc_AlwaysCopy + * are recognized + * @param result result buffer, untouched if |str| is already unescaped + * + * @return TRUE if unescaping was performed, FALSE otherwise. + */ +bool NS_UnescapeURL(const char *str, + int32_t len, + uint32_t flags, + nsACString &result); + +/** returns resultant string length **/ +inline int32_t NS_UnescapeURL(char *str) { + return nsUnescapeCount(str); +} + +/** + * String friendly versions... + */ +inline const nsCSubstring & +NS_EscapeURL(const nsCSubstring &str, uint32_t flags, nsCSubstring &result) { + if (NS_EscapeURL(str.Data(), str.Length(), flags, result)) + return result; + return str; +} +inline const nsCSubstring & +NS_UnescapeURL(const nsCSubstring &str, uint32_t flags, nsCSubstring &result) { + if (NS_UnescapeURL(str.Data(), str.Length(), flags, result)) + return result; + return str; +} + +/** + * CString version of nsEscape. Returns true on success, false + * on out of memory. To reverse this function, use NS_UnescapeURL. + */ +inline bool +NS_Escape(const nsCString& aOriginal, nsCString& aEscaped, + nsEscapeMask aMask) +{ + char* esc = nsEscape(aOriginal.get(), aMask); + if (! esc) + return false; + aEscaped.Adopt(esc); + return true; +} + +/** + * Inline unescape of mutable string object. + */ +inline nsCString & +NS_UnescapeURL(nsCString &str) +{ + str.SetLength(nsUnescapeCount(str.BeginWriting())); + return str; +} + +#endif // _ESCAPE_H_ diff --git a/xpcom/io/nsIAsyncInputStream.idl b/xpcom/io/nsIAsyncInputStream.idl new file mode 100644 index 000000000..5570817dd --- /dev/null +++ b/xpcom/io/nsIAsyncInputStream.idl @@ -0,0 +1,104 @@ +/* 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 "nsIInputStream.idl" + +interface nsIInputStreamCallback; +interface nsIEventTarget; + +/** + * If an input stream is non-blocking, it may return NS_BASE_STREAM_WOULD_BLOCK + * when read. The caller must then wait for the stream to have some data to + * read. If the stream implements nsIAsyncInputStream, then the caller can use + * this interface to request an asynchronous notification when the stream + * becomes readable or closed (via the AsyncWait method). + * + * While this interface is almost exclusively used with non-blocking streams, it + * is not necessary that nsIInputStream::isNonBlocking return true. Nor is it + * necessary that a non-blocking nsIInputStream implementation also implement + * nsIAsyncInputStream. + */ +[scriptable, uuid(a5f255ab-4801-4161-8816-277ac92f6ad1)] +interface nsIAsyncInputStream : nsIInputStream +{ + /** + * This method closes the stream and sets its internal status. If the + * stream is already closed, then this method is ignored. Once the stream + * is closed, the stream's status cannot be changed. Any successful status + * code passed to this method is treated as NS_BASE_STREAM_CLOSED, which + * has an effect equivalent to nsIInputStream::close. + * + * NOTE: this method exists in part to support pipes, which have both an + * input end and an output end. If the input end of a pipe is closed, then + * writes to the output end of the pipe will fail. The error code returned + * when an attempt is made to write to a "broken" pipe corresponds to the + * status code passed in when the input end of the pipe was closed, which + * greatly simplifies working with pipes in some cases. + * + * @param aStatus + * The error that will be reported if this stream is accessed after + * it has been closed. + */ + void closeWithStatus(in nsresult aStatus); + + /** + * Asynchronously wait for the stream to be readable or closed. The + * notification is one-shot, meaning that each asyncWait call will result + * in exactly one notification callback. After the OnInputStreamReady event + * is dispatched, the stream releases its reference to the + * nsIInputStreamCallback object. It is safe to call asyncWait again from the + * notification handler. + * + * This method may be called at any time (even if read has not been called). + * In other words, this method may be called when the stream already has + * data to read. It may also be called when the stream is closed. If the + * stream is already readable or closed when AsyncWait is called, then the + * OnInputStreamReady event will be dispatched immediately. Otherwise, the + * event will be dispatched when the stream becomes readable or closed. + * + * @param aCallback + * This object is notified when the stream becomes ready. This + * parameter may be null to clear an existing callback. + * @param aFlags + * This parameter specifies optional flags passed in to configure + * the behavior of this method. Pass zero to specify no flags. + * @param aRequestedCount + * Wait until at least this many bytes can be read. This is only + * a suggestion to the underlying stream; it may be ignored. The + * caller may pass zero to indicate no preference. + * @param aEventTarget + * Specify NULL to receive notification on ANY thread (possibly even + * recursively on the calling thread -- i.e., synchronously), or + * specify that the notification be delivered to a specific event + * target. + */ + void asyncWait(in nsIInputStreamCallback aCallback, + in unsigned long aFlags, + in unsigned long aRequestedCount, + in nsIEventTarget aEventTarget); + + /** + * If passed to asyncWait, this flag overrides the default behavior, + * causing the OnInputStreamReady notification to be suppressed until the + * stream becomes closed (either as a result of closeWithStatus/close being + * called on the stream or possibly due to some error in the underlying + * stream). + */ + const unsigned long WAIT_CLOSURE_ONLY = (1<<0); +}; + +/** + * This is a companion interface for nsIAsyncInputStream::asyncWait. + */ +[function, scriptable, uuid(d1f28e94-3a6e-4050-a5f5-2e81b1fc2a43)] +interface nsIInputStreamCallback : nsISupports +{ + /** + * Called to indicate that the stream is either readable or closed. + * + * @param aStream + * The stream whose asyncWait method was called. + */ + void onInputStreamReady(in nsIAsyncInputStream aStream); +}; diff --git a/xpcom/io/nsIAsyncOutputStream.idl b/xpcom/io/nsIAsyncOutputStream.idl new file mode 100644 index 000000000..68d0464b1 --- /dev/null +++ b/xpcom/io/nsIAsyncOutputStream.idl @@ -0,0 +1,104 @@ +/* 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 "nsIOutputStream.idl" + +interface nsIOutputStreamCallback; +interface nsIEventTarget; + +/** + * If an output stream is non-blocking, it may return NS_BASE_STREAM_WOULD_BLOCK + * when written to. The caller must then wait for the stream to become + * writable. If the stream implements nsIAsyncOutputStream, then the caller can + * use this interface to request an asynchronous notification when the stream + * becomes writable or closed (via the AsyncWait method). + * + * While this interface is almost exclusively used with non-blocking streams, it + * is not necessary that nsIOutputStream::isNonBlocking return true. Nor is it + * necessary that a non-blocking nsIOutputStream implementation also implement + * nsIAsyncOutputStream. + */ +[scriptable, uuid(beb632d3-d77a-4e90-9134-f9ece69e8200)] +interface nsIAsyncOutputStream : nsIOutputStream +{ + /** + * This method closes the stream and sets its internal status. If the + * stream is already closed, then this method is ignored. Once the stream + * is closed, the stream's status cannot be changed. Any successful status + * code passed to this method is treated as NS_BASE_STREAM_CLOSED, which + * is equivalent to nsIInputStream::close. + * + * NOTE: this method exists in part to support pipes, which have both an + * input end and an output end. If the output end of a pipe is closed, then + * reads from the input end of the pipe will fail. The error code returned + * when an attempt is made to read from a "closed" pipe corresponds to the + * status code passed in when the output end of the pipe is closed, which + * greatly simplifies working with pipes in some cases. + * + * @param aStatus + * The error that will be reported if this stream is accessed after + * it has been closed. + */ + void closeWithStatus(in nsresult reason); + + /** + * Asynchronously wait for the stream to be writable or closed. The + * notification is one-shot, meaning that each asyncWait call will result + * in exactly one notification callback. After the OnOutputStreamReady event + * is dispatched, the stream releases its reference to the + * nsIOutputStreamCallback object. It is safe to call asyncWait again from the + * notification handler. + * + * This method may be called at any time (even if write has not been called). + * In other words, this method may be called when the stream already has + * room for more data. It may also be called when the stream is closed. If + * the stream is already writable or closed when AsyncWait is called, then the + * OnOutputStreamReady event will be dispatched immediately. Otherwise, the + * event will be dispatched when the stream becomes writable or closed. + * + * @param aCallback + * This object is notified when the stream becomes ready. This + * parameter may be null to clear an existing callback. + * @param aFlags + * This parameter specifies optional flags passed in to configure + * the behavior of this method. Pass zero to specify no flags. + * @param aRequestedCount + * Wait until at least this many bytes can be written. This is only + * a suggestion to the underlying stream; it may be ignored. The + * caller may pass zero to indicate no preference. + * @param aEventTarget + * Specify NULL to receive notification on ANY thread (possibly even + * recursively on the calling thread -- i.e., synchronously), or + * specify that the notification be delivered to a specific event + * target. + */ + void asyncWait(in nsIOutputStreamCallback aCallback, + in unsigned long aFlags, + in unsigned long aRequestedCount, + in nsIEventTarget aEventTarget); + + /** + * If passed to asyncWait, this flag overrides the default behavior, + * causing the OnOutputStreamReady notification to be suppressed until the + * stream becomes closed (either as a result of closeWithStatus/close being + * called on the stream or possibly due to some error in the underlying + * stream). + */ + const unsigned long WAIT_CLOSURE_ONLY = (1<<0); +}; + +/** + * This is a companion interface for nsIAsyncOutputStream::asyncWait. + */ +[scriptable, uuid(40dbcdff-9053-42c5-a57c-3ec910d0f148)] +interface nsIOutputStreamCallback : nsISupports +{ + /** + * Called to indicate that the stream is either writable or closed. + * + * @param aStream + * The stream whose asyncWait method was called. + */ + void onOutputStreamReady(in nsIAsyncOutputStream aStream); +}; diff --git a/xpcom/io/nsIBinaryInputStream.idl b/xpcom/io/nsIBinaryInputStream.idl new file mode 100644 index 000000000..5d228dee4 --- /dev/null +++ b/xpcom/io/nsIBinaryInputStream.idl @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 "nsIInputStream.idl" +#include "nsrootidl.idl" + +/** + * This interface allows consumption of primitive data types from a "binary + * stream" containing untagged, big-endian binary data, i.e. as produced by an + * implementation of nsIBinaryOutputStream. This might be used, for example, + * to implement network protocols or to read from architecture-neutral disk + * files, i.e. ones that can be read and written by both big-endian and + * little-endian platforms. + * + * @See nsIBinaryOutputStream + */ + +[scriptable, uuid(42084755-fedc-4310-831c-4f43e7b42e20)] +interface nsIBinaryInputStream : nsIInputStream { + void setInputStream(in nsIInputStream aInputStream); + + /** + * Read 8-bits from the stream. + * + * @return that byte to be treated as a boolean. + */ + boolean readBoolean(); + + uint8_t read8(); + uint16_t read16(); + uint32_t read32(); + uint64_t read64(); + + float readFloat(); + double readDouble(); + + /** + * Read an 8-bit pascal style string from the stream. + * 32-bit length field, followed by length 8-bit chars. + */ + ACString readCString(); + + /** + * Read an 16-bit pascal style string from the stream. + * 32-bit length field, followed by length PRUnichars. + */ + AString readString(); + + /** + * Read an opaque byte array from the stream. + * + * @param aLength the number of bytes that must be read. + * + * @throws NS_ERROR_FAILURE if it can't read aLength bytes + */ + void readBytes(in uint32_t aLength, + [size_is(aLength), retval] out string aString); + + /** + * Read an opaque byte array from the stream, storing the results + * as an array of PRUint8s. + * + * @param aLength the number of bytes that must be read. + * + * @throws NS_ERROR_FAILURE if it can't read aLength bytes + */ + void readByteArray(in uint32_t aLength, + [array, size_is(aLength), retval] out uint8_t aBytes); + + /** + * Read opaque bytes from the stream, storing the results in an ArrayBuffer. + * + * @param aLength the number of bytes that must be read + * @param aArrayBuffer the arraybuffer in which to store the results + * Note: passing view.buffer, where view is an ArrayBufferView of an + * ArrayBuffer, is not valid unless view.byteOffset == 0. + * + * @throws NS_ERROR_FAILURE if it can't read aLength bytes + */ + [implicit_jscontext] + void readArrayBuffer(in uint32_t aLength, in jsval aArrayBuffer); +}; + +%{C++ + +#ifdef MOZILLA_INTERNAL_API +#include "nsString.h" + +inline nsresult +NS_ReadOptionalCString(nsIBinaryInputStream* aStream, nsACString& aResult) +{ + bool nonnull; + nsresult rv = aStream->ReadBoolean(&nonnull); + if (NS_SUCCEEDED(rv)) { + if (nonnull) + rv = aStream->ReadCString(aResult); + else + aResult.Truncate(); + } + return rv; +} + +inline nsresult +NS_ReadOptionalString(nsIBinaryInputStream* aStream, nsAString& aResult) +{ + bool nonnull; + nsresult rv = aStream->ReadBoolean(&nonnull); + if (NS_SUCCEEDED(rv)) { + if (nonnull) + rv = aStream->ReadString(aResult); + else + aResult.Truncate(); + } + return rv; +} +#endif + +%} diff --git a/xpcom/io/nsIBinaryOutputStream.idl b/xpcom/io/nsIBinaryOutputStream.idl new file mode 100644 index 000000000..7711aecd1 --- /dev/null +++ b/xpcom/io/nsIBinaryOutputStream.idl @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 "nsIOutputStream.idl" +#include "nsrootidl.idl" + +/** + * This interface allows writing of primitive data types (integers, + * floating-point values, booleans, etc.) to a stream in a binary, untagged, + * fixed-endianness format. This might be used, for example, to implement + * network protocols or to produce architecture-neutral binary disk files, + * i.e. ones that can be read and written by both big-endian and little-endian + * platforms. Output is written in big-endian order (high-order byte first), + * as this is traditional network order. + * + * @See nsIBinaryInputStream + */ + +[scriptable, uuid(204ee610-8765-11d3-90cf-0040056a906e)] +interface nsIBinaryOutputStream : nsIOutputStream { + void setOutputStream(in nsIOutputStream aOutputStream); + + /** + * Write a boolean as an 8-bit char to the stream. + */ + void writeBoolean(in boolean aBoolean); + + void write8(in uint8_t aByte); + void write16(in uint16_t a16); + void write32(in uint32_t a32); + void write64(in uint64_t a64); + + void writeFloat(in float aFloat); + void writeDouble(in double aDouble); + + /** + * Write an 8-bit pascal style string to the stream. + * 32-bit length field, followed by length 8-bit chars. + */ + void writeStringZ(in string aString); + + /** + * Write a 16-bit pascal style string to the stream. + * 32-bit length field, followed by length PRUnichars. + */ + void writeWStringZ(in wstring aString); + + /** + * Write an 8-bit pascal style string (UTF8-encoded) to the stream. + * 32-bit length field, followed by length 8-bit chars. + */ + void writeUtf8Z(in wstring aString); + + /** + * Write an opaque byte array to the stream. + */ + void writeBytes([size_is(aLength)] in string aString, in uint32_t aLength); + + /** + * Write an opaque byte array to the stream. + */ + void writeByteArray([array, size_is(aLength)] in uint8_t aBytes, + in uint32_t aLength); + +}; + +%{C++ + +inline nsresult +NS_WriteOptionalStringZ(nsIBinaryOutputStream* aStream, const char* aString) +{ + bool nonnull = (aString != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteStringZ(aString); + return rv; +} + +inline nsresult +NS_WriteOptionalWStringZ(nsIBinaryOutputStream* aStream, const PRUnichar* aString) +{ + bool nonnull = (aString != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteWStringZ(aString); + return rv; +} + +%} diff --git a/xpcom/io/nsIConverterInputStream.idl b/xpcom/io/nsIConverterInputStream.idl new file mode 100644 index 000000000..6f3d01071 --- /dev/null +++ b/xpcom/io/nsIConverterInputStream.idl @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIUnicharInputStream.idl" + +interface nsIInputStream; + +/** + * A unichar input stream that wraps an input stream. + * This allows reading unicode strings from a stream, automatically converting + * the bytes from a selected character encoding. + */ +[scriptable, uuid(FC66FFB6-5404-4908-A4A3-27F92FA0579D)] +interface nsIConverterInputStream : nsIUnicharInputStream { + /** + * Default replacement char value, U+FFFD REPLACEMENT CHARACTER. + */ + const PRUnichar DEFAULT_REPLACEMENT_CHARACTER = 0xFFFD; + + /** + * Initialize this stream. + * @param aStream + * The underlying stream to read from. + * @param aCharset + * The character encoding to use for converting the bytes of the + * stream. A null charset will be interpreted as UTF-8. + * @param aBufferSize + * How many bytes to buffer. + * @param aReplacementChar + * The character to replace unknown byte sequences in the stream + * with. The standard replacement character is U+FFFD. + * A value of 0x0000 will cause an exception to be thrown if unknown + * byte sequences are encountered in the stream. + */ + void init (in nsIInputStream aStream, in string aCharset, + in long aBufferSize, in PRUnichar aReplacementChar); +}; + diff --git a/xpcom/io/nsIConverterOutputStream.idl b/xpcom/io/nsIConverterOutputStream.idl new file mode 100644 index 000000000..66c30f0bc --- /dev/null +++ b/xpcom/io/nsIConverterOutputStream.idl @@ -0,0 +1,44 @@ +/* vim:set expandtab ts=4 sw=4 sts=4 cin: */ +/* 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 "nsIUnicharOutputStream.idl" + +interface nsIOutputStream; + +/** + * This interface allows writing strings to a stream, doing automatic + * character encoding conversion. + */ +[scriptable, uuid(4b71113a-cb0d-479f-8ed5-01daeba2e8d4)] +interface nsIConverterOutputStream : nsIUnicharOutputStream +{ + /** + * Initialize this stream. Must be called before any other method on this + * interface, or you will crash. The output stream passed to this method + * must not be null, or you will crash. + * + * @param aOutStream + * The underlying output stream to which the converted strings will + * be written. + * @param aCharset + * The character set to use for encoding the characters. A null + * charset will be interpreted as UTF-8. + * @param aBufferSize + * How many bytes to buffer. A value of 0 means that no bytes will be + * buffered. Implementations not supporting buffering may ignore + * this parameter. + * @param aReplacementCharacter + * The replacement character to use when an unsupported character is found. + * The character must be encodable in the selected character + * encoding; otherwise, attempts to write an unsupported character + * will throw NS_ERROR_LOSS_OF_SIGNIFICANT_DATA. + * + * A value of 0x0000 will cause an exception to be thrown upon + * attempts to write unsupported characters. + */ + void init(in nsIOutputStream aOutStream, in string aCharset, + in unsigned long aBufferSize, + in PRUnichar aReplacementCharacter); +}; diff --git a/xpcom/io/nsIDirectoryEnumerator.idl b/xpcom/io/nsIDirectoryEnumerator.idl new file mode 100644 index 000000000..7a1135fda --- /dev/null +++ b/xpcom/io/nsIDirectoryEnumerator.idl @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +interface nsIFile; + +/** + * This interface provides a means for enumerating the contents of a directory. + * It is similar to nsISimpleEnumerator except the retrieved entries are QI'ed + * to nsIFile, and there is a mechanism for closing the directory when the + * enumeration is complete. + */ +[scriptable, uuid(31f7f4ae-6916-4f2d-a81e-926a4e3022ee)] +interface nsIDirectoryEnumerator : nsISupports +{ + /** + * Retrieves the next file in the sequence. The "nextFile" element is the + * first element upon the first call. This attribute is null if there is no + * next element. + */ + readonly attribute nsIFile nextFile; + + /** + * Closes the directory being enumerated, releasing the system resource. + * @throws NS_OK if the call succeeded and the directory was closed. + * NS_ERROR_FAILURE if the directory close failed. + * It is safe to call this function many times. + */ + void close(); +}; + diff --git a/xpcom/io/nsIDirectoryService.idl b/xpcom/io/nsIDirectoryService.idl new file mode 100644 index 000000000..520c9bc11 --- /dev/null +++ b/xpcom/io/nsIDirectoryService.idl @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" +#include "nsIFile.idl" + +/** + * nsIDirectoryServiceProvider + * + * Used by Directory Service to get file locations. + */ + +[scriptable, uuid(bbf8cab0-d43a-11d3-8cc2-00609792278c)] +interface nsIDirectoryServiceProvider: nsISupports +{ + /** + * getFile + * + * Directory Service calls this when it gets the first request for + * a prop or on every request if the prop is not persistent. + * + * @param prop The symbolic name of the file. + * @param persistent TRUE - The returned file will be cached by Directory + * Service. Subsequent requests for this prop will + * bypass the provider and use the cache. + * FALSE - The provider will be asked for this prop + * each time it is requested. + * + * @return The file represented by the property. + * + */ + nsIFile getFile(in string prop, out boolean persistent); +}; + +/** + * nsIDirectoryServiceProvider2 + * + * An extension of nsIDirectoryServiceProvider which allows + * multiple files to be returned for the given key. + */ + +[scriptable, uuid(2f977d4b-5485-11d4-87e2-0010a4e75ef2)] +interface nsIDirectoryServiceProvider2: nsIDirectoryServiceProvider +{ + /** + * getFiles + * + * Directory Service calls this when it gets a request for + * a prop and the requested type is nsISimpleEnumerator. + * + * @param prop The symbolic name of the file list. + * + * @return An enumerator for a list of file locations. + * The elements in the enumeration are nsIFile + * @returnCode NS_SUCCESS_AGGREGATE_RESULT if this result should be + * aggregated with other "lower" providers. + */ + nsISimpleEnumerator getFiles(in string prop); +}; + +/** + * nsIDirectoryService + */ + +[scriptable, uuid(57a66a60-d43a-11d3-8cc2-00609792278c)] +interface nsIDirectoryService: nsISupports +{ + /** + * init + * + * Must be called. Used internally by XPCOM initialization. + * + */ + void init(); + + /** + * registerProvider + * + * Register a provider with the service. + * + * @param prov The service will keep a strong reference + * to this object. It will be released when + * the service is released. + * + */ + void registerProvider(in nsIDirectoryServiceProvider prov); + + /** + * unregisterProvider + * + * Unregister a provider with the service. + * + * @param prov + * + */ + void unregisterProvider(in nsIDirectoryServiceProvider prov); +}; + + diff --git a/xpcom/io/nsIFile.idl b/xpcom/io/nsIFile.idl new file mode 100644 index 000000000..0f353ecb4 --- /dev/null +++ b/xpcom/io/nsIFile.idl @@ -0,0 +1,465 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +%{C++ +#include "prio.h" +#include "prlink.h" +#include <stdio.h> +%} + +[ptr] native PRFileDescStar(PRFileDesc); +[ptr] native PRLibraryStar(PRLibrary); +[ptr] native FILE(FILE); + +interface nsISimpleEnumerator; + +/** + * This is the only correct cross-platform way to specify a file. + * Strings are not such a way. If you grew up on windows or unix, you + * may think they are. Welcome to reality. + * + * All methods with string parameters have two forms. The preferred + * form operates on UCS-2 encoded characters strings. An alternate + * form operates on characters strings encoded in the "native" charset. + * + * A string containing characters encoded in the native charset cannot + * be safely passed to javascript via xpconnect. Therefore, the "native + * methods" are not scriptable. + */ +[scriptable, uuid(272a5020-64f5-485c-a8c4-44b2882ae0a2), builtinclass] +interface nsIFile : nsISupports +{ + /** + * Create Types + * + * NORMAL_FILE_TYPE - A normal file. + * DIRECTORY_TYPE - A directory/folder. + */ + const unsigned long NORMAL_FILE_TYPE = 0; + const unsigned long DIRECTORY_TYPE = 1; + + /** + * append[Native] + * + * This function is used for constructing a descendent of the + * current nsIFile. + * + * @param node + * A string which is intended to be a child node of the nsIFile. + * For the |appendNative| method, the node must be in the native + * filesystem charset. + */ + void append(in AString node); + [noscript] void appendNative(in ACString node); + + /** + * Normalize the pathName (e.g. removing .. and . components on Unix). + */ + void normalize(); + + /** + * create + * + * This function will create a new file or directory in the + * file system. Any nodes that have not been created or + * resolved, will be. If the file or directory already + * exists create() will return NS_ERROR_FILE_ALREADY_EXISTS. + * + * @param type + * This specifies the type of file system object + * to be made. The only two types at this time + * are file and directory which are defined above. + * If the type is unrecongnized, we will return an + * error (NS_ERROR_FILE_UNKNOWN_TYPE). + * + * @param permissions + * The unix style octal permissions. This may + * be ignored on systems that do not need to do + * permissions. + */ + void create(in unsigned long type, in unsigned long permissions); + + /** + * Accessor to the leaf name of the file itself. + * For the |nativeLeafName| method, the nativeLeafName must + * be in the native filesystem charset. + */ + attribute AString leafName; + [noscript] attribute ACString nativeLeafName; + + /** + * copyTo[Native] + * + * This will copy this file to the specified newParentDir. + * If a newName is specified, the file will be renamed. + * If 'this' is not created we will return an error + * (NS_ERROR_FILE_TARGET_DOES_NOT_EXIST). + * + * copyTo may fail if the file already exists in the destination + * directory. + * + * copyTo will NOT resolve aliases/shortcuts during the copy. + * + * @param newParentDir + * This param is the destination directory. If the + * newParentDir is null, copyTo() will use the parent + * directory of this file. If the newParentDir is not + * empty and is not a directory, an error will be + * returned (NS_ERROR_FILE_DESTINATION_NOT_DIR). For the + * |CopyToNative| method, the newName must be in the + * native filesystem charset. + * + * @param newName + * This param allows you to specify a new name for + * the file to be copied. This param may be empty, in + * which case the current leaf name will be used. + */ + void copyTo(in nsIFile newParentDir, in AString newName); + [noscript] void CopyToNative(in nsIFile newParentDir, in ACString newName); + + /** + * copyToFollowingLinks[Native] + * + * This function is identical to copyTo with the exception that, + * as the name implies, it follows symbolic links. The XP_UNIX + * implementation always follow symbolic links when copying. For + * the |CopyToFollowingLinks| method, the newName must be in the + * native filesystem charset. + */ + void copyToFollowingLinks(in nsIFile newParentDir, in AString newName); + [noscript] void copyToFollowingLinksNative(in nsIFile newParentDir, in ACString newName); + + /** + * moveTo[Native] + * + * A method to move this file or directory to newParentDir. + * If a newName is specified, the file or directory will be renamed. + * If 'this' is not created we will return an error + * (NS_ERROR_FILE_TARGET_DOES_NOT_EXIST). + * If 'this' is a file, and the destination file already exists, moveTo + * will replace the old file. + * This object is updated to refer to the new file. + * + * moveTo will NOT resolve aliases/shortcuts during the copy. + * moveTo will do the right thing and allow copies across volumes. + * moveTo will return an error (NS_ERROR_FILE_DIR_NOT_EMPTY) if 'this' is + * a directory and the destination directory is not empty. + * moveTo will return an error (NS_ERROR_FILE_ACCESS_DENIED) if 'this' is + * a directory and the destination directory is not writable. + * + * @param newParentDir + * This param is the destination directory. If the + * newParentDir is empty, moveTo() will rename the file + * within its current directory. If the newParentDir is + * not empty and does not name a directory, an error will + * be returned (NS_ERROR_FILE_DESTINATION_NOT_DIR). For + * the |moveToNative| method, the newName must be in the + * native filesystem charset. + * + * @param newName + * This param allows you to specify a new name for + * the file to be moved. This param may be empty, in + * which case the current leaf name will be used. + */ + void moveTo(in nsIFile newParentDir, in AString newName); + [noscript] void moveToNative(in nsIFile newParentDir, in ACString newName); + + /** + * This will try to delete this file. The 'recursive' flag + * must be PR_TRUE to delete directories which are not empty. + * + * This will not resolve any symlinks. + */ + void remove(in boolean recursive); + + /** + * Attributes of nsIFile. + */ + + attribute unsigned long permissions; + attribute unsigned long permissionsOfLink; + + /** + * File Times are to be in milliseconds from + * midnight (00:00:00), January 1, 1970 Greenwich Mean + * Time (GMT). + */ + attribute PRTime lastModifiedTime; + attribute PRTime lastModifiedTimeOfLink; + + /** + * WARNING! On the Mac, getting/setting the file size with nsIFile + * only deals with the size of the data fork. If you need to + * know the size of the combined data and resource forks use the + * GetFileSizeWithResFork() method defined on nsILocalFileMac. + */ + attribute int64_t fileSize; + readonly attribute int64_t fileSizeOfLink; + + /** + * target & path + * + * Accessor to the string path. The native version of these + * strings are not guaranteed to be a usable path to pass to + * NSPR or the C stdlib. There are problems that affect + * platforms on which a path does not fully specify a file + * because two volumes can have the same name (e.g., mac). + * This is solved by holding "private", native data in the + * nsIFile implementation. This native data is lost when + * you convert to a string. + * + * DO NOT PASS TO USE WITH NSPR OR STDLIB! + * + * target + * Find out what the symlink points at. Will give error + * (NS_ERROR_FILE_INVALID_PATH) if not a symlink. + * + * path + * Find out what the nsIFile points at. + * + * Note that the ACString attributes are returned in the + * native filesystem charset. + * + */ + readonly attribute AString target; + [noscript] readonly attribute ACString nativeTarget; + readonly attribute AString path; + [noscript] readonly attribute ACString nativePath; + + boolean exists(); + boolean isWritable(); + boolean isReadable(); + boolean isExecutable(); + boolean isHidden(); + boolean isDirectory(); + boolean isFile(); + boolean isSymlink(); + /** + * Not a regular file, not a directory, not a symlink. + */ + boolean isSpecial(); + + /** + * createUnique + * + * This function will create a new file or directory in the + * file system. Any nodes that have not been created or + * resolved, will be. If this file already exists, we try + * variations on the leaf name "suggestedName" until we find + * one that did not already exist. + * + * If the search for nonexistent files takes too long + * (thousands of the variants already exist), we give up and + * return NS_ERROR_FILE_TOO_BIG. + * + * @param type + * This specifies the type of file system object + * to be made. The only two types at this time + * are file and directory which are defined above. + * If the type is unrecongnized, we will return an + * error (NS_ERROR_FILE_UNKNOWN_TYPE). + * + * @param permissions + * The unix style octal permissions. This may + * be ignored on systems that do not need to do + * permissions. + */ + void createUnique(in unsigned long type, in unsigned long permissions); + + /** + * clone() + * + * This function will allocate and initialize a nsIFile object to the + * exact location of the |this| nsIFile. + * + * @param file + * A nsIFile which this object will be initialize + * with. + * + */ + nsIFile clone(); + + /** + * Will determine if the inFile equals this. + */ + boolean equals(in nsIFile inFile); + + /** + * Will determine if inFile is a descendant of this file + * If |recur| is true, look in subdirectories too + */ + boolean contains(in nsIFile inFile, in boolean recur); + + /** + * Parent will be null when this is at the top of the volume. + */ + readonly attribute nsIFile parent; + + /** + * Returns an enumeration of the elements in a directory. Each + * element in the enumeration is an nsIFile. + * + * @throws NS_ERROR_FILE_NOT_DIRECTORY if the current nsIFile does + * not specify a directory. + */ + readonly attribute nsISimpleEnumerator directoryEntries; + + /** + * initWith[Native]Path + * + * This function will initialize the nsIFile object. Any + * internal state information will be reset. + * + * @param filePath + * A string which specifies a full file path to a + * location. Relative paths will be treated as an + * error (NS_ERROR_FILE_UNRECOGNIZED_PATH). For + * initWithNativePath, the filePath must be in the native + * filesystem charset. + */ + void initWithPath(in AString filePath); + [noscript] void initWithNativePath(in ACString filePath); + + /** + * initWithFile + * + * Initialize this object with another file + * + * @param aFile + * the file this becomes equivalent to + */ + void initWithFile(in nsIFile aFile); + + /** + * followLinks + * + * This attribute will determine if the nsLocalFile will auto + * resolve symbolic links. By default, this value will be false + * on all non unix systems. On unix, this attribute is effectively + * a noop. + */ + attribute boolean followLinks; + + /** + * Flag for openNSPRFileDesc(), to hint to the OS that the file will be + * read sequentially with agressive readahead. + */ + const unsigned long OS_READAHEAD = 0x40000000; + + /** + * Flag for openNSPRFileDesc(). Deprecated and unreliable! + * Instead use NS_OpenAnonymousTemporaryFile() to create a temporary + * file which will be deleted upon close! + */ + const unsigned long DELETE_ON_CLOSE = 0x80000000; + + /** + * Return the result of PR_Open on the file. The caller is + * responsible for calling PR_Close on the result. + * + * @param flags the PR_Open flags from prio.h, plus optionally + * OS_READAHEAD or DELETE_ON_CLOSE. OS_READAHEAD is a hint to the + * OS that the file will be read sequentially with agressive + * readahead. DELETE_ON_CLOSE is unreliable on Windows and is deprecated. + * Instead use NS_OpenAnonymousTemporaryFile() to create a temporary + * file which will be deleted upon close. + */ + [noscript] PRFileDescStar openNSPRFileDesc(in long flags, in long mode); + + /** + * Return the result of fopen on the file. The caller is + * responsible for calling fclose on the result. + */ + [noscript] FILE openANSIFileDesc(in string mode); + + /** + * Return the result of PR_LoadLibrary on the file. The caller is + * responsible for calling PR_UnloadLibrary on the result. + */ + [noscript] PRLibraryStar load(); + + // number of bytes available on disk to non-superuser + readonly attribute int64_t diskSpaceAvailable; + + /** + * appendRelative[Native]Path + * + * Append a relative path to the current path of the nsIFile object. + * + * @param relativeFilePath + * relativeFilePath is a native relative path. For security reasons, + * this cannot contain .. or cannot start with a directory separator. + * For the |appendRelativeNativePath| method, the relativeFilePath + * must be in the native filesystem charset. + */ + void appendRelativePath(in AString relativeFilePath); + [noscript] void appendRelativeNativePath(in ACString relativeFilePath); + + /** + * Accessor to a null terminated string which will specify + * the file in a persistent manner for disk storage. + * + * The character set of this attribute is undefined. DO NOT TRY TO + * INTERPRET IT AS HUMAN READABLE TEXT! + */ + attribute ACString persistentDescriptor; + + /** + * reveal + * + * Ask the operating system to open the folder which contains + * this file or folder. This routine only works on platforms which + * support the ability to open a folder and is run async on Windows. + * This routine must be called on the main. + */ + void reveal(); + + /** + * launch + * + * Ask the operating system to attempt to open the file. + * this really just simulates "double clicking" the file on your platform. + * This routine only works on platforms which support this functionality + * and is run async on Windows. This routine must be called on the + * main thread. + */ + void launch(); + + /** + * getRelativeDescriptor + * + * Returns a relative file path in an opaque, XP format. It is therefore + * not a native path. + * + * The character set of the string returned from this function is + * undefined. DO NOT TRY TO INTERPRET IT AS HUMAN READABLE TEXT! + * + * @param fromFile + * the file from which the descriptor is relative. + * There is no defined result if this param is null. + */ + ACString getRelativeDescriptor(in nsIFile fromFile); + + /** + * setRelativeDescriptor + * + * Initializes the file to the location relative to fromFile using + * a string returned by getRelativeDescriptor. + * + * @param fromFile + * the file to which the descriptor is relative + * @param relative + * the relative descriptor obtained from getRelativeDescriptor + */ + void setRelativeDescriptor(in nsIFile fromFile, in ACString relativeDesc); +}; + +%{C++ +#ifdef MOZILLA_INTERNAL_API +#include "nsDirectoryServiceUtils.h" +#endif +%} diff --git a/xpcom/io/nsIIOUtil.idl b/xpcom/io/nsIIOUtil.idl new file mode 100644 index 000000000..577ba4046 --- /dev/null +++ b/xpcom/io/nsIIOUtil.idl @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +interface nsIInputStream; +interface nsIOutputStream; + +/** + * nsIIOUtil provdes various xpcom/io-related utility methods. + */ +[scriptable, uuid(e8152f7f-4209-4c63-ad23-c3d2aa0c5a49)] +interface nsIIOUtil : nsISupports +{ + /** + * Test whether an input stream is buffered. See nsStreamUtils.h + * documentation for NS_InputStreamIsBuffered for the definition of + * "buffered" used here and for edge-case behavior. + * + * @throws NS_ERROR_INVALID_POINTER if null is passed in. + */ + boolean inputStreamIsBuffered(in nsIInputStream aStream); + + /** + * Test whether an output stream is buffered. See nsStreamUtils.h + * documentation for NS_OutputStreamIsBuffered for the definition of + * "buffered" used here and for edge-case behavior. + * + * @throws NS_ERROR_INVALID_POINTER if null is passed in. + */ + boolean outputStreamIsBuffered(in nsIOutputStream aStream); +}; diff --git a/xpcom/io/nsIInputStream.idl b/xpcom/io/nsIInputStream.idl new file mode 100644 index 000000000..b68cdbb7d --- /dev/null +++ b/xpcom/io/nsIInputStream.idl @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +interface nsIInputStream; + +%{C++ +/** + * The signature of the writer function passed to ReadSegments. This + * is the "consumer" of data that gets read from the stream's buffer. + * + * @param aInStream stream being read + * @param aClosure opaque parameter passed to ReadSegments + * @param aFromSegment pointer to memory owned by the input stream. This is + * where the writer function should start consuming data. + * @param aToOffset amount of data already consumed by this writer during this + * ReadSegments call. This is also the sum of the aWriteCount + * returns from this writer over the previous invocations of + * the writer by this ReadSegments call. + * @param aCount Number of bytes available to be read starting at aFromSegment + * @param [out] aWriteCount number of bytes read by this writer function call + * + * Implementers should return the following: + * + * @return NS_OK and (*aWriteCount > 0) if consumed some data + * @return <any-error> if not interested in consuming any data + * + * Errors are never passed to the caller of ReadSegments. + * + * NOTE: returning NS_OK and (*aWriteCount = 0) has undefined behavior. + */ +typedef NS_CALLBACK(nsWriteSegmentFun)(nsIInputStream *aInStream, + void *aClosure, + const char *aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t *aWriteCount); +%} + +native nsWriteSegmentFun(nsWriteSegmentFun); + +/** + * nsIInputStream + * + * An interface describing a readable stream of data. An input stream may be + * "blocking" or "non-blocking" (see the IsNonBlocking method). A blocking + * input stream may suspend the calling thread in order to satisfy a call to + * Close, Available, Read, or ReadSegments. A non-blocking input stream, on + * the other hand, must not block the calling thread of execution. + * + * NOTE: blocking input streams are often read on a background thread to avoid + * locking up the main application thread. For this reason, it is generally + * the case that a blocking input stream should be implemented using thread- + * safe AddRef and Release. + */ +[scriptable, uuid(53cdbc97-c2d7-4e30-b2c3-45b2ee79db18)] +interface nsIInputStream : nsISupports +{ + /** + * Close the stream. This method causes subsequent calls to Read and + * ReadSegments to return 0 bytes read to indicate end-of-file. Any + * subsequent calls to Available should throw NS_BASE_STREAM_CLOSED. + */ + void close(); + + /** + * Determine number of bytes available in the stream. A non-blocking + * stream that does not yet have any data to read should return 0 bytes + * from this method (i.e., it must not throw the NS_BASE_STREAM_WOULD_BLOCK + * exception). + * + * In addition to the number of bytes available in the stream, this method + * also informs the caller of the current status of the stream. A stream + * that is closed will throw an exception when this method is called. That + * enables the caller to know the condition of the stream before attempting + * to read from it. If a stream is at end-of-file, but not closed, then + * this method returns 0 bytes available. (Note: some nsIInputStream + * implementations automatically close when eof is reached; some do not). + * + * @return number of bytes currently available in the stream. + * + * @throws NS_BASE_STREAM_CLOSED if the stream is closed normally. + * @throws <other-error> if the stream is closed due to some error + * condition + */ + unsigned long long available(); + + /** + * Read data from the stream. + * + * @param aBuf the buffer into which the data is to be read + * @param aCount the maximum number of bytes to be read + * + * @return number of bytes read (may be less than aCount). + * @return 0 if reached end-of-file + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream would + * block the calling thread (non-blocking mode only) + * @throws <other-error> on failure + * + * NOTE: this method should not throw NS_BASE_STREAM_CLOSED. + */ + [noscript] unsigned long read(in charPtr aBuf, in unsigned long aCount); + + /** + * Low-level read method that provides access to the stream's underlying + * buffer. The writer function may be called multiple times for segmented + * buffers. ReadSegments is expected to keep calling the writer until + * either there is nothing left to read or the writer returns an error. + * ReadSegments should not call the writer with zero bytes to consume. + * + * @param aWriter the "consumer" of the data to be read + * @param aClosure opaque parameter passed to writer + * @param aCount the maximum number of bytes to be read + * + * @return number of bytes read (may be less than aCount) + * @return 0 if reached end-of-file (or if aWriter refused to consume data) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream would + * block the calling thread (non-blocking mode only) + * @throws NS_ERROR_NOT_IMPLEMENTED if the stream has no underlying buffer + * @throws <other-error> on failure + * + * NOTE: this function may be unimplemented if a stream has no underlying + * buffer (e.g., socket input stream). + * + * NOTE: this method should not throw NS_BASE_STREAM_CLOSED. + */ + [noscript] unsigned long readSegments(in nsWriteSegmentFun aWriter, + in voidPtr aClosure, + in unsigned long aCount); + + /** + * @return true if stream is non-blocking + * + * NOTE: reading from a blocking input stream will block the calling thread + * until at least one byte of data can be extracted from the stream. + * + * NOTE: a non-blocking input stream may implement nsIAsyncInputStream to + * provide consumers with a way to wait for the stream to have more data + * once its read method is unable to return any data without blocking. + */ + boolean isNonBlocking(); +}; diff --git a/xpcom/io/nsIInputStreamTee.idl b/xpcom/io/nsIInputStreamTee.idl new file mode 100644 index 000000000..953be7a7c --- /dev/null +++ b/xpcom/io/nsIInputStreamTee.idl @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIInputStream.idl" + +interface nsIOutputStream; +interface nsIEventTarget; + +/** + * A nsIInputStreamTee is a wrapper for an input stream, that when read + * reads the specified amount of data from its |source| and copies that + * data to its |sink|. |sink| must be a blocking output stream. + */ +[scriptable, uuid(90a9d790-3bca-421e-a73b-98f68e13c917)] +interface nsIInputStreamTee : nsIInputStream +{ + attribute nsIInputStream source; + attribute nsIOutputStream sink; + + /** + * If |eventTarget| is set, copying to sink is done asynchronously using + * the event-target (e.g. a thread). If |eventTarget| is not set, copying + * to sink happens synchronously while reading from the source. + */ + attribute nsIEventTarget eventTarget; +}; + +%{C++ +// factory methods +extern nsresult +NS_NewInputStreamTee(nsIInputStream **tee, // read from this input stream + nsIInputStream *source, + nsIOutputStream *sink); + +extern nsresult +NS_NewInputStreamTeeAsync(nsIInputStream **tee, // read from this input stream + nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *eventTarget); +%} diff --git a/xpcom/io/nsILineInputStream.idl b/xpcom/io/nsILineInputStream.idl new file mode 100644 index 000000000..db53a456d --- /dev/null +++ b/xpcom/io/nsILineInputStream.idl @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 "nsISupports.idl" + +interface nsILineInputStream; + +[scriptable, uuid(c97b466c-1e6e-4773-a4ab-2b2b3190a7a6)] +interface nsILineInputStream : nsISupports +{ + /** + * Read a single line from the stream, where a line is a + * possibly zero length sequence of 8bit chars terminated by a + * CR, LF, CRLF, LFCR, or eof. + * The line terminator is not returned. + * @retval false + * End of file. This line is the last line of the file + * (aLine is valid). + * @retval true + * The file contains further lines. + * @note Do not mix readLine with other read functions. + * Doing so can cause various problems and is not supported. + */ + boolean readLine(out ACString aLine); +}; diff --git a/xpcom/io/nsILocalFile.idl b/xpcom/io/nsILocalFile.idl new file mode 100644 index 000000000..bcfea14a9 --- /dev/null +++ b/xpcom/io/nsILocalFile.idl @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIFile.idl" + +/** + * An empty interface to provide backwards compatibility for existing code. + * + * @see nsIFile + */ +[scriptable, builtinclass, uuid(ce4ef184-7660-445e-9e59-6731bdc65505)] +interface nsILocalFile : nsIFile +{ +}; + diff --git a/xpcom/io/nsILocalFileMac.idl b/xpcom/io/nsILocalFileMac.idl new file mode 100644 index 000000000..2a815eb20 --- /dev/null +++ b/xpcom/io/nsILocalFileMac.idl @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsILocalFile.idl" + +%{C++ +#include <Carbon/Carbon.h> +#include <CoreFoundation/CoreFoundation.h> +%} + + native OSType(OSType); + native FSSpec(FSSpec); + native FSRef(FSRef); +[ptr] native FSRefPtr(FSRef); + native CFURLRef(CFURLRef); + +[scriptable, builtinclass, uuid(E5DE2CC9-BF06-4329-8F91-5D2D45284500)] +interface nsILocalFileMac : nsILocalFile +{ + /** + * initWithCFURL + * + * Init this object with a CFURLRef + * + * NOTE: Supported only for XP_MACOSX + * NOTE: If the path of the CFURL is /a/b/c, at least a/b must exist beforehand. + * + * @param aCFURL the CoreFoundation URL + * + */ + [noscript] void initWithCFURL(in CFURLRef aCFURL); + + /** + * initWithFSRef + * + * Init this object with an FSRef + * + * NOTE: Supported only for XP_MACOSX + * + * @param aFSRef the native FSRef + * + */ + [noscript] void initWithFSRef([const] in FSRefPtr aFSRef); + + /** + * getCFURL + * + * Returns the CFURLRef of the file object. The caller is + * responsible for calling CFRelease() on it. + * + * NOTE: Observes the state of the followLinks attribute. + * If the file object is an alias and followLinks is TRUE, returns + * the target of the alias. If followLinks is FALSE, returns + * the unresolved alias file. + * + * NOTE: Supported only for XP_MACOSX + * + * @return + * + */ + [noscript] CFURLRef getCFURL(); + + /** + * getFSRef + * + * Returns the FSRef of the file object. + * + * NOTE: Observes the state of the followLinks attribute. + * If the file object is an alias and followLinks is TRUE, returns + * the target of the alias. If followLinks is FALSE, returns + * the unresolved alias file. + * + * NOTE: Supported only for XP_MACOSX + * + * @return + * + */ + [noscript] FSRef getFSRef(); + + /** + * getFSSpec + * + * Returns the FSSpec of the file object. + * + * NOTE: Observes the state of the followLinks attribute. + * If the file object is an alias and followLinks is TRUE, returns + * the target of the alias. If followLinks is FALSE, returns + * the unresolved alias file. + * + * @return + * + */ + [noscript] FSSpec getFSSpec(); + + /** + * fileSizeWithResFork + * + * Returns the combined size of both the data fork and the resource + * fork (if present) rather than just the size of the data fork + * as returned by GetFileSize() + * + */ + readonly attribute int64_t fileSizeWithResFork; + + /** + * fileType, creator + * + * File type and creator attributes + * + */ + [noscript] attribute OSType fileType; + [noscript] attribute OSType fileCreator; + + /** + * launchWithDoc + * + * Launch the application that this file points to with a document. + * + * @param aDocToLoad Must not be NULL. If no document, use nsIFile::launch + * @param aLaunchInBackground TRUE if the application should not come to the front. + * + */ + void launchWithDoc(in nsIFile aDocToLoad, in boolean aLaunchInBackground); + + /** + * openDocWithApp + * + * Open the document that this file points to with the given application. + * + * @param aAppToOpenWith The application with which to open the document. + * If NULL, the creator code of the document is used + * to determine the application. + * @param aLaunchInBackground TRUE if the application should not come to the front. + * + */ + void openDocWithApp(in nsIFile aAppToOpenWith, in boolean aLaunchInBackground); + + /** + * isPackage + * + * returns true if a directory is determined to be a package under Mac OS 9/X + * + */ + boolean isPackage(); + + /** + * bundleDisplayName + * + * returns the display name of the application bundle (usually the human + * readable name of the application) + */ + readonly attribute AString bundleDisplayName; + + /** + * bundleIdentifier + * + * returns the identifier of the bundle + */ + readonly attribute AUTF8String bundleIdentifier; + + /** + * Last modified time of a bundle's contents (as opposed to its package + * directory). Our convention is to make the bundle's Info.plist file + * stand in for the rest of its contents -- since this file contains the + * bundle's version information and other identifiers. For non-bundles + * this is the same as lastModifiedTime. + */ + readonly attribute int64_t bundleContentsLastModifiedTime; +}; + +%{C++ +extern "C" +{ +NS_EXPORT nsresult NS_NewLocalFileWithFSRef(const FSRef* aFSRef, bool aFollowSymlinks, nsILocalFileMac** result); +NS_EXPORT nsresult NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowSymlinks, nsILocalFileMac** result); +} +%} diff --git a/xpcom/io/nsILocalFileOS2.idl b/xpcom/io/nsILocalFileOS2.idl new file mode 100644 index 000000000..273715e89 --- /dev/null +++ b/xpcom/io/nsILocalFileOS2.idl @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 "nsILocalFile.idl" + +interface nsIArray; + +[scriptable, uuid(26de2089-239d-4697-818b-bae1fe8e8e0d)] +interface nsILocalFileOS2 : nsILocalFile +{ + /** + * getFileTypes + * + * Returns the file's .TYPE extended attribute as an array of + * nsISupportsCStrings. + * + */ + nsIArray getFileTypes( ); + + /** + * setFileTypes + * + * Sets the file's .TYPE extended attribute from a comma-separated + * list of types (this format is used because clients are unlikely + * to write more than a single type). + * @param fileTypes + * a string in the filesystem's native character set + * + */ + void setFileTypes( in ACString fileTypes ); + + /** + * isFileType + * + * Returns TRUE if the file has a .TYPE extended attribute that + * matches the string passed in. The comparison is case-sensitive. + * @param fileType + * a string in the filesystem's native character set + * + */ + boolean isFileType( in ACString fileType ); + + /** + * setFileSource + * + * Identifies the origin of a downloaded file by writing the + * source URI's spec to the .SUBJECT extended attribute. + * + * @param aURI + * the source URI + * + */ + void setFileSource( in AUTF8String aURI ); +}; diff --git a/xpcom/io/nsILocalFileWin.idl b/xpcom/io/nsILocalFileWin.idl new file mode 100644 index 000000000..150c31a1e --- /dev/null +++ b/xpcom/io/nsILocalFileWin.idl @@ -0,0 +1,98 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 "nsILocalFile.idl" + +[scriptable, builtinclass, uuid(c7b3fd13-30f2-46e5-a0d9-7a79a9b73c5b)] +interface nsILocalFileWin : nsILocalFile +{ + /** + * getVersionInfoValue + * + * Retrieve a metadata field from the file's VERSIONINFO block. + * Throws NS_ERROR_FAILURE if no value is found, or the value is empty. + * + * @param aField The field to look up. + * + */ + AString getVersionInfoField(in string aField); + + /** + * The canonical path of the file, which avoids short/long + * pathname inconsistencies. The nsIFile persistent + * descriptor is not guaranteed to be canonicalized (it may + * persist either the long or the short path name). The format of + * the canonical path will vary with the underlying file system: + * it will typically be the short pathname on filesystems that + * support both short and long path forms. + */ + readonly attribute AString canonicalPath; + [noscript] readonly attribute ACString nativeCanonicalPath; + + /** + * Windows specific file attributes. + */ + + /* + * WFA_SEARCH_INDEXED: Generally the default on files in Windows except + * those created in temp locations. Valid on XP and up. When set the + * file or directory is marked to be indexed by desktop search services. + */ + const unsigned long WFA_SEARCH_INDEXED = 1; + + /* + * WFA_READONLY: Whether the file is readonly or not. + */ + const unsigned long WFA_READONLY = 2; + + /* + * WFA_READWRITE: Used to clear the readonly attribute. + */ + const unsigned long WFA_READWRITE = 4; + + /** + * fileAttributesWin + * + * Set or get windows specific file attributes. + * + * Throws NS_ERROR_FILE_INVALID_PATH for an invalid file. + * Throws NS_ERROR_FAILURE if the set or get fails. + */ + attribute unsigned long fileAttributesWin; + + /** + * setShortcut + * + * Creates the specified shortcut, or updates it if it already exists. + * + * If the shortcut is being updated (i.e. the shortcut already exists), + * any excluded parameters will remain unchanged in the shortcut file. + * For example, if you want to change the description of a specific + * shortcut but keep the target, working dir, args, and icon the same, + * pass null for those parameters and only pass in a value for the + * description. + * + * If the shortcut does not already exist and targetFile is not specified, + * setShortcut will throw NS_ERROR_FILE_TARGET_DOES_NOT_EXIST. + * + * @param targetFile the path that the shortcut should target + * @param workingDir the working dir that should be set for the shortcut + * @param args the args string that should be set for the shortcut + * @param description the description that should be set for the shortcut + * @param iconFile the file containing an icon to be used for this + shortcut + * @param iconIndex this value selects a specific icon from within + iconFile. If iconFile contains only one icon, this + value should be 0. + */ + void setShortcut([optional] in nsIFile targetFile, + [optional] in nsIFile workingDir, + [optional] in wstring args, + [optional] in wstring description, + [optional] in nsIFile iconFile, + [optional] in long iconIndex); +}; + diff --git a/xpcom/io/nsIMultiplexInputStream.idl b/xpcom/io/nsIMultiplexInputStream.idl new file mode 100644 index 000000000..d42adcbd4 --- /dev/null +++ b/xpcom/io/nsIMultiplexInputStream.idl @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIInputStream.idl" + +/** + * The multiplex stream concatenates a list of input streams into a single + * stream. + */ + +[scriptable, uuid(a076fd12-1dd1-11b2-b19a-d53b5dffaade)] +interface nsIMultiplexInputStream : nsIInputStream +{ + /** + * Number of streams in this multiplex-stream + */ + readonly attribute unsigned long count; + + /** + * Appends a stream to the end of the streams. The cursor of the stream + * should be located at the beginning of the stream if the implementation + * of this nsIMultiplexInputStream also is used as an nsISeekableStream. + * @param stream stream to append + */ + void appendStream(in nsIInputStream stream); + + /** + * Insert a stream at specified index. If the cursor of this stream is at + * the beginning of the stream at index, the cursor will be placed at the + * beginning of the inserted stream instead. + * The cursor of the new stream should be located at the beginning of the + * stream if the implementation of this nsIMultiplexInputStream also is + * used as an nsISeekableStream. + * @param stream stream to insert + * @param index index to insert stream at, must be <= count + */ + void insertStream(in nsIInputStream stream, in unsigned long index); + + /** + * Remove stream at specified index. If this stream is the one currently + * being read the readcursor is moved to the beginning of the next + * stream + * @param index remove stream at this index, must be < count + */ + void removeStream(in unsigned long index); + + /** + * Get stream at specified index. + * @param index return stream at this index, must be < count + * @return stream at specified index + */ + nsIInputStream getStream(in unsigned long index); +}; diff --git a/xpcom/io/nsIOUtil.cpp b/xpcom/io/nsIOUtil.cpp new file mode 100644 index 000000000..30bd4fe94 --- /dev/null +++ b/xpcom/io/nsIOUtil.cpp @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIOUtil.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsStreamUtils.h" + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsIOUtil, nsIIOUtil) + +NS_IMETHODIMP +nsIOUtil::InputStreamIsBuffered(nsIInputStream* aStream, bool* _retval) +{ + NS_ENSURE_ARG_POINTER(aStream); + *_retval = NS_InputStreamIsBuffered(aStream); + return NS_OK; +} + +NS_IMETHODIMP +nsIOUtil::OutputStreamIsBuffered(nsIOutputStream* aStream, bool* _retval) +{ + NS_ENSURE_ARG_POINTER(aStream); + *_retval = NS_OutputStreamIsBuffered(aStream); + return NS_OK; +} diff --git a/xpcom/io/nsIOUtil.h b/xpcom/io/nsIOUtil.h new file mode 100644 index 000000000..3e06ddb88 --- /dev/null +++ b/xpcom/io/nsIOUtil.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsIOUtil_h__ +#define nsIOUtil_h__ + +#define NS_IOUTIL_CID \ +{ 0xeb833911, 0x4f49, 0x4623, \ + { 0x84, 0x5f, 0xe5, 0x8a, 0x8e, 0x6d, 0xe4, 0xc2 } } + + +#include "nsIIOUtil.h" +#include "mozilla/Attributes.h" + +class nsIOUtil MOZ_FINAL : public nsIIOUtil +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIIOUTIL +}; + +#endif /* nsIOUtil_h__ */ diff --git a/xpcom/io/nsIObjectInputStream.idl b/xpcom/io/nsIObjectInputStream.idl new file mode 100644 index 000000000..70f2a55e8 --- /dev/null +++ b/xpcom/io/nsIObjectInputStream.idl @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIBinaryInputStream.idl" +#include "nsrootidl.idl" + +/** + * @see nsIObjectOutputStream + * @see nsIBinaryInputStream + */ + +[scriptable, uuid(6c248606-4eae-46fa-9df0-ba58502368eb)] +interface nsIObjectInputStream : nsIBinaryInputStream +{ + /** + * Read an object from this stream to satisfy a strong or weak reference + * to one of its interfaces. If the interface was not along the primary + * inheritance chain ending in the "root" or XPCOM-identity nsISupports, + * readObject will QueryInterface from the deserialized object root to the + * correct interface, which was specified when the object was serialized. + * + * @see nsIObjectOutputStream + */ + nsISupports readObject(in boolean aIsStrongRef); + + [notxpcom] nsresult readID(out nsID aID); + + /** + * Optimized deserialization support -- see nsIStreamBufferAccess.idl. + */ + [notxpcom] charPtr getBuffer(in uint32_t aLength, in uint32_t aAlignMask); + [notxpcom] void putBuffer(in charPtr aBuffer, in uint32_t aLength); +}; + +%{C++ + +inline nsresult +NS_ReadOptionalObject(nsIObjectInputStream* aStream, bool aIsStrongRef, + nsISupports* *aResult) +{ + bool nonnull; + nsresult rv = aStream->ReadBoolean(&nonnull); + if (NS_SUCCEEDED(rv)) { + if (nonnull) + rv = aStream->ReadObject(aIsStrongRef, aResult); + else + *aResult = nullptr; + } + return rv; +} + +%} diff --git a/xpcom/io/nsIObjectOutputStream.idl b/xpcom/io/nsIObjectOutputStream.idl new file mode 100644 index 000000000..92c77b8b1 --- /dev/null +++ b/xpcom/io/nsIObjectOutputStream.idl @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIBinaryOutputStream.idl" +#include "nsrootidl.idl" + +/** + * @See nsIObjectInputStream + * @See nsIBinaryOutputStream + */ + +[scriptable, uuid(92c898ac-5fde-4b99-87b3-5d486422094b)] +interface nsIObjectOutputStream : nsIBinaryOutputStream +{ + /** + * Write the object whose "root" or XPCOM-identity nsISupports is aObject. + * The cause for writing this object is a strong or weak reference, so the + * aIsStrongRef argument must tell which kind of pointer is being followed + * here during serialization. + * + * If the object has only one strong reference in the serialization and no + * weak refs, use writeSingleRefObject. This is a valuable optimization: + * it saves space in the stream, and cycles on both ends of the process. + * + * If the reference being serialized is a pointer to an interface not on + * the primary inheritance chain ending in the root nsISupports, you must + * call writeCompoundObject instead of this method. + */ + void writeObject(in nsISupports aObject, in boolean aIsStrongRef); + + /** + * Write an object referenced singly and strongly via its root nsISupports + * or a subclass of its root nsISupports. There must not be other refs to + * aObject in memory, or in the serialization. + */ + void writeSingleRefObject(in nsISupports aObject); + + /** + * Write the object referenced by an interface pointer at aObject that + * inherits from a non-primary nsISupports, i.e., a reference to one of + * the multiply inherited interfaces derived from an nsISupports other + * than the root or XPCOM-identity nsISupports; or a reference to an + * inner object in the case of true XPCOM aggregation. aIID identifies + * this interface. + */ + void writeCompoundObject(in nsISupports aObject, + in nsIIDRef aIID, + in boolean aIsStrongRef); + + void writeID(in nsIDRef aID); + + /** + * Optimized serialization support -- see nsIStreamBufferAccess.idl. + */ + [notxpcom] charPtr getBuffer(in uint32_t aLength, in uint32_t aAlignMask); + [notxpcom] void putBuffer(in charPtr aBuffer, in uint32_t aLength); +}; + +%{C++ + +inline nsresult +NS_WriteOptionalObject(nsIObjectOutputStream* aStream, nsISupports* aObject, + bool aIsStrongRef) +{ + bool nonnull = (aObject != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteObject(aObject, aIsStrongRef); + return rv; +} + +inline nsresult +NS_WriteOptionalSingleRefObject(nsIObjectOutputStream* aStream, + nsISupports* aObject) +{ + bool nonnull = (aObject != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteSingleRefObject(aObject); + return rv; +} + +inline nsresult +NS_WriteOptionalCompoundObject(nsIObjectOutputStream* aStream, + nsISupports* aObject, + const nsIID& aIID, + bool aIsStrongRef) +{ + bool nonnull = (aObject != nullptr); + nsresult rv = aStream->WriteBoolean(nonnull); + if (NS_SUCCEEDED(rv) && nonnull) + rv = aStream->WriteCompoundObject(aObject, aIID, aIsStrongRef); + return rv; +} + +%} diff --git a/xpcom/io/nsIOutputStream.idl b/xpcom/io/nsIOutputStream.idl new file mode 100644 index 000000000..d3181c517 --- /dev/null +++ b/xpcom/io/nsIOutputStream.idl @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +interface nsIOutputStream; +interface nsIInputStream; + +%{C++ +/** + * The signature for the reader function passed to WriteSegments. This + * is the "provider" of data that gets written into the stream's buffer. + * + * @param aOutStream stream being written to + * @param aClosure opaque parameter passed to WriteSegments + * @param aToSegment pointer to memory owned by the output stream + * @param aFromOffset amount already written (since WriteSegments was called) + * @param aCount length of toSegment + * @param aReadCount number of bytes written + * + * Implementers should return the following: + * + * @throws <any-error> if not interested in providing any data + * + * Errors are never passed to the caller of WriteSegments. + */ +typedef NS_CALLBACK(nsReadSegmentFun)(nsIOutputStream *aOutStream, + void *aClosure, + char *aToSegment, + uint32_t aFromOffset, + uint32_t aCount, + uint32_t *aReadCount); +%} + +native nsReadSegmentFun(nsReadSegmentFun); + +/** + * nsIOutputStream + * + * An interface describing a writable stream of data. An output stream may be + * "blocking" or "non-blocking" (see the IsNonBlocking method). A blocking + * output stream may suspend the calling thread in order to satisfy a call to + * Close, Flush, Write, WriteFrom, or WriteSegments. A non-blocking output + * stream, on the other hand, must not block the calling thread of execution. + * + * NOTE: blocking output streams are often written to on a background thread to + * avoid locking up the main application thread. For this reason, it is + * generally the case that a blocking output stream should be implemented using + * thread- safe AddRef and Release. + */ +[scriptable, uuid(0d0acd2a-61b4-11d4-9877-00c04fa0cf4a)] +interface nsIOutputStream : nsISupports +{ + /** + * Close the stream. Forces the output stream to flush any buffered data. + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if unable to flush without blocking + * the calling thread (non-blocking mode only) + */ + void close(); + + /** + * Flush the stream. + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if unable to flush without blocking + * the calling thread (non-blocking mode only) + */ + void flush(); + + /** + * Write data into the stream. + * + * @param aBuf the buffer containing the data to be written + * @param aCount the maximum number of bytes to be written + * + * @return number of bytes written (may be less than aCount) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if writing to the output stream would + * block the calling thread (non-blocking mode only) + * @throws <other-error> on failure + */ + unsigned long write(in string aBuf, in unsigned long aCount); + + /** + * Writes data into the stream from an input stream. + * + * @param aFromStream the stream containing the data to be written + * @param aCount the maximum number of bytes to be written + * + * @return number of bytes written (may be less than aCount) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if writing to the output stream would + * block the calling thread (non-blocking mode only) + * @throws <other-error> on failure + * + * NOTE: This method is defined by this interface in order to allow the + * output stream to efficiently copy the data from the input stream into + * its internal buffer (if any). If this method was provided as an external + * facility, a separate char* buffer would need to be used in order to call + * the output stream's other Write method. + */ + unsigned long writeFrom(in nsIInputStream aFromStream, + in unsigned long aCount); + + /** + * Low-level write method that has access to the stream's underlying buffer. + * The reader function may be called multiple times for segmented buffers. + * WriteSegments is expected to keep calling the reader until either there + * is nothing left to write or the reader returns an error. WriteSegments + * should not call the reader with zero bytes to provide. + * + * @param aReader the "provider" of the data to be written + * @param aClosure opaque parameter passed to reader + * @param aCount the maximum number of bytes to be written + * + * @return number of bytes written (may be less than aCount) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if writing to the output stream would + * block the calling thread (non-blocking mode only) + * @throws NS_ERROR_NOT_IMPLEMENTED if the stream has no underlying buffer + * @throws <other-error> on failure + * + * NOTE: this function may be unimplemented if a stream has no underlying + * buffer (e.g., socket output stream). + */ + [noscript] unsigned long writeSegments(in nsReadSegmentFun aReader, + in voidPtr aClosure, + in unsigned long aCount); + + /** + * @return true if stream is non-blocking + * + * NOTE: writing to a blocking output stream will block the calling thread + * until all given data can be consumed by the stream. + * + * NOTE: a non-blocking output stream may implement nsIAsyncOutputStream to + * provide consumers with a way to wait for the stream to accept more data + * once its write method is unable to accept any data without blocking. + */ + boolean isNonBlocking(); +}; diff --git a/xpcom/io/nsIPipe.idl b/xpcom/io/nsIPipe.idl new file mode 100644 index 000000000..93951ffe5 --- /dev/null +++ b/xpcom/io/nsIPipe.idl @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIAsyncInputStream.idl" +#include "nsIAsyncOutputStream.idl" + +interface nsIMemory; + +/** + * nsIPipe represents an in-process buffer that can be read using nsIInputStream + * and written using nsIOutputStream. The reader and writer of a pipe do not + * have to be on the same thread. As a result, the pipe is an ideal mechanism + * to bridge data exchange between two threads. For example, a worker thread + * might write data to a pipe from which the main thread will read. + * + * Each end of the pipe can be either blocking or non-blocking. Recall that a + * non-blocking stream will return NS_BASE_STREAM_WOULD_BLOCK if it cannot be + * read or written to without blocking the calling thread. For example, if you + * try to read from an empty pipe that has not yet been closed, then if that + * pipe's input end is non-blocking, then the read call will fail immediately + * with NS_BASE_STREAM_WOULD_BLOCK as the error condition. However, if that + * pipe's input end is blocking, then the read call will not return until the + * pipe has data or until the pipe is closed. This example presumes that the + * pipe is being filled asynchronously on some background thread. + * + * The pipe supports nsIAsyncInputStream and nsIAsyncOutputStream, which give + * the user of a non-blocking pipe the ability to wait for the pipe to become + * ready again. For example, in the case of an empty non-blocking pipe, the + * user can call AsyncWait on the input end of the pipe to be notified when + * the pipe has data to read (or when the pipe becomes closed). + * + * NS_NewPipe2 and NS_NewPipe provide convenient pipe constructors. In most + * cases nsIPipe is not actually used. It is usually enough to just get + * references to the pipe's input and output end. In which case, the pipe is + * automatically closed when the respective pipe ends are released. + */ +[scriptable, uuid(f4211abc-61b3-11d4-9877-00c04fa0cf4a)] +interface nsIPipe : nsISupports +{ + /** + * initialize this pipe + * + * @param nonBlockingInput + * true specifies non-blocking input stream behavior + * @param nonBlockingOutput + * true specifies non-blocking output stream behavior + * @param segmentSize + * specifies the segment size in bytes (pass 0 to use default value) + * @param segmentCount + * specifies the max number of segments (pass 0 to use default + * value). Passing UINT32_MAX here causes the pipe to have + * "infinite" space. This mode can be useful in some cases, but + * should always be used with caution. The default value for this + * parameter is a finite value. + * @param segmentAllocator + * pass reference to nsIMemory to have all pipe allocations use this + * allocator (pass null to use the default allocator) + */ + void init(in boolean nonBlockingInput, + in boolean nonBlockingOutput, + in unsigned long segmentSize, + in unsigned long segmentCount, + in nsIMemory segmentAllocator); + + /** + * The pipe's input end, which also implements nsISearchableInputStream. + */ + readonly attribute nsIAsyncInputStream inputStream; + + /** + * The pipe's output end. + */ + readonly attribute nsIAsyncOutputStream outputStream; +}; + +/** + * XXX this interface doesn't really belong in here. It is here because + * currently nsPipeInputStream is the only implementation of this interface. + */ +[scriptable, uuid(8C39EF62-F7C9-11d4-98F5-001083010E9B)] +interface nsISearchableInputStream : nsISupports +{ + /** + * Searches for a string in the input stream. Since the stream has a notion + * of EOF, it is possible that the string may at some time be in the + * buffer, but is is not currently found up to some offset. Consequently, + * both the found and not found cases return an offset: + * if found, return offset where it was found + * if not found, return offset of the first byte not searched + * In the case the stream is at EOF and the string is not found, the first + * byte not searched will correspond to the length of the buffer. + */ + void search(in string forString, + in boolean ignoreCase, + out boolean found, + out unsigned long offsetSearchedTo); +}; + +%{C++ + +/** + * NS_NewPipe2 + * + * This function supersedes NS_NewPipe. It differs from NS_NewPipe in two + * major ways: + * (1) returns nsIAsyncInputStream and nsIAsyncOutputStream, so it is + * not necessary to QI in order to access these interfaces. + * (2) the size of the pipe is determined by the number of segments + * times the size of each segment. + * + * @param pipeIn + * resulting input end of the pipe + * @param pipeOut + * resulting output end of the pipe + * @param nonBlockingInput + * true specifies non-blocking input stream behavior + * @param nonBlockingOutput + * true specifies non-blocking output stream behavior + * @param segmentSize + * specifies the segment size in bytes (pass 0 to use default value) + * @param segmentCount + * specifies the max number of segments (pass 0 to use default value) + * passing UINT32_MAX here causes the pipe to have "infinite" space. + * this mode can be useful in some cases, but should always be used with + * caution. the default value for this parameter is a finite value. + * @param segmentAlloc + * pass reference to nsIMemory to have all pipe allocations use this + * allocator (pass null to use the default allocator) + */ +extern nsresult +NS_NewPipe2(nsIAsyncInputStream **pipeIn, + nsIAsyncOutputStream **pipeOut, + bool nonBlockingInput = false, + bool nonBlockingOutput = false, + uint32_t segmentSize = 0, + uint32_t segmentCount = 0, + nsIMemory *segmentAlloc = nullptr); + +/** + * NS_NewPipe + * + * Preserved for backwards compatibility. Plus, this interface is more + * amiable in certain contexts (e.g., when you don't need the pipe's async + * capabilities). + * + * @param pipeIn + * resulting input end of the pipe + * @param pipeOut + * resulting output end of the pipe + * @param segmentSize + * specifies the segment size in bytes (pass 0 to use default value) + * @param maxSize + * specifies the max size of the pipe (pass 0 to use default value) + * number of segments is maxSize / segmentSize, and maxSize must be a + * multiple of segmentSize. passing UINT32_MAX here causes the + * pipe to have "infinite" space. this mode can be useful in some + * cases, but should always be used with caution. the default value + * for this parameter is a finite value. + * @param nonBlockingInput + * true specifies non-blocking input stream behavior + * @param nonBlockingOutput + * true specifies non-blocking output stream behavior + * @param segmentAlloc + * pass reference to nsIMemory to have all pipe allocations use this + * allocator (pass null to use the default allocator) + */ +extern nsresult +NS_NewPipe(nsIInputStream **pipeIn, + nsIOutputStream **pipeOut, + uint32_t segmentSize = 0, + uint32_t maxSize = 0, + bool nonBlockingInput = false, + bool nonBlockingOutput = false, + nsIMemory *segmentAlloc = nullptr); + +%} diff --git a/xpcom/io/nsISafeOutputStream.idl b/xpcom/io/nsISafeOutputStream.idl new file mode 100644 index 000000000..c62d85f07 --- /dev/null +++ b/xpcom/io/nsISafeOutputStream.idl @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +/** + * This interface provides a mechanism to control an output stream + * that takes care not to overwrite an existing target until it is known + * that all writes to the destination succeeded. + * + * An object that supports this interface is intended to also support + * nsIOutputStream. + * + * For example, a file output stream that supports this interface writes to + * a temporary file, and moves it over the original file when |finish| is + * called only if the stream can be successfully closed and all writes + * succeeded. If |finish| is called but something went wrong during + * writing, it will delete the temporary file and not touch the original. + * If the stream is closed by calling |close| directly, or the stream + * goes away, the original file will not be overwritten, and the temporary + * file will be deleted. + * + * Currently, this interface is implemented only for file output streams. + */ +[scriptable, uuid(5f914307-5c34-4e1f-8e32-ec749d25b27a)] +interface nsISafeOutputStream : nsISupports +{ + /** + * Call this method to close the stream and cause the original target + * to be overwritten. Note: if any call to |write| failed to write out + * all of the data given to it, then calling this method will |close| the + * stream and return failure. Further, if closing the stream fails, this + * method will return failure. The original target will be overwritten only + * if all calls to |write| succeeded and the stream was successfully closed. + */ + void finish(); +}; diff --git a/xpcom/io/nsIScriptableBase64Encoder.idl b/xpcom/io/nsIScriptableBase64Encoder.idl new file mode 100644 index 000000000..641451805 --- /dev/null +++ b/xpcom/io/nsIScriptableBase64Encoder.idl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsISupports.idl" + +interface nsIInputStream; + +/** + * nsIScriptableBase64Encoder efficiently encodes the contents + * of a nsIInputStream to a Base64 string. This avoids the need + * to read the entire stream into a buffer, and only then do the + * Base64 encoding. + * + * If you already have a buffer full of data, you should use + * btoa instead! + */ +[scriptable, uuid(9479c864-d1f9-45ab-b7b9-28b907bd2ba9)] +interface nsIScriptableBase64Encoder : nsISupports +{ + /** + * These methods take an nsIInputStream and return a narrow or wide + * string with the contents of the nsIInputStream base64 encoded. + * + * The stream passed in must support ReadSegments and must not be + * a non-blocking stream that will return NS_BASE_STREAM_WOULD_BLOCK. + * If either of these restrictions are violated we will abort. + */ + ACString encodeToCString(in nsIInputStream stream, in unsigned long length); + AString encodeToString(in nsIInputStream stream, in unsigned long length); +}; diff --git a/xpcom/io/nsIScriptableInputStream.idl b/xpcom/io/nsIScriptableInputStream.idl new file mode 100644 index 000000000..d9248f425 --- /dev/null +++ b/xpcom/io/nsIScriptableInputStream.idl @@ -0,0 +1,67 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +interface nsIInputStream; + +/** + * nsIScriptableInputStream provides scriptable access to an nsIInputStream + * instance. + */ +[scriptable, uuid(3fce9015-472a-4080-ac3e-cd875dbe361e)] +interface nsIScriptableInputStream : nsISupports +{ + /** + * Closes the stream. + */ + void close(); + + /** + * Wrap the given nsIInputStream with this nsIScriptableInputStream. + * + * @param aInputStream parameter providing the stream to wrap + */ + void init(in nsIInputStream aInputStream); + + /** + * Return the number of bytes currently available in the stream + * + * @return the number of bytes + * + * @throws NS_BASE_STREAM_CLOSED if called after the stream has been closed + */ + unsigned long long available(); + + /** + * Read data from the stream. + * + * WARNING: If the data contains a null byte, then this method will return + * a truncated string. + * + * @param aCount the maximum number of bytes to read + * + * @return the data, which will be an empty string if the stream is at EOF. + * + * @throws NS_BASE_STREAM_CLOSED if called after the stream has been closed + * @throws NS_ERROR_NOT_INITIALIZED if init was not called + */ + string read(in unsigned long aCount); + + /** + * Read data from the stream, including NULL bytes. + * + * @param aCount the maximum number of bytes to read. + * + * @return the data from the stream, which will be an empty string if EOF + * has been reached. + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream + * would block the calling thread (non-blocking mode only). + * @throws NS_ERROR_FAILURE if there are not enough bytes available to read + * aCount amount of data. + */ + ACString readBytes(in unsigned long aCount); +}; diff --git a/xpcom/io/nsISeekableStream.idl b/xpcom/io/nsISeekableStream.idl new file mode 100644 index 000000000..1be7ea60c --- /dev/null +++ b/xpcom/io/nsISeekableStream.idl @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + + +/* + * nsISeekableStream + * + * Note that a stream might not implement all methods (e.g., a readonly stream + * won't implement setEOF) + */ + +#include "nsISupports.idl" + +[scriptable, uuid(8429d350-1040-4661-8b71-f2a6ba455980)] +interface nsISeekableStream : nsISupports +{ + /* + * Sets the stream pointer to the value of the 'offset' parameter + */ + const int32_t NS_SEEK_SET = 0; + + /* + * Sets the stream pointer to its current location plus the value + * of the offset parameter. + */ + const int32_t NS_SEEK_CUR = 1; + + /* + * Sets the stream pointer to the size of the stream plus the value + * of the offset parameter. + */ + const int32_t NS_SEEK_END = 2; + + /** + * seek + * + * This method moves the stream offset of the steam implementing this + * interface. + * + * @param whence specifies how to interpret the 'offset' parameter in + * setting the stream offset associated with the implementing + * stream. + * + * @param offset specifies a value, in bytes, that is used in conjunction + * with the 'whence' parameter to set the stream offset of the + * implementing stream. A negative value causes seeking in + * the reverse direction. + * + * @throws NS_BASE_STREAM_CLOSED if called on a closed stream. + */ + void seek(in long whence, in long long offset); + + /** + * tell + * + * This method reports the current offset, in bytes, from the start of the + * stream. + * + * @throws NS_BASE_STREAM_CLOSED if called on a closed stream. + */ + long long tell(); + + + /** + * setEOF + * + * This method truncates the stream at the current offset. + * + * @throws NS_BASE_STREAM_CLOSED if called on a closed stream. + */ + void setEOF(); +}; diff --git a/xpcom/io/nsISimpleUnicharStreamFactory.idl b/xpcom/io/nsISimpleUnicharStreamFactory.idl new file mode 100644 index 000000000..188d887cc --- /dev/null +++ b/xpcom/io/nsISimpleUnicharStreamFactory.idl @@ -0,0 +1,26 @@ +/* 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 "nsISupports.idl" + +interface nsIInputStream; +interface nsIUnicharInputStream; + +/** + * Factory to create objects that implement nsIUnicharInputStream, + * converting from a unicode string or a UTF-8 stream. + */ +[scriptable, uuid(8238cd2e-e8e3-43e8-b556-56e21389c766)] +interface nsISimpleUnicharStreamFactory : nsISupports +{ + /** + * Create a unicode input stream from a unicode string. + */ + nsIUnicharInputStream createInstanceFromString(in AString aString); + + /** + * Create a unicode stream from an input stream in UTF8. + */ + nsIUnicharInputStream createInstanceFromUTF8Stream(in nsIInputStream aStream); +}; diff --git a/xpcom/io/nsIStorageStream.idl b/xpcom/io/nsIStorageStream.idl new file mode 100644 index 000000000..e49e80d00 --- /dev/null +++ b/xpcom/io/nsIStorageStream.idl @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 "nsISupports.idl" +#include "nsrootidl.idl" + +interface nsIMemory; +interface nsIInputStream; +interface nsIOutputStream; + +/** + * The nsIStorageStream interface maintains an internal data buffer that can be + * filled using a single output stream. One or more independent input streams + * can be created to read the data from the buffer non-destructively. + */ + +[scriptable, uuid(604ad9d0-753e-11d3-90ca-34278643278f)] +interface nsIStorageStream : nsISupports +{ + /** + * + * Initialize the stream, setting up the amount of space that will be + * allocated for the stream's backing-store. + * + * @param segmentSize + * Size of each segment. Must be a power of two. + * @param maxSize + * Maximum total size of this stream. length will always be less + * than or equal to this value. Passing UINT32_MAX is safe. + * @param segmentAllocator + * Which allocator to use for the segments. May be null, in which + * case a default allocator will be used. + */ + void init(in uint32_t segmentSize, in uint32_t maxSize, in nsIMemory segmentAllocator); + + /** + * Get a reference to the one and only output stream for this instance. + * The zero-based startPosition argument is used is used to set the initial + * write cursor position. The startPosition cannot be set larger than the + * current buffer length. Calling this method has the side-effect of + * truncating the internal buffer to startPosition bytes. + */ + nsIOutputStream getOutputStream(in int32_t startPosition); + + /** + * Create a new input stream to read data (written by the singleton output + * stream) from the internal buffer. Multiple, independent input streams + * can be created. + */ + nsIInputStream newInputStream(in int32_t startPosition); + + /** + * The length attribute indicates the total number of bytes stored in the + * nsIStorageStream internal buffer, regardless of any consumption by input + * streams. Assigning to the length field can be used to truncate the + * buffer data, but can not be used when either the instance's output + * stream is in use. + * + * @See #writeInProgress */ + attribute uint32_t length; + + /** + * True, when output stream has not yet been Close'ed + */ + readonly attribute boolean writeInProgress; +}; + +%{C++ +// Factory method +nsresult +NS_NewStorageStream(uint32_t segmentSize, uint32_t maxSize, nsIStorageStream **result); +%} diff --git a/xpcom/io/nsIStreamBufferAccess.idl b/xpcom/io/nsIStreamBufferAccess.idl new file mode 100644 index 000000000..002fb2f1e --- /dev/null +++ b/xpcom/io/nsIStreamBufferAccess.idl @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" +#include "nsrootidl.idl" + +/** + * An interface for access to a buffering stream implementation's underlying + * memory buffer. + * + * Stream implementations that QueryInterface to nsIStreamBufferAccess must + * ensure that all buffers are aligned on the most restrictive type size for + * the current architecture (e.g., sizeof(double) for RISCy CPUs). malloc(3) + * satisfies this requirement. + */ +[scriptable, uuid(ac923b72-ac87-4892-ac7a-ca385d429435)] +interface nsIStreamBufferAccess : nsISupports +{ + /** + * Get access to a contiguous, aligned run of bytes in the stream's buffer. + * Exactly one successful getBuffer call must occur before a putBuffer call + * taking the non-null pointer returned by the successful getBuffer. + * + * The run of bytes are the next bytes (modulo alignment padding) to read + * for an input stream, and the next bytes (modulo alignment padding) to + * store before (eventually) writing buffered data to an output stream. + * There can be space beyond this run of bytes in the buffer for further + * accesses before the fill or flush point is reached. + * + * @param aLength + * Count of contiguous bytes requested at the address A that satisfies + * (A & aAlignMask) == 0 in the buffer, starting from the current stream + * position, mapped to a buffer address B. The stream implementation + * must pad from B to A by skipping bytes (if input stream) or storing + * zero bytes (if output stream). + * + * @param aAlignMask + * Bit-mask computed by subtracting 1 from the power-of-two alignment + * modulus (e.g., 3 or sizeof(uint32_t)-1 for uint32_t alignment). + * + * @return + * The aligned pointer to aLength bytes in the buffer, or null if the + * buffer has no room for aLength bytes starting at the next address A + * after the current position that satisfies (A & aAlignMask) == 0. + */ + [notxpcom,noscript] charPtr getBuffer(in uint32_t aLength, in uint32_t aAlignMask); + + /** + * Relinquish access to the stream's buffer, filling if at end of an input + * buffer, flushing if completing an output buffer. After a getBuffer call + * that returns non-null, putBuffer must be called. + * + * @param aBuffer + * A non-null pointer returned by getBuffer on the same stream buffer + * access object. + * + * @param aLength + * The same count of contiguous bytes passed to the getBuffer call that + * returned aBuffer. + */ + [notxpcom,noscript] void putBuffer(in charPtr aBuffer, in uint32_t aLength); + + /** + * Disable and enable buffering on the stream implementing this interface. + * DisableBuffering flushes an output stream's buffer, and invalidates an + * input stream's buffer. + */ + void disableBuffering(); + void enableBuffering(); + + /** + * The underlying, unbuffered input or output stream. + */ + readonly attribute nsISupports unbufferedStream; +}; + +%{C++ + +/** + * These macros get and put a buffer given either an sba parameter that may + * point to an object implementing nsIStreamBufferAccess, nsIObjectInputStream, + * or nsIObjectOutputStream. + */ +#define NS_GET_BUFFER(sba,n,a) ((sba)->GetBuffer(n, a)) +#define NS_PUT_BUFFER(sba,p,n) ((sba)->PutBuffer(p, n)) + +%} diff --git a/xpcom/io/nsIStringStream.idl b/xpcom/io/nsIStringStream.idl new file mode 100644 index 000000000..fec418a4f --- /dev/null +++ b/xpcom/io/nsIStringStream.idl @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsIInputStream.idl" + +/** + * nsIStringInputStream + * + * Provides scriptable and specialized C++-only methods for initializing a + * nsIInputStream implementation with a simple character array. + */ +[scriptable, uuid(450cd2d4-f0fd-424d-b365-b1251f80fd53)] +interface nsIStringInputStream : nsIInputStream +{ + /** + * SetData - assign data to the input stream (copied on assignment). + * + * @param data - stream data + * @param dataLen - stream data length (-1 if length should be computed) + * + * NOTE: C++ code should consider using AdoptData or ShareData to avoid + * making an extra copy of the stream data. + * + * NOTE: For JS callers, the given data must not contain null characters + * (other than a null terminator) because a null character in the middle of + * the data string will be seen as a terminator when the data is converted + * from a JS string to a C++ character array. + */ + void setData(in string data, in long dataLen); + + /** + * NOTE: the following methods are designed to give C++ code added control + * over the ownership and lifetime of the stream data. Use with care :-) + */ + + /** + * AdoptData - assign data to the input stream. the input stream takes + * ownership of the given data buffer and will nsMemory::Free it when + * the input stream is destroyed. + * + * @param data - stream data + * @param dataLen - stream data length (-1 if length should be computed) + */ + [noscript] void adoptData(in charPtr data, in long dataLen); + + /** + * ShareData - assign data to the input stream. the input stream references + * the given data buffer until the input stream is destroyed. the given + * data buffer must outlive the input stream. + * + * @param data - stream data + * @param dataLen - stream data length (-1 if length should be computed) + */ + [noscript] void shareData(in string data, in long dataLen); +}; diff --git a/xpcom/io/nsIUnicharInputStream.idl b/xpcom/io/nsIUnicharInputStream.idl new file mode 100644 index 000000000..f57557cc9 --- /dev/null +++ b/xpcom/io/nsIUnicharInputStream.idl @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsISupports.idl" + +interface nsIUnicharInputStream; +interface nsIInputStream; + +%{C++ +/** + * The signature of the writer function passed to ReadSegments. This + * is the "consumer" of data that gets read from the stream's buffer. + * + * @param aInStream stream being read + * @param aClosure opaque parameter passed to ReadSegments + * @param aFromSegment pointer to memory owned by the input stream + * @param aToOffset amount already read (since ReadSegments was called) + * @param aCount length of fromSegment + * @param aWriteCount number of bytes read + * + * Implementers should return the following: + * + * @throws <any-error> if not interested in consuming any data + * + * Errors are never passed to the caller of ReadSegments. + * + * NOTE: returning NS_OK and (*aWriteCount = 0) has undefined behavior. + */ +typedef NS_CALLBACK(nsWriteUnicharSegmentFun)(nsIUnicharInputStream *aInStream, + void *aClosure, + const PRUnichar *aFromSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t *aWriteCount); +%} +native nsWriteUnicharSegmentFun(nsWriteUnicharSegmentFun); + +/** + * Abstract unicode character input stream + * @see nsIInputStream + */ +[scriptable, uuid(d5e3bd80-6723-4b92-b0c9-22f6162fd94f)] +interface nsIUnicharInputStream : nsISupports { + /** + * Reads into a caller-provided character array. + * + * @return The number of characters that were successfully read. May be less + * than aCount, even if there is more data in the input stream. + * A return value of 0 means EOF. + * + * @note To read more than 2^32 characters, call this method multiple times. + */ + [noscript] unsigned long read([array, size_is(aCount)] in PRUnichar aBuf, + in unsigned long aCount); + + /** + * Low-level read method that has access to the stream's underlying buffer. + * The writer function may be called multiple times for segmented buffers. + * ReadSegments is expected to keep calling the writer until either there is + * nothing left to read or the writer returns an error. ReadSegments should + * not call the writer with zero characters to consume. + * + * @param aWriter the "consumer" of the data to be read + * @param aClosure opaque parameter passed to writer + * @param aCount the maximum number of characters to be read + * + * @return number of characters read (may be less than aCount) + * @return 0 if reached end of file (or if aWriter refused to consume data) + * + * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream would + * block the calling thread (non-blocking mode only) + * @throws <other-error> on failure + * + * NOTE: this function may be unimplemented if a stream has no underlying + * buffer + */ + [noscript] unsigned long readSegments(in nsWriteUnicharSegmentFun aWriter, + in voidPtr aClosure, + in unsigned long aCount); + + /** + * Read into a string object. + * @param aCount The number of characters that should be read + * @return The number of characters that were read. + */ + unsigned long readString(in unsigned long aCount, out AString aString); + + /** + * Close the stream and free associated resources. This also closes the + * underlying stream, if any. + */ + void close(); +}; diff --git a/xpcom/io/nsIUnicharLineInputStream.idl b/xpcom/io/nsIUnicharLineInputStream.idl new file mode 100644 index 000000000..34a67f099 --- /dev/null +++ b/xpcom/io/nsIUnicharLineInputStream.idl @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 "nsISupports.idl" + +[scriptable, uuid(67f42475-ba80-40f8-ac0b-649c89230184)] +interface nsIUnicharLineInputStream : nsISupports +{ + /** + * Read a single line from the stream, where a line is a + * possibly zero length sequence of characters terminated by a + * CR, LF, CRLF, LFCR, or eof. + * The line terminator is not returned. + * @retval false + * End of file. This line is the last line of the file + * (aLine is valid). + * @retval true + * The file contains further lines. + * @note Do not mix readLine with other read functions. + * Doing so can cause various problems and is not supported. + */ + boolean readLine(out AString aLine); +}; diff --git a/xpcom/io/nsIUnicharOutputStream.idl b/xpcom/io/nsIUnicharOutputStream.idl new file mode 100644 index 000000000..fb5fd241b --- /dev/null +++ b/xpcom/io/nsIUnicharOutputStream.idl @@ -0,0 +1,47 @@ +/* vim:set expandtab ts=4 sw=4 sts=4 cin: */ +/* 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 "nsISupports.idl" + +/** + * An interface that allows writing unicode data. + */ +[scriptable, uuid(2d00b1bb-8b21-4a63-bcc6-7213f513ac2e)] +interface nsIUnicharOutputStream : nsISupports +{ + /** + * Write a single character to the stream. When writing many characters, + * prefer the string-taking write method. + * + * @retval true The character was written successfully + * @retval false Not all bytes of the character could be written. + */ + boolean write(in unsigned long aCount, + [const, array, size_is(aCount)] in PRUnichar c); + + /** + * Write a string to the stream. + * + * @retval true The string was written successfully + * @retval false Not all bytes of the string could be written. + */ + boolean writeString(in AString str); + + /** + * Flush the stream. This finishes the conversion and writes any bytes that + * finish the current byte sequence. + * + * It does NOT flush the underlying stream. + * + * @see nsIUnicodeEncoder::Finish + */ + void flush(); + + /** + * Close the stream and free associated resources. This also closes the + * underlying stream. + */ + void close(); +}; diff --git a/xpcom/io/nsInputStreamTee.cpp b/xpcom/io/nsInputStreamTee.cpp new file mode 100644 index 000000000..dd68fc6f2 --- /dev/null +++ b/xpcom/io/nsInputStreamTee.cpp @@ -0,0 +1,347 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 <stdlib.h> +#include "prlog.h" + +#include "mozilla/Mutex.h" +#include "mozilla/Attributes.h" +#include "nsIInputStreamTee.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsIEventTarget.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +#ifdef PR_LOGGING +static PRLogModuleInfo* +GetTeeLog() +{ + static PRLogModuleInfo *sLog; + if (!sLog) + sLog = PR_NewLogModule("nsInputStreamTee"); + return sLog; +} +#define LOG(args) PR_LOG(GetTeeLog(), PR_LOG_DEBUG, args) +#else +#define LOG(args) +#endif + +class nsInputStreamTee MOZ_FINAL : public nsIInputStreamTee +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIINPUTSTREAMTEE + + nsInputStreamTee(); + bool SinkIsValid(); + void InvalidateSink(); + +private: + ~nsInputStreamTee() {} + + nsresult TeeSegment(const char *buf, uint32_t count); + + static NS_METHOD WriteSegmentFun(nsIInputStream *, void *, const char *, + uint32_t, uint32_t, uint32_t *); + +private: + nsCOMPtr<nsIInputStream> mSource; + nsCOMPtr<nsIOutputStream> mSink; + nsCOMPtr<nsIEventTarget> mEventTarget; + nsWriteSegmentFun mWriter; // for implementing ReadSegments + void *mClosure; // for implementing ReadSegments + nsAutoPtr<Mutex> mLock; // synchronize access to mSinkIsValid + bool mSinkIsValid; // False if TeeWriteEvent fails +}; + +class nsInputStreamTeeWriteEvent : public nsRunnable { +public: + // aTee's lock is held across construction of this object + nsInputStreamTeeWriteEvent(const char *aBuf, uint32_t aCount, + nsIOutputStream *aSink, + nsInputStreamTee *aTee) + { + // copy the buffer - will be free'd by dtor + mBuf = (char *)malloc(aCount); + if (mBuf) memcpy(mBuf, (char *)aBuf, aCount); + mCount = aCount; + mSink = aSink; + bool isNonBlocking; + mSink->IsNonBlocking(&isNonBlocking); + NS_ASSERTION(isNonBlocking == false, "mSink is nonblocking"); + mTee = aTee; + } + + NS_IMETHOD Run() + { + if (!mBuf) { + NS_WARNING("nsInputStreamTeeWriteEvent::Run() " + "memory not allocated\n"); + return NS_OK; + } + NS_ABORT_IF_FALSE(mSink, "mSink is null!"); + + // The output stream could have been invalidated between when + // this event was dispatched and now, so check before writing. + if (!mTee->SinkIsValid()) { + return NS_OK; + } + + LOG(("nsInputStreamTeeWriteEvent::Run() [%p]" + "will write %u bytes to %p\n", + this, mCount, mSink.get())); + + uint32_t totalBytesWritten = 0; + while (mCount) { + nsresult rv; + uint32_t bytesWritten = 0; + rv = mSink->Write(mBuf + totalBytesWritten, mCount, &bytesWritten); + if (NS_FAILED(rv)) { + LOG(("nsInputStreamTeeWriteEvent::Run[%p] error %x in writing", + this,rv)); + mTee->InvalidateSink(); + break; + } + totalBytesWritten += bytesWritten; + NS_ASSERTION(bytesWritten <= mCount, "wrote too much"); + mCount -= bytesWritten; + } + return NS_OK; + } + +protected: + virtual ~nsInputStreamTeeWriteEvent() + { + if (mBuf) free(mBuf); + mBuf = nullptr; + } + +private: + char *mBuf; + uint32_t mCount; + nsCOMPtr<nsIOutputStream> mSink; + // back pointer to the tee that created this runnable + nsRefPtr<nsInputStreamTee> mTee; +}; + +nsInputStreamTee::nsInputStreamTee(): mLock(nullptr) + , mSinkIsValid(true) +{ +} + +bool +nsInputStreamTee::SinkIsValid() +{ + MutexAutoLock lock(*mLock); + return mSinkIsValid; +} + +void +nsInputStreamTee::InvalidateSink() +{ + MutexAutoLock lock(*mLock); + mSinkIsValid = false; +} + +nsresult +nsInputStreamTee::TeeSegment(const char *buf, uint32_t count) +{ + if (!mSink) return NS_OK; // nothing to do + if (mLock) { // asynchronous case + NS_ASSERTION(mEventTarget, "mEventTarget is null, mLock is not null."); + if (!SinkIsValid()) { + return NS_OK; // nothing to do + } + nsRefPtr<nsIRunnable> event = + new nsInputStreamTeeWriteEvent(buf, count, mSink, this); + NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); + LOG(("nsInputStreamTee::TeeSegment [%p] dispatching write %u bytes\n", + this, count)); + return mEventTarget->Dispatch(event, NS_DISPATCH_NORMAL); + } else { // synchronous case + NS_ASSERTION(!mEventTarget, "mEventTarget is not null, mLock is null."); + nsresult rv; + uint32_t totalBytesWritten = 0; + while (count) { + uint32_t bytesWritten = 0; + rv = mSink->Write(buf + totalBytesWritten, count, &bytesWritten); + if (NS_FAILED(rv)) { + // ok, this is not a fatal error... just drop our reference to mSink + // and continue on as if nothing happened. + NS_WARNING("Write failed (non-fatal)"); + // catch possible misuse of the input stream tee + NS_ASSERTION(rv != NS_BASE_STREAM_WOULD_BLOCK, "sink must be a blocking stream"); + mSink = 0; + break; + } + totalBytesWritten += bytesWritten; + NS_ASSERTION(bytesWritten <= count, "wrote too much"); + count -= bytesWritten; + } + return NS_OK; + } +} + +NS_METHOD +nsInputStreamTee::WriteSegmentFun(nsIInputStream *in, void *closure, const char *fromSegment, + uint32_t offset, uint32_t count, uint32_t *writeCount) +{ + nsInputStreamTee *tee = reinterpret_cast<nsInputStreamTee *>(closure); + + nsresult rv = tee->mWriter(in, tee->mClosure, fromSegment, offset, count, writeCount); + if (NS_FAILED(rv) || (*writeCount == 0)) { + NS_ASSERTION((NS_FAILED(rv) ? (*writeCount == 0) : true), + "writer returned an error with non-zero writeCount"); + return rv; + } + + return tee->TeeSegment(fromSegment, *writeCount); +} + +NS_IMPL_THREADSAFE_ISUPPORTS2(nsInputStreamTee, + nsIInputStreamTee, + nsIInputStream) +NS_IMETHODIMP +nsInputStreamTee::Close() +{ + NS_ENSURE_TRUE(mSource, NS_ERROR_NOT_INITIALIZED); + nsresult rv = mSource->Close(); + mSource = 0; + mSink = 0; + return rv; +} + +NS_IMETHODIMP +nsInputStreamTee::Available(uint64_t *avail) +{ + NS_ENSURE_TRUE(mSource, NS_ERROR_NOT_INITIALIZED); + return mSource->Available(avail); +} + +NS_IMETHODIMP +nsInputStreamTee::Read(char *buf, uint32_t count, uint32_t *bytesRead) +{ + NS_ENSURE_TRUE(mSource, NS_ERROR_NOT_INITIALIZED); + + nsresult rv = mSource->Read(buf, count, bytesRead); + if (NS_FAILED(rv) || (*bytesRead == 0)) + return rv; + + return TeeSegment(buf, *bytesRead); +} + +NS_IMETHODIMP +nsInputStreamTee::ReadSegments(nsWriteSegmentFun writer, + void *closure, + uint32_t count, + uint32_t *bytesRead) +{ + NS_ENSURE_TRUE(mSource, NS_ERROR_NOT_INITIALIZED); + + mWriter = writer; + mClosure = closure; + + return mSource->ReadSegments(WriteSegmentFun, this, count, bytesRead); +} + +NS_IMETHODIMP +nsInputStreamTee::IsNonBlocking(bool *result) +{ + NS_ENSURE_TRUE(mSource, NS_ERROR_NOT_INITIALIZED); + return mSource->IsNonBlocking(result); +} + +NS_IMETHODIMP +nsInputStreamTee::SetSource(nsIInputStream *source) +{ + mSource = source; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::GetSource(nsIInputStream **source) +{ + NS_IF_ADDREF(*source = mSource); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::SetSink(nsIOutputStream *sink) +{ +#ifdef DEBUG + if (sink) { + bool nonBlocking; + nsresult rv = sink->IsNonBlocking(&nonBlocking); + if (NS_FAILED(rv) || nonBlocking) + NS_ERROR("sink should be a blocking stream"); + } +#endif + mSink = sink; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::GetSink(nsIOutputStream **sink) +{ + NS_IF_ADDREF(*sink = mSink); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::SetEventTarget(nsIEventTarget *anEventTarget) +{ + mEventTarget = anEventTarget; + if (mEventTarget) { + // Only need synchronization if this is an async tee + mLock = new Mutex("nsInputStreamTee.mLock"); + } + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::GetEventTarget(nsIEventTarget **anEventTarget) +{ + NS_IF_ADDREF(*anEventTarget = mEventTarget); + return NS_OK; +} + + +nsresult +NS_NewInputStreamTeeAsync(nsIInputStream **result, + nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *anEventTarget) +{ + nsresult rv; + + nsCOMPtr<nsIInputStreamTee> tee = new nsInputStreamTee(); + if (!tee) + return NS_ERROR_OUT_OF_MEMORY; + + rv = tee->SetSource(source); + if (NS_FAILED(rv)) return rv; + + rv = tee->SetSink(sink); + if (NS_FAILED(rv)) return rv; + + rv = tee->SetEventTarget(anEventTarget); + if (NS_FAILED(rv)) return rv; + + NS_ADDREF(*result = tee); + return rv; +} + +nsresult +NS_NewInputStreamTee(nsIInputStream **result, + nsIInputStream *source, + nsIOutputStream *sink) +{ + return NS_NewInputStreamTeeAsync(result, source, sink, nullptr); +} diff --git a/xpcom/io/nsLinebreakConverter.cpp b/xpcom/io/nsLinebreakConverter.cpp new file mode 100644 index 000000000..dd4609b00 --- /dev/null +++ b/xpcom/io/nsLinebreakConverter.cpp @@ -0,0 +1,465 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsLinebreakConverter.h" + +#include "nsMemory.h" +#include "nsCRT.h" + + +/*---------------------------------------------------------------------------- + GetLinebreakString + + Could make this inline +----------------------------------------------------------------------------*/ +static const char* GetLinebreakString(nsLinebreakConverter::ELinebreakType aBreakType) +{ + static const char* const sLinebreaks[] = { + "", // any + NS_LINEBREAK, // platform + LFSTR, // content + CRLF, // net + CRSTR, // Mac + LFSTR, // Unix + CRLF, // Windows + " ", // space + nullptr + }; + + return sLinebreaks[aBreakType]; +} + + +/*---------------------------------------------------------------------------- + AppendLinebreak + + Wee inline method to append a line break. Modifies ioDest. +----------------------------------------------------------------------------*/ +template<class T> +void AppendLinebreak(T*& ioDest, const char* lineBreakStr) +{ + *ioDest++ = *lineBreakStr; + + if (lineBreakStr[1]) + *ioDest++ = lineBreakStr[1]; +} + +/*---------------------------------------------------------------------------- + CountChars + + Counts occurrences of breakStr in aSrc +----------------------------------------------------------------------------*/ +template<class T> +int32_t CountLinebreaks(const T* aSrc, int32_t inLen, const char* breakStr) +{ + const T* src = aSrc; + const T* srcEnd = aSrc + inLen; + int32_t theCount = 0; + + while (src < srcEnd) + { + if (*src == *breakStr) + { + src++; + + if (breakStr[1]) + { + if (src < srcEnd && *src == breakStr[1]) + { + src++; + theCount++; + } + } + else + { + theCount++; + } + } + else + { + src++; + } + } + + return theCount; +} + + +/*---------------------------------------------------------------------------- + ConvertBreaks + + ioLen *includes* a terminating null, if any +----------------------------------------------------------------------------*/ +template<class T> +static T* ConvertBreaks(const T* inSrc, int32_t& ioLen, const char* srcBreak, const char* destBreak) +{ + NS_ASSERTION(inSrc && srcBreak && destBreak, "Got a null string"); + + T* resultString = nullptr; + + // handle the no conversion case + if (nsCRT::strcmp(srcBreak, destBreak) == 0) + { + resultString = (T *)nsMemory::Alloc(sizeof(T) * ioLen); + if (!resultString) return nullptr; + memcpy(resultString, inSrc, sizeof(T) * ioLen); // includes the null, if any + return resultString; + } + + int32_t srcBreakLen = strlen(srcBreak); + int32_t destBreakLen = strlen(destBreak); + + // handle the easy case, where the string length does not change, and the + // breaks are only 1 char long, i.e. CR <-> LF + if (srcBreakLen == destBreakLen && srcBreakLen == 1) + { + resultString = (T *)nsMemory::Alloc(sizeof(T) * ioLen); + if (!resultString) return nullptr; + + const T* src = inSrc; + const T* srcEnd = inSrc + ioLen; // includes null, if any + T* dst = resultString; + + char srcBreakChar = *srcBreak; // we know it's one char long already + char dstBreakChar = *destBreak; + + while (src < srcEnd) + { + if (*src == srcBreakChar) + { + *dst++ = dstBreakChar; + src++; + } + else + { + *dst++ = *src++; + } + } + + // ioLen does not change + } + else + { + // src and dest termination is different length. Do it a slower way. + + // count linebreaks in src. Assumes that chars in 2-char linebreaks are unique. + int32_t numLinebreaks = CountLinebreaks(inSrc, ioLen, srcBreak); + + int32_t newBufLen = ioLen - (numLinebreaks * srcBreakLen) + (numLinebreaks * destBreakLen); + resultString = (T *)nsMemory::Alloc(sizeof(T) * newBufLen); + if (!resultString) return nullptr; + + const T* src = inSrc; + const T* srcEnd = inSrc + ioLen; // includes null, if any + T* dst = resultString; + + while (src < srcEnd) + { + if (*src == *srcBreak) + { + *dst++ = *destBreak; + if (destBreak[1]) + *dst++ = destBreak[1]; + + src++; + if (src < srcEnd && srcBreak[1] && *src == srcBreak[1]) + src++; + } + else + { + *dst++ = *src++; + } + } + + ioLen = newBufLen; + } + + return resultString; +} + + +/*---------------------------------------------------------------------------- + ConvertBreaksInSitu + + Convert breaks in situ. Can only do this if the linebreak length + does not change. +----------------------------------------------------------------------------*/ +template<class T> +static void ConvertBreaksInSitu(T* inSrc, int32_t inLen, char srcBreak, char destBreak) +{ + T* src = inSrc; + T* srcEnd = inSrc + inLen; + + while (src < srcEnd) + { + if (*src == srcBreak) + *src = destBreak; + + src++; + } +} + + +/*---------------------------------------------------------------------------- + ConvertUnknownBreaks + + Convert unknown line breaks to the specified break. + + This will convert CRLF pairs to one break, and single CR or LF to a break. +----------------------------------------------------------------------------*/ +template<class T> +static T* ConvertUnknownBreaks(const T* inSrc, int32_t& ioLen, const char* destBreak) +{ + const T* src = inSrc; + const T* srcEnd = inSrc + ioLen; // includes null, if any + + int32_t destBreakLen = strlen(destBreak); + int32_t finalLen = 0; + + while (src < srcEnd) + { + if (*src == nsCRT::CR) + { + if (src < srcEnd && src[1] == nsCRT::LF) + { + // CRLF + finalLen += destBreakLen; + src++; + } + else + { + // Lone CR + finalLen += destBreakLen; + } + } + else if (*src == nsCRT::LF) + { + // Lone LF + finalLen += destBreakLen; + } + else + { + finalLen++; + } + src++; + } + + T* resultString = (T *)nsMemory::Alloc(sizeof(T) * finalLen); + if (!resultString) return nullptr; + + src = inSrc; + srcEnd = inSrc + ioLen; // includes null, if any + + T* dst = resultString; + + while (src < srcEnd) + { + if (*src == nsCRT::CR) + { + if (src < srcEnd && src[1] == nsCRT::LF) + { + // CRLF + AppendLinebreak(dst, destBreak); + src++; + } + else + { + // Lone CR + AppendLinebreak(dst, destBreak); + } + } + else if (*src == nsCRT::LF) + { + // Lone LF + AppendLinebreak(dst, destBreak); + } + else + { + *dst++ = *src; + } + src++; + } + + ioLen = finalLen; + return resultString; +} + + +/*---------------------------------------------------------------------------- + ConvertLineBreaks + +----------------------------------------------------------------------------*/ +char* nsLinebreakConverter::ConvertLineBreaks(const char* aSrc, + ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, int32_t aSrcLen, int32_t* outLen) +{ + NS_ASSERTION(aDestBreaks != eLinebreakAny && + aSrcBreaks != eLinebreakSpace, "Invalid parameter"); + if (!aSrc) return nullptr; + + int32_t sourceLen = (aSrcLen == kIgnoreLen) ? strlen(aSrc) + 1 : aSrcLen; + + char* resultString; + if (aSrcBreaks == eLinebreakAny) + resultString = ConvertUnknownBreaks(aSrc, sourceLen, GetLinebreakString(aDestBreaks)); + else + resultString = ConvertBreaks(aSrc, sourceLen, GetLinebreakString(aSrcBreaks), GetLinebreakString(aDestBreaks)); + + if (outLen) + *outLen = sourceLen; + return resultString; +} + + +/*---------------------------------------------------------------------------- + ConvertLineBreaksInSitu + +----------------------------------------------------------------------------*/ +nsresult nsLinebreakConverter::ConvertLineBreaksInSitu(char **ioBuffer, ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks, int32_t aSrcLen, int32_t* outLen) +{ + NS_ASSERTION(ioBuffer && *ioBuffer, "Null pointer passed"); + if (!ioBuffer || !*ioBuffer) return NS_ERROR_NULL_POINTER; + + NS_ASSERTION(aDestBreaks != eLinebreakAny && + aSrcBreaks != eLinebreakSpace, "Invalid parameter"); + + int32_t sourceLen = (aSrcLen == kIgnoreLen) ? strlen(*ioBuffer) + 1 : aSrcLen; + + // can we convert in-place? + const char* srcBreaks = GetLinebreakString(aSrcBreaks); + const char* dstBreaks = GetLinebreakString(aDestBreaks); + + if ( (aSrcBreaks != eLinebreakAny) && + (strlen(srcBreaks) == 1) && + (strlen(dstBreaks) == 1) ) + { + ConvertBreaksInSitu(*ioBuffer, sourceLen, *srcBreaks, *dstBreaks); + if (outLen) + *outLen = sourceLen; + } + else + { + char* destBuffer; + + if (aSrcBreaks == eLinebreakAny) + destBuffer = ConvertUnknownBreaks(*ioBuffer, sourceLen, dstBreaks); + else + destBuffer = ConvertBreaks(*ioBuffer, sourceLen, srcBreaks, dstBreaks); + + if (!destBuffer) return NS_ERROR_OUT_OF_MEMORY; + *ioBuffer = destBuffer; + if (outLen) + *outLen = sourceLen; + } + + return NS_OK; +} + + +/*---------------------------------------------------------------------------- + ConvertUnicharLineBreaks + +----------------------------------------------------------------------------*/ +PRUnichar* nsLinebreakConverter::ConvertUnicharLineBreaks(const PRUnichar* aSrc, + ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, int32_t aSrcLen, int32_t* outLen) +{ + NS_ASSERTION(aDestBreaks != eLinebreakAny && + aSrcBreaks != eLinebreakSpace, "Invalid parameter"); + if (!aSrc) return nullptr; + + int32_t bufLen = (aSrcLen == kIgnoreLen) ? NS_strlen(aSrc) + 1 : aSrcLen; + + PRUnichar* resultString; + if (aSrcBreaks == eLinebreakAny) + resultString = ConvertUnknownBreaks(aSrc, bufLen, GetLinebreakString(aDestBreaks)); + else + resultString = ConvertBreaks(aSrc, bufLen, GetLinebreakString(aSrcBreaks), GetLinebreakString(aDestBreaks)); + + if (outLen) + *outLen = bufLen; + return resultString; +} + + +/*---------------------------------------------------------------------------- + ConvertStringLineBreaks + +----------------------------------------------------------------------------*/ +nsresult nsLinebreakConverter::ConvertUnicharLineBreaksInSitu(PRUnichar **ioBuffer, + ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, int32_t aSrcLen, int32_t* outLen) +{ + NS_ASSERTION(ioBuffer && *ioBuffer, "Null pointer passed"); + if (!ioBuffer || !*ioBuffer) return NS_ERROR_NULL_POINTER; + NS_ASSERTION(aDestBreaks != eLinebreakAny && + aSrcBreaks != eLinebreakSpace, "Invalid parameter"); + + int32_t sourceLen = (aSrcLen == kIgnoreLen) ? NS_strlen(*ioBuffer) + 1 : aSrcLen; + + // can we convert in-place? + const char* srcBreaks = GetLinebreakString(aSrcBreaks); + const char* dstBreaks = GetLinebreakString(aDestBreaks); + + if ( (aSrcBreaks != eLinebreakAny) && + (strlen(srcBreaks) == 1) && + (strlen(dstBreaks) == 1) ) + { + ConvertBreaksInSitu(*ioBuffer, sourceLen, *srcBreaks, *dstBreaks); + if (outLen) + *outLen = sourceLen; + } + else + { + PRUnichar* destBuffer; + + if (aSrcBreaks == eLinebreakAny) + destBuffer = ConvertUnknownBreaks(*ioBuffer, sourceLen, dstBreaks); + else + destBuffer = ConvertBreaks(*ioBuffer, sourceLen, srcBreaks, dstBreaks); + + if (!destBuffer) return NS_ERROR_OUT_OF_MEMORY; + *ioBuffer = destBuffer; + if (outLen) + *outLen = sourceLen; + } + + return NS_OK; +} + +/*---------------------------------------------------------------------------- + ConvertStringLineBreaks + +----------------------------------------------------------------------------*/ +nsresult nsLinebreakConverter::ConvertStringLineBreaks(nsString& ioString, + ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks) +{ + + NS_ASSERTION(aDestBreaks != eLinebreakAny && + aSrcBreaks != eLinebreakSpace, "Invalid parameter"); + + // nothing to do + if (ioString.IsEmpty()) return NS_OK; + + nsresult rv; + + // remember the old buffer in case + // we blow it away later + nsString::char_iterator stringBuf; + ioString.BeginWriting(stringBuf); + + int32_t newLen; + + rv = ConvertUnicharLineBreaksInSitu(&stringBuf, + aSrcBreaks, aDestBreaks, + ioString.Length() + 1, &newLen); + if (NS_FAILED(rv)) return rv; + + if (stringBuf != ioString.get()) + ioString.Adopt(stringBuf); + + return NS_OK; +} + + + diff --git a/xpcom/io/nsLinebreakConverter.h b/xpcom/io/nsLinebreakConverter.h new file mode 100644 index 000000000..ad445430a --- /dev/null +++ b/xpcom/io/nsLinebreakConverter.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsLinebreakConverter_h_ +#define nsLinebreakConverter_h_ + +#include "nscore.h" +#include "nsString.h" + +// utility class for converting between different line breaks. + +class nsLinebreakConverter +{ +public: + + // Note: enum must match char* array in GetLinebreakString + typedef enum { + eLinebreakAny, // any kind of linebreak (i.e. "don't care" source) + + eLinebreakPlatform, // platform linebreak + eLinebreakContent, // Content model linebreak (LF) + eLinebreakNet, // Form submission linebreak (CRLF) + + eLinebreakMac, // CR + eLinebreakUnix, // LF + eLinebreakWindows, // CRLF + + eLinebreakSpace // space characters. Only valid as destination type + + } ELinebreakType; + + enum { + kIgnoreLen = -1 + }; + + /* ConvertLineBreaks + * Convert line breaks in the supplied string, allocating and returning + * a new buffer. Returns nullptr on failure. + * @param aSrc: the source string. if aSrcLen == kIgnoreLen this string is assumed + * to be null terminated, otherwise it must be at least aSrcLen long. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny. + * If known, pass the known value, as this may be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source. If -1, the source is assumed to be a null- + * terminated string. + * @param aOutLen: used to return character length of returned buffer, if not null. + */ + static char* ConvertLineBreaks(const char* aSrc, + ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, + int32_t aSrcLen = kIgnoreLen, int32_t* aOutLen = nullptr); + + + /* ConvertUnicharLineBreaks + * Convert line breaks in the supplied string, allocating and returning + * a new buffer. Returns nullptr on failure. + * @param aSrc: the source string. if aSrcLen == kIgnoreLen this string is assumed + * to be null terminated, otherwise it must be at least aSrcLen long. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny. + * If known, pass the known value, as this may be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source, in characters. If -1, the source is assumed to be a null- + * terminated string. + * @param aOutLen: used to return character length of returned buffer, if not null. + */ + static PRUnichar* ConvertUnicharLineBreaks(const PRUnichar* aSrc, + ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, + int32_t aSrcLen = kIgnoreLen, int32_t* aOutLen = nullptr); + + + /* ConvertStringLineBreaks + * Convert line breaks in the supplied string, changing the string buffer (i.e. in-place conversion) + * @param ioString: the string to be converted. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny. + * If known, pass the known value, as this may be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source, in characters. If -1, the source is assumed to be a null- + * terminated string. + */ + static nsresult ConvertStringLineBreaks(nsString& ioString, ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks); + + + /* ConvertLineBreaksInSitu + * Convert line breaks in place if possible. NOTE: THIS MAY REALLOCATE THE BUFFER, + * BUT IT WON'T FREE THE OLD BUFFER (because it doesn't know how). So be prepared + * to keep a copy of the old pointer, and free it if this passes back a new pointer. + * ALSO NOTE: DON'T PASS A STATIC STRING POINTER TO THIS FUNCTION. + * + * @param ioBuffer: the source buffer. if aSrcLen == kIgnoreLen this string is assumed + * to be null terminated, otherwise it must be at least aSrcLen long. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny. + * If known, pass the known value, as this may be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source. If -1, the source is assumed to be a null- + * terminated string. + * @param aOutLen: used to return character length of returned buffer, if not null. + */ + static nsresult ConvertLineBreaksInSitu(char **ioBuffer, ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, + int32_t aSrcLen = kIgnoreLen, int32_t* aOutLen = nullptr); + + + /* ConvertUnicharLineBreaksInSitu + * Convert line breaks in place if possible. NOTE: THIS MAY REALLOCATE THE BUFFER, + * BUT IT WON'T FREE THE OLD BUFFER (because it doesn't know how). So be prepared + * to keep a copy of the old pointer, and free it if this passes back a new pointer. + * + * @param ioBuffer: the source buffer. if aSrcLen == kIgnoreLen this string is assumed + * to be null terminated, otherwise it must be at least aSrcLen long. + * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny. + * If known, pass the known value, as this may be more efficient. + * @param aDestBreaks: the line breaks you want in the output. + * @param aSrcLen: length of the source in characters. If -1, the source is assumed to be a null- + * terminated string. + * @param aOutLen: used to return character length of returned buffer, if not null. + */ + static nsresult ConvertUnicharLineBreaksInSitu(PRUnichar **ioBuffer, ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, + int32_t aSrcLen = kIgnoreLen, int32_t* aOutLen = nullptr); + +}; + +#endif // nsLinebreakConverter_h_ diff --git a/xpcom/io/nsLocalFile.h b/xpcom/io/nsLocalFile.h new file mode 100644 index 000000000..1972eb757 --- /dev/null +++ b/xpcom/io/nsLocalFile.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + * + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 build. + */ + +#ifndef _NS_LOCAL_FILE_H_ +#define _NS_LOCAL_FILE_H_ + +#include "nscore.h" + +#define NS_LOCAL_FILE_CID {0x2e23e220, 0x60be, 0x11d3, {0x8c, 0x4a, 0x00, 0x00, 0x64, 0x65, 0x73, 0x74}} + +#define NS_DECL_NSLOCALFILE_UNICODE_METHODS \ + nsresult AppendUnicode(const PRUnichar *aNode); \ + nsresult GetUnicodeLeafName(PRUnichar **aLeafName); \ + nsresult SetUnicodeLeafName(const PRUnichar *aLeafName); \ + nsresult CopyToUnicode(nsIFile *aNewParentDir, const PRUnichar *aNewLeafName); \ + nsresult CopyToFollowingLinksUnicode(nsIFile *aNewParentDir, const PRUnichar *aNewLeafName); \ + nsresult MoveToUnicode(nsIFile *aNewParentDir, const PRUnichar *aNewLeafName); \ + nsresult GetUnicodeTarget(PRUnichar **aTarget); \ + nsresult GetUnicodePath(PRUnichar **aPath); \ + nsresult InitWithUnicodePath(const PRUnichar *aPath); \ + nsresult AppendRelativeUnicodePath(const PRUnichar *aRelativePath); + +// nsXPComInit needs to know about how we are implemented, +// so here we will export it. Other users should not depend +// on this. + +#include <errno.h> +#include "nsILocalFile.h" + +#ifdef XP_WIN +#include "nsLocalFileWin.h" +#elif defined(XP_UNIX) +#include "nsLocalFileUnix.h" +#elif defined(XP_OS2) +#include "nsLocalFileOS2.h" +#else +#error NOT_IMPLEMENTED +#endif + +#define NSRESULT_FOR_RETURN(ret) (((ret) < 0) ? NSRESULT_FOR_ERRNO() : NS_OK) + +inline nsresult +nsresultForErrno(int err) +{ + switch (err) { + case 0: + return NS_OK; + case ENOENT: + return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; + case ENOTDIR: + return NS_ERROR_FILE_DESTINATION_NOT_DIR; +#ifdef ENOLINK + case ENOLINK: + return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; +#endif /* ENOLINK */ + case EEXIST: + return NS_ERROR_FILE_ALREADY_EXISTS; +#ifdef EPERM + case EPERM: +#endif /* EPERM */ + case EACCES: + return NS_ERROR_FILE_ACCESS_DENIED; + /* + * On AIX 4.3, ENOTEMPTY is defined as EEXIST, + * so there can't be cases for both without + * preprocessing. + */ +#if ENOTEMPTY != EEXIST + case ENOTEMPTY: + return NS_ERROR_FILE_DIR_NOT_EMPTY; +#endif /* ENOTEMPTY != EEXIST */ + default: + return NS_ERROR_FAILURE; + } +} + +#define NSRESULT_FOR_ERRNO() nsresultForErrno(errno) + +void NS_StartupLocalFile(); +void NS_ShutdownLocalFile(); + +#endif diff --git a/xpcom/io/nsLocalFileCommon.cpp b/xpcom/io/nsLocalFileCommon.cpp new file mode 100644 index 000000000..5ec4bae22 --- /dev/null +++ b/xpcom/io/nsLocalFileCommon.cpp @@ -0,0 +1,290 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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" + +#ifdef XP_WIN +#include <string.h> +#endif + + +void NS_StartupLocalFile() +{ + nsLocalFile::GlobalInit(); +} + +void NS_ShutdownLocalFile() +{ + nsLocalFile::GlobalShutdown(); +} + +#if !defined(MOZ_WIDGET_COCOA) && !defined(XP_WIN) +NS_IMETHODIMP +nsLocalFile::InitWithFile(nsIFile *aFile) +{ + NS_ENSURE_ARG(aFile); + + 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 type, uint32_t attributes) +{ + 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; + + longName = (pathName.Length() + kMaxSequenceNumberLength > + kMaxFilenameLength); + if (!longName) + { + rv = Create(type, attributes); + if (rv != NS_ERROR_FILE_ALREADY_EXISTS) + return rv; + } + +#ifdef XP_WIN + rv = GetLeafName(leafName); + if (NS_FAILED(rv)) + return rv; + + const int32_t lastDot = leafName.RFindChar(PRUnichar('.')); +#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 rv = Create(type, attributes); + if (rv != NS_ERROR_FILE_ALREADY_EXISTS) + return rv; + } + + 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(type, attributes); + if (NS_SUCCEEDED(rv) || rv != NS_ERROR_FILE_ALREADY_EXISTS) + return rv; + } + + // The disk is full, sort of + return NS_ERROR_FILE_TOO_BIG; +} + +#if defined(XP_WIN) || defined(XP_OS2) +static const PRUnichar kPathSeparatorChar = '\\'; +#elif defined(XP_UNIX) +static const PRUnichar kPathSeparatorChar = '/'; +#else +#error Need to define file path separator for your platform +#endif + +static int32_t SplitPath(PRUnichar *path, PRUnichar **nodeArray, int32_t arrayLen) +{ + if (*path == 0) + return 0; + + PRUnichar **nodePtr = nodeArray; + if (*path == kPathSeparatorChar) + path++; + *nodePtr++ = path; + + for (PRUnichar *cp = path; *cp != 0; cp++) { + if (*cp == kPathSeparatorChar) { + *cp++ = 0; + if (*cp == 0) + break; + if (nodePtr - nodeArray >= arrayLen) + return -1; + *nodePtr++ = cp; + } + } + return nodePtr - nodeArray; +} + + +NS_IMETHODIMP +nsLocalFile::GetRelativeDescriptor(nsIFile *fromFile, nsACString& _retval) +{ + NS_ENSURE_ARG_POINTER(fromFile); + const int32_t kMaxNodesInPath = 32; + + // + // _retval will be UTF-8 encoded + // + + nsresult rv; + _retval.Truncate(0); + + nsAutoString thisPath, fromPath; + PRUnichar *thisNodes[kMaxNodesInPath], *fromNodes[kMaxNodesInPath]; + int32_t thisNodeCnt, fromNodeCnt, nodeIndex; + + rv = GetPath(thisPath); + if (NS_FAILED(rv)) + return rv; + rv = fromFile->GetPath(fromPath); + if (NS_FAILED(rv)) + return rv; + + // get raw pointer to mutable string buffer + PRUnichar *thisPathPtr; thisPath.BeginWriting(thisPathPtr); + PRUnichar *fromPathPtr; fromPath.BeginWriting(fromPathPtr); + + thisNodeCnt = SplitPath(thisPathPtr, thisNodes, kMaxNodesInPath); + fromNodeCnt = SplitPath(fromPathPtr, fromNodes, kMaxNodesInPath); + if (thisNodeCnt < 0 || fromNodeCnt < 0) + return NS_ERROR_FAILURE; + + for (nodeIndex = 0; nodeIndex < thisNodeCnt && nodeIndex < fromNodeCnt; ++nodeIndex) { +#ifdef XP_WIN + if (_wcsicmp(thisNodes[nodeIndex], fromNodes[nodeIndex])) + break; +#else + if (nsCRT::strcmp(thisNodes[nodeIndex], fromNodes[nodeIndex])) + break; +#endif + } + + int32_t branchIndex = nodeIndex; + for (nodeIndex = branchIndex; nodeIndex < fromNodeCnt; nodeIndex++) + _retval.AppendLiteral("../"); + for (nodeIndex = branchIndex; nodeIndex < thisNodeCnt; nodeIndex++) { + NS_ConvertUTF16toUTF8 nodeStr(thisNodes[nodeIndex]); + _retval.Append(nodeStr); + if (nodeIndex + 1 < thisNodeCnt) + _retval.Append('/'); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetRelativeDescriptor(nsIFile *fromFile, const nsACString& relativeDesc) +{ + NS_NAMED_LITERAL_CSTRING(kParentDirStr, "../"); + + nsCOMPtr<nsIFile> targetFile; + nsresult rv = fromFile->Clone(getter_AddRefs(targetFile)); + if (NS_FAILED(rv)) + return rv; + + // + // relativeDesc is UTF-8 encoded + // + + nsCString::const_iterator strBegin, strEnd; + relativeDesc.BeginReading(strBegin); + relativeDesc.EndReading(strEnd); + + nsCString::const_iterator nodeBegin(strBegin), nodeEnd(strEnd); + nsCString::const_iterator pos(strBegin); + + nsCOMPtr<nsIFile> 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); +} diff --git a/xpcom/io/nsLocalFileOS2.cpp b/xpcom/io/nsLocalFileOS2.cpp new file mode 100644 index 000000000..2fbccccb4 --- /dev/null +++ b/xpcom/io/nsLocalFileOS2.cpp @@ -0,0 +1,2572 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +// N.B. the ns* & pr* headers below will #include all +// of the standard library headers this file requires + +#include "nsCOMPtr.h" +#include "nsMemory.h" + +#include "nsLocalFile.h" +#include "nsNativeCharsetUtils.h" + +#include "nsISimpleEnumerator.h" +#include "nsIDirectoryEnumerator.h" +#include "nsIComponentManager.h" +#include "prio.h" + +#include "nsReadableUtils.h" +#include "nsISupportsPrimitives.h" +#include "nsIMutableArray.h" +#include "nsTraceRefcntImpl.h" + +using namespace mozilla; + +#define CHECK_mWorkingPath() \ + PR_BEGIN_MACRO \ + if (mWorkingPath.IsEmpty()) \ + return NS_ERROR_NOT_INITIALIZED; \ + PR_END_MACRO + +//----------------------------------------------------------------------------- +// static helper functions +//----------------------------------------------------------------------------- + +static nsresult ConvertOS2Error(int err) +{ + nsresult rv; + + switch (err) + { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + rv = NS_ERROR_FILE_NOT_FOUND; + break; + case ERROR_ACCESS_DENIED: + case ERROR_NOT_SAME_DEVICE: + rv = NS_ERROR_FILE_ACCESS_DENIED; + break; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_INVALID_BLOCK: + case ERROR_INVALID_HANDLE: + case ERROR_ARENA_TRASHED: + rv = NS_ERROR_OUT_OF_MEMORY; + break; + case ERROR_CURRENT_DIRECTORY: + rv = NS_ERROR_FILE_DIR_NOT_EMPTY; + break; + case ERROR_WRITE_PROTECT: + rv = NS_ERROR_FILE_READ_ONLY; + break; + case ERROR_HANDLE_DISK_FULL: + rv = NS_ERROR_FILE_TOO_BIG; + break; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + case ERROR_CANNOT_MAKE: + rv = NS_ERROR_FILE_ALREADY_EXISTS; + break; + case ERROR_FILENAME_EXCED_RANGE: + rv = NS_ERROR_FILE_NAME_TOO_LONG; + break; + case 0: + rv = NS_OK; + default: + rv = NS_ERROR_FAILURE; + } + return rv; +} + +static void +myLL_L2II(int64_t result, int32_t *hi, int32_t *lo ) +{ + int64_t a64, b64; // probably could have been done with + // only one int64_t, but these are macros, + // and I am a wimp. + + // shift the hi word to the low word, then push it into a long. + a64 = result >> 32; + *hi = int32_t(a64); + + // shift the low word to the hi word first, then shift it back. + b64 = result << 32; + a64 = b64 >> 32; + *lo = int32_t(a64); +} + +// Locates the first occurrence of charToSearchFor in the stringToSearch +static unsigned char* +_mbschr(const unsigned char* stringToSearch, int charToSearchFor) +{ + const unsigned char* p = stringToSearch; + + do { + if (*p == charToSearchFor) + break; + p = (const unsigned char*)WinNextChar(0,0,0,(char*)p); + } while (*p); + + // Result is p or NULL + return *p ? (unsigned char*)p : NULL; +} + +// Locates the first occurrence of subString in the stringToSearch +static unsigned char* +_mbsstr(const unsigned char* stringToSearch, const unsigned char* subString) +{ + const unsigned char* pStr = stringToSearch; + const unsigned char* pSub = subString; + + do { + while (*pStr && *pStr != *pSub) + pStr = (const unsigned char*)WinNextChar(0,0,0,(char*)pStr); + + if (!*pStr) + break; + + const unsigned char* pNxt = pStr; + do { + pSub = (const unsigned char*)WinNextChar(0,0,0,(char*)pSub); + pNxt = (const unsigned char*)WinNextChar(0,0,0,(char*)pNxt); + } while (*pSub && *pSub == *pNxt); + + if (!*pSub) + break; + + pSub = subString; + pStr = (const unsigned char*)WinNextChar(0,0,0,(char*)pStr); + + } while (*pStr); + + // if we got to the end of pSub, we've found it + return *pSub ? NULL : (unsigned char*)pStr; +} + +// Locates last occurence of charToSearchFor in the stringToSearch +NS_EXPORT unsigned char* +_mbsrchr(const unsigned char* stringToSearch, int charToSearchFor) +{ + int length = strlen((const char*)stringToSearch); + const unsigned char* p = stringToSearch+length; + + do { + if (*p == charToSearchFor) + break; + p = (const unsigned char*)WinPrevChar(0,0,0,(char*)stringToSearch,(char*)p); + } while (p > stringToSearch); + + // Result is p or NULL + return (*p == charToSearchFor) ? (unsigned char*)p : NULL; +} + +// Implement equivalent of Win32 CreateDirectoryA +static nsresult +CreateDirectoryA(PSZ path, PEAOP2 ppEABuf) +{ + APIRET rc; + nsresult rv; + FILESTATUS3 pathInfo; + + rc = DosCreateDir(path, ppEABuf); + if (rc != NO_ERROR) + { + rv = ConvertOS2Error(rc); + + // Check if directory already exists and if so, + // reflect that in the return value + rc = DosQueryPathInfo(path, FIL_STANDARD, + &pathInfo, sizeof(pathInfo)); + if (rc == NO_ERROR) + rv = ERROR_FILE_EXISTS; + } + else + rv = rc; + + return rv; +} + +static int isleadbyte(int c) +{ + static BOOL bDBCSFilled = FALSE; + // According to the Control Program Guide&Ref, 12 bytes is sufficient + static BYTE DBCSInfo[12] = { 0 }; + BYTE *curr; + BOOL retval = FALSE; + + if(!bDBCSFilled) + { + COUNTRYCODE ctrycodeInfo = { 0 }; + APIRET rc = NO_ERROR; + ctrycodeInfo.country = 0; // Current Country + ctrycodeInfo.codepage = 0; // Current Codepage + + rc = DosQueryDBCSEnv(sizeof(DBCSInfo), &ctrycodeInfo, DBCSInfo); + // we had an error, do something? + if (rc != NO_ERROR) + return FALSE; + + bDBCSFilled=TRUE; + } + + // DBCSInfo returned by DosQueryDBCSEnv is terminated + // with two '0' bytes in a row + curr = DBCSInfo; + while(*curr != 0 && *(curr+1) != 0) + { + if(c >= *curr && c <= *(curr+1)) + { + retval=TRUE; + break; + } + curr+=2; + } + + return retval; +} + +//----------------------------------------------------------------------------- +// nsDirEnumerator +//----------------------------------------------------------------------------- + +class nsDirEnumerator : public nsISimpleEnumerator, + public nsIDirectoryEnumerator +{ + public: + + NS_DECL_ISUPPORTS + + nsDirEnumerator() : mDir(nullptr) + { + } + + nsresult Init(nsIFile* parent) + { + nsAutoCString filepath; + parent->GetNativeTarget(filepath); + + if (filepath.IsEmpty()) + { + parent->GetNativePath(filepath); + } + + if (filepath.IsEmpty()) + { + return NS_ERROR_UNEXPECTED; + } + + mDir = PR_OpenDir(filepath.get()); + if (mDir == nullptr) // not a directory? + return NS_ERROR_FAILURE; + + mParent = parent; + return NS_OK; + } + + NS_IMETHOD HasMoreElements(bool *result) + { + nsresult rv; + if (mNext == nullptr && mDir) + { + PRDirEntry* entry = PR_ReadDir(mDir, PR_SKIP_BOTH); + if (entry == nullptr) + { + // end of dir entries + + PRStatus status = PR_CloseDir(mDir); + if (status != PR_SUCCESS) + return NS_ERROR_FAILURE; + mDir = nullptr; + + *result = false; + return NS_OK; + } + + nsCOMPtr<nsIFile> file; + rv = mParent->Clone(getter_AddRefs(file)); + if (NS_FAILED(rv)) + return rv; + + rv = file->AppendNative(nsDependentCString(entry->name)); + if (NS_FAILED(rv)) + return rv; + + // make sure the thing exists. If it does, try the next one. + bool exists; + rv = file->Exists(&exists); + if (NS_FAILED(rv) || !exists) + { + return HasMoreElements(result); + } + + mNext = do_QueryInterface(file); + } + *result = mNext != nullptr; + if (!*result) + Close(); + return NS_OK; + } + + NS_IMETHOD GetNext(nsISupports **result) + { + nsresult rv; + bool hasMore; + rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + *result = mNext; // might return nullptr + NS_IF_ADDREF(*result); + + mNext = nullptr; + return NS_OK; + } + + NS_IMETHOD GetNextFile(nsIFile **result) + { + *result = nullptr; + bool hasMore = false; + nsresult rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv) || !hasMore) + return rv; + *result = mNext; + NS_IF_ADDREF(*result); + mNext = nullptr; + return NS_OK; + } + + NS_IMETHOD Close() + { + if (mDir) + { + PRStatus status = PR_CloseDir(mDir); + NS_ASSERTION(status == PR_SUCCESS, "close failed"); + if (status != PR_SUCCESS) + return NS_ERROR_FAILURE; + mDir = nullptr; + } + return NS_OK; + } + + // dtor can be non-virtual since there are no subclasses, but must be + // public to use the class on the stack. + ~nsDirEnumerator() + { + Close(); + } + + protected: + PRDir* mDir; + nsCOMPtr<nsIFile> mParent; + nsCOMPtr<nsIFile> mNext; +}; + +NS_IMPL_ISUPPORTS2(nsDirEnumerator, nsISimpleEnumerator, nsIDirectoryEnumerator) + +//----------------------------------------------------------------------------- +// nsDriveEnumerator +//----------------------------------------------------------------------------- + +class nsDriveEnumerator : public nsISimpleEnumerator +{ +public: + nsDriveEnumerator(); + virtual ~nsDriveEnumerator(); + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + nsresult Init(); + +private: + // mDrives is a bitmap representing the available drives + // mLetter is incremented each time mDrives is shifted rightward + uint32_t mDrives; + uint8_t mLetter; +}; + +NS_IMPL_ISUPPORTS1(nsDriveEnumerator, nsISimpleEnumerator) + +nsDriveEnumerator::nsDriveEnumerator() + : mDrives(0), mLetter(0) +{ +} + +nsDriveEnumerator::~nsDriveEnumerator() +{ +} + +nsresult nsDriveEnumerator::Init() +{ + ULONG ulCurrent; + + // bits 0-25 in mDrives represent each possible drive, A-Z + DosError(FERR_DISABLEHARDERR); + APIRET rc = DosQueryCurrentDisk(&ulCurrent, (PULONG)&mDrives); + DosError(FERR_ENABLEHARDERR); + if (rc) + return NS_ERROR_FAILURE; + + mLetter = 'A'; + return NS_OK; +} + +NS_IMETHODIMP nsDriveEnumerator::HasMoreElements(bool *aHasMore) +{ + // no more bits means no more drives + *aHasMore = (mDrives != 0); + return NS_OK; +} + +NS_IMETHODIMP nsDriveEnumerator::GetNext(nsISupports **aNext) +{ + if (!mDrives) + { + *aNext = nullptr; + return NS_OK; + } + + // if bit 0 is off, advance to the next bit that's on + while ((mDrives & 1) == 0) + { + mDrives >>= 1; + mLetter++; + } + + // format a drive string, then advance to the next possible drive + char drive[4] = "x:\\"; + drive[0] = mLetter; + mDrives >>= 1; + mLetter++; + + nsIFile *file; + nsresult rv = NS_NewNativeLocalFile(nsDependentCString(drive), + false, &file); + *aNext = file; + + return rv; +} + +//----------------------------------------------------------------------------- +// class TypeEaEnumerator - a convenience for accessing +// a file's .TYPE extended attribute +//----------------------------------------------------------------------------- + +// this struct describes the first entry for an MVMT or MVST EA; +// .TYPE is supposed to be MVMT but is sometimes malformed as MVST + +typedef struct _MVHDR { + USHORT usEAType; + USHORT usCodePage; + USHORT usNumEntries; + USHORT usDataType; + USHORT usDataLth; + char data[1]; +} MVHDR; + +typedef MVHDR *PMVHDR; + + +class TypeEaEnumerator +{ +public: + TypeEaEnumerator() : mEaBuf(nullptr) { } + ~TypeEaEnumerator() { if (mEaBuf) NS_Free(mEaBuf); } + + nsresult Init(nsLocalFile * aFile); + char * GetNext(uint32_t *lth); + +private: + char * mEaBuf; + char * mpCur; + PMVHDR mpMvh; + USHORT mLth; + USHORT mCtr; +}; + + +nsresult TypeEaEnumerator::Init(nsLocalFile * aFile) +{ +#define EABUFSIZE 512 + + // provide a buffer for the results + mEaBuf = (char*)NS_Alloc(EABUFSIZE); + if (!mEaBuf) + return NS_ERROR_OUT_OF_MEMORY; + + PFEA2LIST pfea2list = (PFEA2LIST)mEaBuf; + pfea2list->cbList = EABUFSIZE; + + // ask for the .TYPE extended attribute + nsresult rv = aFile->GetEA(".TYPE", pfea2list); + if (NS_FAILED(rv)) + return rv; + + // point at the data - it starts immediately after the EA's name; + // then confirm the EA is MVMT (correct) or MVST (acceptable) + mpMvh = (PMVHDR)&(pfea2list->list[0].szName[pfea2list->list[0].cbName+1]); + if (mpMvh->usEAType != EAT_MVMT) + if (mpMvh->usEAType != EAT_MVST || mpMvh->usDataType != EAT_ASCII) + return NS_ERROR_FAILURE; + + // init the variables that tell us where we are in the lsit + mLth = 0; + mCtr = 0; + mpCur = (char*)(mpMvh->usEAType == EAT_MVMT ? + &mpMvh->usDataType : &mpMvh->usDataLth); + + return NS_OK; +} + + +char * TypeEaEnumerator::GetNext(uint32_t *lth) +{ + char * result = nullptr; + + // this is a loop so we can skip invalid entries if needed; + // normally, it will break out on the first iteration + while (mCtr++ < mpMvh->usNumEntries) { + + // advance to the next entry + mpCur += mLth; + + // if MVMT, ensure the datatype is OK, then advance + // to the length field present in both formats + if (mpMvh->usEAType == EAT_MVMT) { + if (*((PUSHORT)mpCur) != EAT_ASCII) + continue; + mpCur += sizeof(USHORT); + } + + // get the data's length, point at the data itself, then exit + mLth = *lth = *((PUSHORT)mpCur); + mpCur += sizeof(USHORT); + result = mpCur; + break; + } + + return result; +} + +//----------------------------------------------------------------------------- +// nsLocalFile <public> +//----------------------------------------------------------------------------- + +nsLocalFile::nsLocalFile() + : mDirty(true) +{ +} + +nsresult +nsLocalFile::nsLocalFileConstructor(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr) +{ + NS_ENSURE_ARG_POINTER(aInstancePtr); + NS_ENSURE_NO_AGGREGATION(outer); + + nsLocalFile* inst = new nsLocalFile(); + if (inst == NULL) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = inst->QueryInterface(aIID, aInstancePtr); + if (NS_FAILED(rv)) + { + delete inst; + return rv; + } + return NS_OK; +} + + +//----------------------------------------------------------------------------- +// nsLocalFile::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_THREADSAFE_ISUPPORTS4(nsLocalFile, + nsILocalFile, + nsIFile, + nsILocalFileOS2, + nsIHashable) + + +//----------------------------------------------------------------------------- +// nsLocalFile <private> +//----------------------------------------------------------------------------- + +nsLocalFile::nsLocalFile(const nsLocalFile& other) + : mDirty(true) + , mWorkingPath(other.mWorkingPath) +{ +} + + +// Stat the path. After a successful return the path is +// guaranteed valid and the members of mFileInfo64 can be used. +nsresult +nsLocalFile::Stat() +{ + // if we aren't dirty then we are already done + if (!mDirty) + return NS_OK; + + // we can't stat anything that isn't a valid NSPR addressable path + if (mWorkingPath.IsEmpty()) + return NS_ERROR_FILE_INVALID_PATH; + + // hack designed to work around bug 134796 until it is fixed + char temp[4]; + const char *nsprPath = mWorkingPath.get(); + if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == ':') + { + temp[0] = mWorkingPath[0]; + temp[1] = mWorkingPath[1]; + temp[2] = '\\'; + temp[3] = '\0'; + nsprPath = temp; + } + + // see if the working path exists + DosError(FERR_DISABLEHARDERR); + PRStatus status = PR_GetFileInfo64(nsprPath, &mFileInfo64); + DosError(FERR_ENABLEHARDERR); + if (status != PR_SUCCESS) + return NS_ERROR_FILE_NOT_FOUND; + + mDirty = false; + return NS_OK; +} + + +//----------------------------------------------------------------------------- +// nsLocalFile::nsIFile,nsILocalFile +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsLocalFile::Clone(nsIFile **file) +{ + // Just copy-construct ourselves + *file = new nsLocalFile(*this); + if (!*file) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*file); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::InitWithNativePath(const nsACString &filePath) +{ + MakeDirty(); + + nsACString::const_iterator begin, end; + filePath.BeginReading(begin); + filePath.EndReading(end); + + // input string must not be empty + if (begin == end) + return NS_ERROR_FAILURE; + + char firstChar = *begin; + char secondChar = *(++begin); + + // just do a sanity check. if it has any forward slashes, it is not + // a Native path. Also, it must have a colon at after the first char. + if (FindCharInReadable('/', begin, end)) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + if (secondChar != ':' && (secondChar != '\\' || firstChar != '\\')) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + mWorkingPath = filePath; + // kill any trailing '\' provided it isn't the second char of DBCS + int32_t len = mWorkingPath.Length() - 1; + if (mWorkingPath[len] == '\\' && !::isleadbyte(mWorkingPath[len - 1])) + mWorkingPath.Truncate(len); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::OpenNSPRFileDesc(int32_t flags, int32_t mode, PRFileDesc **_retval) +{ + nsresult rv = Stat(); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) + return rv; + + *_retval = PR_Open(mWorkingPath.get(), flags, mode); + if (*_retval) + return NS_OK; + + if (flags & DELETE_ON_CLOSE) { + PR_Delete(mWorkingPath.get()); + } + + return NS_ErrorAccordingToNSPR(); +} + + +NS_IMETHODIMP +nsLocalFile::OpenANSIFileDesc(const char *mode, FILE * *_retval) +{ + nsresult rv = Stat(); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) + return rv; + + *_retval = fopen(mWorkingPath.get(), mode); + if (*_retval) + return NS_OK; + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::Create(uint32_t type, uint32_t attributes) +{ + if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE) + return NS_ERROR_FILE_UNKNOWN_TYPE; + + nsresult rv = Stat(); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) + return rv; + + // create directories to target + // + // A given local file can be either one of these forms: + // + // - normal: X:\some\path\on\this\drive + // ^--- start here + // + // - UNC path: \\machine\volume\some\path\on\this\drive + // ^--- start here + // + // Skip the first 'X:\' for the first form, and skip the first full + // '\\machine\volume\' segment for the second form. + + unsigned char* path = (unsigned char*) mWorkingPath.BeginWriting(); + + if (path[0] == '\\' && path[1] == '\\') + { + // dealing with a UNC path here; skip past '\\machine\' + path = _mbschr(path + 2, '\\'); + if (!path) + return NS_ERROR_FILE_INVALID_PATH; + ++path; + } + + // search for first slash after the drive (or volume) name + unsigned char* slash = _mbschr(path, '\\'); + + if (slash) + { + // skip the first '\\' + ++slash; + slash = _mbschr(slash, '\\'); + + while (slash) + { + *slash = '\0'; + + rv = CreateDirectoryA(const_cast<char*>(mWorkingPath.get()), NULL); + if (rv) { + rv = ConvertOS2Error(rv); + if (rv != NS_ERROR_FILE_ALREADY_EXISTS) + return rv; + } + *slash = '\\'; + ++slash; + slash = _mbschr(slash, '\\'); + } + } + + if (type == NORMAL_FILE_TYPE) + { + PRFileDesc* file = PR_Open(mWorkingPath.get(), PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL, attributes); + if (!file) + return NS_ERROR_FILE_ALREADY_EXISTS; + + PR_Close(file); + return NS_OK; + } + + if (type == DIRECTORY_TYPE) + { + rv = CreateDirectoryA(const_cast<char*>(mWorkingPath.get()), NULL); + if (rv) + return ConvertOS2Error(rv); + else + return NS_OK; + } + + return NS_ERROR_FILE_UNKNOWN_TYPE; +} + + +NS_IMETHODIMP +nsLocalFile::AppendNative(const nsACString &node) +{ + // append this path, multiple components are not permitted + return AppendNativeInternal(PromiseFlatCString(node), false); +} + +NS_IMETHODIMP +nsLocalFile::AppendRelativeNativePath(const nsACString &node) +{ + // append this path, multiple components are permitted + return AppendNativeInternal(PromiseFlatCString(node), true); +} + +nsresult +nsLocalFile::AppendNativeInternal(const nsAFlatCString &node, bool multipleComponents) +{ + if (node.IsEmpty()) + return NS_OK; + + // check the relative path for validity + const unsigned char * nodePath = (const unsigned char *) node.get(); + if (*nodePath == '\\' // can't start with an '\' + || _mbschr(nodePath, '/') // can't contain / + || node.Equals("..")) // can't be .. + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + if (multipleComponents) + { + // can't contain .. as a path component. Ensure that the valid components + // "foo..foo", "..foo", and "foo.." are not falsely detected, but the invalid + // paths "..\", "foo\..", "foo\..\foo", "..\foo", etc are. + const unsigned char * doubleDot = _mbsstr(nodePath, (const unsigned char *)"\\.."); + while (doubleDot) + { + doubleDot += 3; + if (*doubleDot == '\0' || *doubleDot == '\\') + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + doubleDot = _mbsstr(doubleDot, (unsigned char *)"\\.."); + } + // catches the remaining cases of prefixes (i.e. '..\') + // note: this is a substitute for Win32's _mbsncmp(nodePath, "..\\", 3) + if (*nodePath == '.') { + nodePath = (const unsigned char*)WinNextChar(0,0,0,(char*)nodePath); + if (*nodePath == '.' && + *WinNextChar(0,0,0,(char*)nodePath) == '\\') + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + } + else if (_mbschr(nodePath, '\\')) // single components can't contain '\' + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + MakeDirty(); + + mWorkingPath.Append(NS_LITERAL_CSTRING("\\") + node); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Normalize() +{ + // XXX See bug 187957 comment 18 for possible problems with this implementation. + + if (mWorkingPath.IsEmpty()) + return NS_OK; + + // work in unicode for ease + nsAutoString path; + NS_CopyNativeToUnicode(mWorkingPath, path); + + // find the index of the root backslash for the path. Everything before + // this is considered fully normalized and cannot be ascended beyond + // using ".." For a local drive this is the first slash (e.g. "c:\"). + // For a UNC path it is the slash following the share name + // (e.g. "\\server\share\"). + int32_t rootIdx = 2; // default to local drive + if (path.First() == '\\') // if a share then calculate the rootIdx + { + rootIdx = path.FindChar('\\', 2); // skip \\ in front of the server + if (rootIdx == kNotFound) + return NS_OK; // already normalized + rootIdx = path.FindChar('\\', rootIdx+1); + if (rootIdx == kNotFound) + return NS_OK; // already normalized + } + else if (path.CharAt(rootIdx) != '\\') + { + // The path has been specified relative to the current working directory + // for that drive. To normalize it, the current working directory for + // that drive needs to be inserted before the supplied relative path + // which will provide an absolute path (and the rootIdx will still be 2). + char drv[4] = "A:."; + char cwd[CCHMAXPATH]; + + drv[0] = mWorkingPath.First(); + if (DosQueryPathInfo(drv, FIL_QUERYFULLNAME, cwd, sizeof(cwd))) + return NS_ERROR_FILE_NOT_FOUND; + + nsAutoString currentDir; + NS_CopyNativeToUnicode(nsDependentCString(cwd), currentDir); + + if (currentDir.Last() == '\\') + path.Replace(0, 2, currentDir); + else + path.Replace(0, 2, currentDir + NS_LITERAL_STRING("\\")); + } + NS_POSTCONDITION(0 < rootIdx && rootIdx < (int32_t)path.Length(), "rootIdx is invalid"); + NS_POSTCONDITION(path.CharAt(rootIdx) == '\\', "rootIdx is invalid"); + + // if there is nothing following the root path then it is already normalized + if (rootIdx + 1 == (int32_t)path.Length()) + return NS_OK; + + // assign the root + nsAutoString normal; + const PRUnichar * pathBuffer = path.get(); // simplify access to the buffer + normal.SetCapacity(path.Length()); // it won't ever grow longer + normal.Assign(pathBuffer, rootIdx); + + // Normalize the path components. The actions taken are: + // + // "\\" condense to single backslash + // "." remove from path + // ".." up a directory + // "..." remove from path (any number of dots > 2) + // + // The last form is something that Windows 95 and 98 supported and + // is a shortcut for changing up multiple directories. Windows XP + // and ilk ignore it in a path, as is done here. + int32_t len, begin, end = rootIdx; + while (end < (int32_t)path.Length()) + { + // find the current segment (text between the backslashes) to + // be examined, this will set the following variables: + // begin == index of first char in segment + // end == index 1 char after last char in segment + // len == length of segment + begin = end + 1; + end = path.FindChar('\\', begin); + if (end == kNotFound) + end = path.Length(); + len = end - begin; + + // ignore double backslashes + if (len == 0) + continue; + + // len != 0, and interesting paths always begin with a dot + if (pathBuffer[begin] == '.') + { + // ignore single dots + if (len == 1) + continue; + + // handle multiple dots + if (len >= 2 && pathBuffer[begin+1] == '.') + { + // back up a path component on double dot + if (len == 2) + { + int32_t prev = normal.RFindChar('\\'); + if (prev >= rootIdx) + normal.Truncate(prev); + continue; + } + + // length is > 2 and the first two characters are dots. + // if the rest of the string is dots, then ignore it. + int idx = len - 1; + for (; idx >= 2; --idx) + { + if (pathBuffer[begin+idx] != '.') + break; + } + + // this is true if the loop above didn't break + // and all characters in this segment are dots. + if (idx < 2) + continue; + } + } + + // add the current component to the path, including the preceding backslash + normal.Append(pathBuffer + begin - 1, len + 1); + } + + // kill trailing dots and spaces. + int32_t filePathLen = normal.Length() - 1; + while(filePathLen > 0 && (normal[filePathLen] == ' ' || normal[filePathLen] == '.')) + { + normal.Truncate(filePathLen--); + } + + NS_CopyUnicodeToNative(normal, mWorkingPath); + MakeDirty(); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetNativeLeafName(nsACString &aLeafName) +{ + aLeafName.Truncate(); + + const char* temp = mWorkingPath.get(); + if(temp == nullptr) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + const char* leaf = (const char*) _mbsrchr((const unsigned char*) temp, '\\'); + + // if the working path is just a node without any lashes. + if (leaf == nullptr) + leaf = temp; + else + leaf++; + + aLeafName.Assign(leaf); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetNativeLeafName(const nsACString &aLeafName) +{ + MakeDirty(); + + const unsigned char* temp = (const unsigned char*) mWorkingPath.get(); + if(temp == nullptr) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + // cannot use nsCString::RFindChar() due to 0x5c problem + int32_t offset = (int32_t) (_mbsrchr(temp, '\\') - temp); + if (offset) + { + mWorkingPath.Truncate(offset+1); + } + mWorkingPath.Append(aLeafName); + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetNativePath(nsACString &_retval) +{ + _retval = mWorkingPath; + return NS_OK; +} + +//----------------------------------------------------------------------------- + +// get any single extended attribute for the current file or directory + +nsresult +nsLocalFile::GetEA(const char *eaName, PFEA2LIST pfea2list) +{ + // ensure we have an out-buffer whose length is specified + if (!pfea2list || !pfea2list->cbList) + return NS_ERROR_FAILURE; + + // the gea2list's name field is only 1 byte long; + // this expands its allocation to hold a 33 byte name + union { + GEA2LIST gea2list; + char dummy[sizeof(GEA2LIST)+32]; + }; + EAOP2 eaop2; + + eaop2.fpFEA2List = pfea2list; + eaop2.fpGEA2List = &gea2list; + + // fill in the request structure + dummy[sizeof(GEA2LIST)+31] = 0; + gea2list.list[0].oNextEntryOffset = 0; + strcpy(gea2list.list[0].szName, eaName); + gea2list.list[0].cbName = strlen(gea2list.list[0].szName); + gea2list.cbList = sizeof(GEA2LIST) + gea2list.list[0].cbName; + + // see what we get - this will succeed even if the EA doesn't exist + APIRET rc = DosQueryPathInfo(mWorkingPath.get(), FIL_QUERYEASFROMLIST, + &eaop2, sizeof(eaop2)); + if (rc) + return ConvertOS2Error(rc); + + // if the data length is zero, requested EA doesn't exist + if (!pfea2list->list[0].cbValue) + return NS_ERROR_FAILURE; + + return NS_OK; +} + + +// return an array of file types or null if there are none + +NS_IMETHODIMP +nsLocalFile::GetFileTypes(nsIArray **_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = 0; + + // fetch the .TYPE ea & prepare for enumeration + TypeEaEnumerator typeEnum; + nsresult rv = typeEnum.Init(this); + if (NS_FAILED(rv)) + return rv; + + // create an array that's scriptable + nsCOMPtr<nsIMutableArray> mutArray = + do_CreateInstance(NS_ARRAY_CONTRACTID); + NS_ENSURE_STATE(mutArray); + + int32_t cnt; + uint32_t lth; + char * ptr; + + // get each file type, convert to a CString, then add to the array + for (cnt=0, ptr=typeEnum.GetNext(<h); ptr; ptr=typeEnum.GetNext(<h)) { + nsCOMPtr<nsISupportsCString> typeString( + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + nsAutoCString temp; + temp.Assign(ptr, lth); + typeString->SetData(temp); + mutArray->AppendElement(typeString, false); + cnt++; + } + } + + // if the array has any contents, addref & return it + if (cnt) { + *_retval = mutArray; + NS_ADDREF(*_retval); + rv = NS_OK; + } + else + rv = NS_ERROR_FAILURE; + + return rv; +} + + +// see if the file is of the requested type + +NS_IMETHODIMP +nsLocalFile::IsFileType(const nsACString& fileType, bool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = false; + + // fetch the .TYPE ea & prepare for enumeration + TypeEaEnumerator typeEnum; + nsresult rv = typeEnum.Init(this); + if (NS_FAILED(rv)) + return rv; + + uint32_t lth; + char * ptr; + + // compare each type to the request; if there's a match, exit + for (ptr = typeEnum.GetNext(<h); ptr; ptr = typeEnum.GetNext(<h)) + if (fileType.EqualsASCII(ptr, lth)) { + *_retval = true; + break; + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- + +// this struct combines an FEA2LIST, an FEA2, plus additional fields +// needed to write a .TYPE EA in the correct EAT_MVMT format +#pragma pack(1) + typedef struct _TYPEEA { + struct { + ULONG ulcbList; + ULONG uloNextEntryOffset; + BYTE bfEA; + BYTE bcbName; + USHORT uscbValue; + char chszName[sizeof(".TYPE")]; + } hdr; + struct { + USHORT usEAType; + USHORT usCodePage; + USHORT usNumEntries; + USHORT usDataType; + USHORT usDataLth; + } info; + char data[1]; + } TYPEEA; + + typedef struct _TYPEEA2 { + USHORT usDataType; + USHORT usDataLth; + } TYPEEA2; +#pragma pack() + +// writes one or more .TYPE extended attributes taken +// from a comma-delimited string +NS_IMETHODIMP +nsLocalFile::SetFileTypes(const nsACString& fileTypes) +{ + if (fileTypes.IsEmpty()) + return NS_ERROR_FAILURE; + + uint32_t cnt = CountCharInReadable(fileTypes, ','); + uint32_t lth = fileTypes.Length() - cnt + (cnt * sizeof(TYPEEA2)); + uint32_t size = sizeof(TYPEEA) + lth; + + char *pBuf = (char*)NS_Alloc(size); + if (!pBuf) + return NS_ERROR_OUT_OF_MEMORY; + + TYPEEA *pEA = (TYPEEA *)pBuf; + + // the buffer has an extra byte due to TYPEEA.data[1] + pEA->hdr.ulcbList = size - 1; + pEA->hdr.uloNextEntryOffset = 0; + pEA->hdr.bfEA = 0; + pEA->hdr.bcbName = sizeof(".TYPE") - 1; + pEA->hdr.uscbValue = sizeof(pEA->info) + lth; + strcpy(pEA->hdr.chszName, ".TYPE"); + + pEA->info.usEAType = EAT_MVMT; + pEA->info.usCodePage = 0; + pEA->info.usNumEntries = ++cnt; + + nsACString::const_iterator begin, end, delim; + fileTypes.BeginReading(begin); + fileTypes.EndReading(end); + delim = begin; + + // fill in type & length, copy the current type name (which + // is not zero-terminated), then advance the ptr so the next + // iteration can reuse the trailing members of the structure + do { + FindCharInReadable( ',', delim, end); + lth = delim.get() - begin.get(); + pEA->info.usDataType = EAT_ASCII; + pEA->info.usDataLth = lth; + memcpy(pEA->data, begin.get(), lth); + pEA = (TYPEEA *)((char*)pEA + lth + sizeof(TYPEEA2)); + begin = ++delim; + } while (--cnt); + + // write the EA, then free the buffer + EAOP2 eaop2; + eaop2.fpGEA2List = 0; + eaop2.fpFEA2List = (PFEA2LIST)pBuf; + + int rc = DosSetPathInfo(mWorkingPath.get(), FIL_QUERYEASIZE, + &eaop2, sizeof(eaop2), 0); + NS_Free(pBuf); + + if (rc) + return ConvertOS2Error(rc); + + return NS_OK; +} + +//----------------------------------------------------------------------------- + +// this struct combines an FEA2LIST, an FEA2, plus additional fields +// needed to write a .SUBJECT EA in the correct EAT_ASCII format; +#pragma pack(1) + typedef struct _SUBJEA { + struct { + ULONG ulcbList; + ULONG uloNextEntryOffset; + BYTE bfEA; + BYTE bcbName; + USHORT uscbValue; + char chszName[sizeof(".SUBJECT")]; + } hdr; + struct { + USHORT usEAType; + USHORT usDataLth; + } info; + char data[1]; + } SUBJEA; +#pragma pack() + +// saves the file's source URI in its .SUBJECT extended attribute +NS_IMETHODIMP +nsLocalFile::SetFileSource(const nsACString& aURI) +{ + if (aURI.IsEmpty()) + return NS_ERROR_FAILURE; + + // this includes an extra character for the spec's trailing null + uint32_t lth = sizeof(SUBJEA) + aURI.Length(); + char * pBuf = (char*)NS_Alloc(lth); + if (!pBuf) + return NS_ERROR_OUT_OF_MEMORY; + + SUBJEA *pEA = (SUBJEA *)pBuf; + + // the trailing null doesn't get saved, so don't include it in the size + pEA->hdr.ulcbList = lth - 1; + pEA->hdr.uloNextEntryOffset = 0; + pEA->hdr.bfEA = 0; + pEA->hdr.bcbName = sizeof(".SUBJECT") - 1; + pEA->hdr.uscbValue = sizeof(pEA->info) + aURI.Length(); + strcpy(pEA->hdr.chszName, ".SUBJECT"); + pEA->info.usEAType = EAT_ASCII; + pEA->info.usDataLth = aURI.Length(); + strcpy(pEA->data, PromiseFlatCString(aURI).get()); + + // write the EA, then free the buffer + EAOP2 eaop2; + eaop2.fpGEA2List = 0; + eaop2.fpFEA2List = (PFEA2LIST)pEA; + + int rc = DosSetPathInfo(mWorkingPath.get(), FIL_QUERYEASIZE, + &eaop2, sizeof(eaop2), 0); + NS_Free(pBuf); + + if (rc) + return ConvertOS2Error(rc); + + return NS_OK; +} + +//----------------------------------------------------------------------------- + +nsresult +nsLocalFile::CopySingleFile(nsIFile *sourceFile, nsIFile *destParent, + const nsACString &newName, bool move) +{ + nsresult rv; + nsAutoCString filePath; + + nsAutoCString destPath; + destParent->GetNativeTarget(destPath); + + destPath.Append("\\"); + + if (newName.IsEmpty()) + { + nsAutoCString aFileName; + sourceFile->GetNativeLeafName(aFileName); + destPath.Append(aFileName); + } + else + { + destPath.Append(newName); + } + + rv = sourceFile->GetNativePath(filePath); + + if (NS_FAILED(rv)) + return rv; + + APIRET rc = NO_ERROR; + + if (move) + rc = DosMove(filePath.get(), (PSZ)const_cast<char*>(destPath.get())); + + if (!move || rc == ERROR_NOT_SAME_DEVICE || rc == ERROR_ACCESS_DENIED) + { + // will get an error if the destination and source files aren't on + // the same drive. "MoveFile()" on Windows will go ahead and move + // the file without error, so we need to do the same IBM-AKR + + do { + rc = DosCopy(filePath.get(), (PSZ)const_cast<char*>(destPath.get()), DCPY_EXISTING); + if (rc == ERROR_TOO_MANY_OPEN_FILES) { + ULONG CurMaxFH = 0; + LONG ReqCount = 20; + APIRET rc2; + rc2 = DosSetRelMaxFH(&ReqCount, &CurMaxFH); + if (rc2 != NO_ERROR) + break; + } + } while (rc == ERROR_TOO_MANY_OPEN_FILES); + + // WSOD2 HACK - NETWORK_ACCESS_DENIED + if (rc == 65) + { + CHAR achProgram[CCHMAXPATH]; // buffer for program name, parameters + RESULTCODES rescResults; // buffer for results of dosexecpgm + + strcpy(achProgram, "CMD.EXE /C "); + strcat(achProgram, """COPY "); + strcat(achProgram, filePath.get()); + strcat(achProgram, " "); + strcat(achProgram, (PSZ)const_cast<char*>(destPath.get())); + strcat(achProgram, """"); + achProgram[strlen(achProgram) + 1] = '\0'; + achProgram[7] = '\0'; + DosExecPgm(NULL, 0, + EXEC_SYNC, achProgram, (PSZ)NULL, + &rescResults, achProgram); + rc = 0; // Assume it worked + + } // rc == 65 + + // moving the file is supposed to act like a rename, so delete the + // original file if we got this far without error + if(move && (rc == NO_ERROR)) + DosDelete( filePath.get()); + + } // !move or ERROR + + if (rc) + rv = ConvertOS2Error(rc); + + return rv; +} + + +nsresult +nsLocalFile::CopyMove(nsIFile *aParentDir, const nsACString &newName, bool move) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + nsCOMPtr<nsIFile> newParentDir = aParentDir; + + nsresult rv = Stat(); + if (NS_FAILED(rv)) + return rv; + + if (!newParentDir) + { + // no parent was specified. We must rename. + + if (newName.IsEmpty()) + return NS_ERROR_INVALID_ARG; + + rv = GetParent(getter_AddRefs(newParentDir)); + if (NS_FAILED(rv)) + return rv; + } + + if (!newParentDir) + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + + // make sure it exists and is a directory. Create it if not there. + bool exists; + newParentDir->Exists(&exists); + if (!exists) + { + rv = newParentDir->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use + if (NS_FAILED(rv)) + return rv; + } + else + { + bool isDir; + newParentDir->IsDirectory(&isDir); + if (!isDir) + { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + } + + // Try different ways to move/copy files/directories + bool done = false; + bool isDir; + IsDirectory(&isDir); + + // Try to move the file or directory, or try to copy a single file + if (move || !isDir) + { + // when moving things, first try to just MoveFile it, + // even if it is a directory + rv = CopySingleFile(this, newParentDir, newName, move); + done = NS_SUCCEEDED(rv); + // If we are moving a directory and that fails, fallback on directory + // enumeration. See bug 231300 for details. + if (!done && !(move && isDir)) + return rv; + } + + // Not able to copy or move directly, so enumerate it + if (!done) + { + // create a new target destination in the new parentDir; + nsCOMPtr<nsIFile> target; + rv = newParentDir->Clone(getter_AddRefs(target)); + + if (NS_FAILED(rv)) + return rv; + + nsAutoCString allocatedNewName; + if (newName.IsEmpty()) + { + GetNativeLeafName(allocatedNewName); + } + else + { + allocatedNewName = newName; + } + + rv = target->AppendNative(allocatedNewName); + if (NS_FAILED(rv)) + return rv; + + allocatedNewName.Truncate(); + + // check if the destination directory already exists + target->Exists(&exists); + if (!exists) + { + // if the destination directory cannot be created, return an error + rv = target->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use + if (NS_FAILED(rv)) + return rv; + } + else + { + // check if the destination directory is writable and empty + bool isWritable; + + target->IsWritable(&isWritable); + if (!isWritable) + return NS_ERROR_FILE_ACCESS_DENIED; + + nsCOMPtr<nsISimpleEnumerator> targetIterator; + rv = target->GetDirectoryEntries(getter_AddRefs(targetIterator)); + if (NS_FAILED(rv)) + return rv; + + bool more; + targetIterator->HasMoreElements(&more); + // return error if target directory is not empty + if (more) + return NS_ERROR_FILE_DIR_NOT_EMPTY; + } + + nsDirEnumerator dirEnum; + + rv = dirEnum.Init(this); + if (NS_FAILED(rv)) { + NS_WARNING("dirEnum initialization failed"); + return rv; + } + + bool more; + while (NS_SUCCEEDED(dirEnum.HasMoreElements(&more)) && more) + { + nsCOMPtr<nsISupports> item; + nsCOMPtr<nsIFile> file; + dirEnum.GetNext(getter_AddRefs(item)); + file = do_QueryInterface(item); + if (file) + { + if (move) + { + rv = file->MoveToNative(target, EmptyCString()); + NS_ENSURE_SUCCESS(rv,rv); + } + else + { + rv = file->CopyToNative(target, EmptyCString()); + NS_ENSURE_SUCCESS(rv,rv); + } + } + } + // we've finished moving all the children of this directory + // in the new directory. so now delete the directory + // note, we don't need to do a recursive delete. + // MoveTo() is recursive. At this point, + // we've already moved the children of the current folder + // to the new location. nothing should be left in the folder. + if (move) + { + rv = Remove(false); // recursive + NS_ENSURE_SUCCESS(rv,rv); + } + } + + + // If we moved, we want to adjust this. + if (move) + { + MakeDirty(); + + nsAutoCString newParentPath; + newParentDir->GetNativePath(newParentPath); + + if (newParentPath.IsEmpty()) + return NS_ERROR_FAILURE; + + if (newName.IsEmpty()) + { + nsAutoCString aFileName; + GetNativeLeafName(aFileName); + + InitWithNativePath(newParentPath); + AppendNative(aFileName); + } + else + { + InitWithNativePath(newParentPath); + AppendNative(newName); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::CopyToNative(nsIFile *newParentDir, const nsACString &newName) +{ + return CopyMove(newParentDir, newName, false); +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinksNative(nsIFile *newParentDir, const nsACString &newName) +{ + return CopyMove(newParentDir, newName, false); +} + +NS_IMETHODIMP +nsLocalFile::MoveToNative(nsIFile *newParentDir, const nsACString &newName) +{ + return CopyMove(newParentDir, newName, true); +} + +NS_IMETHODIMP +nsLocalFile::Load(PRLibrary * *_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + bool isFile; + nsresult rv = IsFile(&isFile); + + if (NS_FAILED(rv)) + return rv; + + if (!isFile) + return NS_ERROR_FILE_IS_DIRECTORY; + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcntImpl::SetActivityIsLegal(false); +#endif + + *_retval = PR_LoadLibrary(mWorkingPath.get()); + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcntImpl::SetActivityIsLegal(true); +#endif + + if (*_retval) + return NS_OK; + + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsLocalFile::Remove(bool recursive) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + bool isDir = false; + + nsresult rv = IsDirectory(&isDir); + if (NS_FAILED(rv)) + return rv; + + if (isDir) + { + if (recursive) + { + nsDirEnumerator dirEnum; + + rv = dirEnum.Init(this); + if (NS_FAILED(rv)) + return rv; + + bool more; + while (NS_SUCCEEDED(dirEnum.HasMoreElements(&more)) && more) + { + nsCOMPtr<nsISupports> item; + dirEnum.GetNext(getter_AddRefs(item)); + nsCOMPtr<nsIFile> file = do_QueryInterface(item); + if (file) + file->Remove(recursive); + } + } + rv = rmdir(mWorkingPath.get()); + } + else + { + rv = remove(mWorkingPath.get()); + } + + // fixup error code if necessary... + if (rv == (nsresult)-1) + rv = NSRESULT_FOR_ERRNO(); + + MakeDirty(); + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTime(PRTime *aLastModifiedTime) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(aLastModifiedTime); + + *aLastModifiedTime = 0; + nsresult rv = Stat(); + if (NS_FAILED(rv)) + return rv; + + // microseconds -> milliseconds + *aLastModifiedTime = mFileInfo64.modifyTime / PR_USEC_PER_MSEC; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTimeOfLink(PRTime *aLastModifiedTime) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + return nsLocalFile::SetModDate(aLastModifiedTime); +} + + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsLocalFile::SetModDate(PRTime aLastModifiedTime) +{ + nsresult rv = Stat(); + + if (NS_FAILED(rv)) + return rv; + + PRExplodedTime pret; + FILESTATUS3 pathInfo; + + // Level 1 info + rv = DosQueryPathInfo(mWorkingPath.get(), FIL_STANDARD, + &pathInfo, sizeof(pathInfo)); + + if (NS_FAILED(rv)) + { + rv = ConvertOS2Error(rv); + return rv; + } + + // PR_ExplodeTime expects usecs... + PR_ExplodeTime(aLastModifiedTime * PR_USEC_PER_MSEC, PR_LocalTimeParameters, &pret); + + // fdateLastWrite.year is based off of 1980 + if (pret.tm_year >= 1980) + pathInfo.fdateLastWrite.year = pret.tm_year-1980; + else + pathInfo.fdateLastWrite.year = pret.tm_year; + + // Convert start offset -- OS/2: Jan=1; NSPR: Jan=0 + pathInfo.fdateLastWrite.month = pret.tm_month + 1; + pathInfo.fdateLastWrite.day = pret.tm_mday; + pathInfo.ftimeLastWrite.hours = pret.tm_hour; + pathInfo.ftimeLastWrite.minutes = pret.tm_min; + pathInfo.ftimeLastWrite.twosecs = pret.tm_sec / 2; + + rv = DosSetPathInfo(mWorkingPath.get(), FIL_STANDARD, + &pathInfo, sizeof(pathInfo), 0UL); + + if (NS_FAILED(rv)) + return rv; + + MakeDirty(); + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissions(uint32_t *aPermissions) +{ + NS_ENSURE_ARG(aPermissions); + + nsresult rv = Stat(); + if (NS_FAILED(rv)) + return rv; + + bool isWritable, isExecutable; + IsWritable(&isWritable); + IsExecutable(&isExecutable); + + *aPermissions = PR_IRUSR|PR_IRGRP|PR_IROTH; // all read + if (isWritable) + *aPermissions |= PR_IWUSR|PR_IWGRP|PR_IWOTH; // all write + if (isExecutable) + *aPermissions |= PR_IXUSR|PR_IXGRP|PR_IXOTH; // all execute + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissionsOfLink(uint32_t *aPermissionsOfLink) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +// the only Unix-style permission OS/2 knows is whether a file is +// writable; as a matter of policy, a browser shouldn't be able +// to change any of the other DOS-style attributes; to enforce +// this, we use DosSetPathInfo() rather than chmod() +NS_IMETHODIMP +nsLocalFile::SetPermissions(uint32_t aPermissions) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + nsresult rv = Stat(); + if (NS_FAILED(rv)) + return rv; + + APIRET rc; + FILESTATUS3 pathInfo; + + // Level 1 info + rc = DosQueryPathInfo(mWorkingPath.get(), FIL_STANDARD, + &pathInfo, sizeof(pathInfo)); + + if (rc != NO_ERROR) + return ConvertOS2Error(rc); + + ULONG attr = 0; + if (!(aPermissions & (PR_IWUSR|PR_IWGRP|PR_IWOTH))) + attr = FILE_READONLY; + + if (attr == (pathInfo.attrFile & FILE_READONLY)) + return NS_OK; + + pathInfo.attrFile ^= FILE_READONLY; + + rc = DosSetPathInfo(mWorkingPath.get(), FIL_STANDARD, + &pathInfo, sizeof(pathInfo), 0UL); + + if (rc != NO_ERROR) + return ConvertOS2Error(rc); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +NS_IMETHODIMP +nsLocalFile::GetFileSize(int64_t *aFileSize) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(aFileSize); + *aFileSize = 0; + + nsresult rv = Stat(); + if (NS_FAILED(rv)) + return rv; + + *aFileSize = mFileInfo64.size; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetFileSizeOfLink(int64_t *aFileSize) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +NS_IMETHODIMP +nsLocalFile::SetFileSize(int64_t aFileSize) +{ + nsresult rv = Stat(); + if (NS_FAILED(rv)) + return rv; + + APIRET rc; + HFILE hFile; + ULONG actionTaken; + + rc = DosOpen(mWorkingPath.get(), + &hFile, + &actionTaken, + 0, + FILE_NORMAL, + OPEN_ACTION_FAIL_IF_NEW | OPEN_ACTION_OPEN_IF_EXISTS, + OPEN_SHARE_DENYREADWRITE | OPEN_ACCESS_READWRITE, + NULL); + + if (rc != NO_ERROR) + { + MakeDirty(); + return NS_ERROR_FAILURE; + } + + // Seek to new, desired end of file + int32_t hi, lo; + myLL_L2II(aFileSize, &hi, &lo ); + + rc = DosSetFileSize(hFile, lo); + DosClose(hFile); + MakeDirty(); + + if (rc != NO_ERROR) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetDiskSpaceAvailable(int64_t *aDiskSpaceAvailable) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(aDiskSpaceAvailable); + *aDiskSpaceAvailable = 0; + + nsresult rv = Stat(); + if (NS_FAILED(rv)) + return rv; + + ULONG ulDriveNo = toupper(mWorkingPath.CharAt(0)) + 1 - 'A'; + FSALLOCATE fsAllocate; + APIRET rc = DosQueryFSInfo(ulDriveNo, + FSIL_ALLOC, + &fsAllocate, + sizeof(fsAllocate)); + + if (rc != NO_ERROR) + return NS_ERROR_FAILURE; + + *aDiskSpaceAvailable = fsAllocate.cUnitAvail; + *aDiskSpaceAvailable *= fsAllocate.cSectorUnit; + *aDiskSpaceAvailable *= fsAllocate.cbSector; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetParent(nsIFile * *aParent) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG_POINTER(aParent); + + nsAutoCString parentPath(mWorkingPath); + + // cannot use nsCString::RFindChar() due to 0x5c problem + int32_t offset = (int32_t) (_mbsrchr((const unsigned char *) parentPath.get(), '\\') + - (const unsigned char *) parentPath.get()); + // adding this offset check that was removed in bug 241708 fixes mail + // directories that aren't relative to/underneath the profile dir. + // e.g., on a different drive. Before you remove them, please make + // sure local mail directories that aren't underneath the profile dir work. + if (offset < 0) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + if (offset == 1 && parentPath[0] == '\\') { + aParent = nullptr; + return NS_OK; + } + if (offset > 0) + parentPath.Truncate(offset); + else + parentPath.AssignLiteral("\\\\."); + + nsCOMPtr<nsIFile> localFile; + nsresult rv = NS_NewNativeLocalFile(parentPath, false, getter_AddRefs(localFile)); + + if(NS_SUCCEEDED(rv) && localFile) + { + return CallQueryInterface(localFile, aParent); + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::Exists(bool *_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(_retval); + *_retval = false; + + MakeDirty(); + nsresult rv = Stat(); + + *_retval = NS_SUCCEEDED(rv); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsWritable(bool *_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(_retval); + *_retval = false; + + nsresult rv = Stat(); + if (NS_FAILED(rv)) + return rv; + + APIRET rc; + FILESTATUS3 pathInfo; + + // Level 1 info + rc = DosQueryPathInfo(mWorkingPath.get(), FIL_STANDARD, + &pathInfo, sizeof(pathInfo)); + + if (rc != NO_ERROR) + { + rc = ConvertOS2Error(rc); + return rc; + } + + // on OS/2, unlike Windows, directories on writable media + // can not be assigned a readonly attribute + *_retval = !((pathInfo.attrFile & FILE_READONLY) != 0); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsReadable(bool *_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(_retval); + *_retval = false; + + nsresult rv = Stat(); + if (NS_FAILED(rv)) + return rv; + + *_retval = true; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::IsExecutable(bool *_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(_retval); + *_retval = false; + + nsresult rv = Stat(); + if (NS_FAILED(rv)) + return rv; + + // no need to bother if this isn't a file + bool isFile; + rv = IsFile(&isFile); + if (NS_FAILED(rv) || !isFile) + return rv; + + nsAutoCString path; + GetNativeTarget(path); + + // get the filename, including the leading backslash + const char* leaf = (const char*) _mbsrchr((const unsigned char*)path.get(), '\\'); + if (!leaf) + return NS_OK; + + // eliminate trailing dots & spaces in a DBCS-aware manner + // XXX shouldn't this have been done when the path was set? + + char* pathEnd = WinPrevChar(0, 0, 0, leaf, strchr(leaf, 0)); + while (pathEnd > leaf) + { + if (*pathEnd != ' ' && *pathEnd != '.') + break; + *pathEnd = 0; + pathEnd = WinPrevChar(0, 0, 0, leaf, pathEnd); + } + + if (pathEnd == leaf) + return NS_OK; + + // get the extension, including the dot + char* ext = (char*) _mbsrchr((const unsigned char*)leaf, '.'); + if (!ext) + return NS_OK; + + if (stricmp(ext, ".exe") == 0 || + stricmp(ext, ".cmd") == 0 || + stricmp(ext, ".com") == 0 || + stricmp(ext, ".bat") == 0) + *_retval = true; + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::IsDirectory(bool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = false; + + nsresult rv = Stat(); + if (NS_FAILED(rv)) + return rv; + + *_retval = (mFileInfo64.type == PR_FILE_DIRECTORY); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsFile(bool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = false; + + nsresult rv = Stat(); + if (NS_FAILED(rv)) + return rv; + + *_retval = (mFileInfo64.type == PR_FILE_FILE); + return rv; +} + +NS_IMETHODIMP +nsLocalFile::IsHidden(bool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = false; + + nsresult rv = Stat(); + if (NS_FAILED(rv)) + return rv; + + APIRET rc; + FILESTATUS3 pathInfo; + + // Level 1 info + rc = DosQueryPathInfo(mWorkingPath.get(), FIL_STANDARD, + &pathInfo, sizeof(pathInfo)); + + if (rc != NO_ERROR) + { + rc = ConvertOS2Error(rc); + return rc; + } + + *_retval = ((pathInfo.attrFile & FILE_HIDDEN) != 0); + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::IsSymlink(bool *_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG_POINTER(_retval); + + // No Symlinks on OS/2 + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSpecial(bool *_retval) +{ + NS_ENSURE_ARG(_retval); + + // when implemented, IsSpecial will be used for WPS objects + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Equals(nsIFile *inFile, bool *_retval) +{ + NS_ENSURE_ARG(inFile); + NS_ENSURE_ARG(_retval); + + nsAutoCString inFilePath; + inFile->GetNativePath(inFilePath); + + *_retval = inFilePath.Equals(mWorkingPath); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Contains(nsIFile *inFile, bool recur, bool *_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + *_retval = false; + + nsAutoCString myFilePath; + if ( NS_FAILED(GetNativeTarget(myFilePath))) + GetNativePath(myFilePath); + + int32_t myFilePathLen = myFilePath.Length(); + + nsAutoCString inFilePath; + if ( NS_FAILED(inFile->GetNativeTarget(inFilePath))) + inFile->GetNativePath(inFilePath); + + if ( strnicmp( myFilePath.get(), inFilePath.get(), myFilePathLen) == 0) + { + // now make sure that the |inFile|'s path has a trailing + // separator. + + if (inFilePath[myFilePathLen] == '\\') + { + *_retval = true; + } + + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetNativeTarget(nsACString &_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + _retval = mWorkingPath; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetFollowLinks(bool *aFollowLinks) +{ + NS_ENSURE_ARG(aFollowLinks); + *aFollowLinks = false; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetFollowLinks(bool aFollowLinks) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator * *entries) +{ + NS_ENSURE_ARG(entries); + nsresult rv; + *entries = nullptr; + + if (mWorkingPath.EqualsLiteral("\\\\.")) { + nsDriveEnumerator *drives = new nsDriveEnumerator; + if (!drives) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(drives); + rv = drives->Init(); + if (NS_FAILED(rv)) { + NS_RELEASE(drives); + return rv; + } + *entries = drives; + return NS_OK; + } + + bool isDir; + rv = IsDirectory(&isDir); + if (NS_FAILED(rv)) + return rv; + if (!isDir) + return NS_ERROR_FILE_NOT_DIRECTORY; + + nsDirEnumerator* dirEnum = new nsDirEnumerator(); + if (dirEnum == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(dirEnum); + rv = dirEnum->Init(this); + if (NS_FAILED(rv)) + { + NS_RELEASE(dirEnum); + return rv; + } + + *entries = dirEnum; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPersistentDescriptor(nsACString &aPersistentDescriptor) +{ + return GetNativePath(aPersistentDescriptor); +} + +NS_IMETHODIMP +nsLocalFile::SetPersistentDescriptor(const nsACString &aPersistentDescriptor) +{ + return InitWithNativePath(aPersistentDescriptor); +} + +#ifndef OPEN_DEFAULT +#define OPEN_DEFAULT 0 +#define OPEN_CONTENTS 1 +#endif + +NS_IMETHODIMP +nsLocalFile::Reveal() +{ + bool isDirectory = false; + nsAutoCString path; + + IsDirectory(&isDirectory); + if (isDirectory) + { + GetNativePath(path); + } + else + { + nsCOMPtr<nsIFile> parent; + GetParent(getter_AddRefs(parent)); + if (parent) + parent->GetNativePath(path); + } + + HOBJECT hobject = WinQueryObject(path.get()); + WinSetFocus(HWND_DESKTOP, HWND_DESKTOP); + WinOpenObject(hobject, OPEN_DEFAULT, TRUE); + + // we don't care if it succeeded or failed. + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::Launch() +{ + HOBJECT hobject = WinQueryObject(mWorkingPath.get()); + WinSetFocus(HWND_DESKTOP, HWND_DESKTOP); + WinOpenObject(hobject, OPEN_DEFAULT, TRUE); + + // we don't care if it succeeded or failed. + return NS_OK; +} + +nsresult +NS_NewNativeLocalFile(const nsACString &path, bool followLinks, nsIFile* *result) +{ + nsLocalFile* file = new nsLocalFile(); + if (file == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(file); + + if (!path.IsEmpty()) { + nsresult rv = file->InitWithNativePath(path); + if (NS_FAILED(rv)) { + NS_RELEASE(file); + return rv; + } + } + + *result = file; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// UCS2 interface +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsLocalFile::InitWithPath(const nsAString &filePath) +{ + if (filePath.IsEmpty()) + return InitWithNativePath(EmptyCString()); + + nsAutoCString tmp; + nsresult rv = NS_CopyUnicodeToNative(filePath, tmp); + if (NS_SUCCEEDED(rv)) + return InitWithNativePath(tmp); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::Append(const nsAString &node) +{ + if (node.IsEmpty()) + return NS_OK; + + nsAutoCString tmp; + nsresult rv = NS_CopyUnicodeToNative(node, tmp); + if (NS_SUCCEEDED(rv)) + return AppendNative(tmp); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::AppendRelativePath(const nsAString &node) +{ + if (node.IsEmpty()) + return NS_OK; + + nsAutoCString tmp; + nsresult rv = NS_CopyUnicodeToNative(node, tmp); + if (NS_SUCCEEDED(rv)) + return AppendRelativeNativePath(tmp); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetLeafName(nsAString &aLeafName) +{ + nsAutoCString tmp; + nsresult rv = GetNativeLeafName(tmp); + if (NS_SUCCEEDED(rv)) + rv = NS_CopyNativeToUnicode(tmp, aLeafName); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::SetLeafName(const nsAString &aLeafName) +{ + if (aLeafName.IsEmpty()) + return SetNativeLeafName(EmptyCString()); + + nsAutoCString tmp; + nsresult rv = NS_CopyUnicodeToNative(aLeafName, tmp); + if (NS_SUCCEEDED(rv)) + return SetNativeLeafName(tmp); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetPath(nsAString &_retval) +{ + return NS_CopyNativeToUnicode(mWorkingPath, _retval); +} + +NS_IMETHODIMP +nsLocalFile::CopyTo(nsIFile *newParentDir, const nsAString &newName) +{ + if (newName.IsEmpty()) + return CopyToNative(newParentDir, EmptyCString()); + + nsAutoCString tmp; + nsresult rv = NS_CopyUnicodeToNative(newName, tmp); + if (NS_SUCCEEDED(rv)) + return CopyToNative(newParentDir, tmp); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinks(nsIFile *newParentDir, const nsAString &newName) +{ + if (newName.IsEmpty()) + return CopyToFollowingLinksNative(newParentDir, EmptyCString()); + + nsAutoCString tmp; + nsresult rv = NS_CopyUnicodeToNative(newName, tmp); + if (NS_SUCCEEDED(rv)) + return CopyToFollowingLinksNative(newParentDir, tmp); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::MoveTo(nsIFile *newParentDir, const nsAString &newName) +{ + if (newName.IsEmpty()) + return MoveToNative(newParentDir, EmptyCString()); + + nsAutoCString tmp; + nsresult rv = NS_CopyUnicodeToNative(newName, tmp); + if (NS_SUCCEEDED(rv)) + return MoveToNative(newParentDir, tmp); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetTarget(nsAString &_retval) +{ + nsAutoCString tmp; + nsresult rv = GetNativeTarget(tmp); + if (NS_SUCCEEDED(rv)) + rv = NS_CopyNativeToUnicode(tmp, _retval); + + return rv; +} + +// nsIHashable + +NS_IMETHODIMP +nsLocalFile::Equals(nsIHashable* aOther, bool *aResult) +{ + nsCOMPtr<nsIFile> otherfile(do_QueryInterface(aOther)); + if (!otherfile) { + *aResult = false; + return NS_OK; + } + + return Equals(otherfile, aResult); +} + +NS_IMETHODIMP +nsLocalFile::GetHashCode(uint32_t *aResult) +{ + *aResult = HashString(mWorkingPath); + return NS_OK; +} + +nsresult +NS_NewLocalFile(const nsAString &path, bool followLinks, nsIFile* *result) +{ + nsAutoCString buf; + nsresult rv = NS_CopyUnicodeToNative(path, buf); + if (NS_FAILED(rv)) { + *result = nullptr; + return rv; + } + return NS_NewNativeLocalFile(buf, followLinks, result); +} + +//----------------------------------------------------------------------------- +// nsLocalFile <static members> +//----------------------------------------------------------------------------- + +void +nsLocalFile::GlobalInit() +{ +} + +void +nsLocalFile::GlobalShutdown() +{ +} + +//----------------------------------------------------------------------------- + diff --git a/xpcom/io/nsLocalFileOS2.h b/xpcom/io/nsLocalFileOS2.h new file mode 100644 index 000000000..d23fb8d9a --- /dev/null +++ b/xpcom/io/nsLocalFileOS2.h @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + * + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 05/26/2000 IBM Corp. Make more like Windows. + */ + +#ifndef _nsLocalFileOS2_H_ +#define _nsLocalFileOS2_H_ + +#include "nscore.h" +#include "nsError.h" +#include "nsString.h" +#include "nsCRT.h" +#include "nsIFile.h" +#include "nsIFactory.h" +#include "nsILocalFileOS2.h" +#include "nsIHashable.h" +#include "nsIClassInfoImpl.h" + +#define INCL_DOS +#define INCL_DOSERRORS +#define INCL_WINCOUNTRY +#define INCL_WINWORKPLACE + +#include <os2.h> + +class TypeEaEnumerator; + +class nsLocalFile : public nsILocalFileOS2, + public nsIHashable +{ +public: + NS_DEFINE_STATIC_CID_ACCESSOR(NS_LOCAL_FILE_CID) + + nsLocalFile(); + + static nsresult nsLocalFileConstructor(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr); + + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsIFile interface + NS_DECL_NSIFILE + + // nsILocalFile interface + NS_DECL_NSILOCALFILE + + // nsILocalFileOS2 interface + NS_DECL_NSILOCALFILEOS2 + + // nsIHashable interface + NS_DECL_NSIHASHABLE + +public: + static void GlobalInit(); + static void GlobalShutdown(); + +private: + nsLocalFile(const nsLocalFile& other); + ~nsLocalFile() {} + + // cached information can only be used when this is false + bool mDirty; + + // this string will always be in native format! + nsCString mWorkingPath; + + PRFileInfo64 mFileInfo64; + + void MakeDirty() { mDirty = true; } + + nsresult Stat(); + + nsresult CopyMove(nsIFile *newParentDir, const nsACString &newName, bool move); + nsresult CopySingleFile(nsIFile *source, nsIFile* dest, const nsACString &newName, bool move); + + nsresult SetModDate(int64_t aLastModifiedTime); + nsresult AppendNativeInternal(const nsAFlatCString &node, bool multipleComponents); + + nsresult GetEA(const char *eaName, PFEA2LIST pfea2list); + friend class TypeEaEnumerator; +}; + +#endif diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp new file mode 100644 index 000000000..a38f7942f --- /dev/null +++ b/xpcom/io/nsLocalFileUnix.cpp @@ -0,0 +1,2478 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/** + * Implementation of nsIFile for "unixy" systems. + */ + +#include "mozilla/Util.h" +#include "mozilla/Attributes.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <utime.h> +#include <dirent.h> +#include <ctype.h> +#include <locale.h> +#if defined(VMS) + #include <fabdef.h> +#endif + +#if defined(HAVE_SYS_QUOTA_H) && defined(HAVE_LINUX_QUOTA_H) +#define USE_LINUX_QUOTACTL +#include <sys/quota.h> +#endif + +#if (MOZ_PLATFORM_MAEMO == 6) +#include <QUrl> +#include <QString> +#if (MOZ_ENABLE_CONTENTACTION) +#include <contentaction/contentaction.h> +#endif +#endif + +#include "xpcom-private.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCRT.h" +#include "nsCOMPtr.h" +#include "nsMemory.h" +#include "nsIFile.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsLocalFile.h" +#include "nsIComponentManager.h" +#include "nsXPIDLString.h" +#include "prproces.h" +#include "nsIDirectoryEnumerator.h" +#include "nsISimpleEnumerator.h" +#include "private/pprio.h" + +#ifdef MOZ_WIDGET_GTK +#include "nsIGIOService.h" +#include "nsIGnomeVFSService.h" +#endif + +#ifdef MOZ_WIDGET_COCOA +#include <Carbon/Carbon.h> +#include "CocoaFileUtils.h" +#include "prmem.h" +#include "plbase64.h" + +static nsresult MacErrorMapper(OSErr inErr); +#endif + +#if (MOZ_PLATFORM_MAEMO == 5) +#include <glib.h> +#include <hildon-uri.h> +#include <hildon-mime.h> +#include <libosso.h> +#endif + +#ifdef MOZ_WIDGET_ANDROID +#include "AndroidBridge.h" +#include "nsIMIMEService.h" +#include <linux/magic.h> +#endif + +#include "nsNativeCharsetUtils.h" +#include "nsTraceRefcntImpl.h" +#include "nsHashKeys.h" + +using namespace mozilla; + +#define ENSURE_STAT_CACHE() \ + PR_BEGIN_MACRO \ + if (!FillStatCache()) \ + return NSRESULT_FOR_ERRNO(); \ + PR_END_MACRO + +#define CHECK_mPath() \ + PR_BEGIN_MACRO \ + if (mPath.IsEmpty()) \ + return NS_ERROR_NOT_INITIALIZED; \ + PR_END_MACRO + +/* directory enumerator */ +class +nsDirEnumeratorUnix MOZ_FINAL : public nsISimpleEnumerator, + public nsIDirectoryEnumerator +{ + public: + nsDirEnumeratorUnix(); + + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR + + // nsIDirectoryEnumerator interface + NS_DECL_NSIDIRECTORYENUMERATOR + + NS_IMETHOD Init(nsLocalFile *parent, bool ignored); + + private: + ~nsDirEnumeratorUnix(); + + protected: + NS_IMETHOD GetNextEntry(); + + DIR *mDir; + struct dirent *mEntry; + nsCString mParentPath; +}; + +nsDirEnumeratorUnix::nsDirEnumeratorUnix() : + mDir(nullptr), + mEntry(nullptr) +{ +} + +nsDirEnumeratorUnix::~nsDirEnumeratorUnix() +{ + Close(); +} + +NS_IMPL_ISUPPORTS2(nsDirEnumeratorUnix, nsISimpleEnumerator, nsIDirectoryEnumerator) + +NS_IMETHODIMP +nsDirEnumeratorUnix::Init(nsLocalFile *parent, bool resolveSymlinks /*ignored*/) +{ + nsAutoCString dirPath; + if (NS_FAILED(parent->GetNativePath(dirPath)) || + dirPath.IsEmpty()) { + return NS_ERROR_FILE_INVALID_PATH; + } + + if (NS_FAILED(parent->GetNativePath(mParentPath))) + return NS_ERROR_FAILURE; + + mDir = opendir(dirPath.get()); + if (!mDir) + return NSRESULT_FOR_ERRNO(); + return GetNextEntry(); +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::HasMoreElements(bool *result) +{ + *result = mDir && mEntry; + if (!*result) + Close(); + return NS_OK; +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::GetNext(nsISupports **_retval) +{ + nsCOMPtr<nsIFile> file; + nsresult rv = GetNextFile(getter_AddRefs(file)); + if (NS_FAILED(rv)) + return rv; + NS_IF_ADDREF(*_retval = file); + return NS_OK; +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::GetNextEntry() +{ + do { + errno = 0; + mEntry = readdir(mDir); + + // end of dir or error + if (!mEntry) + return NSRESULT_FOR_ERRNO(); + + // keep going past "." and ".." + } while (mEntry->d_name[0] == '.' && + (mEntry->d_name[1] == '\0' || // .\0 + (mEntry->d_name[1] == '.' && + mEntry->d_name[2] == '\0'))); // ..\0 + return NS_OK; +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::GetNextFile(nsIFile **_retval) +{ + nsresult rv; + if (!mDir || !mEntry) { + *_retval = nullptr; + return NS_OK; + } + + nsCOMPtr<nsIFile> file = new nsLocalFile(); + if (!file) + return NS_ERROR_OUT_OF_MEMORY; + + if (NS_FAILED(rv = file->InitWithNativePath(mParentPath)) || + NS_FAILED(rv = file->AppendNative(nsDependentCString(mEntry->d_name)))) + return rv; + + *_retval = file; + NS_ADDREF(*_retval); + return GetNextEntry(); +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::Close() +{ + if (mDir) { + closedir(mDir); + mDir = nullptr; + } + return NS_OK; +} + +nsLocalFile::nsLocalFile() +{ +} + +nsLocalFile::nsLocalFile(const nsLocalFile& other) + : mPath(other.mPath) +{ +} + +#ifdef MOZ_WIDGET_COCOA +NS_IMPL_THREADSAFE_ISUPPORTS4(nsLocalFile, + nsILocalFileMac, + nsILocalFile, + nsIFile, + nsIHashable) +#else +NS_IMPL_THREADSAFE_ISUPPORTS3(nsLocalFile, + nsILocalFile, + nsIFile, + nsIHashable) +#endif + +nsresult +nsLocalFile::nsLocalFileConstructor(nsISupports *outer, + const nsIID &aIID, + void **aInstancePtr) +{ + NS_ENSURE_ARG_POINTER(aInstancePtr); + NS_ENSURE_NO_AGGREGATION(outer); + + *aInstancePtr = nullptr; + + nsCOMPtr<nsIFile> inst = new nsLocalFile(); + if (!inst) + return NS_ERROR_OUT_OF_MEMORY; + return inst->QueryInterface(aIID, aInstancePtr); +} + +bool +nsLocalFile::FillStatCache() { + if (STAT(mPath.get(), &mCachedStat) == -1) { + // try lstat it may be a symlink + if (LSTAT(mPath.get(), &mCachedStat) == -1) { + return false; + } + } + return true; +} + +NS_IMETHODIMP +nsLocalFile::Clone(nsIFile **file) +{ + // Just copy-construct ourselves + *file = new nsLocalFile(*this); + if (!*file) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*file); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::InitWithNativePath(const nsACString &filePath) +{ + if (filePath.Equals("~") || Substring(filePath, 0, 2).EqualsLiteral("~/")) { + nsCOMPtr<nsIFile> homeDir; + nsAutoCString homePath; + if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_HOME_DIR, + getter_AddRefs(homeDir))) || + NS_FAILED(homeDir->GetNativePath(homePath))) { + return NS_ERROR_FAILURE; + } + + mPath = homePath; + if (filePath.Length() > 2) + mPath.Append(Substring(filePath, 1, filePath.Length() - 1)); + } else { + if (filePath.IsEmpty() || filePath.First() != '/') + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + mPath = filePath; + } + + // trim off trailing slashes + ssize_t len = mPath.Length(); + while ((len > 1) && (mPath[len - 1] == '/')) + --len; + mPath.SetLength(len); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::CreateAllAncestors(uint32_t permissions) +{ + // <jband> I promise to play nice + char *buffer = mPath.BeginWriting(), + *slashp = buffer; + +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: before: %s\n", buffer); +#endif + + while ((slashp = strchr(slashp + 1, '/'))) { + /* + * Sequences of '/' are equivalent to a single '/'. + */ + if (slashp[1] == '/') + continue; + + /* + * If the path has a trailing slash, don't make the last component, + * because we'll get EEXIST in Create when we try to build the final + * component again, and it's easier to condition the logic here than + * there. + */ + if (slashp[1] == '\0') + break; + + /* Temporarily NUL-terminate here */ + *slashp = '\0'; +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: mkdir(\"%s\")\n", buffer); +#endif + int mkdir_result = mkdir(buffer, permissions); + int mkdir_errno = errno; + if (mkdir_result == -1) { + /* + * Always set |errno| to EEXIST if the dir already exists + * (we have to do this here since the errno value is not consistent + * in all cases - various reasons like different platform, + * automounter-controlled dir, etc. can affect it (see bug 125489 + * for details)). + */ + if (access(buffer, F_OK) == 0) { + mkdir_errno = EEXIST; + } + } + + /* Put the / back before we (maybe) return */ + *slashp = '/'; + + /* + * We could get EEXIST for an existing file -- not directory -- + * with the name of one of our ancestors, but that's OK: we'll get + * ENOTDIR when we try to make the next component in the path, + * either here on back in Create, and error out appropriately. + */ + if (mkdir_result == -1 && mkdir_errno != EEXIST) + return nsresultForErrno(mkdir_errno); + } + +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: after: %s\n", buffer); +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::OpenNSPRFileDesc(int32_t flags, int32_t mode, PRFileDesc **_retval) +{ + *_retval = PR_Open(mPath.get(), flags, mode); + if (! *_retval) + return NS_ErrorAccordingToNSPR(); + + if (flags & DELETE_ON_CLOSE) { + PR_Delete(mPath.get()); + } + +#if defined(LINUX) && !defined(ANDROID) + if (flags & OS_READAHEAD) { + posix_fadvise(PR_FileDesc2NativeHandle(*_retval), 0, 0, + POSIX_FADV_SEQUENTIAL); + } +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::OpenANSIFileDesc(const char *mode, FILE **_retval) +{ + *_retval = fopen(mPath.get(), mode); + if (! *_retval) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +static int +do_create(const char *path, int flags, mode_t mode, PRFileDesc **_retval) +{ + *_retval = PR_Open(path, flags, mode); + return *_retval ? 0 : -1; +} + +static int +do_mkdir(const char *path, int flags, mode_t mode, PRFileDesc **_retval) +{ + *_retval = nullptr; + return mkdir(path, mode); +} + +nsresult +nsLocalFile::CreateAndKeepOpen(uint32_t type, int flags, + uint32_t permissions, PRFileDesc **_retval) +{ + if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE) + return NS_ERROR_FILE_UNKNOWN_TYPE; + + int result; + int (*createFunc)(const char *, int, mode_t, PRFileDesc **) = + (type == NORMAL_FILE_TYPE) ? do_create : do_mkdir; + + result = createFunc(mPath.get(), flags, permissions, _retval); + if (result == -1 && errno == ENOENT) { + /* + * If we failed because of missing ancestor components, try to create + * them and then retry the original creation. + * + * Ancestor directories get the same permissions as the file we're + * creating, with the X bit set for each of (user,group,other) with + * an R bit in the original permissions. If you want to do anything + * fancy like setgid or sticky bits, do it by hand. + */ + int dirperm = permissions; + if (permissions & S_IRUSR) + dirperm |= S_IXUSR; + if (permissions & S_IRGRP) + dirperm |= S_IXGRP; + if (permissions & S_IROTH) + dirperm |= S_IXOTH; + +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: perm = %o, dirperm = %o\n", permissions, + dirperm); +#endif + + if (NS_FAILED(CreateAllAncestors(dirperm))) + return NS_ERROR_FAILURE; + +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: Create(\"%s\") again\n", mPath.get()); +#endif + result = createFunc(mPath.get(), flags, permissions, _retval); + } + return NSRESULT_FOR_RETURN(result); +} + +NS_IMETHODIMP +nsLocalFile::Create(uint32_t type, uint32_t permissions) +{ + PRFileDesc *junk = nullptr; + nsresult rv = CreateAndKeepOpen(type, + PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE | + PR_EXCL, + permissions, + &junk); + if (junk) + PR_Close(junk); + return rv; +} + +NS_IMETHODIMP +nsLocalFile::AppendNative(const nsACString &fragment) +{ + if (fragment.IsEmpty()) + return NS_OK; + + // only one component of path can be appended + nsACString::const_iterator begin, end; + if (FindCharInReadable('/', fragment.BeginReading(begin), + fragment.EndReading(end))) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + return AppendRelativeNativePath(fragment); +} + +NS_IMETHODIMP +nsLocalFile::AppendRelativeNativePath(const nsACString &fragment) +{ + if (fragment.IsEmpty()) + return NS_OK; + + // No leading '/' + if (fragment.First() == '/') + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + if (mPath.EqualsLiteral("/")) + mPath.Append(fragment); + else + mPath.Append(NS_LITERAL_CSTRING("/") + fragment); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Normalize() +{ + char resolved_path[PATH_MAX] = ""; + char *resolved_path_ptr = nullptr; + + resolved_path_ptr = realpath(mPath.get(), resolved_path); + + // if there is an error, the return is null. + if (!resolved_path_ptr) + return NSRESULT_FOR_ERRNO(); + + mPath = resolved_path; + return NS_OK; +} + +void +nsLocalFile::LocateNativeLeafName(nsACString::const_iterator &begin, + nsACString::const_iterator &end) +{ + // XXX perhaps we should cache this?? + + mPath.BeginReading(begin); + mPath.EndReading(end); + + nsACString::const_iterator it = end; + nsACString::const_iterator stop = begin; + --stop; + while (--it != stop) { + if (*it == '/') { + begin = ++it; + return; + } + } + // else, the entire path is the leaf name (which means this + // isn't an absolute path... unexpected??) +} + +NS_IMETHODIMP +nsLocalFile::GetNativeLeafName(nsACString &aLeafName) +{ + nsACString::const_iterator begin, end; + LocateNativeLeafName(begin, end); + aLeafName = Substring(begin, end); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetNativeLeafName(const nsACString &aLeafName) +{ + nsACString::const_iterator begin, end; + LocateNativeLeafName(begin, end); + mPath.Replace(begin.get() - mPath.get(), Distance(begin, end), aLeafName); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetNativePath(nsACString &_retval) +{ + _retval = mPath; + return NS_OK; +} + +nsresult +nsLocalFile::GetNativeTargetPathName(nsIFile *newParent, + const nsACString &newName, + nsACString &_retval) +{ + nsresult rv; + nsCOMPtr<nsIFile> oldParent; + + if (!newParent) { + if (NS_FAILED(rv = GetParent(getter_AddRefs(oldParent)))) + return rv; + newParent = oldParent.get(); + } else { + // check to see if our target directory exists + bool targetExists; + if (NS_FAILED(rv = newParent->Exists(&targetExists))) + return rv; + + if (!targetExists) { + // XXX create the new directory with some permissions + rv = newParent->Create(DIRECTORY_TYPE, 0755); + if (NS_FAILED(rv)) + return rv; + } else { + // make sure that the target is actually a directory + bool targetIsDirectory; + if (NS_FAILED(rv = newParent->IsDirectory(&targetIsDirectory))) + return rv; + if (!targetIsDirectory) + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + } + + nsACString::const_iterator nameBegin, nameEnd; + if (!newName.IsEmpty()) { + newName.BeginReading(nameBegin); + newName.EndReading(nameEnd); + } + else + LocateNativeLeafName(nameBegin, nameEnd); + + nsAutoCString dirName; + if (NS_FAILED(rv = newParent->GetNativePath(dirName))) + return rv; + + _retval = dirName + + NS_LITERAL_CSTRING("/") + + Substring(nameBegin, nameEnd); + return NS_OK; +} + +nsresult +nsLocalFile::CopyDirectoryTo(nsIFile *newParent) +{ + nsresult rv; + /* + * dirCheck is used for various boolean test results such as from Equals, + * Exists, isDir, etc. + */ + bool dirCheck, isSymlink; + uint32_t oldPerms; + + if (NS_FAILED(rv = IsDirectory(&dirCheck))) + return rv; + if (!dirCheck) + return CopyToNative(newParent, EmptyCString()); + + if (NS_FAILED(rv = Equals(newParent, &dirCheck))) + return rv; + if (dirCheck) { + // can't copy dir to itself + return NS_ERROR_INVALID_ARG; + } + + if (NS_FAILED(rv = newParent->Exists(&dirCheck))) + return rv; + // get the dirs old permissions + if (NS_FAILED(rv = GetPermissions(&oldPerms))) + return rv; + if (!dirCheck) { + if (NS_FAILED(rv = newParent->Create(DIRECTORY_TYPE, oldPerms))) + return rv; + } else { // dir exists lets try to use leaf + nsAutoCString leafName; + if (NS_FAILED(rv = GetNativeLeafName(leafName))) + return rv; + if (NS_FAILED(rv = newParent->AppendNative(leafName))) + return rv; + if (NS_FAILED(rv = newParent->Exists(&dirCheck))) + return rv; + if (dirCheck) + return NS_ERROR_FILE_ALREADY_EXISTS; // dest exists + if (NS_FAILED(rv = newParent->Create(DIRECTORY_TYPE, oldPerms))) + return rv; + } + + nsCOMPtr<nsISimpleEnumerator> dirIterator; + if (NS_FAILED(rv = GetDirectoryEntries(getter_AddRefs(dirIterator)))) + return rv; + + bool hasMore = false; + while (dirIterator->HasMoreElements(&hasMore), hasMore) { + nsCOMPtr<nsIFile> entry; + rv = dirIterator->GetNext((nsISupports**)getter_AddRefs(entry)); + if (NS_FAILED(rv)) + continue; + if (NS_FAILED(rv = entry->IsSymlink(&isSymlink))) + return rv; + if (NS_FAILED(rv = entry->IsDirectory(&dirCheck))) + return rv; + if (dirCheck && !isSymlink) { + nsCOMPtr<nsIFile> destClone; + rv = newParent->Clone(getter_AddRefs(destClone)); + if (NS_SUCCEEDED(rv)) { + if (NS_FAILED(rv = entry->CopyToNative(destClone, EmptyCString()))) { +#ifdef DEBUG + nsresult rv2; + nsAutoCString pathName; + if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) + return rv2; + printf("Operation not supported: %s\n", pathName.get()); +#endif + if (rv == NS_ERROR_OUT_OF_MEMORY) + return rv; + continue; + } + } + } else { + if (NS_FAILED(rv = entry->CopyToNative(newParent, EmptyCString()))) { +#ifdef DEBUG + nsresult rv2; + nsAutoCString pathName; + if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) + return rv2; + printf("Operation not supported: %s\n", pathName.get()); +#endif + if (rv == NS_ERROR_OUT_OF_MEMORY) + return rv; + continue; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::CopyToNative(nsIFile *newParent, const nsACString &newName) +{ + nsresult rv; + // check to make sure that this has been initialized properly + CHECK_mPath(); + + // we copy the parent here so 'newParent' remains immutable + nsCOMPtr <nsIFile> workParent; + if (newParent) { + if (NS_FAILED(rv = newParent->Clone(getter_AddRefs(workParent)))) + return rv; + } else { + if (NS_FAILED(rv = GetParent(getter_AddRefs(workParent)))) + return rv; + } + + // check to see if we are a directory or if we are a file + bool isDirectory; + if (NS_FAILED(rv = IsDirectory(&isDirectory))) + return rv; + + nsAutoCString newPathName; + if (isDirectory) { + if (!newName.IsEmpty()) { + if (NS_FAILED(rv = workParent->AppendNative(newName))) + return rv; + } else { + if (NS_FAILED(rv = GetNativeLeafName(newPathName))) + return rv; + if (NS_FAILED(rv = workParent->AppendNative(newPathName))) + return rv; + } + if (NS_FAILED(rv = CopyDirectoryTo(workParent))) + return rv; + } else { + rv = GetNativeTargetPathName(workParent, newName, newPathName); + if (NS_FAILED(rv)) + return rv; + +#ifdef DEBUG_blizzard + printf("nsLocalFile::CopyTo() %s -> %s\n", mPath.get(), newPathName.get()); +#endif + + // actually create the file. + nsLocalFile *newFile = new nsLocalFile(); + if (!newFile) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr<nsIFile> fileRef(newFile); // release on exit + + rv = newFile->InitWithNativePath(newPathName); + if (NS_FAILED(rv)) + return rv; + + // get the old permissions + uint32_t myPerms; + GetPermissions(&myPerms); + + // Create the new file with the old file's permissions, even if write + // permission is missing. We can't create with write permission and + // then change back to myPerm on all filesystems (FAT on Linux, e.g.). + // But we can write to a read-only file on all Unix filesystems if we + // open it successfully for writing. + + PRFileDesc *newFD; + rv = newFile->CreateAndKeepOpen(NORMAL_FILE_TYPE, + PR_WRONLY|PR_CREATE_FILE|PR_TRUNCATE, + myPerms, + &newFD); + if (NS_FAILED(rv)) + return rv; + + // open the old file, too + bool specialFile; + if (NS_FAILED(rv = IsSpecial(&specialFile))) { + PR_Close(newFD); + return rv; + } + if (specialFile) { +#ifdef DEBUG + printf("Operation not supported: %s\n", mPath.get()); +#endif + // make sure to clean up properly + PR_Close(newFD); + return NS_OK; + } + + PRFileDesc *oldFD; + rv = OpenNSPRFileDesc(PR_RDONLY, myPerms, &oldFD); + if (NS_FAILED(rv)) { + // make sure to clean up properly + PR_Close(newFD); + return rv; + } + +#ifdef DEBUG_blizzard + int32_t totalRead = 0; + int32_t totalWritten = 0; +#endif + char buf[BUFSIZ]; + int32_t bytesRead; + + while ((bytesRead = PR_Read(oldFD, buf, BUFSIZ)) > 0) { +#ifdef DEBUG_blizzard + totalRead += bytesRead; +#endif + + // PR_Write promises never to do a short write + int32_t bytesWritten = PR_Write(newFD, buf, bytesRead); + if (bytesWritten < 0) { + bytesRead = -1; + break; + } + NS_ASSERTION(bytesWritten == bytesRead, "short PR_Write?"); + +#ifdef DEBUG_blizzard + totalWritten += bytesWritten; +#endif + } + +#ifdef DEBUG_blizzard + printf("read %d bytes, wrote %d bytes\n", + totalRead, totalWritten); +#endif + + // close the files + PR_Close(newFD); + PR_Close(oldFD); + + // check for read (or write) error after cleaning up + if (bytesRead < 0) + return NS_ERROR_OUT_OF_MEMORY; + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinksNative(nsIFile *newParent, const nsACString &newName) +{ + return CopyToNative(newParent, newName); +} + +NS_IMETHODIMP +nsLocalFile::MoveToNative(nsIFile *newParent, const nsACString &newName) +{ + nsresult rv; + + // check to make sure that this has been initialized properly + CHECK_mPath(); + + // check to make sure that we have a new parent + nsAutoCString newPathName; + rv = GetNativeTargetPathName(newParent, newName, newPathName); + if (NS_FAILED(rv)) + return rv; + + // try for atomic rename, falling back to copy/delete + if (rename(mPath.get(), newPathName.get()) < 0) { +#ifdef VMS + if (errno == EXDEV || errno == ENXIO) { +#else + if (errno == EXDEV) { +#endif + rv = CopyToNative(newParent, newName); + if (NS_SUCCEEDED(rv)) + rv = Remove(true); + } else { + rv = NSRESULT_FOR_ERRNO(); + } + } + + if (NS_SUCCEEDED(rv)) { + // Adjust this + mPath = newPathName; + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::Remove(bool recursive) +{ + CHECK_mPath(); + ENSURE_STAT_CACHE(); + + bool isSymLink; + + nsresult rv = IsSymlink(&isSymLink); + if (NS_FAILED(rv)) + return rv; + + if (isSymLink || !S_ISDIR(mCachedStat.st_mode)) + return NSRESULT_FOR_RETURN(unlink(mPath.get())); + + if (recursive) { + nsDirEnumeratorUnix *dir = new nsDirEnumeratorUnix(); + if (!dir) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr<nsISimpleEnumerator> dirRef(dir); // release on exit + + rv = dir->Init(this, false); + if (NS_FAILED(rv)) + return rv; + + bool more; + while (dir->HasMoreElements(&more), more) { + nsCOMPtr<nsISupports> item; + rv = dir->GetNext(getter_AddRefs(item)); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIFile> file = do_QueryInterface(item, &rv); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + rv = file->Remove(recursive); + +#ifdef ANDROID + // See bug 580434 - Bionic gives us just deleted files + if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) + continue; +#endif + if (NS_FAILED(rv)) + return rv; + } + } + + return NSRESULT_FOR_RETURN(rmdir(mPath.get())); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTime(PRTime *aLastModTime) +{ + CHECK_mPath(); + NS_ENSURE_ARG(aLastModTime); + + PRFileInfo64 info; + if (PR_GetFileInfo64(mPath.get(), &info) != PR_SUCCESS) + return NSRESULT_FOR_ERRNO(); + PRTime modTime = info.modifyTime; + if (modTime == 0) + *aLastModTime = 0; + else + *aLastModTime = modTime / PR_USEC_PER_MSEC; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTime(PRTime aLastModTime) +{ + CHECK_mPath(); + + int result; + if (aLastModTime != 0) { + ENSURE_STAT_CACHE(); + struct utimbuf ut; + ut.actime = mCachedStat.st_atime; + + // convert milliseconds to seconds since the unix epoch + ut.modtime = (time_t)(aLastModTime / PR_MSEC_PER_SEC); + result = utime(mPath.get(), &ut); + } else { + result = utime(mPath.get(), nullptr); + } + return NSRESULT_FOR_RETURN(result); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTimeOfLink(PRTime *aLastModTimeOfLink) +{ + CHECK_mPath(); + NS_ENSURE_ARG(aLastModTimeOfLink); + + struct STAT sbuf; + if (LSTAT(mPath.get(), &sbuf) == -1) + return NSRESULT_FOR_ERRNO(); + *aLastModTimeOfLink = PRTime(sbuf.st_mtime) * PR_MSEC_PER_SEC; + + return NS_OK; +} + +/* + * utime(2) may or may not dereference symlinks, joy. + */ +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink) +{ + return SetLastModifiedTime(aLastModTimeOfLink); +} + +/* + * Only send back permissions bits: maybe we want to send back the whole + * mode_t to permit checks against other file types? + */ + +#define NORMALIZE_PERMS(mode) ((mode)& (S_IRWXU | S_IRWXG | S_IRWXO)) + +NS_IMETHODIMP +nsLocalFile::GetPermissions(uint32_t *aPermissions) +{ + NS_ENSURE_ARG(aPermissions); + ENSURE_STAT_CACHE(); + *aPermissions = NORMALIZE_PERMS(mCachedStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissionsOfLink(uint32_t *aPermissionsOfLink) +{ + CHECK_mPath(); + NS_ENSURE_ARG(aPermissionsOfLink); + + struct STAT sbuf; + if (LSTAT(mPath.get(), &sbuf) == -1) + return NSRESULT_FOR_ERRNO(); + *aPermissionsOfLink = NORMALIZE_PERMS(sbuf.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetPermissions(uint32_t aPermissions) +{ + CHECK_mPath(); + + /* + * Race condition here: we should use fchmod instead, there's no way to + * guarantee the name still refers to the same file. + */ + if (chmod(mPath.get(), aPermissions) >= 0) + return NS_OK; +#if defined(ANDROID) && defined(STATFS) + // For the time being, this is restricted for use by Android, but we + // will figure out what to do for all platforms in bug 638503 + struct STATFS sfs; + if (STATFS(mPath.get(), &sfs) < 0) + return NSRESULT_FOR_ERRNO(); + + // if this is a FAT file system we can't set file permissions + if (sfs.f_type == MSDOS_SUPER_MAGIC ) + return NS_OK; +#endif + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions) +{ + // There isn't a consistent mechanism for doing this on UNIX platforms. We + // might want to carefully implement this in the future though. + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsLocalFile::GetFileSize(int64_t *aFileSize) +{ + NS_ENSURE_ARG_POINTER(aFileSize); + *aFileSize = 0; + ENSURE_STAT_CACHE(); + +#if defined(VMS) + /* Only two record formats can report correct file content size */ + if ((mCachedStat.st_fab_rfm != FAB$C_STMLF) && + (mCachedStat.st_fab_rfm != FAB$C_STMCR)) { + return NS_ERROR_FAILURE; + } +#endif + + if (!S_ISDIR(mCachedStat.st_mode)) { + *aFileSize = (int64_t)mCachedStat.st_size; + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetFileSize(int64_t aFileSize) +{ + CHECK_mPath(); + +#if defined(ANDROID) + /* no truncate on bionic */ + int fd = open(mPath.get(), O_WRONLY); + if (fd == -1) + return NSRESULT_FOR_ERRNO(); + + int ret = ftruncate(fd, (off_t)aFileSize); + close(fd); + + if (ret == -1) + return NSRESULT_FOR_ERRNO(); +#elif defined(HAVE_TRUNCATE64) + if (truncate64(mPath.get(), (off64_t)aFileSize) == -1) + return NSRESULT_FOR_ERRNO(); +#else + off_t size = (off_t)aFileSize; + if (truncate(mPath.get(), size) == -1) + return NSRESULT_FOR_ERRNO(); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetFileSizeOfLink(int64_t *aFileSize) +{ + CHECK_mPath(); + NS_ENSURE_ARG(aFileSize); + + struct STAT sbuf; + if (LSTAT(mPath.get(), &sbuf) == -1) + return NSRESULT_FOR_ERRNO(); + + *aFileSize = (int64_t)sbuf.st_size; + return NS_OK; +} + +#if defined(USE_LINUX_QUOTACTL) +/* + * Searches /proc/self/mountinfo for given device (Major:Minor), + * returns exported name from /dev + * + * Fails when /proc/self/mountinfo or diven device don't exist. + */ +static bool +GetDeviceName(int deviceMajor, int deviceMinor, nsACString &deviceName) +{ + bool ret = false; + + const int kMountInfoLineLength = 200; + const int kMountInfoDevPosition = 6; + + char mountinfo_line[kMountInfoLineLength]; + char device_num[kMountInfoLineLength]; + + snprintf(device_num,kMountInfoLineLength,"%d:%d", deviceMajor, deviceMinor); + + FILE *f = fopen("/proc/self/mountinfo","rt"); + if(!f) + return ret; + + // Expects /proc/self/mountinfo in format: + // 'ID ID major:minor root mountpoint flags - type devicename flags' + while(fgets(mountinfo_line,kMountInfoLineLength,f)) { + char *p_dev = strstr(mountinfo_line,device_num); + + int i; + for(i = 0; i < kMountInfoDevPosition && p_dev != NULL; i++) { + p_dev = strchr(p_dev,' '); + if(p_dev) + p_dev++; + } + + if(p_dev) { + char *p_dev_end = strchr(p_dev,' '); + if(p_dev_end) { + *p_dev_end = '\0'; + deviceName.Assign(p_dev); + ret = true; + break; + } + } + } + + fclose(f); + return ret; +} +#endif + +NS_IMETHODIMP +nsLocalFile::GetDiskSpaceAvailable(int64_t *aDiskSpaceAvailable) +{ + NS_ENSURE_ARG_POINTER(aDiskSpaceAvailable); + + // These systems have the operations necessary to check disk space. + +#ifdef STATFS + + // check to make sure that mPath is properly initialized + CHECK_mPath(); + + struct STATFS fs_buf; + + /* + * Members of the STATFS struct that you should know about: + * F_BSIZE = block size on disk. + * f_bavail = number of free blocks available to a non-superuser. + * f_bfree = number of total free blocks in file system. + */ + + if (STATFS(mPath.get(), &fs_buf) < 0) { + // The call to STATFS failed. +#ifdef DEBUG + printf("ERROR: GetDiskSpaceAvailable: STATFS call FAILED. \n"); +#endif + return NS_ERROR_FAILURE; + } + + *aDiskSpaceAvailable = (int64_t) fs_buf.F_BSIZE * fs_buf.f_bavail; + +#ifdef DEBUG_DISK_SPACE + printf("DiskSpaceAvailable: %lu bytes\n", + *aDiskSpaceAvailable); +#endif + +#if defined(USE_LINUX_QUOTACTL) + + if(!FillStatCache()) { + // Return available size from statfs + return NS_OK; + } + + nsCString deviceName; + if(!GetDeviceName(major(mCachedStat.st_dev), minor(mCachedStat.st_dev), deviceName)) { + return NS_OK; + } + + struct dqblk dq; + if(!quotactl(QCMD(Q_GETQUOTA, USRQUOTA), deviceName.get(), getuid(), (caddr_t)&dq) +#ifdef QIF_BLIMITS + && dq.dqb_valid & QIF_BLIMITS +#endif + && dq.dqb_bhardlimit) + { + int64_t QuotaSpaceAvailable = 0; + if (dq.dqb_bhardlimit > dq.dqb_curspace) + QuotaSpaceAvailable = int64_t(fs_buf.F_BSIZE * (dq.dqb_bhardlimit - dq.dqb_curspace)); + if(QuotaSpaceAvailable < *aDiskSpaceAvailable) { + *aDiskSpaceAvailable = QuotaSpaceAvailable; + } + } +#endif + + return NS_OK; + +#else + /* + * This platform doesn't have statfs or statvfs. I'm sure that there's + * a way to check for free disk space on platforms that don't have statfs + * (I'm SURE they have df, for example). + * + * Until we figure out how to do that, lets be honest and say that this + * command isn't implemented properly for these platforms yet. + */ +#ifdef DEBUG + printf("ERROR: GetDiskSpaceAvailable: Not implemented for plaforms without statfs.\n"); +#endif + return NS_ERROR_NOT_IMPLEMENTED; + +#endif /* STATFS */ + +} + +NS_IMETHODIMP +nsLocalFile::GetParent(nsIFile **aParent) +{ + CHECK_mPath(); + NS_ENSURE_ARG_POINTER(aParent); + *aParent = nullptr; + + // if '/' we are at the top of the volume, return null + if (mPath.Equals("/")) + return NS_OK; + + // <brendan, after jband> I promise to play nice + char *buffer = mPath.BeginWriting(), + *slashp = buffer; + + // find the last significant slash in buffer + slashp = strrchr(buffer, '/'); + NS_ASSERTION(slashp, "non-canonical path?"); + if (!slashp) + return NS_ERROR_FILE_INVALID_PATH; + + // for the case where we are at '/' + if (slashp == buffer) + slashp++; + + // temporarily terminate buffer at the last significant slash + char c = *slashp; + *slashp = '\0'; + + nsCOMPtr<nsIFile> localFile; + nsresult rv = NS_NewNativeLocalFile(nsDependentCString(buffer), true, + getter_AddRefs(localFile)); + + // make buffer whole again + *slashp = c; + + if (NS_SUCCEEDED(rv) && localFile) + rv = CallQueryInterface(localFile, aParent); + return rv; +} + +/* + * The results of Exists, isWritable and isReadable are not cached. + */ + + +NS_IMETHODIMP +nsLocalFile::Exists(bool *_retval) +{ + CHECK_mPath(); + NS_ENSURE_ARG_POINTER(_retval); + + *_retval = (access(mPath.get(), F_OK) == 0); + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::IsWritable(bool *_retval) +{ + CHECK_mPath(); + NS_ENSURE_ARG_POINTER(_retval); + + *_retval = (access(mPath.get(), W_OK) == 0); + if (*_retval || errno == EACCES) + return NS_OK; + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::IsReadable(bool *_retval) +{ + CHECK_mPath(); + NS_ENSURE_ARG_POINTER(_retval); + + *_retval = (access(mPath.get(), R_OK) == 0); + if (*_retval || errno == EACCES) + return NS_OK; + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::IsExecutable(bool *_retval) +{ + CHECK_mPath(); + NS_ENSURE_ARG_POINTER(_retval); + + // Check extension (bug 663899). On certain platforms, the file + // extension may cause the OS to treat it as executable regardless of + // the execute bit, such as .jar on Mac OS X. We borrow the code from + // nsLocalFileWin, slightly modified. + + // Don't be fooled by symlinks. + bool symLink; + nsresult rv = IsSymlink(&symLink); + if (NS_FAILED(rv)) + return rv; + + nsAutoString path; + if (symLink) + GetTarget(path); + else + GetPath(path); + + int32_t dotIdx = path.RFindChar(PRUnichar('.')); + if (dotIdx != kNotFound) { + // Convert extension to lower case. + PRUnichar *p = path.BeginWriting(); + for(p += dotIdx + 1; *p; p++) + *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0; + + // Search for any of the set of executable extensions. + static const char * const executableExts[] = { + "air", // Adobe AIR installer + "jar"}; // java application bundle + nsDependentSubstring ext = Substring(path, dotIdx + 1); + for (size_t i = 0; i < ArrayLength(executableExts); i++) { + if (ext.EqualsASCII(executableExts[i])) { + // Found a match. Set result and quit. + *_retval = true; + return NS_OK; + } + } + } + + // On OS X, then query Launch Services. +#ifdef MOZ_WIDGET_COCOA + // Certain Mac applications, such as Classic applications, which + // run under Rosetta, might not have the +x mode bit but are still + // considered to be executable by Launch Services (bug 646748). + CFURLRef url; + if (NS_FAILED(GetCFURL(&url))) { + return NS_ERROR_FAILURE; + } + + LSRequestedInfo theInfoRequest = kLSRequestAllInfo; + LSItemInfoRecord theInfo; + OSStatus result = ::LSCopyItemInfoForURL(url, theInfoRequest, &theInfo); + ::CFRelease(url); + if (result == noErr) { + if ((theInfo.flags & kLSItemInfoIsApplication) != 0) { + *_retval = true; + return NS_OK; + } + } +#endif + + // Then check the execute bit. + *_retval = (access(mPath.get(), X_OK) == 0); +#ifdef SOLARIS + // On Solaris, access will always return 0 for root user, however + // the file is only executable if S_IXUSR | S_IXGRP | S_IXOTH is set. + // See bug 351950, https://bugzilla.mozilla.org/show_bug.cgi?id=351950 + if (*_retval) { + struct STAT buf; + + *_retval = (STAT(mPath.get(), &buf) == 0); + if (*_retval || errno == EACCES) { + *_retval = *_retval && + (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH )); + return NS_OK; + } + + return NSRESULT_FOR_ERRNO(); + } +#endif + if (*_retval || errno == EACCES) + return NS_OK; + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::IsDirectory(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + ENSURE_STAT_CACHE(); + *_retval = S_ISDIR(mCachedStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsFile(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + ENSURE_STAT_CACHE(); + *_retval = S_ISREG(mCachedStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsHidden(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + nsACString::const_iterator begin, end; + LocateNativeLeafName(begin, end); + *_retval = (*begin == '.'); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSymlink(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + CHECK_mPath(); + + struct STAT symStat; + if (LSTAT(mPath.get(), &symStat) == -1) + return NSRESULT_FOR_ERRNO(); + *_retval=S_ISLNK(symStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSpecial(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + ENSURE_STAT_CACHE(); + *_retval = S_ISCHR(mCachedStat.st_mode) || + S_ISBLK(mCachedStat.st_mode) || +#ifdef S_ISSOCK + S_ISSOCK(mCachedStat.st_mode) || +#endif + S_ISFIFO(mCachedStat.st_mode); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Equals(nsIFile *inFile, bool *_retval) +{ + NS_ENSURE_ARG(inFile); + NS_ENSURE_ARG_POINTER(_retval); + *_retval = false; + + nsAutoCString inPath; + nsresult rv = inFile->GetNativePath(inPath); + if (NS_FAILED(rv)) + return rv; + + // We don't need to worry about "/foo/" vs. "/foo" here + // because trailing slashes are stripped on init. + *_retval = !strcmp(inPath.get(), mPath.get()); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Contains(nsIFile *inFile, bool recur, bool *_retval) +{ + CHECK_mPath(); + NS_ENSURE_ARG(inFile); + NS_ENSURE_ARG_POINTER(_retval); + + nsAutoCString inPath; + nsresult rv; + + if (NS_FAILED(rv = inFile->GetNativePath(inPath))) + return rv; + + *_retval = false; + + ssize_t len = mPath.Length(); + if (strncmp(mPath.get(), inPath.get(), len) == 0) { + // Now make sure that the |inFile|'s path has a separator at len, + // which implies that it has more components after len. + if (inPath[len] == '/') + *_retval = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetNativeTarget(nsACString &_retval) +{ + CHECK_mPath(); + _retval.Truncate(); + + struct STAT symStat; + if (LSTAT(mPath.get(), &symStat) == -1) + return NSRESULT_FOR_ERRNO(); + + if (!S_ISLNK(symStat.st_mode)) + return NS_ERROR_FILE_INVALID_PATH; + + int32_t size = (int32_t)symStat.st_size; + char *target = (char *)nsMemory::Alloc(size + 1); + if (!target) + return NS_ERROR_OUT_OF_MEMORY; + + if (readlink(mPath.get(), target, (size_t)size) < 0) { + nsMemory::Free(target); + return NSRESULT_FOR_ERRNO(); + } + target[size] = '\0'; + + nsresult rv = NS_OK; + nsCOMPtr<nsIFile> self(this); + int32_t maxLinks = 40; + while (true) { + if (maxLinks-- == 0) { + rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; + break; + } + + if (target[0] != '/') { + nsCOMPtr<nsIFile> parent; + if (NS_FAILED(rv = self->GetParent(getter_AddRefs(parent)))) + break; + if (NS_FAILED(rv = parent->AppendRelativeNativePath(nsDependentCString(target)))) + break; + if (NS_FAILED(rv = parent->GetNativePath(_retval))) + break; + self = parent; + } else { + _retval = target; + } + + const nsPromiseFlatCString &flatRetval = PromiseFlatCString(_retval); + + // Any failure in testing the current target we'll just interpret + // as having reached our destiny. + if (LSTAT(flatRetval.get(), &symStat) == -1) + break; + + // And of course we're done if it isn't a symlink. + if (!S_ISLNK(symStat.st_mode)) + break; + + int32_t newSize = (int32_t)symStat.st_size; + if (newSize > size) { + char *newTarget = (char *)nsMemory::Realloc(target, newSize + 1); + if (!newTarget) { + rv = NS_ERROR_OUT_OF_MEMORY; + break; + } + target = newTarget; + size = newSize; + } + + int32_t linkLen = readlink(flatRetval.get(), target, size); + if (linkLen == -1) { + rv = NSRESULT_FOR_ERRNO(); + break; + } + target[linkLen] = '\0'; + } + + nsMemory::Free(target); + + if (NS_FAILED(rv)) + _retval.Truncate(); + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetFollowLinks(bool *aFollowLinks) +{ + *aFollowLinks = true; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetFollowLinks(bool aFollowLinks) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator **entries) +{ + nsDirEnumeratorUnix *dir = new nsDirEnumeratorUnix(); + if (!dir) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(dir); + nsresult rv = dir->Init(this, false); + if (NS_FAILED(rv)) { + *entries = nullptr; + NS_RELEASE(dir); + } else { + *entries = dir; // transfer reference + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::Load(PRLibrary **_retval) +{ + CHECK_mPath(); + NS_ENSURE_ARG_POINTER(_retval); + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcntImpl::SetActivityIsLegal(false); +#endif + + *_retval = PR_LoadLibrary(mPath.get()); + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcntImpl::SetActivityIsLegal(true); +#endif + + if (!*_retval) + return NS_ERROR_FAILURE; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPersistentDescriptor(nsACString &aPersistentDescriptor) +{ + return GetNativePath(aPersistentDescriptor); +} + +NS_IMETHODIMP +nsLocalFile::SetPersistentDescriptor(const nsACString &aPersistentDescriptor) +{ +#ifdef MOZ_WIDGET_COCOA + if (aPersistentDescriptor.IsEmpty()) + return NS_ERROR_INVALID_ARG; + + // Support pathnames as user-supplied descriptors if they begin with '/' + // or '~'. These characters do not collide with the base64 set used for + // encoding alias records. + char first = aPersistentDescriptor.First(); + if (first == '/' || first == '~') + return InitWithNativePath(aPersistentDescriptor); + + uint32_t dataSize = aPersistentDescriptor.Length(); + char* decodedData = PL_Base64Decode(PromiseFlatCString(aPersistentDescriptor).get(), dataSize, nullptr); + if (!decodedData) { + NS_ERROR("SetPersistentDescriptor was given bad data"); + return NS_ERROR_FAILURE; + } + + // Cast to an alias record and resolve. + AliasRecord aliasHeader = *(AliasPtr)decodedData; + int32_t aliasSize = ::GetAliasSizeFromPtr(&aliasHeader); + if (aliasSize > ((int32_t)dataSize * 3) / 4) { // be paranoid about having too few data + PR_Free(decodedData); + return NS_ERROR_FAILURE; + } + + nsresult rv = NS_OK; + + // Move the now-decoded data into the Handle. + // The size of the decoded data is 3/4 the size of the encoded data. See plbase64.h + Handle newHandle = nullptr; + if (::PtrToHand(decodedData, &newHandle, aliasSize) != noErr) + rv = NS_ERROR_OUT_OF_MEMORY; + PR_Free(decodedData); + if (NS_FAILED(rv)) + return rv; + + Boolean changed; + FSRef resolvedFSRef; + OSErr err = ::FSResolveAlias(nullptr, (AliasHandle)newHandle, &resolvedFSRef, &changed); + + rv = MacErrorMapper(err); + DisposeHandle(newHandle); + if (NS_FAILED(rv)) + return rv; + + return InitWithFSRef(&resolvedFSRef); +#else + return InitWithNativePath(aPersistentDescriptor); +#endif +} + +NS_IMETHODIMP +nsLocalFile::Reveal() +{ +#ifdef MOZ_WIDGET_GTK + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + nsCOMPtr<nsIGnomeVFSService> gnomevfs = do_GetService(NS_GNOMEVFSSERVICE_CONTRACTID); + if (!giovfs && !gnomevfs) + return NS_ERROR_FAILURE; + + bool isDirectory; + if (NS_FAILED(IsDirectory(&isDirectory))) + return NS_ERROR_FAILURE; + + if (isDirectory) { + if (giovfs) + return giovfs->ShowURIForInput(mPath); + else + /* Fallback to GnomeVFS */ + return gnomevfs->ShowURIForInput(mPath); + } else { + nsCOMPtr<nsIFile> parentDir; + nsAutoCString dirPath; + if (NS_FAILED(GetParent(getter_AddRefs(parentDir)))) + return NS_ERROR_FAILURE; + if (NS_FAILED(parentDir->GetNativePath(dirPath))) + return NS_ERROR_FAILURE; + + if (giovfs) + return giovfs->ShowURIForInput(dirPath); + else + return gnomevfs->ShowURIForInput(dirPath); + } +#elif defined(MOZ_WIDGET_COCOA) + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::RevealFileInFinder(url); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +#else + return NS_ERROR_FAILURE; +#endif +} + +NS_IMETHODIMP +nsLocalFile::Launch() +{ +#ifdef MOZ_WIDGET_GTK +#if (MOZ_PLATFORM_MAEMO==5) + const int32_t kHILDON_SUCCESS = 1; + DBusError err; + dbus_error_init(&err); + + DBusConnection *connection = dbus_bus_get(DBUS_BUS_SESSION, &err); + if (dbus_error_is_set(&err)) { + dbus_error_free(&err); + return NS_ERROR_FAILURE; + } + + if (nullptr == connection) + return NS_ERROR_FAILURE; + + if (hildon_mime_open_file(connection, mPath.get()) != kHILDON_SUCCESS) + return NS_ERROR_FAILURE; + return NS_OK; +#else + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + nsCOMPtr<nsIGnomeVFSService> gnomevfs = do_GetService(NS_GNOMEVFSSERVICE_CONTRACTID); + if (giovfs) { + return giovfs->ShowURIForInput(mPath); + } else if (gnomevfs) { + /* GnomeVFS fallback */ + return gnomevfs->ShowURIForInput(mPath); + } + + return NS_ERROR_FAILURE; +#endif +#elif defined(MOZ_ENABLE_CONTENTACTION) + QUrl uri = QUrl::fromLocalFile(QString::fromUtf8(mPath.get())); + ContentAction::Action action = + ContentAction::Action::defaultActionForFile(uri); + + if (action.isValid()) { + action.trigger(); + return NS_OK; + } + + return NS_ERROR_FAILURE; +#elif defined(MOZ_WIDGET_ANDROID) + // Try to get a mimetype, if this fails just use the file uri alone + nsresult rv; + nsAutoCString type; + nsCOMPtr<nsIMIMEService> mimeService(do_GetService("@mozilla.org/mime;1", &rv)); + if (NS_SUCCEEDED(rv)) + rv = mimeService->GetTypeFromFile(this, type); + + nsDependentCString fileUri = NS_LITERAL_CSTRING("file://"); + fileUri.Append(mPath); + mozilla::AndroidBridge* bridge = mozilla::AndroidBridge::Bridge(); + return bridge->OpenUriExternal(fileUri, type) ? NS_OK : NS_ERROR_FAILURE; +#elif defined(MOZ_WIDGET_COCOA) + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::OpenURL(url); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +#else + return NS_ERROR_FAILURE; +#endif +} + +nsresult +NS_NewNativeLocalFile(const nsACString &path, bool followSymlinks, nsIFile **result) +{ + nsLocalFile *file = new nsLocalFile(); + if (!file) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(file); + + file->SetFollowLinks(followSymlinks); + + if (!path.IsEmpty()) { + nsresult rv = file->InitWithNativePath(path); + if (NS_FAILED(rv)) { + NS_RELEASE(file); + return rv; + } + } + *result = file; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// unicode support +//----------------------------------------------------------------------------- + +#define SET_UCS(func, ucsArg) \ + { \ + nsAutoCString buf; \ + nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \ + if (NS_FAILED(rv)) \ + return rv; \ + return (func)(buf); \ + } + +#define GET_UCS(func, ucsArg) \ + { \ + nsAutoCString buf; \ + nsresult rv = (func)(buf); \ + if (NS_FAILED(rv)) return rv; \ + return NS_CopyNativeToUnicode(buf, ucsArg); \ + } + +#define SET_UCS_2ARGS_2(func, opaqueArg, ucsArg) \ + { \ + nsAutoCString buf; \ + nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \ + if (NS_FAILED(rv)) \ + return rv; \ + return (func)(opaqueArg, buf); \ + } + +// Unicode interface Wrapper +nsresult +nsLocalFile::InitWithPath(const nsAString &filePath) +{ + SET_UCS(InitWithNativePath, filePath); +} +nsresult +nsLocalFile::Append(const nsAString &node) +{ + SET_UCS(AppendNative, node); +} +nsresult +nsLocalFile::AppendRelativePath(const nsAString &node) +{ + SET_UCS(AppendRelativeNativePath, node); +} +nsresult +nsLocalFile::GetLeafName(nsAString &aLeafName) +{ + GET_UCS(GetNativeLeafName, aLeafName); +} +nsresult +nsLocalFile::SetLeafName(const nsAString &aLeafName) +{ + SET_UCS(SetNativeLeafName, aLeafName); +} +nsresult +nsLocalFile::GetPath(nsAString &_retval) +{ + return NS_CopyNativeToUnicode(mPath, _retval); +} +nsresult +nsLocalFile::CopyTo(nsIFile *newParentDir, const nsAString &newName) +{ + SET_UCS_2ARGS_2(CopyToNative , newParentDir, newName); +} +nsresult +nsLocalFile::CopyToFollowingLinks(nsIFile *newParentDir, const nsAString &newName) +{ + SET_UCS_2ARGS_2(CopyToFollowingLinksNative , newParentDir, newName); +} +nsresult +nsLocalFile::MoveTo(nsIFile *newParentDir, const nsAString &newName) +{ + SET_UCS_2ARGS_2(MoveToNative, newParentDir, newName); +} +nsresult +nsLocalFile::GetTarget(nsAString &_retval) +{ + GET_UCS(GetNativeTarget, _retval); +} + +// nsIHashable + +NS_IMETHODIMP +nsLocalFile::Equals(nsIHashable* aOther, bool *aResult) +{ + nsCOMPtr<nsIFile> otherFile(do_QueryInterface(aOther)); + if (!otherFile) { + *aResult = false; + return NS_OK; + } + + return Equals(otherFile, aResult); +} + +NS_IMETHODIMP +nsLocalFile::GetHashCode(uint32_t *aResult) +{ + *aResult = HashString(mPath); + return NS_OK; +} + +nsresult +NS_NewLocalFile(const nsAString &path, bool followLinks, nsIFile* *result) +{ + nsAutoCString buf; + nsresult rv = NS_CopyUnicodeToNative(path, buf); + if (NS_FAILED(rv)) + return rv; + return NS_NewNativeLocalFile(buf, followLinks, result); +} + +//----------------------------------------------------------------------------- +// global init/shutdown +//----------------------------------------------------------------------------- + +void +nsLocalFile::GlobalInit() +{ +} + +void +nsLocalFile::GlobalShutdown() +{ +} + +// nsILocalFileMac + +#ifdef MOZ_WIDGET_COCOA + +static nsresult MacErrorMapper(OSErr inErr) +{ + nsresult outErr; + + switch (inErr) + { + case noErr: + outErr = NS_OK; + break; + + case fnfErr: + case afpObjectNotFound: + case afpDirNotFound: + outErr = NS_ERROR_FILE_NOT_FOUND; + break; + + case dupFNErr: + case afpObjectExists: + outErr = NS_ERROR_FILE_ALREADY_EXISTS; + break; + + case dskFulErr: + case afpDiskFull: + outErr = NS_ERROR_FILE_DISK_FULL; + break; + + case fLckdErr: + case afpVolLocked: + outErr = NS_ERROR_FILE_IS_LOCKED; + break; + + case afpAccessDenied: + outErr = NS_ERROR_FILE_ACCESS_DENIED; + break; + + case afpDirNotEmpty: + outErr = NS_ERROR_FILE_DIR_NOT_EMPTY; + break; + + // Can't find good map for some + case bdNamErr: + outErr = NS_ERROR_FAILURE; + break; + + default: + outErr = NS_ERROR_FAILURE; + break; + } + + return outErr; +} + +static nsresult CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr) +{ + // first see if the conversion would succeed and find the length of the result + CFIndex usedBufLen, inStrLen = ::CFStringGetLength(aInStrRef); + CFIndex charsConverted = ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), + kCFStringEncodingUTF8, 0, false, + NULL, 0, &usedBufLen); + if (charsConverted == inStrLen) { + // all characters converted, do the actual conversion + aOutStr.SetLength(usedBufLen); + if (aOutStr.Length() != (unsigned int)usedBufLen) + return NS_ERROR_OUT_OF_MEMORY; + UInt8 *buffer = (UInt8*)aOutStr.BeginWriting(); + ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8, + 0, false, buffer, usedBufLen, &usedBufLen); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::InitWithCFURL(CFURLRef aCFURL) +{ + UInt8 path[PATH_MAX]; + if (::CFURLGetFileSystemRepresentation(aCFURL, false, path, PATH_MAX)) { + nsDependentCString nativePath((char*)path); + return InitWithNativePath(nativePath); + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::InitWithFSRef(const FSRef *aFSRef) +{ + NS_ENSURE_ARG(aFSRef); + + CFURLRef newURLRef = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aFSRef); + if (newURLRef) { + nsresult rv = InitWithCFURL(newURLRef); + ::CFRelease(newURLRef); + return rv; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::GetCFURL(CFURLRef *_retval) +{ + CHECK_mPath(); + + bool isDir; + IsDirectory(&isDir); + *_retval = ::CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, + (UInt8*)mPath.get(), + mPath.Length(), + isDir); + + return (*_retval ? NS_OK : NS_ERROR_FAILURE); +} + +NS_IMETHODIMP +nsLocalFile::GetFSRef(FSRef *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv = NS_ERROR_FAILURE; + + CFURLRef url = NULL; + if (NS_SUCCEEDED(GetCFURL(&url))) { + if (::CFURLGetFSRef(url, _retval)) { + rv = NS_OK; + } + ::CFRelease(url); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetFSSpec(FSSpec *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + FSRef fsRef; + nsresult rv = GetFSRef(&fsRef); + if (NS_SUCCEEDED(rv)) { + OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNone, nullptr, nullptr, _retval, nullptr); + return MacErrorMapper(err); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetFileSizeWithResFork(int64_t *aFileSizeWithResFork) +{ + NS_ENSURE_ARG_POINTER(aFileSizeWithResFork); + + FSRef fsRef; + nsresult rv = GetFSRef(&fsRef); + if (NS_FAILED(rv)) + return rv; + + FSCatalogInfo catalogInfo; + OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoDataSizes + kFSCatInfoRsrcSizes, + &catalogInfo, nullptr, nullptr, nullptr); + if (err != noErr) + return MacErrorMapper(err); + + *aFileSizeWithResFork = catalogInfo.dataLogicalSize + catalogInfo.rsrcLogicalSize; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetFileType(OSType *aFileType) +{ + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::GetFileTypeCode(url, aFileType); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::SetFileType(OSType aFileType) +{ + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::SetFileTypeCode(url, aFileType); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::GetFileCreator(OSType *aFileCreator) +{ + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::GetFileCreatorCode(url, aFileCreator); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::SetFileCreator(OSType aFileCreator) +{ + CFURLRef url; + if (NS_SUCCEEDED(GetCFURL(&url))) { + nsresult rv = CocoaFileUtils::SetFileCreatorCode(url, aFileCreator); + ::CFRelease(url); + return rv; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::LaunchWithDoc(nsIFile *aDocToLoad, bool aLaunchInBackground) +{ + bool isExecutable; + nsresult rv = IsExecutable(&isExecutable); + if (NS_FAILED(rv)) + return rv; + if (!isExecutable) + return NS_ERROR_FILE_EXECUTION_FAILED; + + FSRef appFSRef, docFSRef; + rv = GetFSRef(&appFSRef); + if (NS_FAILED(rv)) + return rv; + + if (aDocToLoad) { + nsCOMPtr<nsILocalFileMac> macDoc = do_QueryInterface(aDocToLoad); + rv = macDoc->GetFSRef(&docFSRef); + if (NS_FAILED(rv)) + return rv; + } + + LSLaunchFlags theLaunchFlags = kLSLaunchDefaults; + LSLaunchFSRefSpec thelaunchSpec; + + if (aLaunchInBackground) + theLaunchFlags |= kLSLaunchDontSwitch; + memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec)); + + thelaunchSpec.appRef = &appFSRef; + if (aDocToLoad) { + thelaunchSpec.numDocs = 1; + thelaunchSpec.itemRefs = &docFSRef; + } + thelaunchSpec.launchFlags = theLaunchFlags; + + OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, NULL); + if (err != noErr) + return MacErrorMapper(err); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::OpenDocWithApp(nsIFile *aAppToOpenWith, bool aLaunchInBackground) +{ + FSRef docFSRef; + nsresult rv = GetFSRef(&docFSRef); + if (NS_FAILED(rv)) + return rv; + + if (!aAppToOpenWith) { + OSErr err = ::LSOpenFSRef(&docFSRef, NULL); + return MacErrorMapper(err); + } + + nsCOMPtr<nsILocalFileMac> appFileMac = do_QueryInterface(aAppToOpenWith, &rv); + if (!appFileMac) + return rv; + + bool isExecutable; + rv = appFileMac->IsExecutable(&isExecutable); + if (NS_FAILED(rv)) + return rv; + if (!isExecutable) + return NS_ERROR_FILE_EXECUTION_FAILED; + + FSRef appFSRef; + rv = appFileMac->GetFSRef(&appFSRef); + if (NS_FAILED(rv)) + return rv; + + LSLaunchFlags theLaunchFlags = kLSLaunchDefaults; + LSLaunchFSRefSpec thelaunchSpec; + + if (aLaunchInBackground) + theLaunchFlags |= kLSLaunchDontSwitch; + memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec)); + + thelaunchSpec.appRef = &appFSRef; + thelaunchSpec.numDocs = 1; + thelaunchSpec.itemRefs = &docFSRef; + thelaunchSpec.launchFlags = theLaunchFlags; + + OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, NULL); + if (err != noErr) + return MacErrorMapper(err); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsPackage(bool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = false; + + CFURLRef url; + nsresult rv = GetCFURL(&url); + if (NS_FAILED(rv)) + return rv; + + LSItemInfoRecord info; + OSStatus status = ::LSCopyItemInfoForURL(url, kLSRequestBasicFlagsOnly, &info); + + ::CFRelease(url); + + if (status != noErr) { + return NS_ERROR_FAILURE; + } + + *_retval = !!(info.flags & kLSItemInfoIsPackage); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetBundleDisplayName(nsAString& outBundleName) +{ + bool isPackage = false; + nsresult rv = IsPackage(&isPackage); + if (NS_FAILED(rv) || !isPackage) + return NS_ERROR_FAILURE; + + nsAutoString name; + rv = GetLeafName(name); + if (NS_FAILED(rv)) + return rv; + + int32_t length = name.Length(); + if (Substring(name, length - 4, length).EqualsLiteral(".app")) { + // 4 characters in ".app" + outBundleName = Substring(name, 0, length - 4); + } + else { + outBundleName = name; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetBundleIdentifier(nsACString& outBundleIdentifier) +{ + nsresult rv = NS_ERROR_FAILURE; + + CFURLRef urlRef; + if (NS_SUCCEEDED(GetCFURL(&urlRef))) { + CFBundleRef bundle = ::CFBundleCreate(NULL, urlRef); + if (bundle) { + CFStringRef bundleIdentifier = ::CFBundleGetIdentifier(bundle); + if (bundleIdentifier) + rv = CFStringReftoUTF8(bundleIdentifier, outBundleIdentifier); + ::CFRelease(bundle); + } + ::CFRelease(urlRef); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetBundleContentsLastModifiedTime(int64_t *aLastModTime) +{ + CHECK_mPath(); + NS_ENSURE_ARG_POINTER(aLastModTime); + + bool isPackage = false; + nsresult rv = IsPackage(&isPackage); + if (NS_FAILED(rv) || !isPackage) { + return GetLastModifiedTime(aLastModTime); + } + + nsAutoCString infoPlistPath(mPath); + infoPlistPath.AppendLiteral("/Contents/Info.plist"); + PRFileInfo64 info; + if (PR_GetFileInfo64(infoPlistPath.get(), &info) != PR_SUCCESS) { + return GetLastModifiedTime(aLastModTime); + } + int64_t modTime = int64_t(info.modifyTime); + if (modTime == 0) { + *aLastModTime = 0; + } else { + *aLastModTime = modTime / int64_t(PR_USEC_PER_MSEC); + } + + return NS_OK; +} + +NS_IMETHODIMP nsLocalFile::InitWithFile(nsIFile *aFile) +{ + NS_ENSURE_ARG(aFile); + + nsAutoCString nativePath; + nsresult rv = aFile->GetNativePath(nativePath); + if (NS_FAILED(rv)) + return rv; + + return InitWithNativePath(nativePath); +} + +nsresult +NS_NewLocalFileWithFSRef(const FSRef* aFSRef, bool aFollowLinks, nsILocalFileMac** result) +{ + nsLocalFile* file = new nsLocalFile(); + if (file == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(file); + + file->SetFollowLinks(aFollowLinks); + + nsresult rv = file->InitWithFSRef(aFSRef); + if (NS_FAILED(rv)) { + NS_RELEASE(file); + return rv; + } + *result = file; + return NS_OK; +} + +nsresult +NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowLinks, nsILocalFileMac** result) +{ + nsLocalFile* file = new nsLocalFile(); + if (!file) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(file); + + file->SetFollowLinks(aFollowLinks); + + nsresult rv = file->InitWithCFURL(aURL); + if (NS_FAILED(rv)) { + NS_RELEASE(file); + return rv; + } + *result = file; + return NS_OK; +} + +#endif diff --git a/xpcom/io/nsLocalFileUnix.h b/xpcom/io/nsLocalFileUnix.h new file mode 100644 index 000000000..7a99b8d28 --- /dev/null +++ b/xpcom/io/nsLocalFileUnix.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/* + * Implementation of nsIFile for ``Unixy'' systems. + */ + +#ifndef _nsLocalFileUNIX_H_ +#define _nsLocalFileUNIX_H_ + +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "nscore.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsIHashable.h" +#include "nsIClassInfoImpl.h" +#include "mozilla/Attributes.h" +#ifdef MOZ_WIDGET_COCOA +#include "nsILocalFileMac.h" +#endif + +/** + * we need these for statfs() + */ +#ifdef HAVE_SYS_STATVFS_H + #if defined(__osf__) && defined(__DECCXX) + extern "C" int statvfs(const char *, struct statvfs *); + #endif + #include <sys/statvfs.h> +#endif + +#ifdef HAVE_SYS_STATFS_H + #include <sys/statfs.h> +#endif + +#ifdef HAVE_SYS_VFS_H + #include <sys/vfs.h> +#endif + +#ifdef HAVE_SYS_MOUNT_H + #include <sys/param.h> + #include <sys/mount.h> +#endif + +#if defined(HAVE_STATVFS64) && (!defined(LINUX) && !defined(__osf__)) + #define STATFS statvfs64 + #define F_BSIZE f_frsize +#elif defined(HAVE_STATVFS) && (!defined(LINUX) && !defined(__osf__)) + #define STATFS statvfs + #define F_BSIZE f_frsize +#elif defined(HAVE_STATFS64) + #define STATFS statfs64 + #define F_BSIZE f_bsize +#elif defined(HAVE_STATFS) + #define STATFS statfs + #define F_BSIZE f_bsize +#endif + +#if defined(HAVE_STAT64) && defined(HAVE_LSTAT64) + #if defined (AIX) + #if defined STAT + #undef STAT + #endif + #endif + #define STAT stat64 + #define LSTAT lstat64 + #define HAVE_STATS64 1 +#else + #define STAT stat + #define LSTAT lstat +#endif + + +class nsLocalFile MOZ_FINAL : +#ifdef MOZ_WIDGET_COCOA + public nsILocalFileMac, +#else + public nsILocalFile, +#endif + public nsIHashable +{ +public: + NS_DEFINE_STATIC_CID_ACCESSOR(NS_LOCAL_FILE_CID) + + nsLocalFile(); + + static nsresult nsLocalFileConstructor(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr); + + NS_DECL_ISUPPORTS + NS_DECL_NSIFILE + NS_DECL_NSILOCALFILE +#ifdef MOZ_WIDGET_COCOA + NS_DECL_NSILOCALFILEMAC +#endif + NS_DECL_NSIHASHABLE + +public: + static void GlobalInit(); + static void GlobalShutdown(); + +private: + nsLocalFile(const nsLocalFile& other); + ~nsLocalFile() {} + +protected: + // This stat cache holds the *last stat* - it does not invalidate. + // Call "FillStatCache" whenever you want to stat our file. + struct STAT mCachedStat; + nsCString mPath; + + void LocateNativeLeafName(nsACString::const_iterator &, + nsACString::const_iterator &); + + nsresult CopyDirectoryTo(nsIFile *newParent); + nsresult CreateAllAncestors(uint32_t permissions); + nsresult GetNativeTargetPathName(nsIFile *newParent, + const nsACString &newName, + nsACString &_retval); + + bool FillStatCache(); + + nsresult CreateAndKeepOpen(uint32_t type, int flags, + uint32_t permissions, PRFileDesc **_retval); +}; + +#endif /* _nsLocalFileUNIX_H_ */ diff --git a/xpcom/io/nsLocalFileWin.cpp b/xpcom/io/nsLocalFileWin.cpp new file mode 100644 index 000000000..460106174 --- /dev/null +++ b/xpcom/io/nsLocalFileWin.cpp @@ -0,0 +1,3455 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "mozilla/DebugOnly.h" +#include "mozilla/Util.h" + +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsMemory.h" + +#include "nsLocalFile.h" +#include "nsIDirectoryEnumerator.h" +#include "nsNativeCharsetUtils.h" + +#include "nsISimpleEnumerator.h" +#include "nsIComponentManager.h" +#include "prio.h" +#include "private/pprio.h" // To get PR_ImportFile +#include "prprf.h" +#include "prmem.h" +#include "nsHashKeys.h" + +#include "nsXPIDLString.h" +#include "nsReadableUtils.h" + +#include <direct.h> +#include <windows.h> +#include <shlwapi.h> +#include <aclapi.h> + +#include "shellapi.h" +#include "shlguid.h" + +#include <io.h> +#include <stdio.h> +#include <stdlib.h> +#include <mbstring.h> + +#include "nsXPIDLString.h" +#include "prproces.h" + +#include "mozilla/Mutex.h" +#include "SpecialSystemDirectory.h" + +#include "nsTraceRefcntImpl.h" +#include "nsXPCOMCIDInternal.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +#define CHECK_mWorkingPath() \ + PR_BEGIN_MACRO \ + if (mWorkingPath.IsEmpty()) \ + return NS_ERROR_NOT_INITIALIZED; \ + PR_END_MACRO + +// CopyFileEx only supports unbuffered I/O in Windows Vista and above +#ifndef COPY_FILE_NO_BUFFERING +#define COPY_FILE_NO_BUFFERING 0x00001000 +#endif + +#ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED +#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000 +#endif + +#ifndef DRIVE_REMOTE +#define DRIVE_REMOTE 4 +#endif + +/** + * A runnable to dispatch back to the main thread when + * AsyncLocalFileWinOperation completes. +*/ +class AsyncLocalFileWinDone : public nsRunnable +{ +public: + AsyncLocalFileWinDone() : + mWorkerThread(do_GetCurrentThread()) + { + // Objects of this type must only be created on worker threads + MOZ_ASSERT(!NS_IsMainThread()); + } + + NS_IMETHOD Run() { + // This event shuts down the worker thread and so must be main thread. + MOZ_ASSERT(NS_IsMainThread()); + + // If we don't destroy the thread when we're done with it, it will hang + // around forever... and that is bad! + mWorkerThread->Shutdown(); + return NS_OK; + } + +private: + nsCOMPtr<nsIThread> mWorkerThread; +}; + +/** + * A runnable to dispatch from the main thread when an async operation should + * be performed. +*/ +class AsyncLocalFileWinOperation : public nsRunnable +{ +public: + enum FileOp { RevealOp, LaunchOp }; + + AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::FileOp aOperation, + const nsAString &aResolvedPath) : + mOperation(aOperation), + mResolvedPath(aResolvedPath) + { + } + + NS_IMETHOD Run() { + MOZ_ASSERT(!NS_IsMainThread(), + "AsyncLocalFileWinOperation should not be run on the main thread!"); + + CoInitialize(NULL); + switch(mOperation) { + case RevealOp: { + Reveal(); + } + break; + case LaunchOp: { + Launch(); + } + break; + } + CoUninitialize(); + + // Send the result back to the main thread so that it can shutdown + nsCOMPtr<nsIRunnable> resultrunnable = new AsyncLocalFileWinDone(); + NS_DispatchToMainThread(resultrunnable); + return NS_OK; + } + +private: + // Reveals the path in explorer. + nsresult Reveal() + { + DWORD attributes = GetFileAttributesW(mResolvedPath.get()); + if (INVALID_FILE_ATTRIBUTES == attributes) { + return NS_ERROR_FILE_INVALID_PATH; + } + + HRESULT hr; + if (attributes & FILE_ATTRIBUTE_DIRECTORY) { + // We have a directory so we should open the directory itself. + ITEMIDLIST *dir = ILCreateFromPathW(mResolvedPath.get()); + if (!dir) { + return NS_ERROR_FAILURE; + } + + const ITEMIDLIST* selection[] = { dir }; + UINT count = ArrayLength(selection); + + //Perform the open of the directory. + hr = SHOpenFolderAndSelectItems(dir, count, selection, 0); + CoTaskMemFree(dir); + } else { + int32_t len = mResolvedPath.Length(); + // We don't currently handle UNC long paths of the form \\?\ anywhere so + // this should be fine. + if (len > MAX_PATH) { + return NS_ERROR_FILE_INVALID_PATH; + } + WCHAR parentDirectoryPath[MAX_PATH + 1] = { 0 }; + wcsncpy(parentDirectoryPath, mResolvedPath.get(), MAX_PATH); + PathRemoveFileSpecW(parentDirectoryPath); + + // We have a file so we should open the parent directory. + ITEMIDLIST *dir = ILCreateFromPathW(parentDirectoryPath); + if (!dir) { + return NS_ERROR_FAILURE; + } + + // Set the item in the directory to select to the file we want to reveal. + ITEMIDLIST *item = ILCreateFromPathW(mResolvedPath.get()); + if (!item) { + CoTaskMemFree(dir); + return NS_ERROR_FAILURE; + } + + const ITEMIDLIST* selection[] = { item }; + UINT count = ArrayLength(selection); + + //Perform the selection of the file. + hr = SHOpenFolderAndSelectItems(dir, count, selection, 0); + + CoTaskMemFree(dir); + CoTaskMemFree(item); + } + + return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE; + } + + // Launches the default shell operation for the file path + nsresult Launch() + { + // use the app registry name to launch a shell execute.... + SHELLEXECUTEINFOW seinfo; + memset(&seinfo, 0, sizeof(seinfo)); + seinfo.cbSize = sizeof(SHELLEXECUTEINFOW); + seinfo.fMask = 0; + seinfo.hwnd = NULL; + seinfo.lpVerb = NULL; + seinfo.lpFile = mResolvedPath.get(); + seinfo.lpParameters = NULL; + seinfo.lpDirectory = NULL; + seinfo.nShow = SW_SHOWNORMAL; + + // Use the directory of the file we're launching as the working + // directory. That way if we have a self extracting EXE it won't + // suggest to extract to the install directory. + WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' }; + wcsncpy(workingDirectory, mResolvedPath.get(), MAX_PATH); + if (PathRemoveFileSpecW(workingDirectory)) { + seinfo.lpDirectory = workingDirectory; + } else { + NS_WARNING("Could not set working directory for launched file."); + } + + if (ShellExecuteExW(&seinfo)) { + return NS_OK; + } + DWORD r = GetLastError(); + // if the file has no association, we launch windows' + // "what do you want to do" dialog + if (r == SE_ERR_NOASSOC) { + nsAutoString shellArg; + shellArg.Assign(NS_LITERAL_STRING("shell32.dll,OpenAs_RunDLL ") + + mResolvedPath); + seinfo.lpFile = L"RUNDLL32.EXE"; + seinfo.lpParameters = shellArg.get(); + if (ShellExecuteExW(&seinfo)) + return NS_OK; + r = GetLastError(); + } + if (r < 32) { + switch (r) { + case 0: + case SE_ERR_OOM: + return NS_ERROR_OUT_OF_MEMORY; + case ERROR_FILE_NOT_FOUND: + return NS_ERROR_FILE_NOT_FOUND; + case ERROR_PATH_NOT_FOUND: + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + case ERROR_BAD_FORMAT: + return NS_ERROR_FILE_CORRUPTED; + case SE_ERR_ACCESSDENIED: + return NS_ERROR_FILE_ACCESS_DENIED; + case SE_ERR_ASSOCINCOMPLETE: + case SE_ERR_NOASSOC: + return NS_ERROR_UNEXPECTED; + case SE_ERR_DDEBUSY: + case SE_ERR_DDEFAIL: + case SE_ERR_DDETIMEOUT: + return NS_ERROR_NOT_AVAILABLE; + case SE_ERR_DLLNOTFOUND: + return NS_ERROR_FAILURE; + case SE_ERR_SHARE: + return NS_ERROR_FILE_IS_LOCKED; + default: + return NS_ERROR_FILE_EXECUTION_FAILED; + } + } + return NS_OK; + } + + // Stores the operation that will be performed on the thread + AsyncLocalFileWinOperation::FileOp mOperation; + + // Stores the path to perform the operation on + nsString mResolvedPath; +}; + +class nsDriveEnumerator : public nsISimpleEnumerator +{ +public: + nsDriveEnumerator(); + virtual ~nsDriveEnumerator(); + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + nsresult Init(); +private: + /* mDrives stores the null-separated drive names. + * Init sets them. + * HasMoreElements checks mStartOfCurrentDrive. + * GetNext advances mStartOfCurrentDrive. + */ + nsString mDrives; + nsAString::const_iterator mStartOfCurrentDrive; + nsAString::const_iterator mEndOfDrivesString; +}; + +//---------------------------------------------------------------------------- +// short cut resolver +//---------------------------------------------------------------------------- +class ShortcutResolver +{ +public: + ShortcutResolver(); + // nonvirtual since we're not subclassed + ~ShortcutResolver(); + + nsresult Init(); + nsresult Resolve(const WCHAR* in, WCHAR* out); + nsresult SetShortcut(bool updateExisting, + const WCHAR* shortcutPath, + const WCHAR* targetPath, + const WCHAR* workingDir, + const WCHAR* args, + const WCHAR* description, + const WCHAR* iconFile, + int32_t iconIndex); + +private: + Mutex mLock; + nsRefPtr<IPersistFile> mPersistFile; + nsRefPtr<IShellLinkW> mShellLink; +}; + +ShortcutResolver::ShortcutResolver() : + mLock("ShortcutResolver.mLock") +{ + CoInitialize(NULL); +} + +ShortcutResolver::~ShortcutResolver() +{ + CoUninitialize(); +} + +nsresult +ShortcutResolver::Init() +{ + // Get a pointer to the IPersistFile interface. + if (FAILED(CoCreateInstance(CLSID_ShellLink, + NULL, + CLSCTX_INPROC_SERVER, + IID_IShellLinkW, + getter_AddRefs(mShellLink))) || + FAILED(mShellLink->QueryInterface(IID_IPersistFile, + getter_AddRefs(mPersistFile)))) { + mShellLink = nullptr; + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +// |out| must be an allocated buffer of size MAX_PATH +nsresult +ShortcutResolver::Resolve(const WCHAR* in, WCHAR* out) +{ + if (!mShellLink) + return NS_ERROR_FAILURE; + + MutexAutoLock lock(mLock); + + if (FAILED(mPersistFile->Load(in, STGM_READ)) || + FAILED(mShellLink->Resolve(nullptr, SLR_NO_UI)) || + FAILED(mShellLink->GetPath(out, MAX_PATH, NULL, SLGP_UNCPRIORITY))) + return NS_ERROR_FAILURE; + return NS_OK; +} + +nsresult +ShortcutResolver::SetShortcut(bool updateExisting, + const WCHAR* shortcutPath, + const WCHAR* targetPath, + const WCHAR* workingDir, + const WCHAR* args, + const WCHAR* description, + const WCHAR* iconPath, + int32_t iconIndex) +{ + if (!mShellLink) { + return NS_ERROR_FAILURE; + } + + if (!shortcutPath) { + return NS_ERROR_FAILURE; + } + + MutexAutoLock lock(mLock); + + if (updateExisting) { + if (FAILED(mPersistFile->Load(shortcutPath, STGM_READWRITE))) { + return NS_ERROR_FAILURE; + } + } else { + if (!targetPath) { + return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; + } + + // Since we reuse our IPersistFile, we have to clear out any values that + // may be left over from previous calls to SetShortcut. + if (FAILED(mShellLink->SetWorkingDirectory(L"")) + || FAILED(mShellLink->SetArguments(L"")) + || FAILED(mShellLink->SetDescription(L"")) + || FAILED(mShellLink->SetIconLocation(L"", 0))) { + return NS_ERROR_FAILURE; + } + } + + if (targetPath && FAILED(mShellLink->SetPath(targetPath))) { + return NS_ERROR_FAILURE; + } + + if (workingDir && FAILED(mShellLink->SetWorkingDirectory(workingDir))) { + return NS_ERROR_FAILURE; + } + + if (args && FAILED(mShellLink->SetArguments(args))) { + return NS_ERROR_FAILURE; + } + + if (description && FAILED(mShellLink->SetDescription(description))) { + return NS_ERROR_FAILURE; + } + + if (iconPath && FAILED(mShellLink->SetIconLocation(iconPath, iconIndex))) { + return NS_ERROR_FAILURE; + } + + if (FAILED(mPersistFile->Save(shortcutPath, + TRUE))) { + // Second argument indicates whether the file path specified in the + // first argument should become the "current working file" for this + // IPersistFile + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +static ShortcutResolver * gResolver = nullptr; + +static nsresult NS_CreateShortcutResolver() +{ + gResolver = new ShortcutResolver(); + if (!gResolver) + return NS_ERROR_OUT_OF_MEMORY; + + return gResolver->Init(); +} + +static void NS_DestroyShortcutResolver() +{ + delete gResolver; + gResolver = nullptr; +} + + +//----------------------------------------------------------------------------- +// static helper functions +//----------------------------------------------------------------------------- + +// certainly not all the error that can be +// encountered, but many of them common ones +static nsresult ConvertWinError(DWORD winErr) +{ + nsresult rv; + + switch (winErr) + { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + rv = NS_ERROR_FILE_NOT_FOUND; + break; + case ERROR_ACCESS_DENIED: + case ERROR_NOT_SAME_DEVICE: + rv = NS_ERROR_FILE_ACCESS_DENIED; + break; + case ERROR_SHARING_VIOLATION: // CreateFile without sharing flags + case ERROR_LOCK_VIOLATION: // LockFile, LockFileEx + rv = NS_ERROR_FILE_IS_LOCKED; + break; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_INVALID_BLOCK: + case ERROR_INVALID_HANDLE: + case ERROR_ARENA_TRASHED: + rv = NS_ERROR_OUT_OF_MEMORY; + break; + case ERROR_CURRENT_DIRECTORY: + rv = NS_ERROR_FILE_DIR_NOT_EMPTY; + break; + case ERROR_WRITE_PROTECT: + rv = NS_ERROR_FILE_READ_ONLY; + break; + case ERROR_HANDLE_DISK_FULL: + rv = NS_ERROR_FILE_TOO_BIG; + break; + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + case ERROR_CANNOT_MAKE: + rv = NS_ERROR_FILE_ALREADY_EXISTS; + break; + case ERROR_FILENAME_EXCED_RANGE: + rv = NS_ERROR_FILE_NAME_TOO_LONG; + break; + case ERROR_DIRECTORY: + rv = NS_ERROR_FILE_NOT_DIRECTORY; + break; + case 0: + rv = NS_OK; + break; + default: + rv = NS_ERROR_FAILURE; + break; + } + return rv; +} + +// as suggested in the MSDN documentation on SetFilePointer +static __int64 +MyFileSeek64(HANDLE aHandle, __int64 aDistance, DWORD aMoveMethod) +{ + LARGE_INTEGER li; + + li.QuadPart = aDistance; + li.LowPart = SetFilePointer(aHandle, li.LowPart, &li.HighPart, aMoveMethod); + if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) + { + li.QuadPart = -1; + } + + return li.QuadPart; +} + +static bool +IsShortcutPath(const nsAString &path) +{ + // Under Windows, the shortcuts are just files with a ".lnk" extension. + // Note also that we don't resolve links in the middle of paths. + // i.e. "c:\foo.lnk\bar.txt" is invalid. + NS_ABORT_IF_FALSE(!path.IsEmpty(), "don't pass an empty string"); + int32_t len = path.Length(); + return len >= 4 && (StringTail(path, 4).LowerCaseEqualsASCII(".lnk")); +} + +//----------------------------------------------------------------------------- +// We need the following three definitions to make |OpenFile| convert a file +// handle to an NSPR file descriptor correctly when |O_APPEND| flag is +// specified. It is defined in a private header of NSPR (primpl.h) we can't +// include. As a temporary workaround until we decide how to extend +// |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER| +// and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion +// of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied. +// Similarly, |_MDFileDesc| is taken from nsprpub/pr/include/md/_win95.h. +// In an unlikely case we switch to 'NT'-dependent NSPR AND this temporary +// workaround last beyond the switch, |PRFilePrivate| and |_MDFileDesc| +// need to be changed to match the definitions for WinNT. +//----------------------------------------------------------------------------- +typedef enum { + _PR_TRI_TRUE = 1, + _PR_TRI_FALSE = 0, + _PR_TRI_UNKNOWN = -1 +} _PRTriStateBool; + +struct _MDFileDesc { + PROsfd osfd; +}; + +struct PRFilePrivate { + int32_t state; + bool nonblocking; + _PRTriStateBool inheritable; + PRFileDesc *next; + int lockCount; /* 0: not locked + * -1: a native lockfile call is in progress + * > 0: # times the file is locked */ + bool appendMode; + _MDFileDesc md; +}; + +//----------------------------------------------------------------------------- +// Six static methods defined below (OpenFile, FileTimeToPRTime, GetFileInfo, +// OpenDir, CloseDir, ReadDir) should go away once the corresponding +// UTF-16 APIs are implemented on all the supported platforms (or at least +// Windows 9x/ME) in NSPR. Currently, they're only implemented on +// Windows NT4 or later. (bug 330665) +//----------------------------------------------------------------------------- + +// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} : +// PR_Open and _PR_MD_OPEN +static nsresult +OpenFile(const nsAFlatString &name, int osflags, int mode, + PRFileDesc **fd) +{ + int32_t access = 0; + + int32_t disposition = 0; + int32_t attributes = 0; + + if (osflags & PR_SYNC) + attributes = FILE_FLAG_WRITE_THROUGH; + if (osflags & PR_RDONLY || osflags & PR_RDWR) + access |= GENERIC_READ; + if (osflags & PR_WRONLY || osflags & PR_RDWR) + access |= GENERIC_WRITE; + + if ( osflags & PR_CREATE_FILE && osflags & PR_EXCL ) + disposition = CREATE_NEW; + else if (osflags & PR_CREATE_FILE) { + if (osflags & PR_TRUNCATE) + disposition = CREATE_ALWAYS; + else + disposition = OPEN_ALWAYS; + } else { + if (osflags & PR_TRUNCATE) + disposition = TRUNCATE_EXISTING; + else + disposition = OPEN_EXISTING; + } + + if (osflags & nsIFile::DELETE_ON_CLOSE) { + attributes |= FILE_FLAG_DELETE_ON_CLOSE; + } + + if (osflags & nsIFile::OS_READAHEAD) { + attributes |= FILE_FLAG_SEQUENTIAL_SCAN; + } + + // If no write permissions are requested, and if we are possibly creating + // the file, then set the new file as read only. + // The flag has no effect if we happen to open the file. + if (!(mode & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) && + disposition != OPEN_EXISTING) { + attributes |= FILE_ATTRIBUTE_READONLY; + } + + HANDLE file = ::CreateFileW(name.get(), access, + FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL, disposition, attributes, NULL); + + if (file == INVALID_HANDLE_VALUE) { + *fd = nullptr; + return ConvertWinError(GetLastError()); + } + + *fd = PR_ImportFile((PROsfd) file); + if (*fd) { + // On Windows, _PR_HAVE_O_APPEND is not defined so that we have to + // add it manually. (see |PR_Open| in nsprpub/pr/src/io/prfile.c) + (*fd)->secret->appendMode = (PR_APPEND & osflags) ? true : false; + return NS_OK; + } + + nsresult rv = NS_ErrorAccordingToNSPR(); + + CloseHandle(file); + + return rv; +} + +// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} : +// PR_FileTimeToPRTime and _PR_FileTimeToPRTime +static +void FileTimeToPRTime(const FILETIME *filetime, PRTime *prtm) +{ +#ifdef __GNUC__ + const PRTime _pr_filetime_offset = 116444736000000000LL; +#else + const PRTime _pr_filetime_offset = 116444736000000000i64; +#endif + + PR_ASSERT(sizeof(FILETIME) == sizeof(PRTime)); + ::CopyMemory(prtm, filetime, sizeof(PRTime)); +#ifdef __GNUC__ + *prtm = (*prtm - _pr_filetime_offset) / 10LL; +#else + *prtm = (*prtm - _pr_filetime_offset) / 10i64; +#endif +} + +// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some +// changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64 +static nsresult +GetFileInfo(const nsAFlatString &name, PRFileInfo64 *info) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData; + + if (name.IsEmpty() || name.FindCharInSet(L"?*") != kNotFound) + return NS_ERROR_INVALID_ARG; + + if (!::GetFileAttributesExW(name.get(), GetFileExInfoStandard, &fileData)) + return ConvertWinError(GetLastError()); + + if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + info->type = PR_FILE_DIRECTORY; + } else { + info->type = PR_FILE_FILE; + } + + info->size = fileData.nFileSizeHigh; + info->size = (info->size << 32) + fileData.nFileSizeLow; + + FileTimeToPRTime(&fileData.ftLastWriteTime, &info->modifyTime); + + if (0 == fileData.ftCreationTime.dwLowDateTime && + 0 == fileData.ftCreationTime.dwHighDateTime) { + info->creationTime = info->modifyTime; + } else { + FileTimeToPRTime(&fileData.ftCreationTime, &info->creationTime); + } + + return NS_OK; +} + +struct nsDir +{ + HANDLE handle; + WIN32_FIND_DATAW data; + bool firstEntry; +}; + +static nsresult +OpenDir(const nsAFlatString &name, nsDir * *dir) +{ + NS_ENSURE_ARG_POINTER(dir); + + *dir = nullptr; + if (name.Length() + 3 >= MAX_PATH) + return NS_ERROR_FILE_NAME_TOO_LONG; + + nsDir *d = PR_NEW(nsDir); + if (!d) + return NS_ERROR_OUT_OF_MEMORY; + + nsAutoString filename(name); + + //If 'name' ends in a slash or backslash, do not append + //another backslash. + if (filename.Last() == L'/' || filename.Last() == L'\\') + filename.Append('*'); + else + filename.AppendLiteral("\\*"); + + filename.ReplaceChar(L'/', L'\\'); + + // FindFirstFileW Will have a last error of ERROR_DIRECTORY if + // <file_path>\* is passed in. If <unknown_path>\* is passed in then + // ERROR_PATH_NOT_FOUND will be the last error. + d->handle = ::FindFirstFileW(filename.get(), &(d->data) ); + + if (d->handle == INVALID_HANDLE_VALUE) { + PR_Free(d); + return ConvertWinError(GetLastError()); + } + d->firstEntry = true; + + *dir = d; + return NS_OK; +} + +static nsresult +ReadDir(nsDir *dir, PRDirFlags flags, nsString& name) +{ + name.Truncate(); + NS_ENSURE_ARG(dir); + + while (1) { + BOOL rv; + if (dir->firstEntry) + { + dir->firstEntry = false; + rv = 1; + } else + rv = ::FindNextFileW(dir->handle, &(dir->data)); + + if (rv == 0) + break; + + const PRUnichar *fileName; + nsString tmp; + fileName = (dir)->data.cFileName; + + if ((flags & PR_SKIP_DOT) && + (fileName[0] == L'.') && (fileName[1] == L'\0')) + continue; + if ((flags & PR_SKIP_DOT_DOT) && + (fileName[0] == L'.') && (fileName[1] == L'.') && + (fileName[2] == L'\0')) + continue; + + DWORD attrib = dir->data.dwFileAttributes; + if ((flags & PR_SKIP_HIDDEN) && (attrib & FILE_ATTRIBUTE_HIDDEN)) + continue; + + if (fileName == tmp.get()) + name = tmp; + else + name = fileName; + return NS_OK; + } + + DWORD err = GetLastError(); + return err == ERROR_NO_MORE_FILES ? NS_OK : ConvertWinError(err); +} + +static nsresult +CloseDir(nsDir *&d) +{ + NS_ENSURE_ARG(d); + + BOOL isOk = FindClose(d->handle); + // PR_DELETE also nulls out the passed in pointer. + PR_DELETE(d); + return isOk ? NS_OK : ConvertWinError(GetLastError()); +} + +//----------------------------------------------------------------------------- +// nsDirEnumerator +//----------------------------------------------------------------------------- + +class nsDirEnumerator MOZ_FINAL : public nsISimpleEnumerator, + public nsIDirectoryEnumerator +{ + public: + + NS_DECL_ISUPPORTS + + nsDirEnumerator() : mDir(nullptr) + { + } + + nsresult Init(nsIFile* parent) + { + nsAutoString filepath; + parent->GetTarget(filepath); + + if (filepath.IsEmpty()) + { + parent->GetPath(filepath); + } + + if (filepath.IsEmpty()) + { + return NS_ERROR_UNEXPECTED; + } + + // IsDirectory is not needed here because OpenDir will return + // NS_ERROR_FILE_NOT_DIRECTORY if the passed in path is a file. + nsresult rv = OpenDir(filepath, &mDir); + if (NS_FAILED(rv)) + return rv; + + mParent = parent; + return NS_OK; + } + + NS_IMETHOD HasMoreElements(bool *result) + { + nsresult rv; + if (mNext == nullptr && mDir) + { + nsString name; + rv = ReadDir(mDir, PR_SKIP_BOTH, name); + if (NS_FAILED(rv)) + return rv; + if (name.IsEmpty()) + { + // end of dir entries + if (NS_FAILED(CloseDir(mDir))) + return NS_ERROR_FAILURE; + + *result = false; + return NS_OK; + } + + nsCOMPtr<nsIFile> file; + rv = mParent->Clone(getter_AddRefs(file)); + if (NS_FAILED(rv)) + return rv; + + rv = file->Append(name); + if (NS_FAILED(rv)) + return rv; + + mNext = do_QueryInterface(file); + } + *result = mNext != nullptr; + if (!*result) + Close(); + return NS_OK; + } + + NS_IMETHOD GetNext(nsISupports **result) + { + nsresult rv; + bool hasMore; + rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + *result = mNext; // might return nullptr + NS_IF_ADDREF(*result); + + mNext = nullptr; + return NS_OK; + } + + NS_IMETHOD GetNextFile(nsIFile **result) + { + *result = nullptr; + bool hasMore = false; + nsresult rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv) || !hasMore) + return rv; + *result = mNext; + NS_IF_ADDREF(*result); + mNext = nullptr; + return NS_OK; + } + + NS_IMETHOD Close() + { + if (mDir) + { + nsresult rv = CloseDir(mDir); + NS_ASSERTION(NS_SUCCEEDED(rv), "close failed"); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + } + return NS_OK; + } + + // dtor can be non-virtual since there are no subclasses, but must be + // public to use the class on the stack. + ~nsDirEnumerator() + { + Close(); + } + + protected: + nsDir* mDir; + nsCOMPtr<nsIFile> mParent; + nsCOMPtr<nsIFile> mNext; +}; + +NS_IMPL_ISUPPORTS2(nsDirEnumerator, nsISimpleEnumerator, nsIDirectoryEnumerator) + + +//----------------------------------------------------------------------------- +// nsLocalFile <public> +//----------------------------------------------------------------------------- + +nsLocalFile::nsLocalFile() + : mDirty(true) + , mResolveDirty(true) + , mFollowSymlinks(false) +{ +} + +nsresult +nsLocalFile::nsLocalFileConstructor(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr) +{ + NS_ENSURE_ARG_POINTER(aInstancePtr); + NS_ENSURE_NO_AGGREGATION(outer); + + nsLocalFile* inst = new nsLocalFile(); + if (inst == NULL) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = inst->QueryInterface(aIID, aInstancePtr); + if (NS_FAILED(rv)) + { + delete inst; + return rv; + } + return NS_OK; +} + + +//----------------------------------------------------------------------------- +// nsLocalFile::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_THREADSAFE_ISUPPORTS4(nsLocalFile, + nsILocalFile, + nsIFile, + nsILocalFileWin, + nsIHashable) + + +//----------------------------------------------------------------------------- +// nsLocalFile <private> +//----------------------------------------------------------------------------- + +nsLocalFile::nsLocalFile(const nsLocalFile& other) + : mDirty(true) + , mResolveDirty(true) + , mFollowSymlinks(other.mFollowSymlinks) + , mWorkingPath(other.mWorkingPath) +{ +} + +// Resolve the shortcut file from mWorkingPath and write the path +// it points to into mResolvedPath. +nsresult +nsLocalFile::ResolveShortcut() +{ + // we can't do anything without the resolver + if (!gResolver) + return NS_ERROR_FAILURE; + + mResolvedPath.SetLength(MAX_PATH); + if (mResolvedPath.Length() != MAX_PATH) + return NS_ERROR_OUT_OF_MEMORY; + + PRUnichar *resolvedPath = mResolvedPath.BeginWriting(); + + // resolve this shortcut + nsresult rv = gResolver->Resolve(mWorkingPath.get(), resolvedPath); + + size_t len = NS_FAILED(rv) ? 0 : wcslen(resolvedPath); + mResolvedPath.SetLength(len); + + return rv; +} + +// Resolve any shortcuts and stat the resolved path. After a successful return +// the path is guaranteed valid and the members of mFileInfo64 can be used. +nsresult +nsLocalFile::ResolveAndStat() +{ + // if we aren't dirty then we are already done + if (!mDirty) + return NS_OK; + + // we can't resolve/stat anything that isn't a valid NSPR addressable path + if (mWorkingPath.IsEmpty()) + return NS_ERROR_FILE_INVALID_PATH; + + // this is usually correct + mResolvedPath.Assign(mWorkingPath); + + // slutty hack designed to work around bug 134796 until it is fixed + nsAutoString nsprPath(mWorkingPath.get()); + if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == L':') + nsprPath.Append('\\'); + + // first we will see if the working path exists. If it doesn't then + // there is nothing more that can be done + nsresult rv = GetFileInfo(nsprPath, &mFileInfo64); + if (NS_FAILED(rv)) + return rv; + + // if this isn't a shortcut file or we aren't following symlinks then we're done + if (!mFollowSymlinks + || mFileInfo64.type != PR_FILE_FILE + || !IsShortcutPath(mWorkingPath)) + { + mDirty = false; + mResolveDirty = false; + return NS_OK; + } + + // we need to resolve this shortcut to what it points to, this will + // set mResolvedPath. Even if it fails we need to have the resolved + // path equal to working path for those functions that always use + // the resolved path. + rv = ResolveShortcut(); + if (NS_FAILED(rv)) + { + mResolvedPath.Assign(mWorkingPath); + return rv; + } + mResolveDirty = false; + + // get the details of the resolved path + rv = GetFileInfo(mResolvedPath, &mFileInfo64); + if (NS_FAILED(rv)) + return rv; + + mDirty = false; + return NS_OK; +} + +/** + * Fills the mResolvedPath member variable with the file or symlink target + * if follow symlinks is on. This is a copy of the Resolve parts from + * ResolveAndStat. ResolveAndStat is much slower though because of the stat. + * + * @return NS_OK on success. +*/ +nsresult +nsLocalFile::Resolve() +{ + // if we aren't dirty then we are already done + if (!mResolveDirty) { + return NS_OK; + } + + // we can't resolve/stat anything that isn't a valid NSPR addressable path + if (mWorkingPath.IsEmpty()) { + return NS_ERROR_FILE_INVALID_PATH; + } + + // this is usually correct + mResolvedPath.Assign(mWorkingPath); + + // if this isn't a shortcut file or we aren't following symlinks then + // we're done. + if (!mFollowSymlinks || + !IsShortcutPath(mWorkingPath)) { + mResolveDirty = false; + return NS_OK; + } + + // we need to resolve this shortcut to what it points to, this will + // set mResolvedPath. Even if it fails we need to have the resolved + // path equal to working path for those functions that always use + // the resolved path. + nsresult rv = ResolveShortcut(); + if (NS_FAILED(rv)) { + mResolvedPath.Assign(mWorkingPath); + return rv; + } + + mResolveDirty = false; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsLocalFile::nsIFile,nsILocalFile +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsLocalFile::Clone(nsIFile **file) +{ + // Just copy-construct ourselves + *file = new nsLocalFile(*this); + if (!*file) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*file); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::InitWithFile(nsIFile *aFile) +{ + NS_ENSURE_ARG(aFile); + + nsAutoString path; + aFile->GetPath(path); + if (path.IsEmpty()) + return NS_ERROR_INVALID_ARG; + return InitWithPath(path); +} + +NS_IMETHODIMP +nsLocalFile::InitWithPath(const nsAString &filePath) +{ + MakeDirty(); + + nsAString::const_iterator begin, end; + filePath.BeginReading(begin); + filePath.EndReading(end); + + // input string must not be empty + if (begin == end) + return NS_ERROR_FAILURE; + + PRUnichar firstChar = *begin; + PRUnichar secondChar = *(++begin); + + // just do a sanity check. if it has any forward slashes, it is not a Native path + // on windows. Also, it must have a colon at after the first char. + if (FindCharInReadable(L'/', begin, end)) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + if (secondChar != L':' && (secondChar != L'\\' || firstChar != L'\\')) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + if (secondChar == L':') { + // Make sure we have a valid drive, later code assumes the drive letter + // is a single char a-z or A-Z. + if (PathGetDriveNumberW(filePath.Data()) == -1) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + } + + mWorkingPath = filePath; + // kill any trailing '\' + if (mWorkingPath.Last() == L'\\') + mWorkingPath.Truncate(mWorkingPath.Length() - 1); + + return NS_OK; + +} + +NS_IMETHODIMP +nsLocalFile::OpenNSPRFileDesc(int32_t flags, int32_t mode, PRFileDesc **_retval) +{ + nsresult rv = Resolve(); + if (NS_FAILED(rv)) + return rv; + + return OpenFile(mResolvedPath, flags, mode, _retval); +} + + +NS_IMETHODIMP +nsLocalFile::OpenANSIFileDesc(const char *mode, FILE * *_retval) +{ + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) + return rv; + + *_retval = _wfopen(mResolvedPath.get(), NS_ConvertASCIItoUTF16(mode).get()); + if (*_retval) + return NS_OK; + + return NS_ERROR_FAILURE; +} + + + +NS_IMETHODIMP +nsLocalFile::Create(uint32_t type, uint32_t attributes) +{ + if (type != NORMAL_FILE_TYPE && type != DIRECTORY_TYPE) + return NS_ERROR_FILE_UNKNOWN_TYPE; + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) + return rv; + + // create directories to target + // + // A given local file can be either one of these forms: + // + // - normal: X:\some\path\on\this\drive + // ^--- start here + // + // - UNC path: \\machine\volume\some\path\on\this\drive + // ^--- start here + // + // Skip the first 'X:\' for the first form, and skip the first full + // '\\machine\volume\' segment for the second form. + + PRUnichar* path = mResolvedPath.BeginWriting(); + + if (path[0] == L'\\' && path[1] == L'\\') + { + // dealing with a UNC path here; skip past '\\machine\' + path = wcschr(path + 2, L'\\'); + if (!path) + return NS_ERROR_FILE_INVALID_PATH; + ++path; + } + + // search for first slash after the drive (or volume) name + PRUnichar* slash = wcschr(path, L'\\'); + + nsresult directoryCreateError = NS_OK; + if (slash) + { + // skip the first '\\' + ++slash; + slash = wcschr(slash, L'\\'); + + while (slash) + { + *slash = L'\0'; + + if (!::CreateDirectoryW(mResolvedPath.get(), NULL)) { + rv = ConvertWinError(GetLastError()); + if (NS_ERROR_FILE_NOT_FOUND == rv && + NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { + // If a previous CreateDirectory failed due to access, return that. + return NS_ERROR_FILE_ACCESS_DENIED; + } + // perhaps the base path already exists, or perhaps we don't have + // permissions to create the directory. NOTE: access denied could + // occur on a parent directory even though it exists. + else if (NS_ERROR_FILE_ALREADY_EXISTS != rv && + NS_ERROR_FILE_ACCESS_DENIED != rv) { + return rv; + } + + directoryCreateError = rv; + } + *slash = L'\\'; + ++slash; + slash = wcschr(slash, L'\\'); + } + } + + if (type == NORMAL_FILE_TYPE) + { + PRFileDesc* file; + rv = OpenFile(mResolvedPath, + PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL, attributes, + &file); + if (file) + PR_Close(file); + + if (rv == NS_ERROR_FILE_ACCESS_DENIED) + { + // need to return already-exists for directories (bug 452217) + bool isdir; + if (NS_SUCCEEDED(IsDirectory(&isdir)) && isdir) + rv = NS_ERROR_FILE_ALREADY_EXISTS; + } else if (NS_ERROR_FILE_NOT_FOUND == rv && + NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { + // If a previous CreateDirectory failed due to access, return that. + return NS_ERROR_FILE_ACCESS_DENIED; + } + return rv; + } + + if (type == DIRECTORY_TYPE) + { + if (!::CreateDirectoryW(mResolvedPath.get(), NULL)) { + rv = ConvertWinError(GetLastError()); + if (NS_ERROR_FILE_NOT_FOUND == rv && + NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { + // If a previous CreateDirectory failed due to access, return that. + return NS_ERROR_FILE_ACCESS_DENIED; + } else { + return rv; + } + } + else + return NS_OK; + } + + return NS_ERROR_FILE_UNKNOWN_TYPE; +} + + +NS_IMETHODIMP +nsLocalFile::Append(const nsAString &node) +{ + // append this path, multiple components are not permitted + return AppendInternal(PromiseFlatString(node), false); +} + +NS_IMETHODIMP +nsLocalFile::AppendRelativePath(const nsAString &node) +{ + // append this path, multiple components are permitted + return AppendInternal(PromiseFlatString(node), true); +} + + +nsresult +nsLocalFile::AppendInternal(const nsAFlatString &node, bool multipleComponents) +{ + if (node.IsEmpty()) + return NS_OK; + + // check the relative path for validity + if (node.First() == L'\\' // can't start with an '\' + || node.FindChar(L'/') != kNotFound // can't contain / + || node.EqualsASCII("..")) // can't be .. + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + if (multipleComponents) + { + // can't contain .. as a path component. Ensure that the valid components + // "foo..foo", "..foo", and "foo.." are not falsely detected, + // but the invalid paths "..\", "foo\..", "foo\..\foo", + // "..\foo", etc are. + NS_NAMED_LITERAL_STRING(doubleDot, "\\.."); + nsAString::const_iterator start, end, offset; + node.BeginReading(start); + node.EndReading(end); + offset = end; + while (FindInReadable(doubleDot, start, offset)) + { + if (offset == end || *offset == L'\\') + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + start = offset; + offset = end; + } + + // catches the remaining cases of prefixes + if (StringBeginsWith(node, NS_LITERAL_STRING("..\\"))) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + // single components can't contain '\' + else if (node.FindChar(L'\\') != kNotFound) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + MakeDirty(); + + mWorkingPath.Append(NS_LITERAL_STRING("\\") + node); + + return NS_OK; +} + +#define TOUPPER(u) (((u) >= L'a' && (u) <= L'z') ? \ + (u) - (L'a' - L'A') : (u)) + +NS_IMETHODIMP +nsLocalFile::Normalize() +{ + // XXX See bug 187957 comment 18 for possible problems with this implementation. + + if (mWorkingPath.IsEmpty()) + return NS_OK; + + nsAutoString path(mWorkingPath); + + // find the index of the root backslash for the path. Everything before + // this is considered fully normalized and cannot be ascended beyond + // using ".." For a local drive this is the first slash (e.g. "c:\"). + // For a UNC path it is the slash following the share name + // (e.g. "\\server\share\"). + int32_t rootIdx = 2; // default to local drive + if (path.First() == L'\\') // if a share then calculate the rootIdx + { + rootIdx = path.FindChar(L'\\', 2); // skip \\ in front of the server + if (rootIdx == kNotFound) + return NS_OK; // already normalized + rootIdx = path.FindChar(L'\\', rootIdx+1); + if (rootIdx == kNotFound) + return NS_OK; // already normalized + } + else if (path.CharAt(rootIdx) != L'\\') + { + // The path has been specified relative to the current working directory + // for that drive. To normalize it, the current working directory for + // that drive needs to be inserted before the supplied relative path + // which will provide an absolute path (and the rootIdx will still be 2). + WCHAR cwd[MAX_PATH]; + WCHAR * pcwd = cwd; + int drive = TOUPPER(path.First()) - 'A' + 1; + /* We need to worry about IPH, for details read bug 419326. + * _getdrives - http://msdn2.microsoft.com/en-us/library/xdhk0xd2.aspx + * uses a bitmask, bit 0 is 'a:' + * _chdrive - http://msdn2.microsoft.com/en-us/library/0d1409hb.aspx + * _getdcwd - http://msdn2.microsoft.com/en-us/library/7t2zk3s4.aspx + * take an int, 1 is 'a:'. + * + * Because of this, we need to do some math. Subtract 1 to convert from + * _chdrive/_getdcwd format to _getdrives drive numbering. + * Shift left x bits to convert from integer indexing to bitfield indexing. + * And of course, we need to find out if the drive is in the bitmask. + * + * If we're really unlucky, we can still lose, but only if the user + * manages to eject the drive between our call to _getdrives() and + * our *calls* to _wgetdcwd. + */ + if (!((1 << (drive - 1)) & _getdrives())) + return NS_ERROR_FILE_INVALID_PATH; + if (!_wgetdcwd(drive, pcwd, MAX_PATH)) + pcwd = _wgetdcwd(drive, 0, 0); + if (!pcwd) + return NS_ERROR_OUT_OF_MEMORY; + nsAutoString currentDir(pcwd); + if (pcwd != cwd) + free(pcwd); + + if (currentDir.Last() == '\\') + path.Replace(0, 2, currentDir); + else + path.Replace(0, 2, currentDir + NS_LITERAL_STRING("\\")); + } + NS_POSTCONDITION(0 < rootIdx && rootIdx < (int32_t)path.Length(), "rootIdx is invalid"); + NS_POSTCONDITION(path.CharAt(rootIdx) == '\\', "rootIdx is invalid"); + + // if there is nothing following the root path then it is already normalized + if (rootIdx + 1 == (int32_t)path.Length()) + return NS_OK; + + // assign the root + const PRUnichar * pathBuffer = path.get(); // simplify access to the buffer + mWorkingPath.SetCapacity(path.Length()); // it won't ever grow longer + mWorkingPath.Assign(pathBuffer, rootIdx); + + // Normalize the path components. The actions taken are: + // + // "\\" condense to single backslash + // "." remove from path + // ".." up a directory + // "..." remove from path (any number of dots > 2) + // + // The last form is something that Windows 95 and 98 supported and + // is a shortcut for changing up multiple directories. Windows XP + // and ilk ignore it in a path, as is done here. + int32_t len, begin, end = rootIdx; + while (end < (int32_t)path.Length()) + { + // find the current segment (text between the backslashes) to + // be examined, this will set the following variables: + // begin == index of first char in segment + // end == index 1 char after last char in segment + // len == length of segment + begin = end + 1; + end = path.FindChar('\\', begin); + if (end == kNotFound) + end = path.Length(); + len = end - begin; + + // ignore double backslashes + if (len == 0) + continue; + + // len != 0, and interesting paths always begin with a dot + if (pathBuffer[begin] == '.') + { + // ignore single dots + if (len == 1) + continue; + + // handle multiple dots + if (len >= 2 && pathBuffer[begin+1] == L'.') + { + // back up a path component on double dot + if (len == 2) + { + int32_t prev = mWorkingPath.RFindChar('\\'); + if (prev >= rootIdx) + mWorkingPath.Truncate(prev); + continue; + } + + // length is > 2 and the first two characters are dots. + // if the rest of the string is dots, then ignore it. + int idx = len - 1; + for (; idx >= 2; --idx) + { + if (pathBuffer[begin+idx] != L'.') + break; + } + + // this is true if the loop above didn't break + // and all characters in this segment are dots. + if (idx < 2) + continue; + } + } + + // add the current component to the path, including the preceding backslash + mWorkingPath.Append(pathBuffer + begin - 1, len + 1); + } + + // kill trailing dots and spaces. + int32_t filePathLen = mWorkingPath.Length() - 1; + while(filePathLen > 0 && (mWorkingPath[filePathLen] == L' ' || + mWorkingPath[filePathLen] == L'.')) + { + mWorkingPath.Truncate(filePathLen--); + } + + MakeDirty(); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetLeafName(nsAString &aLeafName) +{ + aLeafName.Truncate(); + + if (mWorkingPath.IsEmpty()) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + int32_t offset = mWorkingPath.RFindChar(L'\\'); + + // if the working path is just a node without any lashes. + if (offset == kNotFound) + aLeafName = mWorkingPath; + else + aLeafName = Substring(mWorkingPath, offset + 1); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetLeafName(const nsAString &aLeafName) +{ + MakeDirty(); + + if (mWorkingPath.IsEmpty()) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + // cannot use nsCString::RFindChar() due to 0x5c problem + int32_t offset = mWorkingPath.RFindChar(L'\\'); + if (offset) + { + mWorkingPath.Truncate(offset+1); + } + mWorkingPath.Append(aLeafName); + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetPath(nsAString &_retval) +{ + _retval = mWorkingPath; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetCanonicalPath(nsAString &aResult) +{ + EnsureShortPath(); + aResult.Assign(mShortWorkingPath); + return NS_OK; +} + +typedef struct { + WORD wLanguage; + WORD wCodePage; +} LANGANDCODEPAGE; + +NS_IMETHODIMP +nsLocalFile::GetVersionInfoField(const char* aField, nsAString& _retval) +{ + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) + return rv; + + rv = NS_ERROR_FAILURE; + + // Cast away const-ness here because WinAPI functions don't understand it, + // the path is used for [in] parameters only however so it's safe. + WCHAR *path = const_cast<WCHAR*>(mFollowSymlinks ? mResolvedPath.get() + : mWorkingPath.get()); + + DWORD dummy; + DWORD size = ::GetFileVersionInfoSizeW(path, &dummy); + if (!size) + return rv; + + void* ver = calloc(size, 1); + if (!ver) + return NS_ERROR_OUT_OF_MEMORY; + + if (::GetFileVersionInfoW(path, 0, size, ver)) + { + LANGANDCODEPAGE* translate = nullptr; + UINT pageCount; + BOOL queryResult = ::VerQueryValueW(ver, L"\\VarFileInfo\\Translation", + (void**)&translate, &pageCount); + if (queryResult && translate) + { + for (int32_t i = 0; i < 2; ++i) + { + PRUnichar subBlock[MAX_PATH]; + _snwprintf(subBlock, MAX_PATH, + L"\\StringFileInfo\\%04x%04x\\%s", + (i == 0 ? translate[0].wLanguage + : ::GetUserDefaultLangID()), + translate[0].wCodePage, + NS_ConvertASCIItoUTF16( + nsDependentCString(aField)).get()); + subBlock[MAX_PATH - 1] = 0; + LPVOID value = nullptr; + UINT size; + queryResult = ::VerQueryValueW(ver, subBlock, &value, &size); + if (queryResult && value) + { + _retval.Assign(static_cast<PRUnichar*>(value)); + if (!_retval.IsEmpty()) + { + rv = NS_OK; + break; + } + } + } + } + } + free(ver); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::SetShortcut(nsIFile* targetFile, + nsIFile* workingDir, + const PRUnichar* args, + const PRUnichar* description, + nsIFile* iconFile, + int32_t iconIndex) +{ + bool exists; + nsresult rv = this->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + + const WCHAR* targetFilePath = NULL; + const WCHAR* workingDirPath = NULL; + const WCHAR* iconFilePath = NULL; + + nsAutoString targetFilePathAuto; + if (targetFile) { + rv = targetFile->GetPath(targetFilePathAuto); + if (NS_FAILED(rv)) { + return rv; + } + targetFilePath = targetFilePathAuto.get(); + } + + nsAutoString workingDirPathAuto; + if (workingDir) { + rv = workingDir->GetPath(workingDirPathAuto); + if (NS_FAILED(rv)) { + return rv; + } + workingDirPath = workingDirPathAuto.get(); + } + + nsAutoString iconPathAuto; + if (iconFile) { + rv = iconFile->GetPath(iconPathAuto); + if (NS_FAILED(rv)) { + return rv; + } + iconFilePath = iconPathAuto.get(); + } + + rv = gResolver->SetShortcut(exists, + mWorkingPath.get(), + targetFilePath, + workingDirPath, + args, + description, + iconFilePath, + iconFilePath? iconIndex : 0); + if (targetFilePath && NS_SUCCEEDED(rv)) { + MakeDirty(); + } + + return rv; +} + +/** + * Determines if the drive type for the specified file is rmeote or local. + * + * @param path The path of the file to check + * @param remote Out parameter, on function success holds true if the specified + * file path is remote, or false if the file path is local. + * @return true on success. The return value implies absolutely nothing about + * wether the file is local or remote. +*/ +static bool +IsRemoteFilePath(LPCWSTR path, bool &remote) +{ + // Obtain the parent directory path and make sure it ends with + // a trailing backslash. + WCHAR dirPath[MAX_PATH + 1] = { 0 }; + wcsncpy(dirPath, path, MAX_PATH); + if (!PathRemoveFileSpecW(dirPath)) { + return false; + } + size_t len = wcslen(dirPath); + // In case the dirPath holds exaclty MAX_PATH and remains unchanged, we + // recheck the required length here since we need to terminate it with + // a backslash. + if (len >= MAX_PATH) { + return false; + } + + dirPath[len] = L'\\'; + dirPath[len + 1] = L'\0'; + UINT driveType = GetDriveTypeW(dirPath); + remote = driveType == DRIVE_REMOTE; + return true; +} + +nsresult +nsLocalFile::CopySingleFile(nsIFile *sourceFile, nsIFile *destParent, + const nsAString &newName, + bool followSymlinks, bool move, + bool skipNtfsAclReset) +{ + nsresult rv; + nsAutoString filePath; + + // get the path that we are going to copy to. + // Since windows does not know how to auto + // resolve shortcuts, we must work with the + // target. + nsAutoString destPath; + destParent->GetTarget(destPath); + + destPath.Append('\\'); + + if (newName.IsEmpty()) + { + nsAutoString aFileName; + sourceFile->GetLeafName(aFileName); + destPath.Append(aFileName); + } + else + { + destPath.Append(newName); + } + + + if (followSymlinks) + { + rv = sourceFile->GetTarget(filePath); + if (filePath.IsEmpty()) + rv = sourceFile->GetPath(filePath); + } + else + { + rv = sourceFile->GetPath(filePath); + } + + if (NS_FAILED(rv)) + return rv; + + // Pass the flag COPY_FILE_NO_BUFFERING to CopyFileEx as we may be copying + // to a SMBV2 remote drive. Without this parameter subsequent append mode + // file writes can cause the resultant file to become corrupt. We only need to do + // this if the major version of Windows is > 5(Only Windows Vista and above + // can support SMBV2). With a 7200RPM hard drive: + // Copying a 1KB file with COPY_FILE_NO_BUFFERING takes about 30-60ms. + // Copying a 1KB file without COPY_FILE_NO_BUFFERING takes < 1ms. + // So we only use COPY_FILE_NO_BUFFERING when we have a remote drive. + int copyOK; + DWORD dwVersion = GetVersion(); + DWORD dwMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion))); + DWORD dwCopyFlags = COPY_FILE_ALLOW_DECRYPTED_DESTINATION; + if (dwMajorVersion > 5) { + bool path1Remote, path2Remote; + if (!IsRemoteFilePath(filePath.get(), path1Remote) || + !IsRemoteFilePath(destPath.get(), path2Remote) || + path1Remote || path2Remote) { + dwCopyFlags |= COPY_FILE_NO_BUFFERING; + } + } + + if (!move) + { + copyOK = ::CopyFileExW(filePath.get(), destPath.get(), NULL, NULL, NULL, dwCopyFlags); + } + else + { + copyOK = ::MoveFileExW(filePath.get(), destPath.get(), MOVEFILE_REPLACE_EXISTING); + + // Check if copying the source file to a different volume, + // as this could be an SMBV2 mapped drive. + if (!copyOK && GetLastError() == ERROR_NOT_SAME_DEVICE) + { + copyOK = CopyFileExW(filePath.get(), destPath.get(), NULL, NULL, NULL, dwCopyFlags); + + if (copyOK) + DeleteFileW(filePath.get()); + } + } + + if (!copyOK) // CopyFileEx and MoveFileEx return zero at failure. + rv = ConvertWinError(GetLastError()); + else if (move && !skipNtfsAclReset) + { + // Set security permissions to inherit from parent. + // Note: propagates to all children: slow for big file trees + PACL pOldDACL = NULL; + PSECURITY_DESCRIPTOR pSD = NULL; + ::GetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, NULL, &pOldDACL, NULL, &pSD); + if (pOldDACL) + ::SetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | + UNPROTECTED_DACL_SECURITY_INFORMATION, + NULL, NULL, pOldDACL, NULL); + if (pSD) + LocalFree((HLOCAL)pSD); + } + + return rv; +} + +nsresult +nsLocalFile::CopyMove(nsIFile *aParentDir, const nsAString &newName, bool followSymlinks, bool move) +{ + nsCOMPtr<nsIFile> newParentDir = aParentDir; + // check to see if this exists, otherwise return an error. + // we will check this by resolving. If the user wants us + // to follow links, then we are talking about the target, + // hence we can use the |followSymlinks| parameter. + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) + return rv; + + if (!newParentDir) + { + // no parent was specified. We must rename. + if (newName.IsEmpty()) + return NS_ERROR_INVALID_ARG; + + rv = GetParent(getter_AddRefs(newParentDir)); + if (NS_FAILED(rv)) + return rv; + } + + if (!newParentDir) + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + + // make sure it exists and is a directory. Create it if not there. + bool exists; + newParentDir->Exists(&exists); + if (!exists) + { + rv = newParentDir->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use + if (NS_FAILED(rv)) + return rv; + } + else + { + bool isDir; + newParentDir->IsDirectory(&isDir); + if (!isDir) + { + if (followSymlinks) + { + bool isLink; + newParentDir->IsSymlink(&isLink); + if (isLink) + { + nsAutoString target; + newParentDir->GetTarget(target); + + nsCOMPtr<nsIFile> realDest = new nsLocalFile(); + if (realDest == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + + rv = realDest->InitWithPath(target); + + if (NS_FAILED(rv)) + return rv; + + return CopyMove(realDest, newName, followSymlinks, move); + } + } + else + { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + } + } + + // Try different ways to move/copy files/directories + bool done = false; + bool isDir; + IsDirectory(&isDir); + bool isSymlink; + IsSymlink(&isSymlink); + + // Try to move the file or directory, or try to copy a single file (or non-followed symlink) + if (move || !isDir || (isSymlink && !followSymlinks)) + { + // Copy/Move single file, or move a directory + rv = CopySingleFile(this, newParentDir, newName, followSymlinks, move, + !aParentDir); + done = NS_SUCCEEDED(rv); + // If we are moving a directory and that fails, fallback on directory + // enumeration. See bug 231300 for details. + if (!done && !(move && isDir)) + return rv; + } + + // Not able to copy or move directly, so enumerate it + if (!done) + { + // create a new target destination in the new parentDir; + nsCOMPtr<nsIFile> target; + rv = newParentDir->Clone(getter_AddRefs(target)); + + if (NS_FAILED(rv)) + return rv; + + nsAutoString allocatedNewName; + if (newName.IsEmpty()) + { + bool isLink; + IsSymlink(&isLink); + if (isLink) + { + nsAutoString temp; + GetTarget(temp); + int32_t offset = temp.RFindChar(L'\\'); + if (offset == kNotFound) + allocatedNewName = temp; + else + allocatedNewName = Substring(temp, offset + 1); + } + else + { + GetLeafName(allocatedNewName);// this should be the leaf name of the + } + } + else + { + allocatedNewName = newName; + } + + rv = target->Append(allocatedNewName); + if (NS_FAILED(rv)) + return rv; + + allocatedNewName.Truncate(); + + // check if the destination directory already exists + target->Exists(&exists); + if (!exists) + { + // if the destination directory cannot be created, return an error + rv = target->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use + if (NS_FAILED(rv)) + return rv; + } + else + { + // check if the destination directory is writable and empty + bool isWritable; + + target->IsWritable(&isWritable); + if (!isWritable) + return NS_ERROR_FILE_ACCESS_DENIED; + + nsCOMPtr<nsISimpleEnumerator> targetIterator; + rv = target->GetDirectoryEntries(getter_AddRefs(targetIterator)); + if (NS_FAILED(rv)) + return rv; + + bool more; + targetIterator->HasMoreElements(&more); + // return error if target directory is not empty + if (more) + return NS_ERROR_FILE_DIR_NOT_EMPTY; + } + + nsDirEnumerator dirEnum; + + rv = dirEnum.Init(this); + if (NS_FAILED(rv)) { + NS_WARNING("dirEnum initialization failed"); + return rv; + } + + bool more = false; + while (NS_SUCCEEDED(dirEnum.HasMoreElements(&more)) && more) + { + nsCOMPtr<nsISupports> item; + nsCOMPtr<nsIFile> file; + dirEnum.GetNext(getter_AddRefs(item)); + file = do_QueryInterface(item); + if (file) + { + bool isDir, isLink; + + file->IsDirectory(&isDir); + file->IsSymlink(&isLink); + + if (move) + { + if (followSymlinks) + return NS_ERROR_FAILURE; + + rv = file->MoveTo(target, EmptyString()); + NS_ENSURE_SUCCESS(rv,rv); + } + else + { + if (followSymlinks) + rv = file->CopyToFollowingLinks(target, EmptyString()); + else + rv = file->CopyTo(target, EmptyString()); + NS_ENSURE_SUCCESS(rv,rv); + } + } + } + // we've finished moving all the children of this directory + // in the new directory. so now delete the directory + // note, we don't need to do a recursive delete. + // MoveTo() is recursive. At this point, + // we've already moved the children of the current folder + // to the new location. nothing should be left in the folder. + if (move) + { + rv = Remove(false /* recursive */); + NS_ENSURE_SUCCESS(rv,rv); + } + } + + + // If we moved, we want to adjust this. + if (move) + { + MakeDirty(); + + nsAutoString newParentPath; + newParentDir->GetPath(newParentPath); + + if (newParentPath.IsEmpty()) + return NS_ERROR_FAILURE; + + if (newName.IsEmpty()) + { + nsAutoString aFileName; + GetLeafName(aFileName); + + InitWithPath(newParentPath); + Append(aFileName); + } + else + { + InitWithPath(newParentPath); + Append(newName); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::CopyTo(nsIFile *newParentDir, const nsAString &newName) +{ + return CopyMove(newParentDir, newName, false, false); +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinks(nsIFile *newParentDir, const nsAString &newName) +{ + return CopyMove(newParentDir, newName, true, false); +} + +NS_IMETHODIMP +nsLocalFile::MoveTo(nsIFile *newParentDir, const nsAString &newName) +{ + return CopyMove(newParentDir, newName, false, true); +} + + +NS_IMETHODIMP +nsLocalFile::Load(PRLibrary * *_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + bool isFile; + nsresult rv = IsFile(&isFile); + + if (NS_FAILED(rv)) + return rv; + + if (! isFile) + return NS_ERROR_FILE_IS_DIRECTORY; + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcntImpl::SetActivityIsLegal(false); +#endif + + PRLibSpec libSpec; + libSpec.value.pathname_u = mResolvedPath.get(); + libSpec.type = PR_LibSpec_PathnameU; + *_retval = PR_LoadLibraryWithFlags(libSpec, 0); + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcntImpl::SetActivityIsLegal(true); +#endif + + if (*_retval) + return NS_OK; + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsLocalFile::Remove(bool recursive) +{ + // NOTE: + // + // if the working path points to a shortcut, then we will only + // delete the shortcut itself. even if the shortcut points to + // a directory, we will not recurse into that directory or + // delete that directory itself. likewise, if the shortcut + // points to a normal file, we will not delete the real file. + // this is done to be consistent with the other platforms that + // behave this way. we do this even if the followLinks attribute + // is set to true. this helps protect against misuse that could + // lead to security bugs (e.g., bug 210588). + // + // Since shortcut files are no longer permitted to be used as unix-like + // symlinks interspersed in the path (e.g. "c:/file.lnk/foo/bar.txt") + // this processing is a lot simpler. Even if the shortcut file is + // pointing to a directory, only the mWorkingPath value is used and so + // only the shortcut file will be deleted. + + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + bool isDir, isLink; + nsresult rv; + + isDir = false; + rv = IsSymlink(&isLink); + if (NS_FAILED(rv)) + return rv; + + // only check to see if we have a directory if it isn't a link + if (!isLink) + { + rv = IsDirectory(&isDir); + if (NS_FAILED(rv)) + return rv; + } + + if (isDir) + { + if (recursive) + { + nsDirEnumerator dirEnum; + + rv = dirEnum.Init(this); + if (NS_FAILED(rv)) + return rv; + + bool more = false; + while (NS_SUCCEEDED(dirEnum.HasMoreElements(&more)) && more) + { + nsCOMPtr<nsISupports> item; + dirEnum.GetNext(getter_AddRefs(item)); + nsCOMPtr<nsIFile> file = do_QueryInterface(item); + if (file) + file->Remove(recursive); + } + } + if (RemoveDirectoryW(mWorkingPath.get()) == 0) + return ConvertWinError(GetLastError()); + } + else + { + if (DeleteFileW(mWorkingPath.get()) == 0) + return ConvertWinError(GetLastError()); + } + + MakeDirty(); + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTime(PRTime *aLastModifiedTime) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(aLastModifiedTime); + + // get the modified time of the target as determined by mFollowSymlinks + // If true, then this will be for the target of the shortcut file, + // otherwise it will be for the shortcut file itself (i.e. the same + // results as GetLastModifiedTimeOfLink) + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) + return rv; + + // microseconds -> milliseconds + *aLastModifiedTime = mFileInfo64.modifyTime / PR_USEC_PER_MSEC; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTimeOfLink(PRTime *aLastModifiedTime) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(aLastModifiedTime); + + // The caller is assumed to have already called IsSymlink + // and to have found that this file is a link. + + PRFileInfo64 info; + nsresult rv = GetFileInfo(mWorkingPath, &info); + if (NS_FAILED(rv)) + return rv; + + // microseconds -> milliseconds + *aLastModifiedTime = info.modifyTime / PR_USEC_PER_MSEC; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) + return rv; + + // set the modified time of the target as determined by mFollowSymlinks + // If true, then this will be for the target of the shortcut file, + // otherwise it will be for the shortcut file itself (i.e. the same + // results as SetLastModifiedTimeOfLink) + + rv = SetModDate(aLastModifiedTime, mResolvedPath.get()); + if (NS_SUCCEEDED(rv)) + MakeDirty(); + + return rv; +} + + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime) +{ + // The caller is assumed to have already called IsSymlink + // and to have found that this file is a link. + + nsresult rv = SetModDate(aLastModifiedTime, mWorkingPath.get()); + if (NS_SUCCEEDED(rv)) + MakeDirty(); + + return rv; +} + +nsresult +nsLocalFile::SetModDate(PRTime aLastModifiedTime, const PRUnichar *filePath) +{ + // The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the + // modification time for directories. + HANDLE file = ::CreateFileW(filePath, // pointer to name of the file + GENERIC_WRITE, // access (write) mode + 0, // share mode + NULL, // pointer to security attributes + OPEN_EXISTING, // how to create + FILE_FLAG_BACKUP_SEMANTICS, // file attributes + NULL); + + if (file == INVALID_HANDLE_VALUE) + { + return ConvertWinError(GetLastError()); + } + + FILETIME ft; + SYSTEMTIME st; + PRExplodedTime pret; + + // PR_ExplodeTime expects usecs... + PR_ExplodeTime(aLastModifiedTime * PR_USEC_PER_MSEC, PR_GMTParameters, &pret); + st.wYear = pret.tm_year; + st.wMonth = pret.tm_month + 1; // Convert start offset -- Win32: Jan=1; NSPR: Jan=0 + st.wDayOfWeek = pret.tm_wday; + st.wDay = pret.tm_mday; + st.wHour = pret.tm_hour; + st.wMinute = pret.tm_min; + st.wSecond = pret.tm_sec; + st.wMilliseconds = pret.tm_usec/1000; + + nsresult rv = NS_OK; + // if at least one of these fails... + if (!(SystemTimeToFileTime(&st, &ft) != 0 && + SetFileTime(file, NULL, &ft, &ft) != 0)) + { + rv = ConvertWinError(GetLastError()); + } + + CloseHandle(file); + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissions(uint32_t *aPermissions) +{ + NS_ENSURE_ARG(aPermissions); + + // get the permissions of the target as determined by mFollowSymlinks + // If true, then this will be for the target of the shortcut file, + // otherwise it will be for the shortcut file itself (i.e. the same + // results as GetPermissionsOfLink) + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) + return rv; + + bool isWritable, isExecutable; + IsWritable(&isWritable); + IsExecutable(&isExecutable); + + *aPermissions = PR_IRUSR|PR_IRGRP|PR_IROTH; // all read + if (isWritable) + *aPermissions |= PR_IWUSR|PR_IWGRP|PR_IWOTH; // all write + if (isExecutable) + *aPermissions |= PR_IXUSR|PR_IXGRP|PR_IXOTH; // all execute + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissionsOfLink(uint32_t *aPermissions) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(aPermissions); + + // The caller is assumed to have already called IsSymlink + // and to have found that this file is a link. It is not + // possible for a link file to be executable. + + DWORD word = ::GetFileAttributesW(mWorkingPath.get()); + if (word == INVALID_FILE_ATTRIBUTES) + return NS_ERROR_FILE_INVALID_PATH; + + bool isWritable = !(word & FILE_ATTRIBUTE_READONLY); + *aPermissions = PR_IRUSR|PR_IRGRP|PR_IROTH; // all read + if (isWritable) + *aPermissions |= PR_IWUSR|PR_IWGRP|PR_IWOTH; // all write + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::SetPermissions(uint32_t aPermissions) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + // set the permissions of the target as determined by mFollowSymlinks + // If true, then this will be for the target of the shortcut file, + // otherwise it will be for the shortcut file itself (i.e. the same + // results as SetPermissionsOfLink) + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) + return rv; + + // windows only knows about the following permissions + int mode = 0; + if (aPermissions & (PR_IRUSR|PR_IRGRP|PR_IROTH)) // any read + mode |= _S_IREAD; + if (aPermissions & (PR_IWUSR|PR_IWGRP|PR_IWOTH)) // any write + mode |= _S_IWRITE; + + if (_wchmod(mResolvedPath.get(), mode) == -1) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions) +{ + // The caller is assumed to have already called IsSymlink + // and to have found that this file is a link. + + // windows only knows about the following permissions + int mode = 0; + if (aPermissions & (PR_IRUSR|PR_IRGRP|PR_IROTH)) // any read + mode |= _S_IREAD; + if (aPermissions & (PR_IWUSR|PR_IWGRP|PR_IWOTH)) // any write + mode |= _S_IWRITE; + + if (_wchmod(mWorkingPath.get(), mode) == -1) + return NS_ERROR_FAILURE; + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetFileSize(int64_t *aFileSize) +{ + NS_ENSURE_ARG(aFileSize); + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) + return rv; + + *aFileSize = mFileInfo64.size; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetFileSizeOfLink(int64_t *aFileSize) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(aFileSize); + + // The caller is assumed to have already called IsSymlink + // and to have found that this file is a link. + + PRFileInfo64 info; + if (NS_FAILED(GetFileInfo(mWorkingPath, &info))) + return NS_ERROR_FILE_INVALID_PATH; + + *aFileSize = info.size; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetFileSize(int64_t aFileSize) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) + return rv; + + HANDLE hFile = ::CreateFileW(mResolvedPath.get(),// pointer to name of the file + GENERIC_WRITE, // access (write) mode + FILE_SHARE_READ, // share mode + NULL, // pointer to security attributes + OPEN_EXISTING, // how to create + FILE_ATTRIBUTE_NORMAL, // file attributes + NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + return ConvertWinError(GetLastError()); + } + + // seek the file pointer to the new, desired end of file + // and then truncate the file at that position + rv = NS_ERROR_FAILURE; + aFileSize = MyFileSeek64(hFile, aFileSize, FILE_BEGIN); + if (aFileSize != -1 && SetEndOfFile(hFile)) + { + MakeDirty(); + rv = NS_OK; + } + + CloseHandle(hFile); + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetDiskSpaceAvailable(int64_t *aDiskSpaceAvailable) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(aDiskSpaceAvailable); + + ResolveAndStat(); + + if (mFileInfo64.type == PR_FILE_FILE) { + // Since GetDiskFreeSpaceExW works only on directories, use the parent. + nsCOMPtr<nsIFile> parent; + if (NS_SUCCEEDED(GetParent(getter_AddRefs(parent))) && parent) { + return parent->GetDiskSpaceAvailable(aDiskSpaceAvailable); + } + } + + ULARGE_INTEGER liFreeBytesAvailableToCaller, liTotalNumberOfBytes; + if (::GetDiskFreeSpaceExW(mResolvedPath.get(), &liFreeBytesAvailableToCaller, + &liTotalNumberOfBytes, NULL)) + { + *aDiskSpaceAvailable = liFreeBytesAvailableToCaller.QuadPart; + return NS_OK; + } + *aDiskSpaceAvailable = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetParent(nsIFile * *aParent) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG_POINTER(aParent); + + // A two-character path must be a drive such as C:, so it has no parent + if (mWorkingPath.Length() == 2) { + *aParent = nullptr; + return NS_OK; + } + + int32_t offset = mWorkingPath.RFindChar(PRUnichar('\\')); + // adding this offset check that was removed in bug 241708 fixes mail + // directories that aren't relative to/underneath the profile dir. + // e.g., on a different drive. Before you remove them, please make + // sure local mail directories that aren't underneath the profile dir work. + if (offset == kNotFound) + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + + // A path of the form \\NAME is a top-level path and has no parent + if (offset == 1 && mWorkingPath[0] == L'\\') { + *aParent = nullptr; + return NS_OK; + } + + nsAutoString parentPath(mWorkingPath); + + if (offset > 0) + parentPath.Truncate(offset); + else + parentPath.AssignLiteral("\\\\."); + + nsCOMPtr<nsIFile> localFile; + nsresult rv = NS_NewLocalFile(parentPath, mFollowSymlinks, getter_AddRefs(localFile)); + + if (NS_SUCCEEDED(rv) && localFile) { + return CallQueryInterface(localFile, aParent); + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::Exists(bool *_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(_retval); + *_retval = false; + + MakeDirty(); + nsresult rv = ResolveAndStat(); + *_retval = NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_IS_LOCKED; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsWritable(bool *aIsWritable) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + // The read-only attribute on a FAT directory only means that it can't + // be deleted. It is still possible to modify the contents of the directory. + nsresult rv = IsDirectory(aIsWritable); + if (rv == NS_ERROR_FILE_ACCESS_DENIED) { + *aIsWritable = true; + return NS_OK; + } else if (rv == NS_ERROR_FILE_IS_LOCKED) { + // If the file is normally allowed write access + // we should still return that the file is writable. + } else if (NS_FAILED(rv)) { + return rv; + } + if (*aIsWritable) + return NS_OK; + + // writable if the file doesn't have the readonly attribute + rv = HasFileAttribute(FILE_ATTRIBUTE_READONLY, aIsWritable); + if (rv == NS_ERROR_FILE_ACCESS_DENIED) { + *aIsWritable = false; + return NS_OK; + } else if (rv == NS_ERROR_FILE_IS_LOCKED) { + // If the file is normally allowed write access + // we should still return that the file is writable. + } else if (NS_FAILED(rv)) { + return rv; + } + *aIsWritable = !*aIsWritable; + + // If the read only attribute is not set, check to make sure + // we can open the file with write access. + if (*aIsWritable) { + PRFileDesc* file; + rv = OpenFile(mResolvedPath, PR_WRONLY, 0, &file); + if (NS_SUCCEEDED(rv)) { + PR_Close(file); + } else if (rv == NS_ERROR_FILE_ACCESS_DENIED) { + *aIsWritable = false; + } else if (rv == NS_ERROR_FILE_IS_LOCKED) { + // If it is locked and read only we would have + // gotten access denied + *aIsWritable = true; + } else { + return rv; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsReadable(bool *_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(_retval); + *_retval = false; + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) + return rv; + + *_retval = true; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::IsExecutable(bool *_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(_retval); + *_retval = false; + + nsresult rv; + + // only files can be executables + bool isFile; + rv = IsFile(&isFile); + if (NS_FAILED(rv)) + return rv; + if (!isFile) + return NS_OK; + + //TODO: shouldn't we be checking mFollowSymlinks here? + bool symLink; + rv = IsSymlink(&symLink); + if (NS_FAILED(rv)) + return rv; + + nsAutoString path; + if (symLink) + GetTarget(path); + else + GetPath(path); + + // kill trailing dots and spaces. + int32_t filePathLen = path.Length() - 1; + while(filePathLen > 0 && (path[filePathLen] == L' ' || path[filePathLen] == L'.')) + { + path.Truncate(filePathLen--); + } + + // Get extension. + int32_t dotIdx = path.RFindChar(PRUnichar('.')); + if ( dotIdx != kNotFound ) { + // Convert extension to lower case. + PRUnichar *p = path.BeginWriting(); + for( p+= dotIdx + 1; *p; p++ ) + *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0; + + // Search for any of the set of executable extensions. + static const char * const executableExts[] = { + "ad", + "ade", // access project extension + "adp", + "air", // Adobe AIR installer + "app", // executable application + "application", // from bug 348763 + "asp", + "bas", + "bat", + "chm", + "cmd", + "com", + "cpl", + "crt", + "exe", + "fxp", // FoxPro compiled app + "hlp", + "hta", + "inf", + "ins", + "isp", + "jar", // java application bundle + "js", + "jse", + "lnk", + "mad", // Access Module Shortcut + "maf", // Access + "mag", // Access Diagram Shortcut + "mam", // Access Macro Shortcut + "maq", // Access Query Shortcut + "mar", // Access Report Shortcut + "mas", // Access Stored Procedure + "mat", // Access Table Shortcut + "mau", // Media Attachment Unit + "mav", // Access View Shortcut + "maw", // Access Data Access Page + "mda", // Access Add-in, MDA Access 2 Workgroup + "mdb", + "mde", + "mdt", // Access Add-in Data + "mdw", // Access Workgroup Information + "mdz", // Access Wizard Template + "msc", + "msh", // Microsoft Shell + "mshxml", // Microsoft Shell + "msi", + "msp", + "mst", + "ops", // Office Profile Settings + "pcd", + "pif", + "plg", // Developer Studio Build Log + "prf", // windows system file + "prg", + "pst", + "reg", + "scf", // Windows explorer command + "scr", + "sct", + "shb", + "shs", + "url", + "vb", + "vbe", + "vbs", + "vsd", + "vsmacros", // Visual Studio .NET Binary-based Macro Project + "vss", + "vst", + "vsw", + "ws", + "wsc", + "wsf", + "wsh"}; + nsDependentSubstring ext = Substring(path, dotIdx + 1); + for ( size_t i = 0; i < ArrayLength(executableExts); i++ ) { + if ( ext.EqualsASCII(executableExts[i])) { + // Found a match. Set result and quit. + *_retval = true; + break; + } + } + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::IsDirectory(bool *_retval) +{ + return HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, _retval); +} + +NS_IMETHODIMP +nsLocalFile::IsFile(bool *_retval) +{ + nsresult rv = HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, _retval); + if (NS_SUCCEEDED(rv)) { + *_retval = !*_retval; + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::IsHidden(bool *_retval) +{ + return HasFileAttribute(FILE_ATTRIBUTE_HIDDEN, _retval); +} + +nsresult +nsLocalFile::HasFileAttribute(DWORD fileAttrib, bool *_retval) +{ + NS_ENSURE_ARG(_retval); + + nsresult rv = Resolve(); + if (NS_FAILED(rv)) { + return rv; + } + + DWORD attributes = GetFileAttributesW(mResolvedPath.get()); + if (INVALID_FILE_ATTRIBUTES == attributes) { + return ConvertWinError(GetLastError()); + } + + *_retval = ((attributes & fileAttrib) != 0); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSymlink(bool *_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_ENSURE_ARG(_retval); + + // unless it is a valid shortcut path it's not a symlink + if (!IsShortcutPath(mWorkingPath)) { + *_retval = false; + return NS_OK; + } + + // we need to know if this is a file or directory + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + // We should not check mFileInfo64.type here for PR_FILE_FILE because lnk + // files can point to directories or files. Important security checks + // depend on correctly identifying lnk files. mFileInfo64 now holds info + // about the target of the lnk file, not the actual lnk file! + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSpecial(bool *_retval) +{ + return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM, _retval); +} + +NS_IMETHODIMP +nsLocalFile::Equals(nsIFile *inFile, bool *_retval) +{ + NS_ENSURE_ARG(inFile); + NS_ENSURE_ARG(_retval); + + EnsureShortPath(); + + nsCOMPtr<nsILocalFileWin> lf(do_QueryInterface(inFile)); + if (!lf) { + *_retval = false; + return NS_OK; + } + + nsAutoString inFilePath; + lf->GetCanonicalPath(inFilePath); + + // Ok : Win9x + *_retval = _wcsicmp(mShortWorkingPath.get(), inFilePath.get()) == 0; + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::Contains(nsIFile *inFile, bool recur, bool *_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + *_retval = false; + + nsAutoString myFilePath; + if (NS_FAILED(GetTarget(myFilePath))) + GetPath(myFilePath); + + uint32_t myFilePathLen = myFilePath.Length(); + + nsAutoString inFilePath; + if (NS_FAILED(inFile->GetTarget(inFilePath))) + inFile->GetPath(inFilePath); + + // make sure that the |inFile|'s path has a trailing separator. + if (inFilePath.Length() >= myFilePathLen && inFilePath[myFilePathLen] == L'\\') + { + if (_wcsnicmp(myFilePath.get(), inFilePath.get(), myFilePathLen) == 0) + { + *_retval = true; + } + + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetTarget(nsAString &_retval) +{ + _retval.Truncate(); +#if STRICT_FAKE_SYMLINKS + bool symLink; + + nsresult rv = IsSymlink(&symLink); + if (NS_FAILED(rv)) + return rv; + + if (!symLink) + { + return NS_ERROR_FILE_INVALID_PATH; + } +#endif + ResolveAndStat(); + + _retval = mResolvedPath; + return NS_OK; +} + + +/* attribute bool followLinks; */ +NS_IMETHODIMP +nsLocalFile::GetFollowLinks(bool *aFollowLinks) +{ + *aFollowLinks = mFollowSymlinks; + return NS_OK; +} +NS_IMETHODIMP +nsLocalFile::SetFollowLinks(bool aFollowLinks) +{ + MakeDirty(); + mFollowSymlinks = aFollowLinks; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator * *entries) +{ + nsresult rv; + + *entries = nullptr; + if (mWorkingPath.EqualsLiteral("\\\\.")) { + nsDriveEnumerator *drives = new nsDriveEnumerator; + if (!drives) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(drives); + rv = drives->Init(); + if (NS_FAILED(rv)) { + NS_RELEASE(drives); + return rv; + } + *entries = drives; + return NS_OK; + } + + nsDirEnumerator* dirEnum = new nsDirEnumerator(); + if (dirEnum == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(dirEnum); + rv = dirEnum->Init(this); + if (NS_FAILED(rv)) + { + NS_RELEASE(dirEnum); + return rv; + } + + *entries = dirEnum; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPersistentDescriptor(nsACString &aPersistentDescriptor) +{ + CopyUTF16toUTF8(mWorkingPath, aPersistentDescriptor); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetPersistentDescriptor(const nsACString &aPersistentDescriptor) +{ + if (IsUTF8(aPersistentDescriptor)) + return InitWithPath(NS_ConvertUTF8toUTF16(aPersistentDescriptor)); + else + return InitWithNativePath(aPersistentDescriptor); +} + +/* attrib unsigned long fileAttributesWin; */ +NS_IMETHODIMP +nsLocalFile::GetFileAttributesWin(uint32_t *aAttribs) +{ + *aAttribs = 0; + DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get()); + if (dwAttrs == INVALID_FILE_ATTRIBUTES) + return NS_ERROR_FILE_INVALID_PATH; + + if (!(dwAttrs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)) + *aAttribs |= WFA_SEARCH_INDEXED; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetFileAttributesWin(uint32_t aAttribs) +{ + DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get()); + if (dwAttrs == INVALID_FILE_ATTRIBUTES) + return NS_ERROR_FILE_INVALID_PATH; + + if (aAttribs & WFA_SEARCH_INDEXED) { + dwAttrs &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + } else { + dwAttrs |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + } + + if (aAttribs & WFA_READONLY) { + dwAttrs |= FILE_ATTRIBUTE_READONLY; + } else if ((aAttribs & WFA_READWRITE) && + (dwAttrs & FILE_ATTRIBUTE_READONLY)) { + dwAttrs &= ~FILE_ATTRIBUTE_READONLY; + } + + if (SetFileAttributesW(mWorkingPath.get(), dwAttrs) == 0) + return NS_ERROR_FAILURE; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::Reveal() +{ + // This API should be main thread only + MOZ_ASSERT(NS_IsMainThread()); + + // make sure mResolvedPath is set + nsresult rv = Resolve(); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { + return rv; + } + + // To create a new thread, get the thread manager + nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID); + nsCOMPtr<nsIThread> mythread; + rv = tm->NewThread(0, 0, getter_AddRefs(mythread)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIRunnable> runnable = + new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::RevealOp, + mResolvedPath); + + // After the dispatch, the result runnable will shut down the worker + // thread, so we can let it go. + mythread->Dispatch(runnable, NS_DISPATCH_NORMAL); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Launch() +{ + // This API should be main thread only + MOZ_ASSERT(NS_IsMainThread()); + + // make sure mResolvedPath is set + nsresult rv = Resolve(); + if (NS_FAILED(rv)) + return rv; + + // To create a new thread, get the thread manager + nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID); + nsCOMPtr<nsIThread> mythread; + rv = tm->NewThread(0, 0, getter_AddRefs(mythread)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIRunnable> runnable = + new AsyncLocalFileWinOperation(AsyncLocalFileWinOperation::LaunchOp, + mResolvedPath); + + // After the dispatch, the result runnable will shut down the worker + // thread, so we can let it go. + mythread->Dispatch(runnable, NS_DISPATCH_NORMAL); + return NS_OK; +} + +nsresult +NS_NewLocalFile(const nsAString &path, bool followLinks, nsIFile* *result) +{ + nsLocalFile* file = new nsLocalFile(); + if (file == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(file); + + file->SetFollowLinks(followLinks); + + if (!path.IsEmpty()) { + nsresult rv = file->InitWithPath(path); + if (NS_FAILED(rv)) { + NS_RELEASE(file); + return rv; + } + } + + *result = file; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// Native (lossy) interface +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsLocalFile::InitWithNativePath(const nsACString &filePath) +{ + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(filePath, tmp); + if (NS_SUCCEEDED(rv)) + return InitWithPath(tmp); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::AppendNative(const nsACString &node) +{ + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(node, tmp); + if (NS_SUCCEEDED(rv)) + return Append(tmp); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::AppendRelativeNativePath(const nsACString &node) +{ + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(node, tmp); + if (NS_SUCCEEDED(rv)) + return AppendRelativePath(tmp); + return rv; +} + + +NS_IMETHODIMP +nsLocalFile::GetNativeLeafName(nsACString &aLeafName) +{ + //NS_WARNING("This API is lossy. Use GetLeafName !"); + nsAutoString tmp; + nsresult rv = GetLeafName(tmp); + if (NS_SUCCEEDED(rv)) + rv = NS_CopyUnicodeToNative(tmp, aLeafName); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::SetNativeLeafName(const nsACString &aLeafName) +{ + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aLeafName, tmp); + if (NS_SUCCEEDED(rv)) + return SetLeafName(tmp); + + return rv; +} + + +NS_IMETHODIMP +nsLocalFile::GetNativePath(nsACString &_retval) +{ + //NS_WARNING("This API is lossy. Use GetPath !"); + nsAutoString tmp; + nsresult rv = GetPath(tmp); + if (NS_SUCCEEDED(rv)) + rv = NS_CopyUnicodeToNative(tmp, _retval); + + return rv; +} + + +NS_IMETHODIMP +nsLocalFile::GetNativeCanonicalPath(nsACString &aResult) +{ + NS_WARNING("This method is lossy. Use GetCanonicalPath !"); + EnsureShortPath(); + NS_CopyUnicodeToNative(mShortWorkingPath, aResult); + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::CopyToNative(nsIFile *newParentDir, const nsACString &newName) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (newName.IsEmpty()) + return CopyTo(newParentDir, EmptyString()); + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(newName, tmp); + if (NS_SUCCEEDED(rv)) + return CopyTo(newParentDir, tmp); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinksNative(nsIFile *newParentDir, const nsACString &newName) +{ + if (newName.IsEmpty()) + return CopyToFollowingLinks(newParentDir, EmptyString()); + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(newName, tmp); + if (NS_SUCCEEDED(rv)) + return CopyToFollowingLinks(newParentDir, tmp); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::MoveToNative(nsIFile *newParentDir, const nsACString &newName) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (newName.IsEmpty()) + return MoveTo(newParentDir, EmptyString()); + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(newName, tmp); + if (NS_SUCCEEDED(rv)) + return MoveTo(newParentDir, tmp); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetNativeTarget(nsACString &_retval) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + NS_WARNING("This API is lossy. Use GetTarget !"); + nsAutoString tmp; + nsresult rv = GetTarget(tmp); + if (NS_SUCCEEDED(rv)) + rv = NS_CopyUnicodeToNative(tmp, _retval); + + return rv; +} + +nsresult +NS_NewNativeLocalFile(const nsACString &path, bool followLinks, nsIFile* *result) +{ + nsAutoString buf; + nsresult rv = NS_CopyNativeToUnicode(path, buf); + if (NS_FAILED(rv)) { + *result = nullptr; + return rv; + } + return NS_NewLocalFile(buf, followLinks, result); +} + +void +nsLocalFile::EnsureShortPath() +{ + if (!mShortWorkingPath.IsEmpty()) + return; + + WCHAR shortPath[MAX_PATH + 1]; + DWORD lengthNeeded = ::GetShortPathNameW(mWorkingPath.get(), shortPath, + ArrayLength(shortPath)); + // If an error occurred then lengthNeeded is set to 0 or the length of the + // needed buffer including NULL termination. If it succeeds the number of + // wide characters not including NULL termination is returned. + if (lengthNeeded != 0 && lengthNeeded < ArrayLength(shortPath)) + mShortWorkingPath.Assign(shortPath); + else + mShortWorkingPath.Assign(mWorkingPath); +} + +// nsIHashable + +NS_IMETHODIMP +nsLocalFile::Equals(nsIHashable* aOther, bool *aResult) +{ + nsCOMPtr<nsIFile> otherfile(do_QueryInterface(aOther)); + if (!otherfile) { + *aResult = false; + return NS_OK; + } + + return Equals(otherfile, aResult); +} + +NS_IMETHODIMP +nsLocalFile::GetHashCode(uint32_t *aResult) +{ + // In order for short and long path names to hash to the same value we + // always hash on the short pathname. + EnsureShortPath(); + + *aResult = HashString(mShortWorkingPath); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsLocalFile <static members> +//----------------------------------------------------------------------------- + +void +nsLocalFile::GlobalInit() +{ + DebugOnly<nsresult> rv = NS_CreateShortcutResolver(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Shortcut resolver could not be created"); +} + +void +nsLocalFile::GlobalShutdown() +{ + NS_DestroyShortcutResolver(); +} + +NS_IMPL_ISUPPORTS1(nsDriveEnumerator, nsISimpleEnumerator) + +nsDriveEnumerator::nsDriveEnumerator() +{ +} + +nsDriveEnumerator::~nsDriveEnumerator() +{ +} + +nsresult nsDriveEnumerator::Init() +{ + /* If the length passed to GetLogicalDriveStrings is smaller + * than the length of the string it would return, it returns + * the length required for the string. */ + DWORD length = GetLogicalDriveStringsW(0, 0); + /* The string is null terminated */ + if (!mDrives.SetLength(length+1, fallible_t())) + return NS_ERROR_OUT_OF_MEMORY; + if (!GetLogicalDriveStringsW(length, mDrives.BeginWriting())) + return NS_ERROR_FAILURE; + mDrives.BeginReading(mStartOfCurrentDrive); + mDrives.EndReading(mEndOfDrivesString); + return NS_OK; +} + +NS_IMETHODIMP nsDriveEnumerator::HasMoreElements(bool *aHasMore) +{ + *aHasMore = *mStartOfCurrentDrive != L'\0'; + return NS_OK; +} + +NS_IMETHODIMP nsDriveEnumerator::GetNext(nsISupports **aNext) +{ + /* GetLogicalDrives stored in mDrives is a concatenation + * of null terminated strings, followed by a null terminator. + * mStartOfCurrentDrive is an iterator pointing at the first + * character of the current drive. */ + if (*mStartOfCurrentDrive == L'\0') { + *aNext = nullptr; + return NS_OK; + } + + nsAString::const_iterator driveEnd = mStartOfCurrentDrive; + FindCharInReadable(L'\0', driveEnd, mEndOfDrivesString); + nsString drive(Substring(mStartOfCurrentDrive, driveEnd)); + mStartOfCurrentDrive = ++driveEnd; + + nsIFile *file; + nsresult rv = NS_NewLocalFile(drive, false, &file); + + *aNext = file; + return rv; +} diff --git a/xpcom/io/nsLocalFileWin.h b/xpcom/io/nsLocalFileWin.h new file mode 100644 index 000000000..e60020882 --- /dev/null +++ b/xpcom/io/nsLocalFileWin.h @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef _nsLocalFileWIN_H_ +#define _nsLocalFileWIN_H_ + +#include "nscore.h" +#include "nsError.h" +#include "nsString.h" +#include "nsCRT.h" +#include "nsIFile.h" +#include "nsIFactory.h" +#include "nsILocalFileWin.h" +#include "nsIHashable.h" +#include "nsIClassInfoImpl.h" + +#include "mozilla/Attributes.h" + +#include "windows.h" +#include "shlobj.h" + +#include <sys/stat.h> + +class nsLocalFile MOZ_FINAL : public nsILocalFileWin, + public nsIHashable +{ +public: + NS_DEFINE_STATIC_CID_ACCESSOR(NS_LOCAL_FILE_CID) + + nsLocalFile(); + + static nsresult nsLocalFileConstructor(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr); + + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsIFile interface + NS_DECL_NSIFILE + + // nsILocalFile interface + NS_DECL_NSILOCALFILE + + // nsILocalFileWin interface + NS_DECL_NSILOCALFILEWIN + + // nsIHashable interface + NS_DECL_NSIHASHABLE + +public: + static void GlobalInit(); + static void GlobalShutdown(); + +private: + nsLocalFile(const nsLocalFile& other); + ~nsLocalFile() {} + + bool mDirty; // cached information can only be used when this is false + bool mResolveDirty; + bool mFollowSymlinks; // should we follow symlinks when working on this file + + // this string will always be in native format! + nsString mWorkingPath; + + // this will be the resolved path of shortcuts, it will *NEVER* + // be returned to the user + nsString mResolvedPath; + + // this string, if not empty, is the *short* pathname that represents + // mWorkingPath + nsString mShortWorkingPath; + + PRFileInfo64 mFileInfo64; + + void MakeDirty() + { + mDirty = true; + mResolveDirty = true; + mShortWorkingPath.Truncate(); + } + + nsresult ResolveAndStat(); + nsresult Resolve(); + nsresult ResolveShortcut(); + + void EnsureShortPath(); + + nsresult CopyMove(nsIFile *newParentDir, const nsAString &newName, + bool followSymlinks, bool move); + nsresult CopySingleFile(nsIFile *source, nsIFile* dest, + const nsAString &newName, + bool followSymlinks, bool move, + bool skipNtfsAclReset = false); + + nsresult SetModDate(int64_t aLastModifiedTime, const PRUnichar *filePath); + nsresult HasFileAttribute(DWORD fileAttrib, bool *_retval); + nsresult AppendInternal(const nsAFlatString &node, + bool multipleComponents); +}; + +#endif diff --git a/xpcom/io/nsMultiplexInputStream.cpp b/xpcom/io/nsMultiplexInputStream.cpp new file mode 100644 index 000000000..553e56e20 --- /dev/null +++ b/xpcom/io/nsMultiplexInputStream.cpp @@ -0,0 +1,683 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/** + * The multiplex stream concatenates a list of input streams into a single + * stream. + */ + +#include "mozilla/Attributes.h" +#include "mozilla/MathAlgorithms.h" + +#include "base/basictypes.h" + +#include "nsMultiplexInputStream.h" +#include "nsIMultiplexInputStream.h" +#include "nsISeekableStream.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsIClassInfoImpl.h" +#include "nsIIPCSerializableInputStream.h" +#include "mozilla/ipc/InputStreamUtils.h" + +using namespace mozilla::ipc; + +using mozilla::DeprecatedAbs; + +class nsMultiplexInputStream MOZ_FINAL : public nsIMultiplexInputStream, + public nsISeekableStream, + public nsIIPCSerializableInputStream +{ +public: + nsMultiplexInputStream(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIMULTIPLEXINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + +private: + ~nsMultiplexInputStream() {} + + struct ReadSegmentsState { + nsIInputStream* mThisStream; + uint32_t mOffset; + nsWriteSegmentFun mWriter; + void* mClosure; + bool mDone; + }; + + static NS_METHOD ReadSegCb(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t *aWriteCount); + + nsTArray<nsCOMPtr<nsIInputStream> > mStreams; + uint32_t mCurrentStream; + bool mStartedReadingCurrent; + nsresult mStatus; +}; + +NS_IMPL_THREADSAFE_ADDREF(nsMultiplexInputStream) +NS_IMPL_THREADSAFE_RELEASE(nsMultiplexInputStream) + +NS_IMPL_CLASSINFO(nsMultiplexInputStream, NULL, nsIClassInfo::THREADSAFE, + NS_MULTIPLEXINPUTSTREAM_CID) + +NS_IMPL_QUERY_INTERFACE4_CI(nsMultiplexInputStream, + nsIMultiplexInputStream, + nsIInputStream, + nsISeekableStream, + nsIIPCSerializableInputStream) +NS_IMPL_CI_INTERFACE_GETTER3(nsMultiplexInputStream, + nsIMultiplexInputStream, + nsIInputStream, + nsISeekableStream) + +nsMultiplexInputStream::nsMultiplexInputStream() + : mCurrentStream(0), + mStartedReadingCurrent(false), + mStatus(NS_OK) +{ +} + +/* readonly attribute unsigned long count; */ +NS_IMETHODIMP +nsMultiplexInputStream::GetCount(uint32_t *aCount) +{ + *aCount = mStreams.Length(); + return NS_OK; +} + +#ifdef DEBUG +static bool +SeekableStreamAtBeginning(nsIInputStream *aStream) +{ + int64_t streamPos; + nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(aStream); + if (stream && NS_SUCCEEDED(stream->Tell(&streamPos)) && streamPos != 0) { + return false; + } + return true; +} +#endif + +/* void appendStream (in nsIInputStream stream); */ +NS_IMETHODIMP +nsMultiplexInputStream::AppendStream(nsIInputStream *aStream) +{ + NS_ASSERTION(SeekableStreamAtBeginning(aStream), "Appended stream not at beginning."); + return mStreams.AppendElement(aStream) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +/* void insertStream (in nsIInputStream stream, in unsigned long index); */ +NS_IMETHODIMP +nsMultiplexInputStream::InsertStream(nsIInputStream *aStream, uint32_t aIndex) +{ + NS_ASSERTION(SeekableStreamAtBeginning(aStream), "Inserted stream not at beginning."); + bool result = mStreams.InsertElementAt(aIndex, aStream); + NS_ENSURE_TRUE(result, NS_ERROR_OUT_OF_MEMORY); + if (mCurrentStream > aIndex || + (mCurrentStream == aIndex && mStartedReadingCurrent)) + ++mCurrentStream; + return NS_OK; +} + +/* void removeStream (in unsigned long index); */ +NS_IMETHODIMP +nsMultiplexInputStream::RemoveStream(uint32_t aIndex) +{ + mStreams.RemoveElementAt(aIndex); + if (mCurrentStream > aIndex) + --mCurrentStream; + else if (mCurrentStream == aIndex) + mStartedReadingCurrent = false; + + return NS_OK; +} + +/* nsIInputStream getStream (in unsigned long index); */ +NS_IMETHODIMP +nsMultiplexInputStream::GetStream(uint32_t aIndex, nsIInputStream **_retval) +{ + *_retval = mStreams.SafeElementAt(aIndex, nullptr); + NS_ENSURE_TRUE(*_retval, NS_ERROR_NOT_AVAILABLE); + + NS_ADDREF(*_retval); + return NS_OK; +} + +/* void close (); */ +NS_IMETHODIMP +nsMultiplexInputStream::Close() +{ + mStatus = NS_BASE_STREAM_CLOSED; + + nsresult rv = NS_OK; + + uint32_t len = mStreams.Length(); + for (uint32_t i = 0; i < len; ++i) { + nsresult rv2 = mStreams[i]->Close(); + // We still want to close all streams, but we should return an error + if (NS_FAILED(rv2)) + rv = rv2; + } + return rv; +} + +/* unsigned long long available (); */ +NS_IMETHODIMP +nsMultiplexInputStream::Available(uint64_t *_retval) +{ + if (NS_FAILED(mStatus)) + return mStatus; + + nsresult rv; + uint64_t avail = 0; + + uint32_t len = mStreams.Length(); + for (uint32_t i = mCurrentStream; i < len; i++) { + uint64_t streamAvail; + rv = mStreams[i]->Available(&streamAvail); + NS_ENSURE_SUCCESS(rv, rv); + avail += streamAvail; + } + *_retval = avail; + return NS_OK; +} + +/* [noscript] unsigned long read (in charPtr buf, in unsigned long count); */ +NS_IMETHODIMP +nsMultiplexInputStream::Read(char * aBuf, uint32_t aCount, uint32_t *_retval) +{ + // It is tempting to implement this method in terms of ReadSegments, but + // that would prevent this class from being used with streams that only + // implement Read (e.g., file streams). + + *_retval = 0; + + if (mStatus == NS_BASE_STREAM_CLOSED) + return NS_OK; + if (NS_FAILED(mStatus)) + return mStatus; + + nsresult rv = NS_OK; + + uint32_t len = mStreams.Length(); + while (mCurrentStream < len && aCount) { + uint32_t read; + rv = mStreams[mCurrentStream]->Read(aBuf, aCount, &read); + + // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF. + // (This is a bug in those stream implementations) + if (rv == NS_BASE_STREAM_CLOSED) { + NS_NOTREACHED("Input stream's Read method returned NS_BASE_STREAM_CLOSED"); + rv = NS_OK; + read = 0; + } + else if (NS_FAILED(rv)) + break; + + if (read == 0) { + ++mCurrentStream; + mStartedReadingCurrent = false; + } + else { + NS_ASSERTION(aCount >= read, "Read more than requested"); + *_retval += read; + aCount -= read; + aBuf += read; + mStartedReadingCurrent = true; + } + } + return *_retval ? NS_OK : rv; +} + +/* [noscript] unsigned long readSegments (in nsWriteSegmentFun writer, + * in voidPtr closure, + * in unsigned long count); */ +NS_IMETHODIMP +nsMultiplexInputStream::ReadSegments(nsWriteSegmentFun aWriter, void *aClosure, + uint32_t aCount, uint32_t *_retval) +{ + if (mStatus == NS_BASE_STREAM_CLOSED) { + *_retval = 0; + return NS_OK; + } + if (NS_FAILED(mStatus)) + return mStatus; + + NS_ASSERTION(aWriter, "missing aWriter"); + + nsresult rv = NS_OK; + ReadSegmentsState state; + state.mThisStream = this; + state.mOffset = 0; + state.mWriter = aWriter; + state.mClosure = aClosure; + state.mDone = false; + + uint32_t len = mStreams.Length(); + while (mCurrentStream < len && aCount) { + uint32_t read; + rv = mStreams[mCurrentStream]->ReadSegments(ReadSegCb, &state, aCount, &read); + + // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF. + // (This is a bug in those stream implementations) + if (rv == NS_BASE_STREAM_CLOSED) { + NS_NOTREACHED("Input stream's Read method returned NS_BASE_STREAM_CLOSED"); + rv = NS_OK; + read = 0; + } + + // if |aWriter| decided to stop reading segments... + if (state.mDone || NS_FAILED(rv)) + break; + + // if stream is empty, then advance to the next stream. + if (read == 0) { + ++mCurrentStream; + mStartedReadingCurrent = false; + } + else { + NS_ASSERTION(aCount >= read, "Read more than requested"); + state.mOffset += read; + aCount -= read; + mStartedReadingCurrent = true; + } + } + + // if we successfully read some data, then this call succeeded. + *_retval = state.mOffset; + return state.mOffset ? NS_OK : rv; +} + +NS_METHOD +nsMultiplexInputStream::ReadSegCb(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t *aWriteCount) +{ + nsresult rv; + ReadSegmentsState* state = (ReadSegmentsState*)aClosure; + rv = (state->mWriter)(state->mThisStream, + state->mClosure, + aFromRawSegment, + aToOffset + state->mOffset, + aCount, + aWriteCount); + if (NS_FAILED(rv)) + state->mDone = true; + return rv; +} + +/* readonly attribute boolean nonBlocking; */ +NS_IMETHODIMP +nsMultiplexInputStream::IsNonBlocking(bool *aNonBlocking) +{ + uint32_t len = mStreams.Length(); + if (len == 0) { + // Claim to be non-blocking, since we won't block the caller. + // On the other hand we'll never return NS_BASE_STREAM_WOULD_BLOCK, + // so maybe we should claim to be blocking? It probably doesn't + // matter in practice. + *aNonBlocking = true; + return NS_OK; + } + for (uint32_t i = 0; i < len; ++i) { + nsresult rv = mStreams[i]->IsNonBlocking(aNonBlocking); + NS_ENSURE_SUCCESS(rv, rv); + // If one is non-blocking the entire stream becomes non-blocking + // (except that we don't implement nsIAsyncInputStream, so there's + // not much for the caller to do if Read returns "would block") + if (*aNonBlocking) + return NS_OK; + } + return NS_OK; +} + +/* void seek (in int32_t whence, in int32_t offset); */ +NS_IMETHODIMP +nsMultiplexInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + if (NS_FAILED(mStatus)) + return mStatus; + + nsresult rv; + + uint32_t oldCurrentStream = mCurrentStream; + bool oldStartedReadingCurrent = mStartedReadingCurrent; + + if (aWhence == NS_SEEK_SET) { + int64_t remaining = aOffset; + if (aOffset == 0) { + mCurrentStream = 0; + } + for (uint32_t i = 0; i < mStreams.Length(); ++i) { + nsCOMPtr<nsISeekableStream> stream = + do_QueryInterface(mStreams[i]); + if (!stream) { + return NS_ERROR_FAILURE; + } + + // See if all remaining streams should be rewound + if (remaining == 0) { + if (i < oldCurrentStream || + (i == oldCurrentStream && oldStartedReadingCurrent)) { + rv = stream->Seek(NS_SEEK_SET, 0); + NS_ENSURE_SUCCESS(rv, rv); + continue; + } + else { + break; + } + } + + // Get position in current stream + int64_t streamPos; + if (i > oldCurrentStream || + (i == oldCurrentStream && !oldStartedReadingCurrent)) { + streamPos = 0; + } + else { + rv = stream->Tell(&streamPos); + NS_ENSURE_SUCCESS(rv, rv); + } + + // See if we need to seek current stream forward or backward + if (remaining < streamPos) { + rv = stream->Seek(NS_SEEK_SET, remaining); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentStream = i; + mStartedReadingCurrent = remaining != 0; + + remaining = 0; + } + else if (remaining > streamPos) { + if (i < oldCurrentStream) { + // We're already at end so no need to seek this stream + remaining -= streamPos; + NS_ASSERTION(remaining >= 0, "Remaining invalid"); + } + else { + uint64_t avail; + rv = mStreams[i]->Available(&avail); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t newPos = XPCOM_MIN(remaining, streamPos + (int64_t)avail); + + rv = stream->Seek(NS_SEEK_SET, newPos); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentStream = i; + mStartedReadingCurrent = true; + + remaining -= newPos; + NS_ASSERTION(remaining >= 0, "Remaining invalid"); + } + } + else { + NS_ASSERTION(remaining == streamPos, "Huh?"); + remaining = 0; + } + } + + return NS_OK; + } + + if (aWhence == NS_SEEK_CUR && aOffset > 0) { + int64_t remaining = aOffset; + for (uint32_t i = mCurrentStream; remaining && i < mStreams.Length(); ++i) { + nsCOMPtr<nsISeekableStream> stream = + do_QueryInterface(mStreams[i]); + + uint64_t avail; + rv = mStreams[i]->Available(&avail); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t seek = XPCOM_MIN((int64_t)avail, remaining); + + rv = stream->Seek(NS_SEEK_CUR, seek); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentStream = i; + mStartedReadingCurrent = true; + + remaining -= seek; + } + + return NS_OK; + } + + if (aWhence == NS_SEEK_CUR && aOffset < 0) { + int64_t remaining = -aOffset; + for (uint32_t i = mCurrentStream; remaining && i != (uint32_t)-1; --i) { + nsCOMPtr<nsISeekableStream> stream = + do_QueryInterface(mStreams[i]); + + int64_t pos; + rv = stream->Tell(&pos); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t seek = XPCOM_MIN(pos, remaining); + + rv = stream->Seek(NS_SEEK_CUR, -seek); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentStream = i; + mStartedReadingCurrent = seek != -pos; + + remaining -= seek; + } + + return NS_OK; + } + + if (aWhence == NS_SEEK_CUR) { + NS_ASSERTION(aOffset == 0, "Should have handled all non-zero values"); + + return NS_OK; + } + + if (aWhence == NS_SEEK_END) { + if (aOffset > 0) { + return NS_ERROR_INVALID_ARG; + } + int64_t remaining = aOffset; + for (uint32_t i = mStreams.Length() - 1; i != (uint32_t)-1; --i) { + nsCOMPtr<nsISeekableStream> stream = + do_QueryInterface(mStreams[i]); + + // See if all remaining streams should be seeked to end + if (remaining == 0) { + if (i >= oldCurrentStream) { + rv = stream->Seek(NS_SEEK_END, 0); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + break; + } + } + + // Get position in current stream + int64_t streamPos; + if (i < oldCurrentStream) { + streamPos = 0; + } else { + uint64_t avail; + rv = mStreams[i]->Available(&avail); + NS_ENSURE_SUCCESS(rv, rv); + + streamPos = avail; + } + + // See if we have enough data in the current stream. + if (DeprecatedAbs(remaining) < streamPos) { + rv = stream->Seek(NS_SEEK_END, remaining); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentStream = i; + mStartedReadingCurrent = true; + + remaining = 0; + } else if (DeprecatedAbs(remaining) > streamPos) { + if (i > oldCurrentStream || + (i == oldCurrentStream && !oldStartedReadingCurrent)) { + // We're already at start so no need to seek this stream + remaining += streamPos; + } else { + int64_t avail; + rv = stream->Tell(&avail); + NS_ENSURE_SUCCESS(rv, rv); + + int64_t newPos = streamPos + XPCOM_MIN(avail, DeprecatedAbs(remaining)); + + rv = stream->Seek(NS_SEEK_END, -newPos); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentStream = i; + mStartedReadingCurrent = true; + + remaining += newPos; + } + } + else { + NS_ASSERTION(remaining == streamPos, "Huh?"); + remaining = 0; + } + } + + return NS_OK; + } + + // other Seeks not implemented yet + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* uint32_t tell (); */ +NS_IMETHODIMP +nsMultiplexInputStream::Tell(int64_t *_retval) +{ + if (NS_FAILED(mStatus)) + return mStatus; + + nsresult rv; + int64_t ret64 = 0; + uint32_t i, last; + last = mStartedReadingCurrent ? mCurrentStream+1 : mCurrentStream; + for (i = 0; i < last; ++i) { + nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStreams[i]); + NS_ENSURE_TRUE(stream, NS_ERROR_NO_INTERFACE); + + int64_t pos; + rv = stream->Tell(&pos); + NS_ENSURE_SUCCESS(rv, rv); + ret64 += pos; + } + *_retval = ret64; + + return NS_OK; +} + +/* void setEOF (); */ +NS_IMETHODIMP +nsMultiplexInputStream::SetEOF() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsMultiplexInputStreamConstructor(nsISupports *outer, + REFNSIID iid, + void **result) +{ + *result = nullptr; + + if (outer) + return NS_ERROR_NO_AGGREGATION; + + nsMultiplexInputStream *inst = new nsMultiplexInputStream(); + if (!inst) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(inst); + nsresult rv = inst->QueryInterface(iid, result); + NS_RELEASE(inst); + + return rv; +} + +void +nsMultiplexInputStream::Serialize(InputStreamParams& aParams) +{ + MultiplexInputStreamParams params; + + uint32_t streamCount = mStreams.Length(); + + if (streamCount) { + InfallibleTArray<InputStreamParams>& streams = params.streams(); + + streams.SetCapacity(streamCount); + for (uint32_t index = 0; index < streamCount; index++) { + nsCOMPtr<nsIIPCSerializableInputStream> serializable = + do_QueryInterface(mStreams[index]); + NS_ASSERTION(serializable, "Child stream isn't serializable!"); + + if (serializable) { + InputStreamParams childStreamParams; + serializable->Serialize(childStreamParams); + + NS_ASSERTION(childStreamParams.type() != + InputStreamParams::T__None, + "Serialize failed!"); + + streams.AppendElement(childStreamParams); + } + } + } + + params.currentStream() = mCurrentStream; + params.status() = mStatus; + params.startedReadingCurrent() = mStartedReadingCurrent; + + aParams = params; +} + +bool +nsMultiplexInputStream::Deserialize(const InputStreamParams& aParams) +{ + if (aParams.type() != + InputStreamParams::TMultiplexInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const MultiplexInputStreamParams& params = + aParams.get_MultiplexInputStreamParams(); + + const InfallibleTArray<InputStreamParams>& streams = params.streams(); + + uint32_t streamCount = streams.Length(); + for (uint32_t index = 0; index < streamCount; index++) { + nsCOMPtr<nsIInputStream> stream = + DeserializeInputStream(streams[index]); + if (!stream) { + NS_WARNING("Deserialize failed!"); + return false; + } + + if (NS_FAILED(AppendStream(stream))) { + NS_WARNING("AppendStream failed!"); + return false; + } + } + + mCurrentStream = params.currentStream(); + mStatus = params.status(); + mStartedReadingCurrent = params.startedReadingCurrent(); + + return true; +} diff --git a/xpcom/io/nsMultiplexInputStream.h b/xpcom/io/nsMultiplexInputStream.h new file mode 100644 index 000000000..2d472d1f7 --- /dev/null +++ b/xpcom/io/nsMultiplexInputStream.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/** + * The multiplex stream concatenates a list of input streams into a single + * stream. + */ + +#ifndef _nsMultiplexInputStream_h_ +#define _nsMultiplexInputStream_h_ + +#include "nsIMultiplexInputStream.h" + +#define NS_MULTIPLEXINPUTSTREAM_CONTRACTID "@mozilla.org/io/multiplex-input-stream;1" +#define NS_MULTIPLEXINPUTSTREAM_CID \ +{ /* 565e3a2c-1dd2-11b2-8da1-b4cef17e568d */ \ + 0x565e3a2c, \ + 0x1dd2, \ + 0x11b2, \ + {0x8d, 0xa1, 0xb4, 0xce, 0xf1, 0x7e, 0x56, 0x8d} \ +} + +extern nsresult nsMultiplexInputStreamConstructor(nsISupports *outer, + REFNSIID iid, + void **result); + +#endif // _nsMultiplexInputStream_h_ diff --git a/xpcom/io/nsNativeCharsetUtils.cpp b/xpcom/io/nsNativeCharsetUtils.cpp new file mode 100644 index 000000000..39cd21bcb --- /dev/null +++ b/xpcom/io/nsNativeCharsetUtils.cpp @@ -0,0 +1,1136 @@ +/* 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 "xpcom-private.h" + +//----------------------------------------------------------------------------- +// XP_MACOSX or ANDROID +//----------------------------------------------------------------------------- +#if defined(XP_MACOSX) || defined(ANDROID) + +#include "nsAString.h" +#include "nsReadableUtils.h" +#include "nsString.h" + +nsresult +NS_CopyNativeToUnicode(const nsACString &input, nsAString &output) +{ + CopyUTF8toUTF16(input, output); + return NS_OK; +} + +nsresult +NS_CopyUnicodeToNative(const nsAString &input, nsACString &output) +{ + CopyUTF16toUTF8(input, output); + return NS_OK; +} + +void +NS_StartupNativeCharsetUtils() +{ +} + +void +NS_ShutdownNativeCharsetUtils() +{ +} + + +//----------------------------------------------------------------------------- +// XP_UNIX +//----------------------------------------------------------------------------- +#elif defined(XP_UNIX) + +#include <stdlib.h> // mbtowc, wctomb +#include <locale.h> // setlocale +#include "mozilla/Mutex.h" +#include "nscore.h" +#include "nsAString.h" +#include "nsReadableUtils.h" + +using namespace mozilla; + +// +// choose a conversion library. we used to use mbrtowc/wcrtomb under Linux, +// but that doesn't work for non-BMP characters whether we use '-fshort-wchar' +// or not (see bug 206811 and +// news://news.mozilla.org:119/bajml3$fvr1@ripley.netscape.com). we now use +// iconv for all platforms where nltypes.h and nllanginfo.h are present +// along with iconv. +// +#if defined(HAVE_ICONV) && defined(HAVE_NL_TYPES_H) && defined(HAVE_LANGINFO_CODESET) +#define USE_ICONV 1 +#else +#define USE_STDCONV 1 +#endif + +static void +isolatin1_to_utf16(const char **input, uint32_t *inputLeft, PRUnichar **output, uint32_t *outputLeft) +{ + while (*inputLeft && *outputLeft) { + **output = (unsigned char) **input; + (*input)++; + (*inputLeft)--; + (*output)++; + (*outputLeft)--; + } +} + +static void +utf16_to_isolatin1(const PRUnichar **input, uint32_t *inputLeft, char **output, uint32_t *outputLeft) +{ + while (*inputLeft && *outputLeft) { + **output = (unsigned char) **input; + (*input)++; + (*inputLeft)--; + (*output)++; + (*outputLeft)--; + } +} + +//----------------------------------------------------------------------------- +// conversion using iconv +//----------------------------------------------------------------------------- +#if defined(USE_ICONV) +#include <nl_types.h> // CODESET +#include <langinfo.h> // nl_langinfo +#include <iconv.h> // iconv_open, iconv, iconv_close +#include <errno.h> +#include "plstr.h" + +#if defined(HAVE_ICONV_WITH_CONST_INPUT) +#define ICONV_INPUT(x) (x) +#else +#define ICONV_INPUT(x) ((char **)x) +#endif + +// solaris definitely needs this, but we'll enable it by default +// just in case... but we know for sure that iconv(3) in glibc +// doesn't need this. +#if !defined(__GLIBC__) +#define ENABLE_UTF8_FALLBACK_SUPPORT +#endif + +#define INVALID_ICONV_T ((iconv_t) -1) + +static inline size_t +xp_iconv(iconv_t converter, + const char **input, + size_t *inputLeft, + char **output, + size_t *outputLeft) +{ + size_t res, outputAvail = outputLeft ? *outputLeft : 0; + res = iconv(converter, ICONV_INPUT(input), inputLeft, output, outputLeft); + if (res == (size_t) -1) { + // on some platforms (e.g., linux) iconv will fail with + // E2BIG if it cannot convert _all_ of its input. it'll + // still adjust all of the in/out params correctly, so we + // can ignore this error. the assumption is that we will + // be called again to complete the conversion. + if ((errno == E2BIG) && (*outputLeft < outputAvail)) + res = 0; + } + return res; +} + +static inline void +xp_iconv_reset(iconv_t converter) +{ + // NOTE: the man pages on Solaris claim that you can pass NULL + // for all parameter to reset the converter, but beware the + // evil Solaris crash if you go down this route >:-) + + const char *zero_char_in_ptr = NULL; + char *zero_char_out_ptr = NULL; + size_t zero_size_in = 0, + zero_size_out = 0; + + xp_iconv(converter, &zero_char_in_ptr, + &zero_size_in, + &zero_char_out_ptr, + &zero_size_out); +} + +static inline iconv_t +xp_iconv_open(const char **to_list, const char **from_list) +{ + iconv_t res; + const char **from_name; + const char **to_name; + + // try all possible combinations to locate a converter. + to_name = to_list; + while (*to_name) { + if (**to_name) { + from_name = from_list; + while (*from_name) { + if (**from_name) { + res = iconv_open(*to_name, *from_name); + if (res != INVALID_ICONV_T) + return res; + } + from_name++; + } + } + to_name++; + } + + return INVALID_ICONV_T; +} + +/* + * PRUnichar[] is NOT a UCS-2 array BUT a UTF-16 string. Therefore, we + * have to use UTF-16 with iconv(3) on platforms where it's supported. + * However, the way UTF-16 and UCS-2 are interpreted varies across platforms + * and implementations of iconv(3). On Tru64, it also depends on the environment + * variable. To avoid the trouble arising from byte-swapping + * (bug 208809), we have to try UTF-16LE/BE and UCS-2LE/BE before falling + * back to UTF-16 and UCS-2 and variants. We assume that UTF-16 and UCS-2 + * on systems without UTF-16LE/BE and UCS-2LE/BE have the native endianness, + * which isn't the case of glibc 2.1.x, for which we use 'UNICODELITTLE' + * and 'UNICODEBIG'. It's also not true of Tru64 V4 when the environment + * variable ICONV_BYTEORDER is set to 'big-endian', about which not much + * can be done other than adding a note in the release notes. (bug 206811) + */ +static const char *UTF_16_NAMES[] = { +#if defined(IS_LITTLE_ENDIAN) + "UTF-16LE", +#if defined(__GLIBC__) + "UNICODELITTLE", +#endif + "UCS-2LE", +#else + "UTF-16BE", +#if defined(__GLIBC__) + "UNICODEBIG", +#endif + "UCS-2BE", +#endif + "UTF-16", + "UCS-2", + "UCS2", + "UCS_2", + "ucs-2", + "ucs2", + "ucs_2", + NULL +}; + +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) +static const char *UTF_8_NAMES[] = { + "UTF-8", + "UTF8", + "UTF_8", + "utf-8", + "utf8", + "utf_8", + NULL +}; +#endif + +static const char *ISO_8859_1_NAMES[] = { + "ISO-8859-1", +#if !defined(__GLIBC__) + "ISO8859-1", + "ISO88591", + "ISO_8859_1", + "ISO8859_1", + "iso-8859-1", + "iso8859-1", + "iso88591", + "iso_8859_1", + "iso8859_1", +#endif + NULL +}; + +class nsNativeCharsetConverter +{ +public: + nsNativeCharsetConverter(); + ~nsNativeCharsetConverter(); + + nsresult NativeToUnicode(const char **input , uint32_t *inputLeft, + PRUnichar **output, uint32_t *outputLeft); + nsresult UnicodeToNative(const PRUnichar **input , uint32_t *inputLeft, + char **output, uint32_t *outputLeft); + + static void GlobalInit(); + static void GlobalShutdown(); + static bool IsNativeUTF8(); + +private: + static iconv_t gNativeToUnicode; + static iconv_t gUnicodeToNative; +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) + static iconv_t gNativeToUTF8; + static iconv_t gUTF8ToNative; + static iconv_t gUnicodeToUTF8; + static iconv_t gUTF8ToUnicode; +#endif + static Mutex *gLock; + static bool gInitialized; + static bool gIsNativeUTF8; + + static void LazyInit(); + + static void Lock() { if (gLock) gLock->Lock(); } + static void Unlock() { if (gLock) gLock->Unlock(); } +}; + +iconv_t nsNativeCharsetConverter::gNativeToUnicode = INVALID_ICONV_T; +iconv_t nsNativeCharsetConverter::gUnicodeToNative = INVALID_ICONV_T; +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) +iconv_t nsNativeCharsetConverter::gNativeToUTF8 = INVALID_ICONV_T; +iconv_t nsNativeCharsetConverter::gUTF8ToNative = INVALID_ICONV_T; +iconv_t nsNativeCharsetConverter::gUnicodeToUTF8 = INVALID_ICONV_T; +iconv_t nsNativeCharsetConverter::gUTF8ToUnicode = INVALID_ICONV_T; +#endif +Mutex *nsNativeCharsetConverter::gLock = nullptr; +bool nsNativeCharsetConverter::gInitialized = false; +bool nsNativeCharsetConverter::gIsNativeUTF8 = false; + +void +nsNativeCharsetConverter::LazyInit() +{ + // LazyInit may be called before NS_StartupNativeCharsetUtils, but + // the setlocale it does has to be called before nl_langinfo. Like in + // NS_StartupNativeCharsetUtils, assume we are called early enough that + // we are the first to care about the locale's charset. + if (!gLock) + setlocale(LC_CTYPE, ""); + const char *blank_list[] = { "", NULL }; + const char **native_charset_list = blank_list; + const char *native_charset = nl_langinfo(CODESET); + if (native_charset == nullptr) { + NS_ERROR("native charset is unknown"); + // fallback to ISO-8859-1 + native_charset_list = ISO_8859_1_NAMES; + } + else + native_charset_list[0] = native_charset; + + // Most, if not all, Unixen supporting UTF-8 and nl_langinfo(CODESET) + // return 'UTF-8' (or 'utf-8') + if (!PL_strcasecmp(native_charset, "UTF-8")) + gIsNativeUTF8 = true; + + gNativeToUnicode = xp_iconv_open(UTF_16_NAMES, native_charset_list); + gUnicodeToNative = xp_iconv_open(native_charset_list, UTF_16_NAMES); + +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) + if (gNativeToUnicode == INVALID_ICONV_T) { + gNativeToUTF8 = xp_iconv_open(UTF_8_NAMES, native_charset_list); + gUTF8ToUnicode = xp_iconv_open(UTF_16_NAMES, UTF_8_NAMES); + NS_ASSERTION(gNativeToUTF8 != INVALID_ICONV_T, "no native to utf-8 converter"); + NS_ASSERTION(gUTF8ToUnicode != INVALID_ICONV_T, "no utf-8 to utf-16 converter"); + } + if (gUnicodeToNative == INVALID_ICONV_T) { + gUnicodeToUTF8 = xp_iconv_open(UTF_8_NAMES, UTF_16_NAMES); + gUTF8ToNative = xp_iconv_open(native_charset_list, UTF_8_NAMES); + NS_ASSERTION(gUnicodeToUTF8 != INVALID_ICONV_T, "no utf-16 to utf-8 converter"); + NS_ASSERTION(gUTF8ToNative != INVALID_ICONV_T, "no utf-8 to native converter"); + } +#else + NS_ASSERTION(gNativeToUnicode != INVALID_ICONV_T, "no native to utf-16 converter"); + NS_ASSERTION(gUnicodeToNative != INVALID_ICONV_T, "no utf-16 to native converter"); +#endif + + /* + * On Solaris 8 (and newer?), the iconv modules converting to UCS-2 + * prepend a byte order mark unicode character (BOM, u+FEFF) during + * the first use of the iconv converter. The same is the case of + * glibc 2.2.9x and Tru64 V5 (see bug 208809) when 'UTF-16' is used. + * However, we use 'UTF-16LE/BE' in both cases, instead so that we + * should be safe. But just in case... + * + * This dummy conversion gets rid of the BOMs and fixes bug 153562. + */ + char dummy_input[1] = { ' ' }; + char dummy_output[4]; + + if (gNativeToUnicode != INVALID_ICONV_T) { + const char *input = dummy_input; + size_t input_left = sizeof(dummy_input); + char *output = dummy_output; + size_t output_left = sizeof(dummy_output); + + xp_iconv(gNativeToUnicode, &input, &input_left, &output, &output_left); + } +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) + if (gUTF8ToUnicode != INVALID_ICONV_T) { + const char *input = dummy_input; + size_t input_left = sizeof(dummy_input); + char *output = dummy_output; + size_t output_left = sizeof(dummy_output); + + xp_iconv(gUTF8ToUnicode, &input, &input_left, &output, &output_left); + } +#endif + + gInitialized = true; +} + +void +nsNativeCharsetConverter::GlobalInit() +{ + gLock = new Mutex("nsNativeCharsetConverter.gLock"); +} + +void +nsNativeCharsetConverter::GlobalShutdown() +{ + if (gLock) { + delete gLock; + gLock = nullptr; + } + + if (gNativeToUnicode != INVALID_ICONV_T) { + iconv_close(gNativeToUnicode); + gNativeToUnicode = INVALID_ICONV_T; + } + + if (gUnicodeToNative != INVALID_ICONV_T) { + iconv_close(gUnicodeToNative); + gUnicodeToNative = INVALID_ICONV_T; + } + +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) + if (gNativeToUTF8 != INVALID_ICONV_T) { + iconv_close(gNativeToUTF8); + gNativeToUTF8 = INVALID_ICONV_T; + } + if (gUTF8ToNative != INVALID_ICONV_T) { + iconv_close(gUTF8ToNative); + gUTF8ToNative = INVALID_ICONV_T; + } + if (gUnicodeToUTF8 != INVALID_ICONV_T) { + iconv_close(gUnicodeToUTF8); + gUnicodeToUTF8 = INVALID_ICONV_T; + } + if (gUTF8ToUnicode != INVALID_ICONV_T) { + iconv_close(gUTF8ToUnicode); + gUTF8ToUnicode = INVALID_ICONV_T; + } +#endif + + gInitialized = false; +} + +nsNativeCharsetConverter::nsNativeCharsetConverter() +{ + Lock(); + if (!gInitialized) + LazyInit(); +} + +nsNativeCharsetConverter::~nsNativeCharsetConverter() +{ + // reset converters for next time + if (gNativeToUnicode != INVALID_ICONV_T) + xp_iconv_reset(gNativeToUnicode); + if (gUnicodeToNative != INVALID_ICONV_T) + xp_iconv_reset(gUnicodeToNative); +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) + if (gNativeToUTF8 != INVALID_ICONV_T) + xp_iconv_reset(gNativeToUTF8); + if (gUTF8ToNative != INVALID_ICONV_T) + xp_iconv_reset(gUTF8ToNative); + if (gUnicodeToUTF8 != INVALID_ICONV_T) + xp_iconv_reset(gUnicodeToUTF8); + if (gUTF8ToUnicode != INVALID_ICONV_T) + xp_iconv_reset(gUTF8ToUnicode); +#endif + Unlock(); +} + +nsresult +nsNativeCharsetConverter::NativeToUnicode(const char **input, + uint32_t *inputLeft, + PRUnichar **output, + uint32_t *outputLeft) +{ + size_t res = 0; + size_t inLeft = (size_t) *inputLeft; + size_t outLeft = (size_t) *outputLeft * 2; + + if (gNativeToUnicode != INVALID_ICONV_T) { + + res = xp_iconv(gNativeToUnicode, input, &inLeft, (char **) output, &outLeft); + + *inputLeft = inLeft; + *outputLeft = outLeft / 2; + if (res != (size_t) -1) + return NS_OK; + + NS_WARNING("conversion from native to utf-16 failed"); + + // reset converter + xp_iconv_reset(gNativeToUnicode); + } +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) + else if ((gNativeToUTF8 != INVALID_ICONV_T) && + (gUTF8ToUnicode != INVALID_ICONV_T)) { + // convert first to UTF8, then from UTF8 to UCS2 + const char *in = *input; + + char ubuf[1024]; + + // we assume we're always called with enough space in |output|, + // so convert many chars at a time... + while (inLeft) { + char *p = ubuf; + size_t n = sizeof(ubuf); + res = xp_iconv(gNativeToUTF8, &in, &inLeft, &p, &n); + if (res == (size_t) -1) { + NS_ERROR("conversion from native to utf-8 failed"); + break; + } + NS_ASSERTION(outLeft > 0, "bad assumption"); + p = ubuf; + n = sizeof(ubuf) - n; + res = xp_iconv(gUTF8ToUnicode, (const char **) &p, &n, (char **) output, &outLeft); + if (res == (size_t) -1) { + NS_ERROR("conversion from utf-8 to utf-16 failed"); + break; + } + } + + (*input) += (*inputLeft - inLeft); + *inputLeft = inLeft; + *outputLeft = outLeft / 2; + + if (res != (size_t) -1) + return NS_OK; + + // reset converters + xp_iconv_reset(gNativeToUTF8); + xp_iconv_reset(gUTF8ToUnicode); + } +#endif + + // fallback: zero-pad and hope for the best + // XXX This is lame and we have to do better. + isolatin1_to_utf16(input, inputLeft, output, outputLeft); + + return NS_OK; +} + +nsresult +nsNativeCharsetConverter::UnicodeToNative(const PRUnichar **input, + uint32_t *inputLeft, + char **output, + uint32_t *outputLeft) +{ + size_t res = 0; + size_t inLeft = (size_t) *inputLeft * 2; + size_t outLeft = (size_t) *outputLeft; + + if (gUnicodeToNative != INVALID_ICONV_T) { + res = xp_iconv(gUnicodeToNative, (const char **) input, &inLeft, output, &outLeft); + + *inputLeft = inLeft / 2; + *outputLeft = outLeft; + if (res != (size_t) -1) { + return NS_OK; + } + + NS_ERROR("iconv failed"); + + // reset converter + xp_iconv_reset(gUnicodeToNative); + } +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) + else if ((gUnicodeToUTF8 != INVALID_ICONV_T) && + (gUTF8ToNative != INVALID_ICONV_T)) { + const char *in = (const char *) *input; + + char ubuf[6]; // max utf-8 char length (really only needs to be 4 bytes) + + // convert one uchar at a time... + while (inLeft && outLeft) { + char *p = ubuf; + size_t n = sizeof(ubuf), one_uchar = sizeof(PRUnichar); + res = xp_iconv(gUnicodeToUTF8, &in, &one_uchar, &p, &n); + if (res == (size_t) -1) { + NS_ERROR("conversion from utf-16 to utf-8 failed"); + break; + } + p = ubuf; + n = sizeof(ubuf) - n; + res = xp_iconv(gUTF8ToNative, (const char **) &p, &n, output, &outLeft); + if (res == (size_t) -1) { + if (errno == E2BIG) { + // not enough room for last uchar... back up and return. + in -= sizeof(PRUnichar); + res = 0; + } + else + NS_ERROR("conversion from utf-8 to native failed"); + break; + } + inLeft -= sizeof(PRUnichar); + } + + (*input) += (*inputLeft - inLeft / 2); + *inputLeft = inLeft / 2; + *outputLeft = outLeft; + if (res != (size_t) -1) { + return NS_OK; + } + + // reset converters + xp_iconv_reset(gUnicodeToUTF8); + xp_iconv_reset(gUTF8ToNative); + } +#endif + + // fallback: truncate and hope for the best + // XXX This is lame and we have to do better. + utf16_to_isolatin1(input, inputLeft, output, outputLeft); + + return NS_OK; +} + +bool +nsNativeCharsetConverter::IsNativeUTF8() +{ + if (!gInitialized) { + Lock(); + if (!gInitialized) + LazyInit(); + Unlock(); + } + return gIsNativeUTF8; +} + +#endif // USE_ICONV + +//----------------------------------------------------------------------------- +// conversion using mb[r]towc/wc[r]tomb +//----------------------------------------------------------------------------- +#if defined(USE_STDCONV) +#if defined(HAVE_WCRTOMB) || defined(HAVE_MBRTOWC) +#include <wchar.h> // mbrtowc, wcrtomb +#endif + +class nsNativeCharsetConverter +{ +public: + nsNativeCharsetConverter(); + + nsresult NativeToUnicode(const char **input , uint32_t *inputLeft, + PRUnichar **output, uint32_t *outputLeft); + nsresult UnicodeToNative(const PRUnichar **input , uint32_t *inputLeft, + char **output, uint32_t *outputLeft); + + static void GlobalInit(); + static void GlobalShutdown() { } + static bool IsNativeUTF8(); + +private: + static bool gWCharIsUnicode; + +#if defined(HAVE_WCRTOMB) || defined(HAVE_MBRTOWC) + mbstate_t ps; +#endif +}; + +bool nsNativeCharsetConverter::gWCharIsUnicode = false; + +nsNativeCharsetConverter::nsNativeCharsetConverter() +{ +#if defined(HAVE_WCRTOMB) || defined(HAVE_MBRTOWC) + memset(&ps, 0, sizeof(ps)); +#endif +} + +void +nsNativeCharsetConverter::GlobalInit() +{ + // verify that wchar_t for the current locale is actually unicode. + // if it is not, then we should avoid calling mbtowc/wctomb and + // just fallback on zero-pad/truncation conversion. + // + // this test cannot be done at build time because the encoding of + // wchar_t may depend on the runtime locale. sad, but true!! + // + // so, if wchar_t is unicode then converting an ASCII character + // to wchar_t should not change its numeric value. we'll just + // check what happens with the ASCII 'a' character. + // + // this test is not perfect... obviously, it could yield false + // positives, but then at least ASCII text would be converted + // properly (or maybe just the 'a' character) -- oh well :( + + char a = 'a'; + unsigned int w = 0; + + int res = mbtowc((wchar_t *) &w, &a, 1); + + gWCharIsUnicode = (res != -1 && w == 'a'); + +#ifdef DEBUG + if (!gWCharIsUnicode) + NS_WARNING("wchar_t is not unicode (unicode conversion will be lossy)"); +#endif +} + +nsresult +nsNativeCharsetConverter::NativeToUnicode(const char **input, + uint32_t *inputLeft, + PRUnichar **output, + uint32_t *outputLeft) +{ + if (gWCharIsUnicode) { + int incr; + + // cannot use wchar_t here since it may have been redefined (e.g., + // via -fshort-wchar). hopefully, sizeof(tmp) is sufficient XP. + unsigned int tmp = 0; + while (*inputLeft && *outputLeft) { +#ifdef HAVE_MBRTOWC + incr = (int) mbrtowc((wchar_t *) &tmp, *input, *inputLeft, &ps); +#else + // XXX is this thread-safe? + incr = (int) mbtowc((wchar_t *) &tmp, *input, *inputLeft); +#endif + if (incr < 0) { + NS_WARNING("mbtowc failed: possible charset mismatch"); + // zero-pad and hope for the best + tmp = (unsigned char) **input; + incr = 1; + } + **output = (PRUnichar) tmp; + (*input) += incr; + (*inputLeft) -= incr; + (*output)++; + (*outputLeft)--; + } + } + else { + // wchar_t isn't unicode, so the best we can do is treat the + // input as if it is isolatin1 :( + isolatin1_to_utf16(input, inputLeft, output, outputLeft); + } + + return NS_OK; +} + +nsresult +nsNativeCharsetConverter::UnicodeToNative(const PRUnichar **input, + uint32_t *inputLeft, + char **output, + uint32_t *outputLeft) +{ + if (gWCharIsUnicode) { + int incr; + + while (*inputLeft && *outputLeft >= MB_CUR_MAX) { +#ifdef HAVE_WCRTOMB + incr = (int) wcrtomb(*output, (wchar_t) **input, &ps); +#else + // XXX is this thread-safe? + incr = (int) wctomb(*output, (wchar_t) **input); +#endif + if (incr < 0) { + NS_WARNING("mbtowc failed: possible charset mismatch"); + **output = (unsigned char) **input; // truncate + incr = 1; + } + // most likely we're dead anyways if this assertion should fire + NS_ASSERTION(uint32_t(incr) <= *outputLeft, "wrote beyond end of string"); + (*output) += incr; + (*outputLeft) -= incr; + (*input)++; + (*inputLeft)--; + } + } + else { + // wchar_t isn't unicode, so the best we can do is treat the + // input as if it is isolatin1 :( + utf16_to_isolatin1(input, inputLeft, output, outputLeft); + } + + return NS_OK; +} + +// XXX : for now, return false +bool +nsNativeCharsetConverter::IsNativeUTF8() +{ + return false; +} + +#endif // USE_STDCONV + +//----------------------------------------------------------------------------- +// API implementation +//----------------------------------------------------------------------------- + +nsresult +NS_CopyNativeToUnicode(const nsACString &input, nsAString &output) +{ + output.Truncate(); + + uint32_t inputLen = input.Length(); + + nsACString::const_iterator iter; + input.BeginReading(iter); + + // + // OPTIMIZATION: preallocate space for largest possible result; convert + // directly into the result buffer to avoid intermediate buffer copy. + // + // this will generally result in a larger allocation, but that seems + // better than an extra buffer copy. + // + if (!output.SetLength(inputLen, fallible_t())) + return NS_ERROR_OUT_OF_MEMORY; + nsAString::iterator out_iter; + output.BeginWriting(out_iter); + + PRUnichar *result = out_iter.get(); + uint32_t resultLeft = inputLen; + + const char *buf = iter.get(); + uint32_t bufLeft = inputLen; + + nsNativeCharsetConverter conv; + nsresult rv = conv.NativeToUnicode(&buf, &bufLeft, &result, &resultLeft); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(bufLeft == 0, "did not consume entire input buffer"); + output.SetLength(inputLen - resultLeft); + } + return rv; +} + +nsresult +NS_CopyUnicodeToNative(const nsAString &input, nsACString &output) +{ + output.Truncate(); + + nsAString::const_iterator iter, end; + input.BeginReading(iter); + input.EndReading(end); + + // cannot easily avoid intermediate buffer copy. + char temp[4096]; + + nsNativeCharsetConverter conv; + + const PRUnichar *buf = iter.get(); + uint32_t bufLeft = Distance(iter, end); + while (bufLeft) { + char *p = temp; + uint32_t tempLeft = sizeof(temp); + + nsresult rv = conv.UnicodeToNative(&buf, &bufLeft, &p, &tempLeft); + if (NS_FAILED(rv)) return rv; + + if (tempLeft < sizeof(temp)) + output.Append(temp, sizeof(temp) - tempLeft); + } + return NS_OK; +} + +bool +NS_IsNativeUTF8() +{ + return nsNativeCharsetConverter::IsNativeUTF8(); +} + +void +NS_StartupNativeCharsetUtils() +{ + // + // need to initialize the locale or else charset conversion will fail. + // better not delay this in case some other component alters the locale + // settings. + // + // XXX we assume that we are called early enough that we should + // always be the first to care about the locale's charset. + // + setlocale(LC_CTYPE, ""); + + nsNativeCharsetConverter::GlobalInit(); +} + +void +NS_ShutdownNativeCharsetUtils() +{ + nsNativeCharsetConverter::GlobalShutdown(); +} + +//----------------------------------------------------------------------------- +// XP_WIN +//----------------------------------------------------------------------------- +#elif defined(XP_WIN) + +#include <windows.h> +#include "nsAString.h" +#include "nsReadableUtils.h" + +using namespace mozilla; + +nsresult +NS_CopyNativeToUnicode(const nsACString &input, nsAString &output) +{ + uint32_t inputLen = input.Length(); + + nsACString::const_iterator iter; + input.BeginReading(iter); + + const char *buf = iter.get(); + + // determine length of result + uint32_t resultLen = 0; + int n = ::MultiByteToWideChar(CP_ACP, 0, buf, inputLen, NULL, 0); + if (n > 0) + resultLen += n; + + // allocate sufficient space + if (!output.SetLength(resultLen, fallible_t())) + return NS_ERROR_OUT_OF_MEMORY; + if (resultLen > 0) { + nsAString::iterator out_iter; + output.BeginWriting(out_iter); + + PRUnichar *result = out_iter.get(); + + ::MultiByteToWideChar(CP_ACP, 0, buf, inputLen, result, resultLen); + } + return NS_OK; +} + +nsresult +NS_CopyUnicodeToNative(const nsAString &input, nsACString &output) +{ + uint32_t inputLen = input.Length(); + + nsAString::const_iterator iter; + input.BeginReading(iter); + + const PRUnichar *buf = iter.get(); + + // determine length of result + uint32_t resultLen = 0; + + int n = ::WideCharToMultiByte(CP_ACP, 0, buf, inputLen, NULL, 0, NULL, NULL); + if (n > 0) + resultLen += n; + + // allocate sufficient space + if (!output.SetLength(resultLen, fallible_t())) + return NS_ERROR_OUT_OF_MEMORY; + if (resultLen > 0) { + nsACString::iterator out_iter; + output.BeginWriting(out_iter); + + // default "defaultChar" is '?', which is an illegal character on windows + // file system. That will cause file uncreatable. Change it to '_' + const char defaultChar = '_'; + + char *result = out_iter.get(); + + ::WideCharToMultiByte(CP_ACP, 0, buf, inputLen, result, resultLen, + &defaultChar, NULL); + } + return NS_OK; +} + +// moved from widget/windows/nsToolkit.cpp +int32_t +NS_ConvertAtoW(const char *aStrInA, int aBufferSize, PRUnichar *aStrOutW) +{ + return MultiByteToWideChar(CP_ACP, 0, aStrInA, -1, aStrOutW, aBufferSize); +} + +int32_t +NS_ConvertWtoA(const PRUnichar *aStrInW, int aBufferSizeOut, + char *aStrOutA, const char *aDefault) +{ + if ((!aStrInW) || (!aStrOutA) || (aBufferSizeOut <= 0)) + return 0; + + int numCharsConverted = WideCharToMultiByte(CP_ACP, 0, aStrInW, -1, + aStrOutA, aBufferSizeOut, + aDefault, NULL); + + if (!numCharsConverted) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + // Overflow, add missing null termination but return 0 + aStrOutA[aBufferSizeOut-1] = '\0'; + } + else { + // Other error, clear string and return 0 + aStrOutA[0] = '\0'; + } + } + else if (numCharsConverted < aBufferSizeOut) { + // Add 2nd null (really necessary?) + aStrOutA[numCharsConverted] = '\0'; + } + + return numCharsConverted; +} + +//----------------------------------------------------------------------------- +// XP_OS2 +//----------------------------------------------------------------------------- +#elif defined(XP_OS2) + +#define INCL_DOS +#include <os2.h> +#include <uconv.h> +#include "nsAString.h" +#include "nsReadableUtils.h" +#include <ulserrno.h> +#include "nsNativeCharsetUtils.h" + +using namespace mozilla; + +static UconvObject UnicodeConverter = NULL; + +nsresult +NS_CopyNativeToUnicode(const nsACString &input, nsAString &output) +{ + uint32_t inputLen = input.Length(); + + nsACString::const_iterator iter; + input.BeginReading(iter); + const char *inputStr = iter.get(); + + // determine length of result + uint32_t resultLen = inputLen; + if (!output.SetLength(resultLen, fallible_t())) + return NS_ERROR_OUT_OF_MEMORY; + + nsAString::iterator out_iter; + output.BeginWriting(out_iter); + UniChar *result = (UniChar*)out_iter.get(); + + size_t cSubs = 0; + size_t resultLeft = resultLen; + + if (!UnicodeConverter) + NS_StartupNativeCharsetUtils(); + + int unirc = ::UniUconvToUcs(UnicodeConverter, (void**)&inputStr, &inputLen, + &result, &resultLeft, &cSubs); + + NS_ASSERTION(unirc != UCONV_E2BIG, "Path too big"); + + if (unirc != ULS_SUCCESS) { + output.Truncate(); + return NS_ERROR_FAILURE; + } + + // Need to update string length to reflect how many bytes were actually + // written. + output.Truncate(resultLen - resultLeft); + return NS_OK; +} + +nsresult +NS_CopyUnicodeToNative(const nsAString &input, nsACString &output) +{ + size_t inputLen = input.Length(); + + nsAString::const_iterator iter; + input.BeginReading(iter); + UniChar* inputStr = (UniChar*) const_cast<PRUnichar*>(iter.get()); + + // maximum length of unicode string of length x converted to native + // codepage is x*2 + size_t resultLen = inputLen * 2; + if (!output.SetLength(resultLen, fallible_t())) + return NS_ERROR_OUT_OF_MEMORY; + + nsACString::iterator out_iter; + output.BeginWriting(out_iter); + char *result = out_iter.get(); + + size_t cSubs = 0; + size_t resultLeft = resultLen; + + if (!UnicodeConverter) + NS_StartupNativeCharsetUtils(); + + int unirc = ::UniUconvFromUcs(UnicodeConverter, &inputStr, &inputLen, + (void**)&result, &resultLeft, &cSubs); + + NS_ASSERTION(unirc != UCONV_E2BIG, "Path too big"); + + if (unirc != ULS_SUCCESS) { + output.Truncate(); + return NS_ERROR_FAILURE; + } + + // Need to update string length to reflect how many bytes were actually + // written. + output.Truncate(resultLen - resultLeft); + return NS_OK; +} + +void +NS_StartupNativeCharsetUtils() +{ + ULONG ulLength; + ULONG ulCodePage; + DosQueryCp(sizeof(ULONG), &ulCodePage, &ulLength); + + UniChar codepage[20]; + int unirc = ::UniMapCpToUcsCp(ulCodePage, codepage, 20); + if (unirc == ULS_SUCCESS) { + unirc = ::UniCreateUconvObject(codepage, &UnicodeConverter); + if (unirc == ULS_SUCCESS) { + uconv_attribute_t attr; + ::UniQueryUconvObject(UnicodeConverter, &attr, sizeof(uconv_attribute_t), + NULL, NULL, NULL); + attr.options = UCONV_OPTION_SUBSTITUTE_BOTH; + attr.subchar_len=1; + attr.subchar[0]='_'; + ::UniSetUconvObject(UnicodeConverter, &attr); + } + } +} + +void +NS_ShutdownNativeCharsetUtils() +{ + ::UniFreeUconvObject(UnicodeConverter); +} + +#else + +#include "nsReadableUtils.h" + +nsresult +NS_CopyNativeToUnicode(const nsACString &input, nsAString &output) +{ + CopyASCIItoUTF16(input, output); + return NS_OK; +} + +nsresult +NS_CopyUnicodeToNative(const nsAString &input, nsACString &output) +{ + LossyCopyUTF16toASCII(input, output); + return NS_OK; +} + +void +NS_StartupNativeCharsetUtils() +{ +} + +void +NS_ShutdownNativeCharsetUtils() +{ +} + +#endif diff --git a/xpcom/io/nsNativeCharsetUtils.h b/xpcom/io/nsNativeCharsetUtils.h new file mode 100644 index 000000000..0dc6aec15 --- /dev/null +++ b/xpcom/io/nsNativeCharsetUtils.h @@ -0,0 +1,60 @@ +/* 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/. */ + +#ifndef nsNativeCharsetUtils_h__ +#define nsNativeCharsetUtils_h__ + + +/*****************************************************************************\ + * * + * **** NOTICE **** * + * * + * *** THESE ARE NOT GENERAL PURPOSE CONVERTERS *** * + * * + * NS_CopyNativeToUnicode / NS_CopyUnicodeToNative should only be used * + * for converting *FILENAMES* between native and unicode. They are not * + * designed or tested for general encoding converter use. * + * * +\*****************************************************************************/ + +/** + * thread-safe conversion routines that do not depend on uconv libraries. + */ +nsresult NS_CopyNativeToUnicode(const nsACString &input, nsAString &output); +nsresult NS_CopyUnicodeToNative(const nsAString &input, nsACString &output); + +/* + * This function indicates whether the character encoding used in the file + * system (more exactly what's used for |GetNativeFoo| and |SetNativeFoo| + * of |nsIFile|) is UTF-8 or not. Knowing that helps us avoid an + * unncessary encoding conversion in some cases. For instance, to get the leaf + * name in UTF-8 out of nsIFile, we can just use |GetNativeLeafName| rather + * than using |GetLeafName| and converting the result to UTF-8 if the file + * system encoding is UTF-8. + * On Unix (but not on Mac OS X), it depends on the locale and is not known + * in advance (at the compilation time) so that this function needs to be + * a real function. On Mac OS X it's always UTF-8 while on Windows + * and other platforms (e.g. OS2), it's never UTF-8. + */ +#if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(ANDROID) +bool NS_IsNativeUTF8(); +#else +inline bool NS_IsNativeUTF8() +{ +#if defined(XP_MACOSX) || defined(ANDROID) + return true; +#else + return false; +#endif +} +#endif + + +/** + * internal + */ +void NS_StartupNativeCharsetUtils(); +void NS_ShutdownNativeCharsetUtils(); + +#endif // nsNativeCharsetUtils_h__ diff --git a/xpcom/io/nsPipe.h b/xpcom/io/nsPipe.h new file mode 100644 index 000000000..383065bd0 --- /dev/null +++ b/xpcom/io/nsPipe.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#ifndef nsPipe_h__ +#define nsPipe_h__ + +#define NS_PIPE_CONTRACTID \ + "@mozilla.org/pipe;1" +#define NS_PIPE_CID \ +{ /* e4a0ee4e-0775-457b-9118-b3ae97a7c758 */ \ + 0xe4a0ee4e, \ + 0x0775, \ + 0x457b, \ + {0x91,0x18,0xb3,0xae,0x97,0xa7,0xc7,0x58} \ +} + +// Generic factory constructor for the nsPipe class +nsresult NS_HIDDEN +nsPipeConstructor(nsISupports *outer, REFNSIID iid, void **result); + +#endif // !defined(nsPipe_h__) diff --git a/xpcom/io/nsPipe3.cpp b/xpcom/io/nsPipe3.cpp new file mode 100644 index 000000000..adc225f98 --- /dev/null +++ b/xpcom/io/nsPipe3.cpp @@ -0,0 +1,1308 @@ +/* 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 "mozilla/Attributes.h" +#include "mozilla/ReentrantMonitor.h" +#include "nsIPipe.h" +#include "nsIEventTarget.h" +#include "nsISeekableStream.h" +#include "nsIProgrammingLanguage.h" +#include "nsSegmentedBuffer.h" +#include "nsStreamUtils.h" +#include "nsCOMPtr.h" +#include "nsCRT.h" +#include "prlog.h" +#include "nsIClassInfoImpl.h" +#include "nsAtomicRefcnt.h" +#include "nsAlgorithm.h" + +using namespace mozilla; + +#if defined(PR_LOGGING) +// +// set NSPR_LOG_MODULES=nsPipe:5 +// +static PRLogModuleInfo * +GetPipeLog() +{ + static PRLogModuleInfo *sLog; + if (!sLog) + sLog = PR_NewLogModule("nsPipe"); + return sLog; +} +#define LOG(args) PR_LOG(GetPipeLog(), PR_LOG_DEBUG, args) +#else +#define LOG(args) +#endif + +#define DEFAULT_SEGMENT_SIZE 4096 +#define DEFAULT_SEGMENT_COUNT 16 + +class nsPipe; +class nsPipeEvents; +class nsPipeInputStream; +class nsPipeOutputStream; + +//----------------------------------------------------------------------------- + +// this class is used to delay notifications until the end of a particular +// scope. it helps avoid the complexity of issuing callbacks while inside +// a critical section. +class nsPipeEvents +{ +public: + nsPipeEvents() { } + ~nsPipeEvents(); + + inline void NotifyInputReady(nsIAsyncInputStream *stream, + nsIInputStreamCallback *callback) + { + NS_ASSERTION(!mInputCallback, "already have an input event"); + mInputStream = stream; + mInputCallback = callback; + } + + inline void NotifyOutputReady(nsIAsyncOutputStream *stream, + nsIOutputStreamCallback *callback) + { + NS_ASSERTION(!mOutputCallback, "already have an output event"); + mOutputStream = stream; + mOutputCallback = callback; + } + +private: + nsCOMPtr<nsIAsyncInputStream> mInputStream; + nsCOMPtr<nsIInputStreamCallback> mInputCallback; + nsCOMPtr<nsIAsyncOutputStream> mOutputStream; + nsCOMPtr<nsIOutputStreamCallback> mOutputCallback; +}; + +//----------------------------------------------------------------------------- + +// the input end of a pipe (allocated as a member of the pipe). +class nsPipeInputStream : public nsIAsyncInputStream + , public nsISeekableStream + , public nsISearchableInputStream + , public nsIClassInfo +{ +public: + // since this class will be allocated as a member of the pipe, we do not + // need our own ref count. instead, we share the lifetime (the ref count) + // of the entire pipe. this macro is just convenience since it does not + // declare a mRefCount variable; however, don't let the name fool you... + // we are not inheriting from nsPipe ;-) + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSISEARCHABLEINPUTSTREAM + NS_DECL_NSICLASSINFO + + nsPipeInputStream(nsPipe *pipe) + : mPipe(pipe) + , mReaderRefCnt(0) + , mLogicalOffset(0) + , mBlocking(true) + , mBlocked(false) + , mAvailable(0) + , mCallbackFlags(0) + { } + + nsresult Fill(); + void SetNonBlocking(bool aNonBlocking) { mBlocking = !aNonBlocking; } + + uint32_t Available() { return mAvailable; } + void ReduceAvailable(uint32_t avail) { mAvailable -= avail; } + + // synchronously wait for the pipe to become readable. + nsresult Wait(); + + // these functions return true to indicate that the pipe's monitor should + // be notified, to wake up a blocked reader if any. + bool OnInputReadable(uint32_t bytesWritten, nsPipeEvents &); + bool OnInputException(nsresult, nsPipeEvents &); + +private: + nsPipe *mPipe; + + // separate refcnt so that we know when to close the consumer + nsrefcnt mReaderRefCnt; + int64_t mLogicalOffset; + bool mBlocking; + + // these variables can only be accessed while inside the pipe's monitor + bool mBlocked; + uint32_t mAvailable; + nsCOMPtr<nsIInputStreamCallback> mCallback; + uint32_t mCallbackFlags; +}; + +//----------------------------------------------------------------------------- + +// the output end of a pipe (allocated as a member of the pipe). +class nsPipeOutputStream : public nsIAsyncOutputStream + , public nsIClassInfo +{ +public: + // since this class will be allocated as a member of the pipe, we do not + // need our own ref count. instead, we share the lifetime (the ref count) + // of the entire pipe. this macro is just convenience since it does not + // declare a mRefCount variable; however, don't let the name fool you... + // we are not inheriting from nsPipe ;-) + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSIASYNCOUTPUTSTREAM + NS_DECL_NSICLASSINFO + + nsPipeOutputStream(nsPipe *pipe) + : mPipe(pipe) + , mWriterRefCnt(0) + , mLogicalOffset(0) + , mBlocking(true) + , mBlocked(false) + , mWritable(true) + , mCallbackFlags(0) + { } + + void SetNonBlocking(bool aNonBlocking) { mBlocking = !aNonBlocking; } + void SetWritable(bool writable) { mWritable = writable; } + + // synchronously wait for the pipe to become writable. + nsresult Wait(); + + // these functions return true to indicate that the pipe's monitor should + // be notified, to wake up a blocked writer if any. + bool OnOutputWritable(nsPipeEvents &); + bool OnOutputException(nsresult, nsPipeEvents &); + +private: + nsPipe *mPipe; + + // separate refcnt so that we know when to close the producer + nsrefcnt mWriterRefCnt; + int64_t mLogicalOffset; + bool mBlocking; + + // these variables can only be accessed while inside the pipe's monitor + bool mBlocked; + bool mWritable; + nsCOMPtr<nsIOutputStreamCallback> mCallback; + uint32_t mCallbackFlags; +}; + +//----------------------------------------------------------------------------- + +class nsPipe MOZ_FINAL : public nsIPipe +{ +public: + friend class nsPipeInputStream; + friend class nsPipeOutputStream; + + NS_DECL_ISUPPORTS + NS_DECL_NSIPIPE + + // nsPipe methods: + nsPipe(); + +private: + ~nsPipe(); + +public: + // + // methods below may only be called while inside the pipe's monitor + // + + void PeekSegment(uint32_t n, char *&cursor, char *&limit); + + // + // methods below may be called while outside the pipe's monitor + // + + nsresult GetReadSegment(const char *&segment, uint32_t &segmentLen); + void AdvanceReadCursor(uint32_t count); + + nsresult GetWriteSegment(char *&segment, uint32_t &segmentLen); + void AdvanceWriteCursor(uint32_t count); + + void OnPipeException(nsresult reason, bool outputOnly = false); + +protected: + // We can't inherit from both nsIInputStream and nsIOutputStream + // because they collide on their Close method. Consequently we nest their + // implementations to avoid the extra object allocation. + nsPipeInputStream mInput; + nsPipeOutputStream mOutput; + + ReentrantMonitor mReentrantMonitor; + nsSegmentedBuffer mBuffer; + + char* mReadCursor; + char* mReadLimit; + + int32_t mWriteSegment; + char* mWriteCursor; + char* mWriteLimit; + + nsresult mStatus; + bool mInited; +}; + +// +// NOTES on buffer architecture: +// +// +-----------------+ - - mBuffer.GetSegment(0) +// | | +// + - - - - - - - - + - - mReadCursor +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// +-----------------+ - - mReadLimit +// | +// +-----------------+ +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// +-----------------+ +// | +// +-----------------+ - - mBuffer.GetSegment(mWriteSegment) +// |/////////////////| +// |/////////////////| +// |/////////////////| +// + - - - - - - - - + - - mWriteCursor +// | | +// | | +// +-----------------+ - - mWriteLimit +// +// (shaded region contains data) +// +// NOTE: on some systems (notably OS/2), the heap allocator uses an arena for +// small allocations (e.g., 64 byte allocations). this means that buffers may +// be allocated back-to-back. in the diagram above, for example, mReadLimit +// would actually be pointing at the beginning of the next segment. when +// making changes to this file, please keep this fact in mind. +// + +//----------------------------------------------------------------------------- +// nsPipe methods: +//----------------------------------------------------------------------------- + +nsPipe::nsPipe() + : mInput(this) + , mOutput(this) + , mReentrantMonitor("nsPipe.mReentrantMonitor") + , mReadCursor(nullptr) + , mReadLimit(nullptr) + , mWriteSegment(-1) + , mWriteCursor(nullptr) + , mWriteLimit(nullptr) + , mStatus(NS_OK) + , mInited(false) +{ +} + +nsPipe::~nsPipe() +{ +} + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsPipe, nsIPipe) + +NS_IMETHODIMP +nsPipe::Init(bool nonBlockingIn, + bool nonBlockingOut, + uint32_t segmentSize, + uint32_t segmentCount, + nsIMemory *segmentAlloc) +{ + mInited = true; + + if (segmentSize == 0) + segmentSize = DEFAULT_SEGMENT_SIZE; + if (segmentCount == 0) + segmentCount = DEFAULT_SEGMENT_COUNT; + + // protect against overflow + uint32_t maxCount = uint32_t(-1) / segmentSize; + if (segmentCount > maxCount) + segmentCount = maxCount; + + nsresult rv = mBuffer.Init(segmentSize, segmentSize * segmentCount, segmentAlloc); + if (NS_FAILED(rv)) + return rv; + + mInput.SetNonBlocking(nonBlockingIn); + mOutput.SetNonBlocking(nonBlockingOut); + return NS_OK; +} + +NS_IMETHODIMP +nsPipe::GetInputStream(nsIAsyncInputStream **aInputStream) +{ + NS_ADDREF(*aInputStream = &mInput); + return NS_OK; +} + +NS_IMETHODIMP +nsPipe::GetOutputStream(nsIAsyncOutputStream **aOutputStream) +{ + NS_ENSURE_TRUE(mInited, NS_ERROR_NOT_INITIALIZED); + NS_ADDREF(*aOutputStream = &mOutput); + return NS_OK; +} + +void +nsPipe::PeekSegment(uint32_t index, char *&cursor, char *&limit) +{ + if (index == 0) { + NS_ASSERTION(!mReadCursor || mBuffer.GetSegmentCount(), "unexpected state"); + cursor = mReadCursor; + limit = mReadLimit; + } + else { + uint32_t numSegments = mBuffer.GetSegmentCount(); + if (index >= numSegments) + cursor = limit = nullptr; + else { + cursor = mBuffer.GetSegment(index); + if (mWriteSegment == (int32_t) index) + limit = mWriteCursor; + else + limit = cursor + mBuffer.GetSegmentSize(); + } + } +} + +nsresult +nsPipe::GetReadSegment(const char *&segment, uint32_t &segmentLen) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + if (mReadCursor == mReadLimit) + return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_WOULD_BLOCK; + + segment = mReadCursor; + segmentLen = mReadLimit - mReadCursor; + return NS_OK; +} + +void +nsPipe::AdvanceReadCursor(uint32_t bytesRead) +{ + NS_ASSERTION(bytesRead, "don't call if no bytes read"); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + LOG(("III advancing read cursor by %u\n", bytesRead)); + NS_ASSERTION(bytesRead <= mBuffer.GetSegmentSize(), "read too much"); + + mReadCursor += bytesRead; + NS_ASSERTION(mReadCursor <= mReadLimit, "read cursor exceeds limit"); + + mInput.ReduceAvailable(bytesRead); + + if (mReadCursor == mReadLimit) { + // we've reached the limit of how much we can read from this segment. + // if at the end of this segment, then we must discard this segment. + + // if still writing in this segment then bail because we're not done + // with the segment and have to wait for now... + if (mWriteSegment == 0 && mWriteLimit > mWriteCursor) { + NS_ASSERTION(mReadLimit == mWriteCursor, "unexpected state"); + return; + } + + // shift write segment index (-1 indicates an empty buffer). + --mWriteSegment; + + // done with this segment + mBuffer.DeleteFirstSegment(); + LOG(("III deleting first segment\n")); + + if (mWriteSegment == -1) { + // buffer is completely empty + mReadCursor = nullptr; + mReadLimit = nullptr; + mWriteCursor = nullptr; + mWriteLimit = nullptr; + } + else { + // advance read cursor and limit to next buffer segment + mReadCursor = mBuffer.GetSegment(0); + if (mWriteSegment == 0) + mReadLimit = mWriteCursor; + else + mReadLimit = mReadCursor + mBuffer.GetSegmentSize(); + } + + // we've free'd up a segment, so notify output stream that pipe has + // room for a new segment. + if (mOutput.OnOutputWritable(events)) + mon.Notify(); + } + } +} + +nsresult +nsPipe::GetWriteSegment(char *&segment, uint32_t &segmentLen) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + if (NS_FAILED(mStatus)) + return mStatus; + + // write cursor and limit may both be null indicating an empty buffer. + if (mWriteCursor == mWriteLimit) { + char *seg = mBuffer.AppendNewSegment(); + // pipe is full + if (seg == nullptr) + return NS_BASE_STREAM_WOULD_BLOCK; + LOG(("OOO appended new segment\n")); + mWriteCursor = seg; + mWriteLimit = mWriteCursor + mBuffer.GetSegmentSize(); + ++mWriteSegment; + } + + // make sure read cursor is initialized + if (mReadCursor == nullptr) { + NS_ASSERTION(mWriteSegment == 0, "unexpected null read cursor"); + mReadCursor = mReadLimit = mWriteCursor; + } + + // check to see if we can roll-back our read and write cursors to the + // beginning of the current/first segment. this is purely an optimization. + if (mReadCursor == mWriteCursor && mWriteSegment == 0) { + char *head = mBuffer.GetSegment(0); + LOG(("OOO rolling back write cursor %u bytes\n", mWriteCursor - head)); + mWriteCursor = mReadCursor = mReadLimit = head; + } + + segment = mWriteCursor; + segmentLen = mWriteLimit - mWriteCursor; + return NS_OK; +} + +void +nsPipe::AdvanceWriteCursor(uint32_t bytesWritten) +{ + NS_ASSERTION(bytesWritten, "don't call if no bytes written"); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + LOG(("OOO advancing write cursor by %u\n", bytesWritten)); + + char *newWriteCursor = mWriteCursor + bytesWritten; + NS_ASSERTION(newWriteCursor <= mWriteLimit, "write cursor exceeds limit"); + + // update read limit if reading in the same segment + if (mWriteSegment == 0 && mReadLimit == mWriteCursor) + mReadLimit = newWriteCursor; + + mWriteCursor = newWriteCursor; + + // The only way mReadCursor == mWriteCursor is if: + // + // - mReadCursor is at the start of a segment (which, based on how + // nsSegmentedBuffer works, means that this segment is the "first" + // segment) + // - mWriteCursor points at the location past the end of the current + // write segment (so the current write filled the current write + // segment, so we've incremented mWriteCursor to point past the end + // of it) + // - the segment to which data has just been written is located + // exactly one segment's worth of bytes before the first segment + // where mReadCursor is located + // + // Consequently, the byte immediately after the end of the current + // write segment is the first byte of the first segment, so + // mReadCursor == mWriteCursor. (Another way to think about this is + // to consider the buffer architecture diagram above, but consider it + // with an arena allocator which allocates from the *end* of the + // arena to the *beginning* of the arena.) + NS_ASSERTION(mReadCursor != mWriteCursor || + (mBuffer.GetSegment(0) == mReadCursor && + mWriteCursor == mWriteLimit), + "read cursor is bad"); + + // update the writable flag on the output stream + if (mWriteCursor == mWriteLimit) { + if (mBuffer.GetSize() >= mBuffer.GetMaxSize()) + mOutput.SetWritable(false); + } + + // notify input stream that pipe now contains additional data + if (mInput.OnInputReadable(bytesWritten, events)) + mon.Notify(); + } +} + +void +nsPipe::OnPipeException(nsresult reason, bool outputOnly) +{ + LOG(("PPP nsPipe::OnPipeException [reason=%x output-only=%d]\n", + reason, outputOnly)); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + // if we've already hit an exception, then ignore this one. + if (NS_FAILED(mStatus)) + return; + + mStatus = reason; + + // an output-only exception applies to the input end if the pipe has + // zero bytes available. + if (outputOnly && !mInput.Available()) + outputOnly = false; + + if (!outputOnly) + if (mInput.OnInputException(reason, events)) + mon.Notify(); + + if (mOutput.OnOutputException(reason, events)) + mon.Notify(); + } +} + +//----------------------------------------------------------------------------- +// nsPipeEvents methods: +//----------------------------------------------------------------------------- + +nsPipeEvents::~nsPipeEvents() +{ + // dispatch any pending events + + if (mInputCallback) { + mInputCallback->OnInputStreamReady(mInputStream); + mInputCallback = 0; + mInputStream = 0; + } + if (mOutputCallback) { + mOutputCallback->OnOutputStreamReady(mOutputStream); + mOutputCallback = 0; + mOutputStream = 0; + } +} + +//----------------------------------------------------------------------------- +// nsPipeInputStream methods: +//----------------------------------------------------------------------------- + +NS_IMPL_QUERY_INTERFACE5(nsPipeInputStream, + nsIInputStream, + nsIAsyncInputStream, + nsISeekableStream, + nsISearchableInputStream, + nsIClassInfo) + +NS_IMPL_CI_INTERFACE_GETTER4(nsPipeInputStream, + nsIInputStream, + nsIAsyncInputStream, + nsISeekableStream, + nsISearchableInputStream) + +NS_IMPL_THREADSAFE_CI(nsPipeInputStream) + +nsresult +nsPipeInputStream::Wait() +{ + NS_ASSERTION(mBlocking, "wait on non-blocking pipe input stream"); + + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + while (NS_SUCCEEDED(mPipe->mStatus) && (mAvailable == 0)) { + LOG(("III pipe input: waiting for data\n")); + + mBlocked = true; + mon.Wait(); + mBlocked = false; + + LOG(("III pipe input: woke up [pipe-status=%x available=%u]\n", + mPipe->mStatus, mAvailable)); + } + + return mPipe->mStatus == NS_BASE_STREAM_CLOSED ? NS_OK : mPipe->mStatus; +} + +bool +nsPipeInputStream::OnInputReadable(uint32_t bytesWritten, nsPipeEvents &events) +{ + bool result = false; + + mAvailable += bytesWritten; + + if (mCallback && !(mCallbackFlags & WAIT_CLOSURE_ONLY)) { + events.NotifyInputReady(this, mCallback); + mCallback = 0; + mCallbackFlags = 0; + } + else if (mBlocked) + result = true; + + return result; +} + +bool +nsPipeInputStream::OnInputException(nsresult reason, nsPipeEvents &events) +{ + LOG(("nsPipeInputStream::OnInputException [this=%x reason=%x]\n", + this, reason)); + + bool result = false; + + NS_ASSERTION(NS_FAILED(reason), "huh? successful exception"); + + // force count of available bytes to zero. + mAvailable = 0; + + if (mCallback) { + events.NotifyInputReady(this, mCallback); + mCallback = 0; + mCallbackFlags = 0; + } + else if (mBlocked) + result = true; + + return result; +} + +NS_IMETHODIMP_(nsrefcnt) +nsPipeInputStream::AddRef(void) +{ + NS_AtomicIncrementRefcnt(mReaderRefCnt); + return mPipe->AddRef(); +} + +NS_IMETHODIMP_(nsrefcnt) +nsPipeInputStream::Release(void) +{ + if (NS_AtomicDecrementRefcnt(mReaderRefCnt) == 0) + Close(); + return mPipe->Release(); +} + +NS_IMETHODIMP +nsPipeInputStream::CloseWithStatus(nsresult reason) +{ + LOG(("III CloseWithStatus [this=%x reason=%x]\n", this, reason)); + + if (NS_SUCCEEDED(reason)) + reason = NS_BASE_STREAM_CLOSED; + + mPipe->OnPipeException(reason); + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::Close() +{ + return CloseWithStatus(NS_BASE_STREAM_CLOSED); +} + +NS_IMETHODIMP +nsPipeInputStream::Available(uint64_t *result) +{ + // nsPipeInputStream supports under 4GB stream only + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // return error if pipe closed + if (!mAvailable && NS_FAILED(mPipe->mStatus)) + return mPipe->mStatus; + + *result = (uint64_t)mAvailable; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::ReadSegments(nsWriteSegmentFun writer, + void *closure, + uint32_t count, + uint32_t *readCount) +{ + LOG(("III ReadSegments [this=%x count=%u]\n", this, count)); + + nsresult rv = NS_OK; + + const char *segment; + uint32_t segmentLen; + + *readCount = 0; + while (count) { + rv = mPipe->GetReadSegment(segment, segmentLen); + if (NS_FAILED(rv)) { + // ignore this error if we've already read something. + if (*readCount > 0) { + rv = NS_OK; + break; + } + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + // pipe is empty + if (!mBlocking) + break; + // wait for some data to be written to the pipe + rv = Wait(); + if (NS_SUCCEEDED(rv)) + continue; + } + // ignore this error, just return. + if (rv == NS_BASE_STREAM_CLOSED) { + rv = NS_OK; + break; + } + mPipe->OnPipeException(rv); + break; + } + + // read no more than count + if (segmentLen > count) + segmentLen = count; + + uint32_t writeCount, originalLen = segmentLen; + while (segmentLen) { + writeCount = 0; + + rv = writer(this, closure, segment, *readCount, segmentLen, &writeCount); + + if (NS_FAILED(rv) || writeCount == 0) { + count = 0; + // any errors returned from the writer end here: do not + // propagate to the caller of ReadSegments. + rv = NS_OK; + break; + } + + NS_ASSERTION(writeCount <= segmentLen, "wrote more than expected"); + segment += writeCount; + segmentLen -= writeCount; + count -= writeCount; + *readCount += writeCount; + mLogicalOffset += writeCount; + } + + if (segmentLen < originalLen) + mPipe->AdvanceReadCursor(originalLen - segmentLen); + } + + return rv; +} + +NS_IMETHODIMP +nsPipeInputStream::Read(char* toBuf, uint32_t bufLen, uint32_t *readCount) +{ + return ReadSegments(NS_CopySegmentToBuffer, toBuf, bufLen, readCount); +} + +NS_IMETHODIMP +nsPipeInputStream::IsNonBlocking(bool *aNonBlocking) +{ + *aNonBlocking = !mBlocking; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::AsyncWait(nsIInputStreamCallback *callback, + uint32_t flags, + uint32_t requestedCount, + nsIEventTarget *target) +{ + LOG(("III AsyncWait [this=%x]\n", this)); + + nsPipeEvents pipeEvents; + { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // replace a pending callback + mCallback = 0; + mCallbackFlags = 0; + + if (!callback) + return NS_OK; + + nsCOMPtr<nsIInputStreamCallback> proxy; + if (target) { + proxy = NS_NewInputStreamReadyEvent(callback, target); + callback = proxy; + } + + if (NS_FAILED(mPipe->mStatus) || + (mAvailable && !(flags & WAIT_CLOSURE_ONLY))) { + // stream is already closed or readable; post event. + pipeEvents.NotifyInputReady(this, callback); + } + else { + // queue up callback object to be notified when data becomes available + mCallback = callback; + mCallbackFlags = flags; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::Seek(int32_t whence, int64_t offset) +{ + NS_NOTREACHED("nsPipeInputStream::Seek"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPipeInputStream::Tell(int64_t *offset) +{ + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // return error if pipe closed + if (!mAvailable && NS_FAILED(mPipe->mStatus)) + return mPipe->mStatus; + + *offset = mLogicalOffset; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::SetEOF() +{ + NS_NOTREACHED("nsPipeInputStream::SetEOF"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +#define COMPARE(s1, s2, i) \ + (ignoreCase \ + ? nsCRT::strncasecmp((const char *)s1, (const char *)s2, (uint32_t)i) \ + : nsCRT::strncmp((const char *)s1, (const char *)s2, (uint32_t)i)) + +NS_IMETHODIMP +nsPipeInputStream::Search(const char *forString, + bool ignoreCase, + bool *found, + uint32_t *offsetSearchedTo) +{ + LOG(("III Search [for=%s ic=%u]\n", forString, ignoreCase)); + + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + char *cursor1, *limit1; + uint32_t index = 0, offset = 0; + uint32_t strLen = strlen(forString); + + mPipe->PeekSegment(0, cursor1, limit1); + if (cursor1 == limit1) { + *found = false; + *offsetSearchedTo = 0; + LOG((" result [found=%u offset=%u]\n", *found, *offsetSearchedTo)); + return NS_OK; + } + + while (true) { + uint32_t i, len1 = limit1 - cursor1; + + // check if the string is in the buffer segment + for (i = 0; i < len1 - strLen + 1; i++) { + if (COMPARE(&cursor1[i], forString, strLen) == 0) { + *found = true; + *offsetSearchedTo = offset + i; + LOG((" result [found=%u offset=%u]\n", *found, *offsetSearchedTo)); + return NS_OK; + } + } + + // get the next segment + char *cursor2, *limit2; + uint32_t len2; + + index++; + offset += len1; + + mPipe->PeekSegment(index, cursor2, limit2); + if (cursor2 == limit2) { + *found = false; + *offsetSearchedTo = offset - strLen + 1; + LOG((" result [found=%u offset=%u]\n", *found, *offsetSearchedTo)); + return NS_OK; + } + len2 = limit2 - cursor2; + + // check if the string is straddling the next buffer segment + uint32_t lim = XPCOM_MIN(strLen, len2 + 1); + for (i = 0; i < lim; ++i) { + uint32_t strPart1Len = strLen - i - 1; + uint32_t strPart2Len = strLen - strPart1Len; + const char* strPart2 = &forString[strLen - strPart2Len]; + uint32_t bufSeg1Offset = len1 - strPart1Len; + if (COMPARE(&cursor1[bufSeg1Offset], forString, strPart1Len) == 0 && + COMPARE(cursor2, strPart2, strPart2Len) == 0) { + *found = true; + *offsetSearchedTo = offset - strPart1Len; + LOG((" result [found=%u offset=%u]\n", *found, *offsetSearchedTo)); + return NS_OK; + } + } + + // finally continue with the next buffer + cursor1 = cursor2; + limit1 = limit2; + } + + NS_NOTREACHED("can't get here"); + return NS_ERROR_UNEXPECTED; // keep compiler happy +} + +//----------------------------------------------------------------------------- +// nsPipeOutputStream methods: +//----------------------------------------------------------------------------- + +NS_IMPL_QUERY_INTERFACE3(nsPipeOutputStream, + nsIOutputStream, + nsIAsyncOutputStream, + nsIClassInfo) + +NS_IMPL_CI_INTERFACE_GETTER2(nsPipeOutputStream, + nsIOutputStream, + nsIAsyncOutputStream) + +NS_IMPL_THREADSAFE_CI(nsPipeOutputStream) + +nsresult +nsPipeOutputStream::Wait() +{ + NS_ASSERTION(mBlocking, "wait on non-blocking pipe output stream"); + + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + if (NS_SUCCEEDED(mPipe->mStatus) && !mWritable) { + LOG(("OOO pipe output: waiting for space\n")); + mBlocked = true; + mon.Wait(); + mBlocked = false; + LOG(("OOO pipe output: woke up [pipe-status=%x writable=%u]\n", + mPipe->mStatus, mWritable)); + } + + return mPipe->mStatus == NS_BASE_STREAM_CLOSED ? NS_OK : mPipe->mStatus; +} + +bool +nsPipeOutputStream::OnOutputWritable(nsPipeEvents &events) +{ + bool result = false; + + mWritable = true; + + if (mCallback && !(mCallbackFlags & WAIT_CLOSURE_ONLY)) { + events.NotifyOutputReady(this, mCallback); + mCallback = 0; + mCallbackFlags = 0; + } + else if (mBlocked) + result = true; + + return result; +} + +bool +nsPipeOutputStream::OnOutputException(nsresult reason, nsPipeEvents &events) +{ + LOG(("nsPipeOutputStream::OnOutputException [this=%x reason=%x]\n", + this, reason)); + + bool result = false; + + NS_ASSERTION(NS_FAILED(reason), "huh? successful exception"); + mWritable = false; + + if (mCallback) { + events.NotifyOutputReady(this, mCallback); + mCallback = 0; + mCallbackFlags = 0; + } + else if (mBlocked) + result = true; + + return result; +} + + +NS_IMETHODIMP_(nsrefcnt) +nsPipeOutputStream::AddRef() +{ + NS_AtomicIncrementRefcnt(mWriterRefCnt); + return mPipe->AddRef(); +} + +NS_IMETHODIMP_(nsrefcnt) +nsPipeOutputStream::Release() +{ + if (NS_AtomicDecrementRefcnt(mWriterRefCnt) == 0) + Close(); + return mPipe->Release(); +} + +NS_IMETHODIMP +nsPipeOutputStream::CloseWithStatus(nsresult reason) +{ + LOG(("OOO CloseWithStatus [this=%x reason=%x]\n", this, reason)); + + if (NS_SUCCEEDED(reason)) + reason = NS_BASE_STREAM_CLOSED; + + // input stream may remain open + mPipe->OnPipeException(reason, true); + return NS_OK; +} + +NS_IMETHODIMP +nsPipeOutputStream::Close() +{ + return CloseWithStatus(NS_BASE_STREAM_CLOSED); +} + +NS_IMETHODIMP +nsPipeOutputStream::WriteSegments(nsReadSegmentFun reader, + void* closure, + uint32_t count, + uint32_t *writeCount) +{ + LOG(("OOO WriteSegments [this=%x count=%u]\n", this, count)); + + nsresult rv = NS_OK; + + char *segment; + uint32_t segmentLen; + + *writeCount = 0; + while (count) { + rv = mPipe->GetWriteSegment(segment, segmentLen); + if (NS_FAILED(rv)) { + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + // pipe is full + if (!mBlocking) { + // ignore this error if we've already written something + if (*writeCount > 0) + rv = NS_OK; + break; + } + // wait for the pipe to have an empty segment. + rv = Wait(); + if (NS_SUCCEEDED(rv)) + continue; + } + mPipe->OnPipeException(rv); + break; + } + + // write no more than count + if (segmentLen > count) + segmentLen = count; + + uint32_t readCount, originalLen = segmentLen; + while (segmentLen) { + readCount = 0; + + rv = reader(this, closure, segment, *writeCount, segmentLen, &readCount); + + if (NS_FAILED(rv) || readCount == 0) { + count = 0; + // any errors returned from the reader end here: do not + // propagate to the caller of WriteSegments. + rv = NS_OK; + break; + } + + NS_ASSERTION(readCount <= segmentLen, "read more than expected"); + segment += readCount; + segmentLen -= readCount; + count -= readCount; + *writeCount += readCount; + mLogicalOffset += readCount; + } + + if (segmentLen < originalLen) + mPipe->AdvanceWriteCursor(originalLen - segmentLen); + } + + return rv; +} + +static NS_METHOD +nsReadFromRawBuffer(nsIOutputStream* outStr, + void* closure, + char* toRawSegment, + uint32_t offset, + uint32_t count, + uint32_t *readCount) +{ + const char* fromBuf = (const char*)closure; + memcpy(toRawSegment, &fromBuf[offset], count); + *readCount = count; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeOutputStream::Write(const char* fromBuf, + uint32_t bufLen, + uint32_t *writeCount) +{ + return WriteSegments(nsReadFromRawBuffer, (void*)fromBuf, bufLen, writeCount); +} + +NS_IMETHODIMP +nsPipeOutputStream::Flush(void) +{ + // nothing to do + return NS_OK; +} + +static NS_METHOD +nsReadFromInputStream(nsIOutputStream* outStr, + void* closure, + char* toRawSegment, + uint32_t offset, + uint32_t count, + uint32_t *readCount) +{ + nsIInputStream* fromStream = (nsIInputStream*)closure; + return fromStream->Read(toRawSegment, count, readCount); +} + +NS_IMETHODIMP +nsPipeOutputStream::WriteFrom(nsIInputStream* fromStream, + uint32_t count, + uint32_t *writeCount) +{ + return WriteSegments(nsReadFromInputStream, fromStream, count, writeCount); +} + +NS_IMETHODIMP +nsPipeOutputStream::IsNonBlocking(bool *aNonBlocking) +{ + *aNonBlocking = !mBlocking; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeOutputStream::AsyncWait(nsIOutputStreamCallback *callback, + uint32_t flags, + uint32_t requestedCount, + nsIEventTarget *target) +{ + LOG(("OOO AsyncWait [this=%x]\n", this)); + + nsPipeEvents pipeEvents; + { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // replace a pending callback + mCallback = 0; + mCallbackFlags = 0; + + if (!callback) + return NS_OK; + + nsCOMPtr<nsIOutputStreamCallback> proxy; + if (target) { + proxy = NS_NewOutputStreamReadyEvent(callback, target); + callback = proxy; + } + + if (NS_FAILED(mPipe->mStatus) || + (mWritable && !(flags & WAIT_CLOSURE_ONLY))) { + // stream is already closed or writable; post event. + pipeEvents.NotifyOutputReady(this, callback); + } + else { + // queue up callback object to be notified when data becomes available + mCallback = callback; + mCallbackFlags = flags; + } + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +nsresult +NS_NewPipe(nsIInputStream **pipeIn, + nsIOutputStream **pipeOut, + uint32_t segmentSize, + uint32_t maxSize, + bool nonBlockingInput, + bool nonBlockingOutput, + nsIMemory *segmentAlloc) +{ + if (segmentSize == 0) + segmentSize = DEFAULT_SEGMENT_SIZE; + + // Handle maxSize of UINT32_MAX as a special case + uint32_t segmentCount; + if (maxSize == UINT32_MAX) + segmentCount = UINT32_MAX; + else + segmentCount = maxSize / segmentSize; + + nsIAsyncInputStream *in; + nsIAsyncOutputStream *out; + nsresult rv = NS_NewPipe2(&in, &out, nonBlockingInput, nonBlockingOutput, + segmentSize, segmentCount, segmentAlloc); + if (NS_FAILED(rv)) return rv; + + *pipeIn = in; + *pipeOut = out; + return NS_OK; +} + +nsresult +NS_NewPipe2(nsIAsyncInputStream **pipeIn, + nsIAsyncOutputStream **pipeOut, + bool nonBlockingInput, + bool nonBlockingOutput, + uint32_t segmentSize, + uint32_t segmentCount, + nsIMemory *segmentAlloc) +{ + nsresult rv; + + nsPipe *pipe = new nsPipe(); + if (!pipe) + return NS_ERROR_OUT_OF_MEMORY; + + rv = pipe->Init(nonBlockingInput, + nonBlockingOutput, + segmentSize, + segmentCount, + segmentAlloc); + if (NS_FAILED(rv)) { + NS_ADDREF(pipe); + NS_RELEASE(pipe); + return rv; + } + + pipe->GetInputStream(pipeIn); + pipe->GetOutputStream(pipeOut); + return NS_OK; +} + +nsresult +nsPipeConstructor(nsISupports *outer, REFNSIID iid, void **result) +{ + if (outer) + return NS_ERROR_NO_AGGREGATION; + nsPipe *pipe = new nsPipe(); + if (!pipe) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(pipe); + nsresult rv = pipe->QueryInterface(iid, result); + NS_RELEASE(pipe); + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/io/nsScriptableBase64Encoder.cpp b/xpcom/io/nsScriptableBase64Encoder.cpp new file mode 100644 index 000000000..8f54d98e6 --- /dev/null +++ b/xpcom/io/nsScriptableBase64Encoder.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsScriptableBase64Encoder.h" +#include "mozilla/Base64.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS1(nsScriptableBase64Encoder, nsIScriptableBase64Encoder) + +/* ACString encodeToCString (in nsIInputStream stream, in unsigned long length); */ +NS_IMETHODIMP +nsScriptableBase64Encoder::EncodeToCString(nsIInputStream *aStream, + uint32_t aLength, + nsACString & _retval) +{ + Base64EncodeInputStream(aStream, _retval, aLength); + return NS_OK; +} + +/* AString encodeToString (in nsIInputStream stream, in unsigned long length); */ +NS_IMETHODIMP +nsScriptableBase64Encoder::EncodeToString(nsIInputStream *aStream, + uint32_t aLength, + nsAString & _retval) +{ + Base64EncodeInputStream(aStream, _retval, aLength); + return NS_OK; +} diff --git a/xpcom/io/nsScriptableBase64Encoder.h b/xpcom/io/nsScriptableBase64Encoder.h new file mode 100644 index 000000000..4436344de --- /dev/null +++ b/xpcom/io/nsScriptableBase64Encoder.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsScriptableBase64Encoder_h__ +#define nsScriptableBase64Encoder_h__ + +#include "nsIScriptableBase64Encoder.h" +#include "mozilla/Attributes.h" + +#define NS_SCRIPTABLEBASE64ENCODER_CID \ + {0xaaf68860, 0xf849, 0x40ee, \ + {0xbb, 0x7a, 0xb2, 0x29, 0xbc, 0xe0, 0x36, 0xa3} } +#define NS_SCRIPTABLEBASE64ENCODER_CONTRACTID \ + "@mozilla.org/scriptablebase64encoder;1" + +class nsScriptableBase64Encoder MOZ_FINAL : public nsIScriptableBase64Encoder +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISCRIPTABLEBASE64ENCODER +private: + ~nsScriptableBase64Encoder() {} +}; + +#endif diff --git a/xpcom/io/nsScriptableInputStream.cpp b/xpcom/io/nsScriptableInputStream.cpp new file mode 100644 index 000000000..36f43ea5e --- /dev/null +++ b/xpcom/io/nsScriptableInputStream.cpp @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsScriptableInputStream.h" +#include "nsMemory.h" +#include "nsString.h" + +NS_IMPL_ISUPPORTS1(nsScriptableInputStream, nsIScriptableInputStream) + +// nsIScriptableInputStream methods +NS_IMETHODIMP +nsScriptableInputStream::Close(void) { + if (!mInputStream) return NS_ERROR_NOT_INITIALIZED; + return mInputStream->Close(); +} + +NS_IMETHODIMP +nsScriptableInputStream::Init(nsIInputStream *aInputStream) { + if (!aInputStream) return NS_ERROR_NULL_POINTER; + mInputStream = aInputStream; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptableInputStream::Available(uint64_t *_retval) { + if (!mInputStream) return NS_ERROR_NOT_INITIALIZED; + return mInputStream->Available(_retval); +} + +NS_IMETHODIMP +nsScriptableInputStream::Read(uint32_t aCount, char **_retval) { + nsresult rv = NS_OK; + uint64_t count64 = 0; + char *buffer = nullptr; + + if (!mInputStream) return NS_ERROR_NOT_INITIALIZED; + + rv = mInputStream->Available(&count64); + if (NS_FAILED(rv)) return rv; + + // bug716556 - Ensure count+1 doesn't overflow + uint32_t count = XPCOM_MIN((uint32_t)XPCOM_MIN<uint64_t>(count64, aCount), UINT32_MAX - 1); + buffer = (char*)nsMemory::Alloc(count+1); // make room for '\0' + if (!buffer) return NS_ERROR_OUT_OF_MEMORY; + + uint32_t amtRead = 0; + rv = mInputStream->Read(buffer, count, &amtRead); + if (NS_FAILED(rv)) { + nsMemory::Free(buffer); + return rv; + } + + buffer[amtRead] = '\0'; + *_retval = buffer; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptableInputStream::ReadBytes(uint32_t aCount, nsACString &_retval) { + if (!mInputStream) { + return NS_ERROR_NOT_INITIALIZED; + } + + _retval.SetLength(aCount); + if (_retval.Length() != aCount) { + return NS_ERROR_OUT_OF_MEMORY; + } + + char *ptr = _retval.BeginWriting(); + uint32_t totalBytesRead = 0; + while (1) { + uint32_t bytesRead; + nsresult rv = mInputStream->Read(ptr + totalBytesRead, + aCount - totalBytesRead, + &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + + totalBytesRead += bytesRead; + if (totalBytesRead == aCount) { + break; + } + + // If we have read zero bytes, we have hit EOF. + if (bytesRead == 0) { + _retval.Truncate(); + return NS_ERROR_FAILURE; + } + + } + return NS_OK; +} + +nsresult +nsScriptableInputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) { + if (aOuter) return NS_ERROR_NO_AGGREGATION; + + nsScriptableInputStream *sis = new nsScriptableInputStream(); + if (!sis) return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(sis); + nsresult rv = sis->QueryInterface(aIID, aResult); + NS_RELEASE(sis); + return rv; +} diff --git a/xpcom/io/nsScriptableInputStream.h b/xpcom/io/nsScriptableInputStream.h new file mode 100644 index 000000000..080816c7f --- /dev/null +++ b/xpcom/io/nsScriptableInputStream.h @@ -0,0 +1,39 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef ___nsscriptableinputstream___h_ +#define ___nsscriptableinputstream___h_ + +#include "nsIScriptableInputStream.h" +#include "nsIInputStream.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" + +#define NS_SCRIPTABLEINPUTSTREAM_CID \ +{ 0x7225c040, 0xa9bf, 0x11d3, { 0xa1, 0x97, 0x0, 0x50, 0x4, 0x1c, 0xaf, 0x44 } } + +#define NS_SCRIPTABLEINPUTSTREAM_CONTRACTID "@mozilla.org/scriptableinputstream;1" + +class nsScriptableInputStream MOZ_FINAL : public nsIScriptableInputStream { +public: + // nsISupports methods + NS_DECL_ISUPPORTS + + // nsIScriptableInputStream methods + NS_DECL_NSISCRIPTABLEINPUTSTREAM + + // nsScriptableInputStream methods + nsScriptableInputStream() {} + + static nsresult + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +private: + ~nsScriptableInputStream() {} + + nsCOMPtr<nsIInputStream> mInputStream; +}; + +#endif // ___nsscriptableinputstream___h_ diff --git a/xpcom/io/nsSegmentedBuffer.cpp b/xpcom/io/nsSegmentedBuffer.cpp new file mode 100644 index 000000000..1938fa16f --- /dev/null +++ b/xpcom/io/nsSegmentedBuffer.cpp @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsSegmentedBuffer.h" +#include "nsCRT.h" + +nsresult +nsSegmentedBuffer::Init(uint32_t segmentSize, uint32_t maxSize, + nsIMemory* allocator) +{ + if (mSegmentArrayCount != 0) + return NS_ERROR_FAILURE; // initialized more than once + mSegmentSize = segmentSize; + mMaxSize = maxSize; + mSegAllocator = allocator; + if (mSegAllocator == nullptr) { + mSegAllocator = nsMemory::GetGlobalMemoryService(); + } + else { + NS_ADDREF(mSegAllocator); + } +#if 0 // testing... + mSegmentArrayCount = 2; +#else + mSegmentArrayCount = NS_SEGMENTARRAY_INITIAL_COUNT; +#endif + return NS_OK; +} + +char* +nsSegmentedBuffer::AppendNewSegment() +{ + if (GetSize() >= mMaxSize) + return nullptr; + + if (mSegmentArray == nullptr) { + uint32_t bytes = mSegmentArrayCount * sizeof(char*); + mSegmentArray = (char**)nsMemory::Alloc(bytes); + if (mSegmentArray == nullptr) + return nullptr; + memset(mSegmentArray, 0, bytes); + } + + if (IsFull()) { + uint32_t newArraySize = mSegmentArrayCount * 2; + uint32_t bytes = newArraySize * sizeof(char*); + char** newSegArray = (char**)nsMemory::Realloc(mSegmentArray, bytes); + if (newSegArray == nullptr) + return nullptr; + mSegmentArray = newSegArray; + // copy wrapped content to new extension + if (mFirstSegmentIndex > mLastSegmentIndex) { + // deal with wrap around case + memcpy(&mSegmentArray[mSegmentArrayCount], + mSegmentArray, + mLastSegmentIndex * sizeof(char*)); + memset(mSegmentArray, 0, mLastSegmentIndex * sizeof(char*)); + mLastSegmentIndex += mSegmentArrayCount; + memset(&mSegmentArray[mLastSegmentIndex], 0, + (newArraySize - mLastSegmentIndex) * sizeof(char*)); + } + else { + memset(&mSegmentArray[mLastSegmentIndex], 0, + (newArraySize - mLastSegmentIndex) * sizeof(char*)); + } + mSegmentArrayCount = newArraySize; + } + + char* seg = (char*)mSegAllocator->Alloc(mSegmentSize); + if (seg == nullptr) { + return nullptr; + } + mSegmentArray[mLastSegmentIndex] = seg; + mLastSegmentIndex = ModSegArraySize(mLastSegmentIndex + 1); + return seg; +} + +bool +nsSegmentedBuffer::DeleteFirstSegment() +{ + NS_ASSERTION(mSegmentArray[mFirstSegmentIndex] != nullptr, "deleting bad segment"); + (void)mSegAllocator->Free(mSegmentArray[mFirstSegmentIndex]); + mSegmentArray[mFirstSegmentIndex] = nullptr; + int32_t last = ModSegArraySize(mLastSegmentIndex - 1); + if (mFirstSegmentIndex == last) { + mLastSegmentIndex = last; + return true; + } + else { + mFirstSegmentIndex = ModSegArraySize(mFirstSegmentIndex + 1); + return false; + } +} + +bool +nsSegmentedBuffer::DeleteLastSegment() +{ + int32_t last = ModSegArraySize(mLastSegmentIndex - 1); + NS_ASSERTION(mSegmentArray[last] != nullptr, "deleting bad segment"); + (void)mSegAllocator->Free(mSegmentArray[last]); + mSegmentArray[last] = nullptr; + mLastSegmentIndex = last; + return (bool)(mLastSegmentIndex == mFirstSegmentIndex); +} + +bool +nsSegmentedBuffer::ReallocLastSegment(size_t newSize) +{ + int32_t last = ModSegArraySize(mLastSegmentIndex - 1); + NS_ASSERTION(mSegmentArray[last] != nullptr, "realloc'ing bad segment"); + char *newSegment = + (char*)mSegAllocator->Realloc(mSegmentArray[last], newSize); + if (newSegment) { + mSegmentArray[last] = newSegment; + return true; + } else { + return false; + } +} + +void +nsSegmentedBuffer::Empty() +{ + if (mSegmentArray) { + for (uint32_t i = 0; i < mSegmentArrayCount; i++) { + if (mSegmentArray[i]) + mSegAllocator->Free(mSegmentArray[i]); + } + nsMemory::Free(mSegmentArray); + mSegmentArray = nullptr; + } + mSegmentArrayCount = NS_SEGMENTARRAY_INITIAL_COUNT; + mFirstSegmentIndex = mLastSegmentIndex = 0; +} + +#if 0 +void +TestSegmentedBuffer() +{ + nsSegmentedBuffer* buf = new nsSegmentedBuffer(); + NS_ASSERTION(buf, "out of memory"); + buf->Init(4, 16); + char* seg; + bool empty; + seg = buf->AppendNewSegment(); + NS_ASSERTION(seg, "AppendNewSegment failed"); + seg = buf->AppendNewSegment(); + NS_ASSERTION(seg, "AppendNewSegment failed"); + seg = buf->AppendNewSegment(); + NS_ASSERTION(seg, "AppendNewSegment failed"); + empty = buf->DeleteFirstSegment(); + NS_ASSERTION(!empty, "DeleteFirstSegment failed"); + empty = buf->DeleteFirstSegment(); + NS_ASSERTION(!empty, "DeleteFirstSegment failed"); + seg = buf->AppendNewSegment(); + NS_ASSERTION(seg, "AppendNewSegment failed"); + seg = buf->AppendNewSegment(); + NS_ASSERTION(seg, "AppendNewSegment failed"); + seg = buf->AppendNewSegment(); + NS_ASSERTION(seg, "AppendNewSegment failed"); + empty = buf->DeleteFirstSegment(); + NS_ASSERTION(!empty, "DeleteFirstSegment failed"); + empty = buf->DeleteFirstSegment(); + NS_ASSERTION(!empty, "DeleteFirstSegment failed"); + empty = buf->DeleteFirstSegment(); + NS_ASSERTION(!empty, "DeleteFirstSegment failed"); + empty = buf->DeleteFirstSegment(); + NS_ASSERTION(empty, "DeleteFirstSegment failed"); + delete buf; +} +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/io/nsSegmentedBuffer.h b/xpcom/io/nsSegmentedBuffer.h new file mode 100644 index 000000000..82ef31266 --- /dev/null +++ b/xpcom/io/nsSegmentedBuffer.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsSegmentedBuffer_h__ +#define nsSegmentedBuffer_h__ + +#include "nsMemory.h" +#include "prclist.h" + +class nsSegmentedBuffer +{ +public: + nsSegmentedBuffer() + : mSegmentSize(0), mMaxSize(0), + mSegAllocator(nullptr), mSegmentArray(nullptr), + mSegmentArrayCount(0), + mFirstSegmentIndex(0), mLastSegmentIndex(0) {} + + ~nsSegmentedBuffer() { + Empty(); + NS_IF_RELEASE(mSegAllocator); + } + + + nsresult Init(uint32_t segmentSize, uint32_t maxSize, + nsIMemory* allocator = nullptr); + + char* AppendNewSegment(); // pushes at end + + // returns true if no more segments remain: + bool DeleteFirstSegment(); // pops from beginning + + // returns true if no more segments remain: + bool DeleteLastSegment(); // pops from beginning + + // Call Realloc() on last segment. This is used to reduce memory + // consumption when data is not an exact multiple of segment size. + bool ReallocLastSegment(size_t newSize); + + void Empty(); // frees all segments + + inline uint32_t GetSegmentCount() { + if (mFirstSegmentIndex <= mLastSegmentIndex) + return mLastSegmentIndex - mFirstSegmentIndex; + else + return mSegmentArrayCount + mLastSegmentIndex - mFirstSegmentIndex; + } + + inline uint32_t GetSegmentSize() { return mSegmentSize; } + inline uint32_t GetMaxSize() { return mMaxSize; } + inline uint32_t GetSize() { return GetSegmentCount() * mSegmentSize; } + + inline char* GetSegment(uint32_t indx) { + NS_ASSERTION(indx < GetSegmentCount(), "index out of bounds"); + int32_t i = ModSegArraySize(mFirstSegmentIndex + (int32_t)indx); + return mSegmentArray[i]; + } + +protected: + inline int32_t ModSegArraySize(int32_t n) { + uint32_t result = n & (mSegmentArrayCount - 1); + NS_ASSERTION(result == n % mSegmentArrayCount, + "non-power-of-2 mSegmentArrayCount"); + return result; + } + + inline bool IsFull() { + return ModSegArraySize(mLastSegmentIndex + 1) == mFirstSegmentIndex; + } + +protected: + uint32_t mSegmentSize; + uint32_t mMaxSize; + nsIMemory* mSegAllocator; + char** mSegmentArray; + uint32_t mSegmentArrayCount; + int32_t mFirstSegmentIndex; + int32_t mLastSegmentIndex; +}; + +// NS_SEGMENTARRAY_INITIAL_SIZE: This number needs to start out as a +// power of 2 given how it gets used. We double the segment array +// when we overflow it, and use that fact that it's a power of 2 +// to compute a fast modulus operation in IsFull. +// +// 32 segment array entries can accommodate 128k of data if segments +// are 4k in size. That seems like a reasonable amount that will avoid +// needing to grow the segment array. +#define NS_SEGMENTARRAY_INITIAL_COUNT 32 + +#endif // nsSegmentedBuffer_h__ diff --git a/xpcom/io/nsStorageStream.cpp b/xpcom/io/nsStorageStream.cpp new file mode 100644 index 000000000..107e292c9 --- /dev/null +++ b/xpcom/io/nsStorageStream.cpp @@ -0,0 +1,537 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sts=4 sw=4 cin et: */ +/* 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/. */ + +/* + * The storage stream provides an internal buffer that can be filled by a + * client using a single output stream. One or more independent input streams + * can be created to read the data out non-destructively. The implementation + * uses a segmented buffer internally to avoid realloc'ing of large buffers, + * with the attendant performance loss and heap fragmentation. + */ + +#include "nsAlgorithm.h" +#include "nsStorageStream.h" +#include "nsSegmentedBuffer.h" +#include "nsStreamUtils.h" +#include "nsCOMPtr.h" +#include "prbit.h" +#include "nsIInputStream.h" +#include "nsISeekableStream.h" +#include "prlog.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" + +#if defined(PR_LOGGING) +// +// Log module for StorageStream logging... +// +// To enable logging (see prlog.h for full details): +// +// set NSPR_LOG_MODULES=StorageStreamLog:5 +// set NSPR_LOG_FILE=nspr.log +// +// this enables PR_LOG_DEBUG level information and places all output in +// the file nspr.log +// +static PRLogModuleInfo* +GetStorageStreamLog() +{ + static PRLogModuleInfo *sLog; + if (!sLog) + sLog = PR_NewLogModule("nsStorageStream"); + return sLog; +} +#endif +#define LOG(args) PR_LOG(GetStorageStreamLog(), PR_LOG_DEBUG, args) + +nsStorageStream::nsStorageStream() + : mSegmentedBuffer(0), mSegmentSize(0), mWriteInProgress(false), + mLastSegmentNum(-1), mWriteCursor(0), mSegmentEnd(0), mLogicalLength(0) +{ + LOG(("Creating nsStorageStream [%p].\n", this)); +} + +nsStorageStream::~nsStorageStream() +{ + delete mSegmentedBuffer; +} + +NS_IMPL_THREADSAFE_ISUPPORTS2(nsStorageStream, + nsIStorageStream, + nsIOutputStream) + +NS_IMETHODIMP +nsStorageStream::Init(uint32_t segmentSize, uint32_t maxSize, + nsIMemory *segmentAllocator) +{ + mSegmentedBuffer = new nsSegmentedBuffer(); + if (!mSegmentedBuffer) + return NS_ERROR_OUT_OF_MEMORY; + + mSegmentSize = segmentSize; + mSegmentSizeLog2 = PR_FloorLog2(segmentSize); + + // Segment size must be a power of two + if (mSegmentSize != ((uint32_t)1 << mSegmentSizeLog2)) + return NS_ERROR_INVALID_ARG; + + return mSegmentedBuffer->Init(segmentSize, maxSize, segmentAllocator); +} + +NS_IMETHODIMP +nsStorageStream::GetOutputStream(int32_t aStartingOffset, + nsIOutputStream * *aOutputStream) +{ + NS_ENSURE_ARG(aOutputStream); + NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED); + + if (mWriteInProgress) + return NS_ERROR_NOT_AVAILABLE; + + nsresult rv = Seek(aStartingOffset); + if (NS_FAILED(rv)) return rv; + + // Enlarge the last segment in the buffer so that it is the same size as + // all the other segments in the buffer. (It may have been realloc'ed + // smaller in the Close() method.) + if (mLastSegmentNum >= 0) + if (mSegmentedBuffer->ReallocLastSegment(mSegmentSize)) { + // Need to re-Seek, since realloc changed segment base pointer + rv = Seek(aStartingOffset); + if (NS_FAILED(rv)) return rv; + } + + NS_ADDREF(this); + *aOutputStream = static_cast<nsIOutputStream*>(this); + mWriteInProgress = true; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::Close() +{ + NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED); + + mWriteInProgress = false; + + int32_t segmentOffset = SegOffset(mLogicalLength); + + // Shrink the final segment in the segmented buffer to the minimum size + // needed to contain the data, so as to conserve memory. + if (segmentOffset) + mSegmentedBuffer->ReallocLastSegment(segmentOffset); + + mWriteCursor = 0; + mSegmentEnd = 0; + + LOG(("nsStorageStream [%p] Close mWriteCursor=%x mSegmentEnd=%x\n", + this, mWriteCursor, mSegmentEnd)); + + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::Flush() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::Write(const char *aBuffer, uint32_t aCount, uint32_t *aNumWritten) +{ + NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED); + + const char* readCursor; + uint32_t count, availableInSegment, remaining; + nsresult rv = NS_OK; + + NS_ENSURE_ARG_POINTER(aNumWritten); + NS_ENSURE_ARG(aBuffer); + + LOG(("nsStorageStream [%p] Write mWriteCursor=%x mSegmentEnd=%x aCount=%d\n", + this, mWriteCursor, mSegmentEnd, aCount)); + + remaining = aCount; + readCursor = aBuffer; + // If no segments have been created yet, create one even if we don't have + // to write any data; this enables creating an input stream which reads from + // the very end of the data for any amount of data in the stream (i.e. + // this stream contains N bytes of data and newInputStream(N) is called), + // even for N=0 (with the caveat that we require .write("", 0) be called to + // initialize internal buffers). + bool firstTime = mSegmentedBuffer->GetSegmentCount() == 0; + while (remaining || MOZ_UNLIKELY(firstTime)) { + firstTime = false; + availableInSegment = mSegmentEnd - mWriteCursor; + if (!availableInSegment) { + mWriteCursor = mSegmentedBuffer->AppendNewSegment(); + if (!mWriteCursor) { + mSegmentEnd = 0; + rv = NS_ERROR_OUT_OF_MEMORY; + goto out; + } + mLastSegmentNum++; + mSegmentEnd = mWriteCursor + mSegmentSize; + availableInSegment = mSegmentEnd - mWriteCursor; + LOG(("nsStorageStream [%p] Write (new seg) mWriteCursor=%x mSegmentEnd=%x\n", + this, mWriteCursor, mSegmentEnd)); + } + + count = XPCOM_MIN(availableInSegment, remaining); + memcpy(mWriteCursor, readCursor, count); + remaining -= count; + readCursor += count; + mWriteCursor += count; + LOG(("nsStorageStream [%p] Writing mWriteCursor=%x mSegmentEnd=%x count=%d\n", + this, mWriteCursor, mSegmentEnd, count)); + }; + + out: + *aNumWritten = aCount - remaining; + mLogicalLength += *aNumWritten; + + LOG(("nsStorageStream [%p] Wrote mWriteCursor=%x mSegmentEnd=%x numWritten=%d\n", + this, mWriteCursor, mSegmentEnd, *aNumWritten)); + return rv; +} + +NS_IMETHODIMP +nsStorageStream::WriteFrom(nsIInputStream *inStr, uint32_t count, uint32_t *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsStorageStream::WriteSegments(nsReadSegmentFun reader, void * closure, uint32_t count, uint32_t *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsStorageStream::IsNonBlocking(bool *aNonBlocking) +{ + *aNonBlocking = false; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::GetLength(uint32_t *aLength) +{ + NS_ENSURE_ARG(aLength); + *aLength = mLogicalLength; + return NS_OK; +} + +// Truncate the buffer by deleting the end segments +NS_IMETHODIMP +nsStorageStream::SetLength(uint32_t aLength) +{ + NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED); + + if (mWriteInProgress) + return NS_ERROR_NOT_AVAILABLE; + + if (aLength > mLogicalLength) + return NS_ERROR_INVALID_ARG; + + int32_t newLastSegmentNum = SegNum(aLength); + int32_t segmentOffset = SegOffset(aLength); + if (segmentOffset == 0) + newLastSegmentNum--; + + while (newLastSegmentNum < mLastSegmentNum) { + mSegmentedBuffer->DeleteLastSegment(); + mLastSegmentNum--; + } + + mLogicalLength = aLength; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::GetWriteInProgress(bool *aWriteInProgress) +{ + NS_ENSURE_ARG(aWriteInProgress); + + *aWriteInProgress = mWriteInProgress; + return NS_OK; +} + +NS_METHOD +nsStorageStream::Seek(int32_t aPosition) +{ + NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED); + + // An argument of -1 means "seek to end of stream" + if (aPosition == -1) + aPosition = mLogicalLength; + + // Seeking beyond the buffer end is illegal + if ((uint32_t)aPosition > mLogicalLength) + return NS_ERROR_INVALID_ARG; + + // Seeking backwards in the write stream results in truncation + SetLength(aPosition); + + // Special handling for seek to start-of-buffer + if (aPosition == 0) { + mWriteCursor = 0; + mSegmentEnd = 0; + LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n", + this, mWriteCursor, mSegmentEnd)); + return NS_OK; + } + + // Segment may have changed, so reset pointers + mWriteCursor = mSegmentedBuffer->GetSegment(mLastSegmentNum); + NS_ASSERTION(mWriteCursor, "null mWriteCursor"); + mSegmentEnd = mWriteCursor + mSegmentSize; + + // Adjust write cursor for current segment offset. This test is necessary + // because SegNum may reference the next-to-be-allocated segment, in which + // case we need to be pointing at the end of the last segment. + int32_t segmentOffset = SegOffset(aPosition); + if (segmentOffset == 0 && (SegNum(aPosition) > (uint32_t) mLastSegmentNum)) + mWriteCursor = mSegmentEnd; + else + mWriteCursor += segmentOffset; + + LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n", + this, mWriteCursor, mSegmentEnd)); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +// There can be many nsStorageInputStreams for a single nsStorageStream +class nsStorageInputStream MOZ_FINAL : public nsIInputStream + , public nsISeekableStream +{ +public: + nsStorageInputStream(nsStorageStream *aStorageStream, uint32_t aSegmentSize) + : mStorageStream(aStorageStream), mReadCursor(0), + mSegmentEnd(0), mSegmentNum(0), + mSegmentSize(aSegmentSize), mLogicalCursor(0), + mStatus(NS_OK) + { + NS_ADDREF(mStorageStream); + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + +private: + ~nsStorageInputStream() + { + NS_IF_RELEASE(mStorageStream); + } + +protected: + NS_METHOD Seek(uint32_t aPosition); + + friend class nsStorageStream; + +private: + nsStorageStream* mStorageStream; + uint32_t mReadCursor; // Next memory location to read byte, or NULL + uint32_t mSegmentEnd; // One byte past end of current buffer segment + uint32_t mSegmentNum; // Segment number containing read cursor + uint32_t mSegmentSize; // All segments, except the last, are of this size + uint32_t mLogicalCursor; // Logical offset into stream + nsresult mStatus; + + uint32_t SegNum(uint32_t aPosition) {return aPosition >> mStorageStream->mSegmentSizeLog2;} + uint32_t SegOffset(uint32_t aPosition) {return aPosition & (mSegmentSize - 1);} +}; + +NS_IMPL_THREADSAFE_ISUPPORTS2(nsStorageInputStream, + nsIInputStream, + nsISeekableStream) + +NS_IMETHODIMP +nsStorageStream::NewInputStream(int32_t aStartingOffset, nsIInputStream* *aInputStream) +{ + NS_ENSURE_TRUE(mSegmentedBuffer, NS_ERROR_NOT_INITIALIZED); + + nsStorageInputStream *inputStream = new nsStorageInputStream(this, mSegmentSize); + if (!inputStream) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(inputStream); + + nsresult rv = inputStream->Seek(aStartingOffset); + if (NS_FAILED(rv)) { + NS_RELEASE(inputStream); + return rv; + } + + *aInputStream = inputStream; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::Close() +{ + mStatus = NS_BASE_STREAM_CLOSED; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::Available(uint64_t *aAvailable) +{ + if (NS_FAILED(mStatus)) + return mStatus; + + *aAvailable = mStorageStream->mLogicalLength - mLogicalCursor; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aNumRead) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aNumRead); +} + +NS_IMETHODIMP +nsStorageInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t aCount, uint32_t *aNumRead) +{ + *aNumRead = 0; + if (mStatus == NS_BASE_STREAM_CLOSED) + return NS_OK; + if (NS_FAILED(mStatus)) + return mStatus; + + uint32_t count, availableInSegment, remainingCapacity, bytesConsumed; + nsresult rv; + + remainingCapacity = aCount; + while (remainingCapacity) { + availableInSegment = mSegmentEnd - mReadCursor; + if (!availableInSegment) { + uint32_t available = mStorageStream->mLogicalLength - mLogicalCursor; + if (!available) + goto out; + + mSegmentNum++; + mReadCursor = 0; + mSegmentEnd = XPCOM_MIN(mSegmentSize, available); + availableInSegment = mSegmentEnd; + } + const char *cur = mStorageStream->mSegmentedBuffer->GetSegment(mSegmentNum); + + count = XPCOM_MIN(availableInSegment, remainingCapacity); + rv = writer(this, closure, cur + mReadCursor, aCount - remainingCapacity, + count, &bytesConsumed); + if (NS_FAILED(rv) || (bytesConsumed == 0)) + break; + remainingCapacity -= bytesConsumed; + mReadCursor += bytesConsumed; + mLogicalCursor += bytesConsumed; + }; + + out: + *aNumRead = aCount - remainingCapacity; + + bool isWriteInProgress = false; + if (NS_FAILED(mStorageStream->GetWriteInProgress(&isWriteInProgress))) + isWriteInProgress = false; + + if (*aNumRead == 0 && isWriteInProgress) + return NS_BASE_STREAM_WOULD_BLOCK; + + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::IsNonBlocking(bool *aNonBlocking) +{ + // TODO: This class should implement nsIAsyncInputStream so that callers + // have some way of dealing with NS_BASE_STREAM_WOULD_BLOCK errors. + + *aNonBlocking = true; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + if (NS_FAILED(mStatus)) + return mStatus; + + int64_t pos = aOffset; + + switch (aWhence) { + case NS_SEEK_SET: + break; + case NS_SEEK_CUR: + pos += mLogicalCursor; + break; + case NS_SEEK_END: + pos += mStorageStream->mLogicalLength; + break; + default: + NS_NOTREACHED("unexpected whence value"); + return NS_ERROR_UNEXPECTED; + } + if (pos == int64_t(mLogicalCursor)) + return NS_OK; + + return Seek(pos); +} + +NS_IMETHODIMP +nsStorageInputStream::Tell(int64_t *aResult) +{ + if (NS_FAILED(mStatus)) + return mStatus; + + *aResult = mLogicalCursor; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::SetEOF() +{ + NS_NOTREACHED("nsStorageInputStream::SetEOF"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_METHOD +nsStorageInputStream::Seek(uint32_t aPosition) +{ + uint32_t length = mStorageStream->mLogicalLength; + if (aPosition > length) + return NS_ERROR_INVALID_ARG; + + if (length == 0) + return NS_OK; + + mSegmentNum = SegNum(aPosition); + mReadCursor = SegOffset(aPosition); + uint32_t available = length - aPosition; + mSegmentEnd = mReadCursor + XPCOM_MIN(mSegmentSize - mReadCursor, available); + mLogicalCursor = aPosition; + return NS_OK; +} + +nsresult +NS_NewStorageStream(uint32_t segmentSize, uint32_t maxSize, nsIStorageStream **result) +{ + NS_ENSURE_ARG(result); + + nsStorageStream* storageStream = new nsStorageStream(); + if (!storageStream) return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(storageStream); + nsresult rv = storageStream->Init(segmentSize, maxSize, nullptr); + if (NS_FAILED(rv)) { + NS_RELEASE(storageStream); + return rv; + } + *result = storageStream; + return NS_OK; +} diff --git a/xpcom/io/nsStorageStream.h b/xpcom/io/nsStorageStream.h new file mode 100644 index 000000000..e50ce3ffc --- /dev/null +++ b/xpcom/io/nsStorageStream.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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/. */ + +/* + * The storage stream provides an internal buffer that can be filled by a + * client using a single output stream. One or more independent input streams + * can be created to read the data out non-destructively. The implementation + * uses a segmented buffer internally to avoid realloc'ing of large buffers, + * with the attendant performance loss and heap fragmentation. + */ + +#ifndef _nsStorageStream_h_ +#define _nsStorageStream_h_ + +#include "nsIStorageStream.h" +#include "nsIOutputStream.h" +#include "nsMemory.h" +#include "mozilla/Attributes.h" + +#define NS_STORAGESTREAM_CID \ +{ /* 669a9795-6ff7-4ed4-9150-c34ce2971b63 */ \ + 0x669a9795, \ + 0x6ff7, \ + 0x4ed4, \ + {0x91, 0x50, 0xc3, 0x4c, 0xe2, 0x97, 0x1b, 0x63} \ +} + +#define NS_STORAGESTREAM_CONTRACTID "@mozilla.org/storagestream;1" + +class nsSegmentedBuffer; + +class nsStorageStream MOZ_FINAL : public nsIStorageStream, + public nsIOutputStream +{ +public: + nsStorageStream(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISTORAGESTREAM + NS_DECL_NSIOUTPUTSTREAM + + friend class nsStorageInputStream; + +private: + ~nsStorageStream(); + + nsSegmentedBuffer* mSegmentedBuffer; + uint32_t mSegmentSize; // All segments, except possibly the last, are of this size + // Must be power-of-2 + uint32_t mSegmentSizeLog2; // log2(mSegmentSize) + bool mWriteInProgress; // true, if an un-Close'ed output stream exists + int32_t mLastSegmentNum; // Last segment # in use, -1 initially + char* mWriteCursor; // Pointer to next byte to be written + char* mSegmentEnd; // Pointer to one byte after end of segment + // containing the write cursor + uint32_t mLogicalLength; // Number of bytes written to stream + + NS_METHOD Seek(int32_t aPosition); + uint32_t SegNum(uint32_t aPosition) {return aPosition >> mSegmentSizeLog2;} + uint32_t SegOffset(uint32_t aPosition) {return aPosition & (mSegmentSize - 1);} +}; + +#endif // _nsStorageStream_h_ diff --git a/xpcom/io/nsStreamUtils.cpp b/xpcom/io/nsStreamUtils.cpp new file mode 100644 index 000000000..fe79bc0c9 --- /dev/null +++ b/xpcom/io/nsStreamUtils.cpp @@ -0,0 +1,775 @@ +/* vim:set ts=4 sw=4 sts=4 et cin: */ +/* 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 "mozilla/Mutex.h" +#include "mozilla/Attributes.h" +#include "nsStreamUtils.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsIPipe.h" +#include "nsIEventTarget.h" +#include "nsIRunnable.h" +#include "nsISafeOutputStream.h" +#include "nsString.h" + +using namespace mozilla; + +//----------------------------------------------------------------------------- + +class nsInputStreamReadyEvent MOZ_FINAL : public nsIRunnable + , public nsIInputStreamCallback +{ +public: + NS_DECL_ISUPPORTS + + nsInputStreamReadyEvent(nsIInputStreamCallback *callback, + nsIEventTarget *target) + : mCallback(callback) + , mTarget(target) + { + } + +private: + ~nsInputStreamReadyEvent() + { + if (!mCallback) + return; + // + // whoa!! looks like we never posted this event. take care to + // release mCallback on the correct thread. if mTarget lives on the + // calling thread, then we are ok. otherwise, we have to try to + // proxy the Release over the right thread. if that thread is dead, + // then there's nothing we can do... better to leak than crash. + // + bool val; + nsresult rv = mTarget->IsOnCurrentThread(&val); + if (NS_FAILED(rv) || !val) { + nsCOMPtr<nsIInputStreamCallback> event = + NS_NewInputStreamReadyEvent(mCallback, mTarget); + mCallback = nullptr; + if (event) { + rv = event->OnInputStreamReady(nullptr); + if (NS_FAILED(rv)) { + NS_NOTREACHED("leaking stream event"); + nsISupports *sup = event; + NS_ADDREF(sup); + } + } + } + } + +public: + NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream *stream) + { + mStream = stream; + + nsresult rv = + mTarget->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("Dispatch failed"); + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + NS_IMETHOD Run() + { + if (mCallback) { + if (mStream) + mCallback->OnInputStreamReady(mStream); + mCallback = nullptr; + } + return NS_OK; + } + +private: + nsCOMPtr<nsIAsyncInputStream> mStream; + nsCOMPtr<nsIInputStreamCallback> mCallback; + nsCOMPtr<nsIEventTarget> mTarget; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS2(nsInputStreamReadyEvent, nsIRunnable, + nsIInputStreamCallback) + +//----------------------------------------------------------------------------- + +class nsOutputStreamReadyEvent MOZ_FINAL : public nsIRunnable + , public nsIOutputStreamCallback +{ +public: + NS_DECL_ISUPPORTS + + nsOutputStreamReadyEvent(nsIOutputStreamCallback *callback, + nsIEventTarget *target) + : mCallback(callback) + , mTarget(target) + { + } + +private: + ~nsOutputStreamReadyEvent() + { + if (!mCallback) + return; + // + // whoa!! looks like we never posted this event. take care to + // release mCallback on the correct thread. if mTarget lives on the + // calling thread, then we are ok. otherwise, we have to try to + // proxy the Release over the right thread. if that thread is dead, + // then there's nothing we can do... better to leak than crash. + // + bool val; + nsresult rv = mTarget->IsOnCurrentThread(&val); + if (NS_FAILED(rv) || !val) { + nsCOMPtr<nsIOutputStreamCallback> event = + NS_NewOutputStreamReadyEvent(mCallback, mTarget); + mCallback = nullptr; + if (event) { + rv = event->OnOutputStreamReady(nullptr); + if (NS_FAILED(rv)) { + NS_NOTREACHED("leaking stream event"); + nsISupports *sup = event; + NS_ADDREF(sup); + } + } + } + } + +public: + NS_IMETHOD OnOutputStreamReady(nsIAsyncOutputStream *stream) + { + mStream = stream; + + nsresult rv = + mTarget->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("PostEvent failed"); + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + NS_IMETHOD Run() + { + if (mCallback) { + if (mStream) + mCallback->OnOutputStreamReady(mStream); + mCallback = nullptr; + } + return NS_OK; + } + +private: + nsCOMPtr<nsIAsyncOutputStream> mStream; + nsCOMPtr<nsIOutputStreamCallback> mCallback; + nsCOMPtr<nsIEventTarget> mTarget; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS2(nsOutputStreamReadyEvent, nsIRunnable, + nsIOutputStreamCallback) + +//----------------------------------------------------------------------------- + +already_AddRefed<nsIInputStreamCallback> +NS_NewInputStreamReadyEvent(nsIInputStreamCallback *callback, + nsIEventTarget *target) +{ + NS_ASSERTION(callback, "null callback"); + NS_ASSERTION(target, "null target"); + nsRefPtr<nsInputStreamReadyEvent> ev = + new nsInputStreamReadyEvent(callback, target); + return ev.forget(); +} + +already_AddRefed<nsIOutputStreamCallback> +NS_NewOutputStreamReadyEvent(nsIOutputStreamCallback *callback, + nsIEventTarget *target) +{ + NS_ASSERTION(callback, "null callback"); + NS_ASSERTION(target, "null target"); + nsRefPtr<nsOutputStreamReadyEvent> ev = + new nsOutputStreamReadyEvent(callback, target); + return ev.forget(); +} + +//----------------------------------------------------------------------------- +// NS_AsyncCopy implementation + +// abstract stream copier... +class nsAStreamCopier : public nsIInputStreamCallback + , public nsIOutputStreamCallback + , public nsIRunnable +{ +public: + NS_DECL_ISUPPORTS + + nsAStreamCopier() + : mLock("nsAStreamCopier.mLock") + , mCallback(nullptr) + , mProgressCallback(nullptr) + , mClosure(nullptr) + , mChunkSize(0) + , mEventInProcess(false) + , mEventIsPending(false) + , mCloseSource(true) + , mCloseSink(true) + , mCanceled(false) + , mCancelStatus(NS_OK) + { + } + + // virtual since subclasses call superclass Release() + virtual ~nsAStreamCopier() + { + } + + // kick off the async copy... + nsresult Start(nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *target, + nsAsyncCopyCallbackFun callback, + void *closure, + uint32_t chunksize, + bool closeSource, + bool closeSink, + nsAsyncCopyProgressFun progressCallback) + { + mSource = source; + mSink = sink; + mTarget = target; + mCallback = callback; + mClosure = closure; + mChunkSize = chunksize; + mCloseSource = closeSource; + mCloseSink = closeSink; + mProgressCallback = progressCallback; + + mAsyncSource = do_QueryInterface(mSource); + mAsyncSink = do_QueryInterface(mSink); + + return PostContinuationEvent(); + } + + // implemented by subclasses, returns number of bytes copied and + // sets source and sink condition before returning. + virtual uint32_t DoCopy(nsresult *sourceCondition, nsresult *sinkCondition) = 0; + + void Process() + { + if (!mSource || !mSink) + return; + + nsresult sourceCondition, sinkCondition; + nsresult cancelStatus; + bool canceled; + { + MutexAutoLock lock(mLock); + canceled = mCanceled; + cancelStatus = mCancelStatus; + } + + // Copy data from the source to the sink until we hit failure or have + // copied all the data. + for (;;) { + // Note: copyFailed will be true if the source or the sink have + // reported an error, or if we failed to write any bytes + // because we have consumed all of our data. + bool copyFailed = false; + if (!canceled) { + uint32_t n = DoCopy(&sourceCondition, &sinkCondition); + if (n > 0 && mProgressCallback) { + mProgressCallback(mClosure, n); + } + copyFailed = NS_FAILED(sourceCondition) || + NS_FAILED(sinkCondition) || n == 0; + + MutexAutoLock lock(mLock); + canceled = mCanceled; + cancelStatus = mCancelStatus; + } + if (copyFailed && !canceled) { + if (sourceCondition == NS_BASE_STREAM_WOULD_BLOCK && mAsyncSource) { + // need to wait for more data from source. while waiting for + // more source data, be sure to observe failures on output end. + mAsyncSource->AsyncWait(this, 0, 0, nullptr); + + if (mAsyncSink) + mAsyncSink->AsyncWait(this, + nsIAsyncOutputStream::WAIT_CLOSURE_ONLY, + 0, nullptr); + break; + } + else if (sinkCondition == NS_BASE_STREAM_WOULD_BLOCK && mAsyncSink) { + // need to wait for more room in the sink. while waiting for + // more room in the sink, be sure to observer failures on the + // input end. + mAsyncSink->AsyncWait(this, 0, 0, nullptr); + + if (mAsyncSource) + mAsyncSource->AsyncWait(this, + nsIAsyncInputStream::WAIT_CLOSURE_ONLY, + 0, nullptr); + break; + } + } + if (copyFailed || canceled) { + if (mCloseSource) { + // close source + if (mAsyncSource) + mAsyncSource->CloseWithStatus(canceled ? cancelStatus : + sinkCondition); + else + mSource->Close(); + } + mAsyncSource = nullptr; + mSource = nullptr; + + if (mCloseSink) { + // close sink + if (mAsyncSink) + mAsyncSink->CloseWithStatus(canceled ? cancelStatus : + sourceCondition); + else { + // If we have an nsISafeOutputStream, and our + // sourceCondition and sinkCondition are not set to a + // failure state, finish writing. + nsCOMPtr<nsISafeOutputStream> sostream = + do_QueryInterface(mSink); + if (sostream && NS_SUCCEEDED(sourceCondition) && + NS_SUCCEEDED(sinkCondition)) + sostream->Finish(); + else + mSink->Close(); + } + } + mAsyncSink = nullptr; + mSink = nullptr; + + // notify state complete... + if (mCallback) { + nsresult status; + if (!canceled) { + status = sourceCondition; + if (NS_SUCCEEDED(status)) + status = sinkCondition; + if (status == NS_BASE_STREAM_CLOSED) + status = NS_OK; + } else { + status = cancelStatus; + } + mCallback(mClosure, status); + } + break; + } + } + } + + nsresult Cancel(nsresult aReason) + { + MutexAutoLock lock(mLock); + if (mCanceled) + return NS_ERROR_FAILURE; + + if (NS_SUCCEEDED(aReason)) { + NS_WARNING("cancel with non-failure status code"); + aReason = NS_BASE_STREAM_CLOSED; + } + + mCanceled = true; + mCancelStatus = aReason; + return NS_OK; + } + + NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream *source) + { + PostContinuationEvent(); + return NS_OK; + } + + NS_IMETHOD OnOutputStreamReady(nsIAsyncOutputStream *sink) + { + PostContinuationEvent(); + return NS_OK; + } + + // continuation event handler + NS_IMETHOD Run() + { + Process(); + + // clear "in process" flag and post any pending continuation event + MutexAutoLock lock(mLock); + mEventInProcess = false; + if (mEventIsPending) { + mEventIsPending = false; + PostContinuationEvent_Locked(); + } + + return NS_OK; + } + + nsresult PostContinuationEvent() + { + // we cannot post a continuation event if there is currently + // an event in process. doing so could result in Process being + // run simultaneously on multiple threads, so we mark the event + // as pending, and if an event is already in process then we + // just let that existing event take care of posting the real + // continuation event. + + MutexAutoLock lock(mLock); + return PostContinuationEvent_Locked(); + } + + nsresult PostContinuationEvent_Locked() + { + nsresult rv = NS_OK; + if (mEventInProcess) + mEventIsPending = true; + else { + rv = mTarget->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_SUCCEEDED(rv)) + mEventInProcess = true; + else + NS_WARNING("unable to post continuation event"); + } + return rv; + } + +protected: + nsCOMPtr<nsIInputStream> mSource; + nsCOMPtr<nsIOutputStream> mSink; + nsCOMPtr<nsIAsyncInputStream> mAsyncSource; + nsCOMPtr<nsIAsyncOutputStream> mAsyncSink; + nsCOMPtr<nsIEventTarget> mTarget; + Mutex mLock; + nsAsyncCopyCallbackFun mCallback; + nsAsyncCopyProgressFun mProgressCallback; + void *mClosure; + uint32_t mChunkSize; + bool mEventInProcess; + bool mEventIsPending; + bool mCloseSource; + bool mCloseSink; + bool mCanceled; + nsresult mCancelStatus; +}; + +NS_IMPL_THREADSAFE_ISUPPORTS3(nsAStreamCopier, + nsIInputStreamCallback, + nsIOutputStreamCallback, + nsIRunnable) + +class nsStreamCopierIB MOZ_FINAL : public nsAStreamCopier +{ +public: + nsStreamCopierIB() : nsAStreamCopier() {} + virtual ~nsStreamCopierIB() {} + + struct ReadSegmentsState { + nsIOutputStream *mSink; + nsresult mSinkCondition; + }; + + static NS_METHOD ConsumeInputBuffer(nsIInputStream *inStr, + void *closure, + const char *buffer, + uint32_t offset, + uint32_t count, + uint32_t *countWritten) + { + ReadSegmentsState *state = (ReadSegmentsState *) closure; + + nsresult rv = state->mSink->Write(buffer, count, countWritten); + if (NS_FAILED(rv)) + state->mSinkCondition = rv; + else if (*countWritten == 0) + state->mSinkCondition = NS_BASE_STREAM_CLOSED; + + return state->mSinkCondition; + } + + uint32_t DoCopy(nsresult *sourceCondition, nsresult *sinkCondition) + { + ReadSegmentsState state; + state.mSink = mSink; + state.mSinkCondition = NS_OK; + + uint32_t n; + *sourceCondition = + mSource->ReadSegments(ConsumeInputBuffer, &state, mChunkSize, &n); + *sinkCondition = state.mSinkCondition; + return n; + } +}; + +class nsStreamCopierOB MOZ_FINAL : public nsAStreamCopier +{ +public: + nsStreamCopierOB() : nsAStreamCopier() {} + virtual ~nsStreamCopierOB() {} + + struct WriteSegmentsState { + nsIInputStream *mSource; + nsresult mSourceCondition; + }; + + static NS_METHOD FillOutputBuffer(nsIOutputStream *outStr, + void *closure, + char *buffer, + uint32_t offset, + uint32_t count, + uint32_t *countRead) + { + WriteSegmentsState *state = (WriteSegmentsState *) closure; + + nsresult rv = state->mSource->Read(buffer, count, countRead); + if (NS_FAILED(rv)) + state->mSourceCondition = rv; + else if (*countRead == 0) + state->mSourceCondition = NS_BASE_STREAM_CLOSED; + + return state->mSourceCondition; + } + + uint32_t DoCopy(nsresult *sourceCondition, nsresult *sinkCondition) + { + WriteSegmentsState state; + state.mSource = mSource; + state.mSourceCondition = NS_OK; + + uint32_t n; + *sinkCondition = + mSink->WriteSegments(FillOutputBuffer, &state, mChunkSize, &n); + *sourceCondition = state.mSourceCondition; + return n; + } +}; + +//----------------------------------------------------------------------------- + +nsresult +NS_AsyncCopy(nsIInputStream *source, + nsIOutputStream *sink, + nsIEventTarget *target, + nsAsyncCopyMode mode, + uint32_t chunkSize, + nsAsyncCopyCallbackFun callback, + void *closure, + bool closeSource, + bool closeSink, + nsISupports **aCopierCtx, + nsAsyncCopyProgressFun progressCallback) +{ + NS_ASSERTION(target, "non-null target required"); + + nsresult rv; + nsAStreamCopier *copier; + + if (mode == NS_ASYNCCOPY_VIA_READSEGMENTS) + copier = new nsStreamCopierIB(); + else + copier = new nsStreamCopierOB(); + + if (!copier) + return NS_ERROR_OUT_OF_MEMORY; + + // Start() takes an owning ref to the copier... + NS_ADDREF(copier); + rv = copier->Start(source, sink, target, callback, closure, chunkSize, + closeSource, closeSink, progressCallback); + + if (aCopierCtx) { + *aCopierCtx = static_cast<nsISupports*>( + static_cast<nsIRunnable*>(copier)); + NS_ADDREF(*aCopierCtx); + } + NS_RELEASE(copier); + + return rv; +} + +//----------------------------------------------------------------------------- + +nsresult +NS_CancelAsyncCopy(nsISupports *aCopierCtx, nsresult aReason) +{ + nsAStreamCopier *copier = static_cast<nsAStreamCopier *>( + static_cast<nsIRunnable *>(aCopierCtx)); + return copier->Cancel(aReason); +} + +//----------------------------------------------------------------------------- + +nsresult +NS_ConsumeStream(nsIInputStream *stream, uint32_t maxCount, nsACString &result) +{ + nsresult rv = NS_OK; + result.Truncate(); + + while (maxCount) { + uint64_t avail64; + rv = stream->Available(&avail64); + if (NS_FAILED(rv)) { + if (rv == NS_BASE_STREAM_CLOSED) + rv = NS_OK; + break; + } + if (avail64 == 0) + break; + + uint32_t avail = (uint32_t)XPCOM_MIN<uint64_t>(avail64, maxCount); + + // resize result buffer + uint32_t length = result.Length(); + if (avail > UINT32_MAX - length) + return NS_ERROR_FILE_TOO_BIG; + + result.SetLength(length + avail); + if (result.Length() != (length + avail)) + return NS_ERROR_OUT_OF_MEMORY; + char *buf = result.BeginWriting() + length; + + uint32_t n; + rv = stream->Read(buf, avail, &n); + if (NS_FAILED(rv)) + break; + if (n != avail) + result.SetLength(length + n); + if (n == 0) + break; + maxCount -= n; + } + + return rv; +} + +//----------------------------------------------------------------------------- + +static NS_METHOD +TestInputStream(nsIInputStream *inStr, + void *closure, + const char *buffer, + uint32_t offset, + uint32_t count, + uint32_t *countWritten) +{ + bool *result = static_cast<bool *>(closure); + *result = true; + return NS_ERROR_ABORT; // don't call me anymore +} + +bool +NS_InputStreamIsBuffered(nsIInputStream *stream) +{ + bool result = false; + uint32_t n; + nsresult rv = stream->ReadSegments(TestInputStream, + &result, 1, &n); + return result || NS_SUCCEEDED(rv); +} + +static NS_METHOD +TestOutputStream(nsIOutputStream *outStr, + void *closure, + char *buffer, + uint32_t offset, + uint32_t count, + uint32_t *countRead) +{ + bool *result = static_cast<bool *>(closure); + *result = true; + return NS_ERROR_ABORT; // don't call me anymore +} + +bool +NS_OutputStreamIsBuffered(nsIOutputStream *stream) +{ + bool result = false; + uint32_t n; + stream->WriteSegments(TestOutputStream, &result, 1, &n); + return result; +} + +//----------------------------------------------------------------------------- + +NS_METHOD +NS_CopySegmentToStream(nsIInputStream *inStr, + void *closure, + const char *buffer, + uint32_t offset, + uint32_t count, + uint32_t *countWritten) +{ + nsIOutputStream *outStr = static_cast<nsIOutputStream *>(closure); + *countWritten = 0; + while (count) { + uint32_t n; + nsresult rv = outStr->Write(buffer, count, &n); + if (NS_FAILED(rv)) + return rv; + buffer += n; + count -= n; + *countWritten += n; + } + return NS_OK; +} + +NS_METHOD +NS_CopySegmentToBuffer(nsIInputStream *inStr, + void *closure, + const char *buffer, + uint32_t offset, + uint32_t count, + uint32_t *countWritten) +{ + char *toBuf = static_cast<char *>(closure); + memcpy(&toBuf[offset], buffer, count); + *countWritten = count; + return NS_OK; +} + +NS_METHOD +NS_CopySegmentToBuffer(nsIOutputStream *outStr, + void *closure, + char *buffer, + uint32_t offset, + uint32_t count, + uint32_t *countRead) +{ + const char* fromBuf = static_cast<const char*>(closure); + memcpy(buffer, &fromBuf[offset], count); + *countRead = count; + return NS_OK; +} + +NS_METHOD +NS_DiscardSegment(nsIInputStream *inStr, + void *closure, + const char *buffer, + uint32_t offset, + uint32_t count, + uint32_t *countWritten) +{ + *countWritten = count; + return NS_OK; +} + +//----------------------------------------------------------------------------- + +NS_METHOD +NS_WriteSegmentThunk(nsIInputStream *inStr, + void *closure, + const char *buffer, + uint32_t offset, + uint32_t count, + uint32_t *countWritten) +{ + nsWriteSegmentThunk *thunk = static_cast<nsWriteSegmentThunk *>(closure); + return thunk->mFun(thunk->mStream, thunk->mClosure, buffer, offset, count, + countWritten); +} diff --git a/xpcom/io/nsStreamUtils.h b/xpcom/io/nsStreamUtils.h new file mode 100644 index 000000000..fcb47052a --- /dev/null +++ b/xpcom/io/nsStreamUtils.h @@ -0,0 +1,235 @@ +/* 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/. */ + +#ifndef nsStreamUtils_h__ +#define nsStreamUtils_h__ + +#include "nsCOMPtr.h" +#include "nsStringFwd.h" +#include "nsIInputStream.h" + +class nsIOutputStream; +class nsIInputStreamCallback; +class nsIOutputStreamCallback; +class nsIEventTarget; + +/** + * A "one-shot" proxy of the OnInputStreamReady callback. The resulting + * proxy object's OnInputStreamReady function may only be called once! The + * proxy object ensures that the real notify object will be free'd on the + * thread corresponding to the given event target regardless of what thread + * the proxy object is destroyed on. + * + * This function is designed to be used to implement AsyncWait when the + * aTarget parameter is non-null. + */ +extern already_AddRefed<nsIInputStreamCallback> +NS_NewInputStreamReadyEvent(nsIInputStreamCallback *aNotify, + nsIEventTarget *aTarget); + +/** + * A "one-shot" proxy of the OnOutputStreamReady callback. The resulting + * proxy object's OnOutputStreamReady function may only be called once! The + * proxy object ensures that the real notify object will be free'd on the + * thread corresponding to the given event target regardless of what thread + * the proxy object is destroyed on. + * + * This function is designed to be used to implement AsyncWait when the + * aTarget parameter is non-null. + */ +extern already_AddRefed<nsIOutputStreamCallback> +NS_NewOutputStreamReadyEvent(nsIOutputStreamCallback *aNotify, + nsIEventTarget *aTarget); + +/* ------------------------------------------------------------------------- */ + +enum nsAsyncCopyMode { + NS_ASYNCCOPY_VIA_READSEGMENTS, + NS_ASYNCCOPY_VIA_WRITESEGMENTS +}; + +/** + * This function is called when a new chunk of data has been copied. The + * reported count is the size of the current chunk. + */ +typedef void (* nsAsyncCopyProgressFun)(void *closure, uint32_t count); + +/** + * This function is called when the async copy process completes. The reported + * status is NS_OK on success and some error code on failure. + */ +typedef void (* nsAsyncCopyCallbackFun)(void *closure, nsresult status); + +/** + * This function asynchronously copies data from the source to the sink. All + * data transfer occurs on the thread corresponding to the given event target. + * A null event target is not permitted. + * + * The copier handles blocking or non-blocking streams transparently. If a + * stream operation returns NS_BASE_STREAM_WOULD_BLOCK, then the stream will + * be QI'd to nsIAsync{In,Out}putStream and its AsyncWait method will be used + * to determine when to resume copying. + * + * Source and sink are closed by default when copying finishes or when error + * occurs. Caller can prevent closing source or sink by setting aCloseSource + * or aCloseSink to false. + * + * Caller can obtain aCopierCtx to be able to cancel copying. + */ +extern nsresult +NS_AsyncCopy(nsIInputStream *aSource, + nsIOutputStream *aSink, + nsIEventTarget *aTarget, + nsAsyncCopyMode aMode = NS_ASYNCCOPY_VIA_READSEGMENTS, + uint32_t aChunkSize = 4096, + nsAsyncCopyCallbackFun aCallbackFun = nullptr, + void *aCallbackClosure = nullptr, + bool aCloseSource = true, + bool aCloseSink = true, + nsISupports **aCopierCtx = nullptr, + nsAsyncCopyProgressFun aProgressCallbackFun = nullptr); + +/** + * This function cancels copying started by function NS_AsyncCopy. + * + * @param aCopierCtx + * Copier context returned by NS_AsyncCopy. + * @param aReason + * A failure code indicating why the operation is being canceled. + * It is an error to pass a success code. + */ +extern nsresult +NS_CancelAsyncCopy(nsISupports *aCopierCtx, nsresult aReason); + +/** + * This function copies all of the available data from the stream (up to at + * most aMaxCount bytes) into the given buffer. The buffer is truncated at + * the start of the function. + * + * If an error occurs while reading from the stream or while attempting to + * resize the buffer, then the corresponding error code is returned from this + * function, and any data that has already been read will be returned in the + * output buffer. This allows one to use this function with a non-blocking + * input stream that may return NS_BASE_STREAM_WOULD_BLOCK if it only has + * partial data available. + * + * @param aSource + * The input stream to read. + * @param aMaxCount + * The maximum number of bytes to consume from the stream. Pass the + * value UINT32_MAX to consume the entire stream. The number of + * bytes actually read is given by the length of aBuffer upon return. + * @param aBuffer + * The string object that will contain the stream data upon return. + * Note: The data copied to the string may contain null bytes and may + * contain non-ASCII values. + */ +extern nsresult +NS_ConsumeStream(nsIInputStream *aSource, uint32_t aMaxCount, + nsACString &aBuffer); + +/** + * This function tests whether or not the input stream is buffered. A buffered + * input stream is one that implements readSegments. The test for this is to + * simply call readSegments, without actually consuming any data from the + * stream, to verify that it functions. + * + * NOTE: If the stream is non-blocking and has no data available yet, then this + * test will fail. In that case, we return false even though the test is not + * really conclusive. + * + * @param aInputStream + * The input stream to test. + */ +extern bool +NS_InputStreamIsBuffered(nsIInputStream *aInputStream); + +/** + * This function tests whether or not the output stream is buffered. A + * buffered output stream is one that implements writeSegments. The test for + * this is to simply call writeSegments, without actually writing any data into + * the stream, to verify that it functions. + * + * NOTE: If the stream is non-blocking and has no available space yet, then + * this test will fail. In that case, we return false even though the test is + * not really conclusive. + * + * @param aOutputStream + * The output stream to test. + */ +extern bool +NS_OutputStreamIsBuffered(nsIOutputStream *aOutputStream); + +/** + * This function is intended to be passed to nsIInputStream::ReadSegments to + * copy data from the nsIInputStream into a nsIOutputStream passed as the + * aClosure parameter to the ReadSegments function. + * + * @see nsIInputStream.idl for a description of this function's parameters. + */ +extern NS_METHOD +NS_CopySegmentToStream(nsIInputStream *aInputStream, void *aClosure, + const char *aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t *aWriteCount); + +/** + * This function is intended to be passed to nsIInputStream::ReadSegments to + * copy data from the nsIInputStream into a character buffer passed as the + * aClosure parameter to the ReadSegments function. The character buffer + * must be at least as large as the aCount parameter passed to ReadSegments. + * + * @see nsIInputStream.idl for a description of this function's parameters. + */ +extern NS_METHOD +NS_CopySegmentToBuffer(nsIInputStream *aInputStream, void *aClosure, + const char *aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t *aWriteCount); + +/** + * This function is intended to be passed to nsIOutputStream::WriteSegments to + * copy data into the nsIOutputStream from a character buffer passed as the + * aClosure parameter to the WriteSegments function. + * + * @see nsIOutputStream.idl for a description of this function's parameters. + */ +extern NS_METHOD +NS_CopySegmentToBuffer(nsIOutputStream *aOutputStream, void *aClosure, + char *aToSegment, uint32_t aFromOffset, + uint32_t aCount, uint32_t *aReadCount); + +/** + * This function is intended to be passed to nsIInputStream::ReadSegments to + * discard data from the nsIInputStream. This can be used to efficiently read + * data from the stream without actually copying any bytes. + * + * @see nsIInputStream.idl for a description of this function's parameters. + */ +extern NS_METHOD +NS_DiscardSegment(nsIInputStream *aInputStream, void *aClosure, + const char *aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t *aWriteCount); + +/** + * This function is intended to be passed to nsIInputStream::ReadSegments to + * adjust the aInputStream parameter passed to a consumer's WriteSegmentFun. + * The aClosure parameter must be a pointer to a nsWriteSegmentThunk object. + * The mStream and mClosure members of that object will be passed to the mFun + * function, with the remainder of the parameters being what are passed to + * NS_WriteSegmentThunk. + * + * This function comes in handy when implementing ReadSegments in terms of an + * inner stream's ReadSegments. + */ +extern NS_METHOD +NS_WriteSegmentThunk(nsIInputStream *aInputStream, void *aClosure, + const char *aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t *aWriteCount); + +struct nsWriteSegmentThunk { + nsIInputStream *mStream; + nsWriteSegmentFun mFun; + void *mClosure; +}; + +#endif // !nsStreamUtils_h__ diff --git a/xpcom/io/nsStringStream.cpp b/xpcom/io/nsStringStream.cpp new file mode 100644 index 000000000..591e130b3 --- /dev/null +++ b/xpcom/io/nsStringStream.cpp @@ -0,0 +1,400 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sts=4 sw=4 cin et: */ +/* 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/. */ + +/** + * Based on original code from nsIStringStream.cpp + */ + +#include "ipc/IPCMessageUtils.h" + +#include "nsStringStream.h" +#include "nsStreamUtils.h" +#include "nsReadableUtils.h" +#include "nsISeekableStream.h" +#include "nsISupportsPrimitives.h" +#include "nsCRT.h" +#include "prerror.h" +#include "plstr.h" +#include "nsIClassInfoImpl.h" +#include "mozilla/Attributes.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsIIPCSerializableInputStream.h" + +using namespace mozilla::ipc; + +//----------------------------------------------------------------------------- +// nsIStringInputStream implementation +//----------------------------------------------------------------------------- + +class nsStringInputStream MOZ_FINAL : public nsIStringInputStream + , public nsISeekableStream + , public nsISupportsCString + , public nsIIPCSerializableInputStream +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISTRINGINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCSTRING + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + + nsStringInputStream() + { + Clear(); + } + +private: + ~nsStringInputStream() + {} + + uint32_t Length() const + { + return mData.Length(); + } + + uint32_t LengthRemaining() const + { + return Length() - mOffset; + } + + void Clear() + { + mData.SetIsVoid(true); + } + + bool Closed() + { + return mData.IsVoid(); + } + + nsDependentCSubstring mData; + uint32_t mOffset; +}; + +// This class needs to support threadsafe refcounting since people often +// allocate a string stream, and then read it from a background thread. +NS_IMPL_THREADSAFE_ADDREF(nsStringInputStream) +NS_IMPL_THREADSAFE_RELEASE(nsStringInputStream) + +NS_IMPL_CLASSINFO(nsStringInputStream, NULL, nsIClassInfo::THREADSAFE, + NS_STRINGINPUTSTREAM_CID) +NS_IMPL_QUERY_INTERFACE5_CI(nsStringInputStream, + nsIStringInputStream, + nsIInputStream, + nsISupportsCString, + nsISeekableStream, + nsIIPCSerializableInputStream) +NS_IMPL_CI_INTERFACE_GETTER4(nsStringInputStream, + nsIStringInputStream, + nsIInputStream, + nsISupportsCString, + nsISeekableStream) + +///////// +// nsISupportsCString implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::GetType(uint16_t *type) +{ + *type = TYPE_CSTRING; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::GetData(nsACString &data) +{ + // The stream doesn't have any data when it is closed. We could fake it + // and return an empty string here, but it seems better to keep this return + // value consistent with the behavior of the other 'getter' methods. + NS_ENSURE_TRUE(!Closed(), NS_BASE_STREAM_CLOSED); + + data.Assign(mData); + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::SetData(const nsACString &data) +{ + mData.Assign(data); + mOffset = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::ToString(char **result) +{ + // NOTE: This method may result in data loss, so we do not implement it. + return NS_ERROR_NOT_IMPLEMENTED; +} + +///////// +// nsIStringInputStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::SetData(const char *data, int32_t dataLen) +{ + NS_ENSURE_ARG_POINTER(data); + mData.Assign(data, dataLen); + mOffset = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::AdoptData(char *data, int32_t dataLen) +{ + NS_ENSURE_ARG_POINTER(data); + mData.Adopt(data, dataLen); + mOffset = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::ShareData(const char *data, int32_t dataLen) +{ + NS_ENSURE_ARG_POINTER(data); + + if (dataLen < 0) + dataLen = strlen(data); + + mData.Rebind(data, dataLen); + mOffset = 0; + return NS_OK; +} + +///////// +// nsIInputStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::Close() +{ + Clear(); + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::Available(uint64_t *aLength) +{ + NS_ASSERTION(aLength, "null ptr"); + + if (Closed()) + return NS_BASE_STREAM_CLOSED; + + *aLength = LengthRemaining(); + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::Read(char* aBuf, uint32_t aCount, uint32_t *aReadCount) +{ + NS_ASSERTION(aBuf, "null ptr"); + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount); +} + +NS_IMETHODIMP +nsStringInputStream::ReadSegments(nsWriteSegmentFun writer, void *closure, + uint32_t aCount, uint32_t *result) +{ + NS_ASSERTION(result, "null ptr"); + NS_ASSERTION(Length() >= mOffset, "bad stream state"); + + if (Closed()) + return NS_BASE_STREAM_CLOSED; + + // We may be at end-of-file + uint32_t maxCount = LengthRemaining(); + if (maxCount == 0) { + *result = 0; + return NS_OK; + } + + if (aCount > maxCount) + aCount = maxCount; + nsresult rv = writer(this, closure, mData.BeginReading() + mOffset, 0, aCount, result); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(*result <= aCount, + "writer should not write more than we asked it to write"); + mOffset += *result; + } + + // errors returned from the writer end here! + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::IsNonBlocking(bool *aNonBlocking) +{ + *aNonBlocking = true; + return NS_OK; +} + +///////// +// nsISeekableStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::Seek(int32_t whence, int64_t offset) +{ + if (Closed()) + return NS_BASE_STREAM_CLOSED; + + // Compute new stream position. The given offset may be a negative value. + + int64_t newPos = offset; + switch (whence) { + case NS_SEEK_SET: + break; + case NS_SEEK_CUR: + newPos += mOffset; + break; + case NS_SEEK_END: + newPos += Length(); + break; + default: + NS_ERROR("invalid whence"); + return NS_ERROR_INVALID_ARG; + } + + NS_ENSURE_ARG(newPos >= 0); + NS_ENSURE_ARG(newPos <= Length()); + + mOffset = (uint32_t)newPos; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::Tell(int64_t* outWhere) +{ + if (Closed()) + return NS_BASE_STREAM_CLOSED; + + *outWhere = mOffset; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::SetEOF() +{ + if (Closed()) + return NS_BASE_STREAM_CLOSED; + + mOffset = Length(); + return NS_OK; +} + +void +nsStringInputStream::Serialize(InputStreamParams& aParams) +{ + StringInputStreamParams params; + params.data() = PromiseFlatCString(mData); + aParams = params; +} + +bool +nsStringInputStream::Deserialize(const InputStreamParams& aParams) +{ + if (aParams.type() != InputStreamParams::TStringInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const StringInputStreamParams& params = + aParams.get_StringInputStreamParams(); + + if (NS_FAILED(SetData(params.data()))) { + NS_WARNING("SetData failed!"); + return false; + } + + return true; +} + +nsresult +NS_NewByteInputStream(nsIInputStream** aStreamResult, + const char* aStringToRead, int32_t aLength, + nsAssignmentType aAssignment) +{ + NS_PRECONDITION(aStreamResult, "null out ptr"); + + nsStringInputStream* stream = new nsStringInputStream(); + if (! stream) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(stream); + + nsresult rv; + switch (aAssignment) { + case NS_ASSIGNMENT_COPY: + rv = stream->SetData(aStringToRead, aLength); + break; + case NS_ASSIGNMENT_DEPEND: + rv = stream->ShareData(aStringToRead, aLength); + break; + case NS_ASSIGNMENT_ADOPT: + rv = stream->AdoptData(const_cast<char*>(aStringToRead), aLength); + break; + default: + NS_ERROR("invalid assignment type"); + rv = NS_ERROR_INVALID_ARG; + } + + if (NS_FAILED(rv)) { + NS_RELEASE(stream); + return rv; + } + + *aStreamResult = stream; + return NS_OK; +} + +nsresult +NS_NewStringInputStream(nsIInputStream** aStreamResult, + const nsAString& aStringToRead) +{ + NS_LossyConvertUTF16toASCII data(aStringToRead); // truncates high-order bytes + return NS_NewCStringInputStream(aStreamResult, data); +} + +nsresult +NS_NewCStringInputStream(nsIInputStream** aStreamResult, + const nsACString& aStringToRead) +{ + NS_PRECONDITION(aStreamResult, "null out ptr"); + + nsStringInputStream* stream = new nsStringInputStream(); + if (! stream) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(stream); + + stream->SetData(aStringToRead); + + *aStreamResult = stream; + return NS_OK; +} + +// factory method for constructing a nsStringInputStream object +nsresult +nsStringInputStreamConstructor(nsISupports *outer, REFNSIID iid, void **result) +{ + *result = nullptr; + + NS_ENSURE_TRUE(!outer, NS_ERROR_NO_AGGREGATION); + + nsStringInputStream *inst = new nsStringInputStream(); + if (!inst) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(inst); + nsresult rv = inst->QueryInterface(iid, result); + NS_RELEASE(inst); + + return rv; +} diff --git a/xpcom/io/nsStringStream.h b/xpcom/io/nsStringStream.h new file mode 100644 index 000000000..5b5c66eef --- /dev/null +++ b/xpcom/io/nsStringStream.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsStringStream_h__ +#define nsStringStream_h__ + +#include "nsIStringStream.h" +#include "nsStringGlue.h" +#include "nsMemory.h" + +/** + * Implements: + * nsIStringInputStream + * nsIInputStream + * nsISeekableStream + * nsISupportsCString + */ +#define NS_STRINGINPUTSTREAM_CONTRACTID "@mozilla.org/io/string-input-stream;1" +#define NS_STRINGINPUTSTREAM_CID \ +{ /* 0abb0835-5000-4790-af28-61b3ba17c295 */ \ + 0x0abb0835, \ + 0x5000, \ + 0x4790, \ + {0xaf, 0x28, 0x61, 0xb3, 0xba, 0x17, 0xc2, 0x95} \ +} + +/** + * Factory method to get an nsInputStream from a byte buffer. Result will + * implement nsIStringInputStream and nsISeekableStream. + * + * If aAssignment is NS_ASSIGNMENT_COPY, then the resulting stream holds a copy + * of the given buffer (aStringToRead), and the caller is free to discard + * aStringToRead after this function returns. + * + * If aAssignment is NS_ASSIGNMENT_DEPEND, then the resulting stream refers + * directly to the given buffer (aStringToRead), so the caller must ensure that + * the buffer remains valid for the lifetime of the stream object. Use with + * care!! + * + * If aAssignment is NS_ASSIGNMENT_ADOPT, then the resulting stream refers + * directly to the given buffer (aStringToRead) and will free aStringToRead + * once the stream is closed. + * + * If aLength is less than zero, then the length of aStringToRead will be + * determined by scanning the buffer for the first null byte. + */ +extern nsresult +NS_NewByteInputStream(nsIInputStream** aStreamResult, + const char* aStringToRead, int32_t aLength = -1, + nsAssignmentType aAssignment = NS_ASSIGNMENT_DEPEND); + +/** + * Factory method to get an nsInputStream from an nsAString. Result will + * implement nsIStringInputStream and nsISeekableStream. + * + * The given string data will be converted to a single-byte data buffer via + * truncation (i.e., the high-order byte of each character will be discarded). + * This could result in data-loss, so be careful when using this function. + */ +extern nsresult +NS_NewStringInputStream(nsIInputStream** aStreamResult, + const nsAString& aStringToRead); + +/** + * Factory method to get an nsInputStream from an nsACString. Result will + * implement nsIStringInputStream and nsISeekableStream. + */ +extern nsresult +NS_NewCStringInputStream(nsIInputStream** aStreamResult, + const nsACString& aStringToRead); + +#endif // nsStringStream_h__ diff --git a/xpcom/io/nsUnicharInputStream.cpp b/xpcom/io/nsUnicharInputStream.cpp new file mode 100644 index 000000000..a1e76a4c3 --- /dev/null +++ b/xpcom/io/nsUnicharInputStream.cpp @@ -0,0 +1,434 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsUnicharInputStream.h" +#include "nsIInputStream.h" +#include "nsIByteBuffer.h" +#include "nsIUnicharBuffer.h" +#include "nsIServiceManager.h" +#include "nsString.h" +#include "nsAutoPtr.h" +#include "nsCRT.h" +#include "nsUTF8Utils.h" +#include "mozilla/Attributes.h" +#include <fcntl.h> +#if defined(XP_WIN) +#include <io.h> +#else +#include <unistd.h> +#endif + +#define STRING_BUFFER_SIZE 8192 + +class StringUnicharInputStream MOZ_FINAL : public nsIUnicharInputStream { +public: + StringUnicharInputStream(const nsAString& aString) : + mString(aString), mPos(0), mLen(aString.Length()) { } + + NS_DECL_ISUPPORTS + NS_DECL_NSIUNICHARINPUTSTREAM + + nsString mString; + uint32_t mPos; + uint32_t mLen; + +private: + ~StringUnicharInputStream() { } +}; + +NS_IMETHODIMP +StringUnicharInputStream::Read(PRUnichar* aBuf, + uint32_t aCount, + uint32_t *aReadCount) +{ + if (mPos >= mLen) { + *aReadCount = 0; + return NS_OK; + } + nsAString::const_iterator iter; + mString.BeginReading(iter); + const PRUnichar* us = iter.get(); + uint32_t amount = mLen - mPos; + if (amount > aCount) { + amount = aCount; + } + memcpy(aBuf, us + mPos, sizeof(PRUnichar) * amount); + mPos += amount; + *aReadCount = amount; + return NS_OK; +} + +NS_IMETHODIMP +StringUnicharInputStream::ReadSegments(nsWriteUnicharSegmentFun aWriter, + void* aClosure, + uint32_t aCount, uint32_t *aReadCount) +{ + uint32_t bytesWritten; + uint32_t totalBytesWritten = 0; + + nsresult rv; + aCount = XPCOM_MIN(mString.Length() - mPos, aCount); + + nsAString::const_iterator iter; + mString.BeginReading(iter); + + while (aCount) { + rv = aWriter(this, aClosure, iter.get() + mPos, + totalBytesWritten, aCount, &bytesWritten); + + if (NS_FAILED(rv)) { + // don't propagate errors to the caller + break; + } + + aCount -= bytesWritten; + totalBytesWritten += bytesWritten; + mPos += bytesWritten; + } + + *aReadCount = totalBytesWritten; + + return NS_OK; +} + +NS_IMETHODIMP +StringUnicharInputStream::ReadString(uint32_t aCount, nsAString& aString, + uint32_t* aReadCount) +{ + if (mPos >= mLen) { + *aReadCount = 0; + return NS_OK; + } + uint32_t amount = mLen - mPos; + if (amount > aCount) { + amount = aCount; + } + aString = Substring(mString, mPos, amount); + mPos += amount; + *aReadCount = amount; + return NS_OK; +} + +nsresult StringUnicharInputStream::Close() +{ + mPos = mLen; + return NS_OK; +} + +NS_IMPL_ISUPPORTS1(StringUnicharInputStream, nsIUnicharInputStream) + +//---------------------------------------------------------------------- + +class UTF8InputStream MOZ_FINAL : public nsIUnicharInputStream { +public: + UTF8InputStream(); + nsresult Init(nsIInputStream* aStream); + + NS_DECL_ISUPPORTS + NS_DECL_NSIUNICHARINPUTSTREAM + +private: + ~UTF8InputStream(); + +protected: + int32_t Fill(nsresult * aErrorCode); + + static void CountValidUTF8Bytes(const char *aBuf, uint32_t aMaxBytes, uint32_t& aValidUTF8bytes, uint32_t& aValidUTF16CodeUnits); + + nsCOMPtr<nsIInputStream> mInput; + nsCOMPtr<nsIByteBuffer> mByteData; + nsCOMPtr<nsIUnicharBuffer> mUnicharData; + + uint32_t mByteDataOffset; + uint32_t mUnicharDataOffset; + uint32_t mUnicharDataLength; +}; + +UTF8InputStream::UTF8InputStream() : + mByteDataOffset(0), + mUnicharDataOffset(0), + mUnicharDataLength(0) +{ +} + +nsresult +UTF8InputStream::Init(nsIInputStream* aStream) +{ + nsresult rv = NS_NewByteBuffer(getter_AddRefs(mByteData), nullptr, + STRING_BUFFER_SIZE); + if (NS_FAILED(rv)) return rv; + rv = NS_NewUnicharBuffer(getter_AddRefs(mUnicharData), nullptr, + STRING_BUFFER_SIZE); + if (NS_FAILED(rv)) return rv; + + mInput = aStream; + + return NS_OK; +} + +NS_IMPL_ISUPPORTS1(UTF8InputStream,nsIUnicharInputStream) + +UTF8InputStream::~UTF8InputStream() +{ + Close(); +} + +nsresult UTF8InputStream::Close() +{ + mInput = nullptr; + mByteData = nullptr; + mUnicharData = nullptr; + + return NS_OK; +} + +nsresult UTF8InputStream::Read(PRUnichar* aBuf, + uint32_t aCount, + uint32_t *aReadCount) +{ + NS_ASSERTION(mUnicharDataLength >= mUnicharDataOffset, "unsigned madness"); + uint32_t readCount = mUnicharDataLength - mUnicharDataOffset; + nsresult errorCode; + if (0 == readCount) { + // Fill the unichar buffer + int32_t bytesRead = Fill(&errorCode); + if (bytesRead <= 0) { + *aReadCount = 0; + return errorCode; + } + readCount = bytesRead; + } + if (readCount > aCount) { + readCount = aCount; + } + memcpy(aBuf, mUnicharData->GetBuffer() + mUnicharDataOffset, + readCount * sizeof(PRUnichar)); + mUnicharDataOffset += readCount; + *aReadCount = readCount; + return NS_OK; +} + +NS_IMETHODIMP +UTF8InputStream::ReadSegments(nsWriteUnicharSegmentFun aWriter, + void* aClosure, + uint32_t aCount, uint32_t *aReadCount) +{ + NS_ASSERTION(mUnicharDataLength >= mUnicharDataOffset, "unsigned madness"); + uint32_t bytesToWrite = mUnicharDataLength - mUnicharDataOffset; + nsresult rv = NS_OK; + if (0 == bytesToWrite) { + // Fill the unichar buffer + int32_t bytesRead = Fill(&rv); + if (bytesRead <= 0) { + *aReadCount = 0; + return rv; + } + bytesToWrite = bytesRead; + } + + if (bytesToWrite > aCount) + bytesToWrite = aCount; + + uint32_t bytesWritten; + uint32_t totalBytesWritten = 0; + + while (bytesToWrite) { + rv = aWriter(this, aClosure, + mUnicharData->GetBuffer() + mUnicharDataOffset, + totalBytesWritten, bytesToWrite, &bytesWritten); + + if (NS_FAILED(rv)) { + // don't propagate errors to the caller + break; + } + + bytesToWrite -= bytesWritten; + totalBytesWritten += bytesWritten; + mUnicharDataOffset += bytesWritten; + } + + *aReadCount = totalBytesWritten; + + return NS_OK; +} + +NS_IMETHODIMP +UTF8InputStream::ReadString(uint32_t aCount, nsAString& aString, + uint32_t* aReadCount) +{ + NS_ASSERTION(mUnicharDataLength >= mUnicharDataOffset, "unsigned madness"); + uint32_t readCount = mUnicharDataLength - mUnicharDataOffset; + nsresult errorCode; + if (0 == readCount) { + // Fill the unichar buffer + int32_t bytesRead = Fill(&errorCode); + if (bytesRead <= 0) { + *aReadCount = 0; + return errorCode; + } + readCount = bytesRead; + } + if (readCount > aCount) { + readCount = aCount; + } + const PRUnichar* buf = reinterpret_cast<const PRUnichar*>(mUnicharData->GetBuffer() + + mUnicharDataOffset); + aString.Assign(buf, readCount); + + mUnicharDataOffset += readCount; + *aReadCount = readCount; + return NS_OK; +} + + +int32_t UTF8InputStream::Fill(nsresult * aErrorCode) +{ + if (nullptr == mInput) { + // We already closed the stream! + *aErrorCode = NS_BASE_STREAM_CLOSED; + return -1; + } + + NS_ASSERTION(mByteData->GetLength() >= mByteDataOffset, "unsigned madness"); + uint32_t remainder = mByteData->GetLength() - mByteDataOffset; + mByteDataOffset = remainder; + int32_t nb = mByteData->Fill(aErrorCode, mInput, remainder); + if (nb <= 0) { + // Because we assume a many to one conversion, the lingering data + // in the byte buffer must be a partial conversion + // fragment. Because we know that we have received no more new + // data to add to it, we can't convert it. Therefore, we discard + // it. + return nb; + } + NS_ASSERTION(remainder + nb == mByteData->GetLength(), "bad nb"); + + // Now convert as much of the byte buffer to unicode as possible + uint32_t srcLen, dstLen; + CountValidUTF8Bytes(mByteData->GetBuffer(),remainder + nb, srcLen, dstLen); + + // the number of UCS2 characters should always be <= the number of + // UTF8 chars + NS_ASSERTION( (remainder+nb >= srcLen), "cannot be longer than out buffer"); + NS_ASSERTION(int32_t(dstLen) <= mUnicharData->GetBufferSize(), + "Ouch. I would overflow my buffer if I wasn't so careful."); + if (int32_t(dstLen) > mUnicharData->GetBufferSize()) return 0; + + ConvertUTF8toUTF16 converter(mUnicharData->GetBuffer()); + + nsASingleFragmentCString::const_char_iterator start = mByteData->GetBuffer(); + nsASingleFragmentCString::const_char_iterator end = mByteData->GetBuffer() + srcLen; + + copy_string(start, end, converter); + if (converter.Length() != dstLen) { + *aErrorCode = NS_BASE_STREAM_BAD_CONVERSION; + return -1; + } + + mUnicharDataOffset = 0; + mUnicharDataLength = dstLen; + mByteDataOffset = srcLen; + + return dstLen; +} + +void +UTF8InputStream::CountValidUTF8Bytes(const char* aBuffer, uint32_t aMaxBytes, uint32_t& aValidUTF8bytes, uint32_t& aValidUTF16CodeUnits) +{ + const char *c = aBuffer; + const char *end = aBuffer + aMaxBytes; + const char *lastchar = c; // pre-initialize in case of 0-length buffer + uint32_t utf16length = 0; + while (c < end && *c) { + lastchar = c; + utf16length++; + + if (UTF8traits::isASCII(*c)) + c++; + else if (UTF8traits::is2byte(*c)) + c += 2; + else if (UTF8traits::is3byte(*c)) + c += 3; + else if (UTF8traits::is4byte(*c)) { + c += 4; + utf16length++; // add 1 more because this will be converted to a + // surrogate pair. + } + else if (UTF8traits::is5byte(*c)) + c += 5; + else if (UTF8traits::is6byte(*c)) + c += 6; + else { + NS_WARNING("Unrecognized UTF8 string in UTF8InputStream::CountValidUTF8Bytes()"); + break; // Otherwise we go into an infinite loop. But what happens now? + } + } + if (c > end) { + c = lastchar; + utf16length--; + } + + aValidUTF8bytes = c - aBuffer; + aValidUTF16CodeUnits = utf16length; +} + +NS_IMPL_QUERY_INTERFACE2(nsSimpleUnicharStreamFactory, + nsIFactory, + nsISimpleUnicharStreamFactory) + +NS_IMETHODIMP_(nsrefcnt) nsSimpleUnicharStreamFactory::AddRef() { return 2; } +NS_IMETHODIMP_(nsrefcnt) nsSimpleUnicharStreamFactory::Release() { return 1; } + +NS_IMETHODIMP +nsSimpleUnicharStreamFactory::CreateInstance(nsISupports* aOuter, REFNSIID aIID, + void **aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsSimpleUnicharStreamFactory::LockFactory(bool aLock) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleUnicharStreamFactory::CreateInstanceFromString(const nsAString& aString, + nsIUnicharInputStream* *aResult) +{ + StringUnicharInputStream* it = new StringUnicharInputStream(aString); + if (!it) { + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_ADDREF(*aResult = it); + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleUnicharStreamFactory::CreateInstanceFromUTF8Stream(nsIInputStream* aStreamToWrap, + nsIUnicharInputStream* *aResult) +{ + *aResult = nullptr; + + // Create converter input stream + nsRefPtr<UTF8InputStream> it = new UTF8InputStream(); + if (!it) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = it->Init(aStreamToWrap); + if (NS_FAILED(rv)) + return rv; + + NS_ADDREF(*aResult = it); + return NS_OK; +} + +nsSimpleUnicharStreamFactory* +nsSimpleUnicharStreamFactory::GetInstance() +{ + static const nsSimpleUnicharStreamFactory kInstance; + return const_cast<nsSimpleUnicharStreamFactory*>(&kInstance); +} diff --git a/xpcom/io/nsUnicharInputStream.h b/xpcom/io/nsUnicharInputStream.h new file mode 100644 index 000000000..9b8e6c004 --- /dev/null +++ b/xpcom/io/nsUnicharInputStream.h @@ -0,0 +1,29 @@ +/* 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/. */ + +#ifndef nsUnicharInputStream_h__ +#define nsUnicharInputStream_h__ + +#include "nsISimpleUnicharStreamFactory.h" +#include "nsIUnicharInputStream.h" +#include "nsIFactory.h" + +// {428DCA6F-1A0F-4cda-B516-0D5244745A6A} +#define NS_SIMPLE_UNICHAR_STREAM_FACTORY_CID \ +{ 0x428dca6f, 0x1a0f, 0x4cda, { 0xb5, 0x16, 0xd, 0x52, 0x44, 0x74, 0x5a, 0x6a } } + +class nsSimpleUnicharStreamFactory : + public nsIFactory, + private nsISimpleUnicharStreamFactory +{ +public: + nsSimpleUnicharStreamFactory() {} + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFACTORY + NS_DECL_NSISIMPLEUNICHARSTREAMFACTORY + + static nsSimpleUnicharStreamFactory* GetInstance(); +}; + +#endif // nsUnicharInputStream_h__ diff --git a/xpcom/io/nsWildCard.cpp b/xpcom/io/nsWildCard.cpp new file mode 100644 index 000000000..3c228daff --- /dev/null +++ b/xpcom/io/nsWildCard.cpp @@ -0,0 +1,439 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sts=4 sw=4 cin et: */ +/* 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/. */ + +/* * + * + * + * nsWildCard.cpp: shell-like wildcard match routines + * + * See nsIZipReader.findEntries documentation in nsIZipReader.idl for + * a description of the syntax supported by the routines in this file. + * + * Rob McCool + * + */ + +#include "nsWildCard.h" +#include "nsXPCOM.h" +#include "nsCRTGlue.h" +#include "nsCharTraits.h" + +/* -------------------- ASCII-specific character methods ------------------- */ + +typedef int static_assert_character_code_arrangement['a' > 'A' ? 1 : -1]; + +template<class T> +static int +alpha(T c) +{ + return ('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z'); +} + +template<class T> +static int +alphanumeric(T c) +{ + return ('0' <= c && c <= '9') || ::alpha(c); +} + +template<class T> +static int +lower(T c) +{ + return ('A' <= c && c <= 'Z') ? c + ('a' - 'A') : c; +} + +template<class T> +static int +upper(T c) +{ + return ('a' <= c && c <= 'z') ? c - ('a' - 'A') : c; +} + +/* ----------------------------- _valid_subexp ---------------------------- */ + +template<class T> +static int +_valid_subexp(const T *expr, T stop1, T stop2) +{ + register int x; + int nsc = 0; /* Number of special characters */ + int np; /* Number of pipe characters in union */ + int tld = 0; /* Number of tilde characters */ + + for (x = 0; expr[x] && (expr[x] != stop1) && (expr[x] != stop2); ++x) { + switch(expr[x]) { + case '~': + if(tld) /* at most one exclusion */ + return INVALID_SXP; + if (stop1) /* no exclusions within unions */ + return INVALID_SXP; + if (!expr[x+1]) /* exclusion cannot be last character */ + return INVALID_SXP; + if (!x) /* exclusion cannot be first character */ + return INVALID_SXP; + ++tld; + /* fall through */ + case '*': + case '?': + case '$': + ++nsc; + break; + case '[': + ++nsc; + if((!expr[++x]) || (expr[x] == ']')) + return INVALID_SXP; + for(; expr[x] && (expr[x] != ']'); ++x) { + if(expr[x] == '\\' && !expr[++x]) + return INVALID_SXP; + } + if(!expr[x]) + return INVALID_SXP; + break; + case '(': + ++nsc; + if (stop1) /* no nested unions */ + return INVALID_SXP; + np = -1; + do { + int t = ::_valid_subexp(&expr[++x], T(')'), T('|')); + if(t == 0 || t == INVALID_SXP) + return INVALID_SXP; + x+=t; + if(!expr[x]) + return INVALID_SXP; + ++np; + } while (expr[x] == '|' ); + if(np < 1) /* must be at least one pipe */ + return INVALID_SXP; + break; + case ')': + case ']': + case '|': + return INVALID_SXP; + case '\\': + ++nsc; + if(!expr[++x]) + return INVALID_SXP; + break; + default: + break; + } + } + if((!stop1) && (!nsc)) /* must be at least one special character */ + return NON_SXP; + return ((expr[x] == stop1 || expr[x] == stop2) ? x : INVALID_SXP); +} + + +template<class T> +int +NS_WildCardValid_(const T *expr) +{ + int x = ::_valid_subexp(expr, T('\0'), T('\0')); + return (x < 0 ? x : VALID_SXP); +} + +int +NS_WildCardValid(const char *expr) +{ + return NS_WildCardValid_(expr); +} + +int +NS_WildCardValid(const PRUnichar *expr) +{ + return NS_WildCardValid_(expr); +} + +/* ----------------------------- _shexp_match ----------------------------- */ + + +#define MATCH 0 +#define NOMATCH 1 +#define ABORTED -1 + +template<class T> +static int +_shexp_match(const T *str, const T *expr, bool case_insensitive, unsigned int level); + +/** + * Count characters until we reach a NUL character or either of the + * two delimiter characters, stop1 or stop2. If we encounter a bracketed + * expression, look only for NUL or ']' inside it. Do not look for stop1 + * or stop2 inside it. Return ABORTED if bracketed expression is unterminated. + * Handle all escaping. + * Return index in input string of first stop found, or ABORTED if not found. + * If "dest" is non-NULL, copy counted characters to it and NUL terminate. + */ +template<class T> +static int +_scan_and_copy(const T *expr, T stop1, T stop2, T *dest) +{ + register int sx; /* source index */ + register T cc; + + for (sx = 0; (cc = expr[sx]) && cc != stop1 && cc != stop2; sx++) { + if (cc == '\\') { + if (!expr[++sx]) + return ABORTED; /* should be impossible */ + } + else if (cc == '[') { + while ((cc = expr[++sx]) && cc != ']') { + if(cc == '\\' && !expr[++sx]) + return ABORTED; + } + if (!cc) + return ABORTED; /* should be impossible */ + } + } + if (dest && sx) { + /* Copy all but the closing delimiter. */ + memcpy(dest, expr, sx * sizeof(T)); + dest[sx] = 0; + } + return cc ? sx : ABORTED; /* index of closing delimiter */ +} + +/* On input, expr[0] is the opening parenthesis of a union. + * See if any of the alternatives in the union matches as a pattern. + * The strategy is to take each of the alternatives, in turn, and append + * the rest of the expression (after the closing ')' that marks the end of + * this union) to that alternative, and then see if the resultant expression + * matches the input string. Repeat this until some alternative matches, + * or we have an abort. + */ +template<class T> +static int +_handle_union(const T *str, const T *expr, bool case_insensitive, + unsigned int level) +{ + register int sx; /* source index */ + int cp; /* source index of closing parenthesis */ + int count; + int ret = NOMATCH; + T *e2; + + /* Find the closing parenthesis that ends this union in the expression */ + cp = ::_scan_and_copy(expr, T(')'), T('\0'), static_cast<T*>(NULL)); + if (cp == ABORTED || cp < 4) /* must be at least "(a|b" before ')' */ + return ABORTED; + ++cp; /* now index of char after closing parenthesis */ + e2 = (T *) NS_Alloc((1 + nsCharTraits<T>::length(expr)) * sizeof(T)); + if (!e2) + return ABORTED; + for (sx = 1; ; ++sx) { + /* Here, expr[sx] is one character past the preceding '(' or '|'. */ + /* Copy everything up to the next delimiter to e2 */ + count = ::_scan_and_copy(expr + sx, T(')'), T('|'), e2); + if (count == ABORTED || !count) { + ret = ABORTED; + break; + } + sx += count; + /* Append everything after closing parenthesis to e2. This is safe. */ + nsCharTraits<T>::copy(e2 + count, expr + cp, nsCharTraits<T>::length(expr + cp) + 1); + ret = ::_shexp_match(str, e2, case_insensitive, level + 1); + if (ret != NOMATCH || !expr[sx] || expr[sx] == ')') + break; + } + NS_Free(e2); + if (sx < 2) + ret = ABORTED; + return ret; +} + +/* returns 1 if val is in range from start..end, case insensitive. */ +static int +_is_char_in_range(unsigned char start, unsigned char end, unsigned char val) +{ + char map[256]; + memset(map, 0, sizeof map); + while (start <= end) + map[lower(start++)] = 1; + return map[lower(val)]; +} + +template<class T> +static int +_shexp_match(const T *str, const T *expr, bool case_insensitive, + unsigned int level) +{ + register int x; /* input string index */ + register int y; /* expression index */ + int ret,neg; + + if (level > 20) /* Don't let the stack get too deep. */ + return ABORTED; + for(x = 0, y = 0; expr[y]; ++y, ++x) { + if((!str[x]) && (expr[y] != '$') && (expr[y] != '*')) { + return NOMATCH; + } + switch(expr[y]) { + case '$': + if(str[x]) + return NOMATCH; + --x; /* we don't want loop to increment x */ + break; + case '*': + while(expr[++y] == '*'){} + if(!expr[y]) + return MATCH; + while(str[x]) { + ret = ::_shexp_match(&str[x++], &expr[y], case_insensitive, + level + 1); + switch(ret) { + case NOMATCH: + continue; + case ABORTED: + return ABORTED; + default: + return MATCH; + } + } + if((expr[y] == '$') && (expr[y+1] == '\0') && (!str[x])) + return MATCH; + else + return NOMATCH; + case '[': { + T start, end = 0; + int i; + neg = ((expr[++y] == '^') && (expr[y+1] != ']')); + if (neg) + ++y; + i = y; + start = expr[i++]; + if (start == '\\') + start = expr[i++]; + if (::alphanumeric(start) && expr[i++] == '-') { + end = expr[i++]; + if (end == '\\') + end = expr[i++]; + } + if (::alphanumeric(end) && expr[i] == ']') { + /* This is a range form: a-b */ + T val = str[x]; + if (end < start) { /* swap them */ + T tmp = end; + end = start; + start = tmp; + } + if (case_insensitive && ::alpha(val)) { + val = ::_is_char_in_range((unsigned char) start, + (unsigned char) end, + (unsigned char) val); + if (neg == val) + return NOMATCH; + } + else if (neg != ((val < start) || (val > end))) { + return NOMATCH; + } + y = i; + } + else { + /* Not range form */ + int matched = 0; + for (; expr[y] != ']'; y++) { + if (expr[y] == '\\') + ++y; + if(case_insensitive) + matched |= (::upper(str[x]) == ::upper(expr[y])); + else + matched |= (str[x] == expr[y]); + } + if (neg == matched) + return NOMATCH; + } + } + break; + case '(': + if (!expr[y+1]) + return ABORTED; + return ::_handle_union(&str[x], &expr[y], case_insensitive, level + 1); + case '?': + break; + case ')': + case ']': + case '|': + return ABORTED; + case '\\': + ++y; + /* fall through */ + default: + if(case_insensitive) { + if(::upper(str[x]) != ::upper(expr[y])) + return NOMATCH; + } + else { + if(str[x] != expr[y]) + return NOMATCH; + } + break; + } + } + return (str[x] ? NOMATCH : MATCH); +} + + +template<class T> +static int +ns_WildCardMatch(const T *str, const T *xp, bool case_insensitive) +{ + T *expr = NULL; + int x, ret = MATCH; + + if (!nsCharTraits<T>::find(xp, nsCharTraits<T>::length(xp), T('~'))) + return ::_shexp_match(str, xp, case_insensitive, 0); + + expr = (T *) NS_Alloc((nsCharTraits<T>::length(xp) + 1) * sizeof(T)); + if(!expr) + return NOMATCH; + memcpy(expr, xp, (nsCharTraits<T>::length(xp) + 1) * sizeof(T)); + + x = ::_scan_and_copy(expr, T('~'), T('\0'), static_cast<T*>(NULL)); + if (x != ABORTED && expr[x] == '~') { + expr[x++] = '\0'; + ret = ::_shexp_match(str, &expr[x], case_insensitive, 0); + switch (ret) { + case NOMATCH: ret = MATCH; break; + case MATCH: ret = NOMATCH; break; + default: break; + } + } + if (ret == MATCH) + ret = ::_shexp_match(str, expr, case_insensitive, 0); + + NS_Free(expr); + return ret; +} + +template<class T> +int +NS_WildCardMatch_(const T *str, const T *expr, bool case_insensitive) +{ + int is_valid = NS_WildCardValid(expr); + switch(is_valid) { + case INVALID_SXP: + return -1; + default: + return ::ns_WildCardMatch(str, expr, case_insensitive); + } +} + +int +NS_WildCardMatch(const char *str, const char *xp, + bool case_insensitive) +{ + return NS_WildCardMatch_(str, xp, case_insensitive); +} + +int +NS_WildCardMatch(const PRUnichar *str, const PRUnichar *xp, + bool case_insensitive) +{ + return NS_WildCardMatch_(str, xp, case_insensitive); +} diff --git a/xpcom/io/nsWildCard.h b/xpcom/io/nsWildCard.h new file mode 100644 index 000000000..ef6fd349d --- /dev/null +++ b/xpcom/io/nsWildCard.h @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* + * nsWildCard.h: Defines and prototypes for shell exp. match routines + * + * See nsIZipReader.findEntries docs in nsIZipReader.idl for a description of + * the supported expression syntax. + * + * Note that the syntax documentation explicitly says the results of certain + * expressions are undefined. This is intentional to require less robustness + * in the code. Regular expression parsing is hard; the smaller the set of + * features and interactions this code must support, the easier it is to + * ensure it works. + * + */ + +#ifndef nsWildCard_h__ +#define nsWildCard_h__ + +#include "prtypes.h" +#include "nscore.h" + +/* --------------------------- Public routines ---------------------------- */ + + +/* + * NS_WildCardValid takes a shell expression exp as input. It returns: + * + * NON_SXP if exp is a standard string + * INVALID_SXP if exp is a shell expression, but invalid + * VALID_SXP if exp is a valid shell expression + */ + +#define NON_SXP -1 +#define INVALID_SXP -2 +#define VALID_SXP 1 + +int NS_WildCardValid(const char *expr); + +int NS_WildCardValid(const PRUnichar *expr); + +/* return values for the search routines */ +#define MATCH 0 +#define NOMATCH 1 +#define ABORTED -1 + +/* + * NS_WildCardMatch + * + * Takes a prevalidated shell expression exp, and a string str. + * + * Returns 0 on match and 1 on non-match. + */ + +int NS_WildCardMatch(const char *str, const char *expr, + bool case_insensitive); + +int NS_WildCardMatch(const PRUnichar *str, const PRUnichar *expr, + bool case_insensitive); + +#endif /* nsWildCard_h__ */ |