diff options
Diffstat (limited to 'xpcom/io')
98 files changed, 27198 insertions, 0 deletions
diff --git a/xpcom/io/Base64.cpp b/xpcom/io/Base64.cpp new file mode 100644 index 0000000000..911c0595ac --- /dev/null +++ b/xpcom/io/Base64.cpp @@ -0,0 +1,645 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Base64.h" + +#include "mozilla/UniquePtrExtensions.h" +#include "nsIInputStream.h" +#include "nsString.h" +#include "nsTArray.h" + +#include "plbase64.h" + +namespace { + +// BEGIN base64 encode code copied and modified from NSPR +const unsigned char* base = + (unsigned char*)"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +template<typename T> +static void +Encode3to4(const unsigned char* aSrc, T* aDest) +{ + uint32_t b32 = (uint32_t)0; + int i, j = 18; + + for (i = 0; i < 3; ++i) { + b32 <<= 8; + b32 |= (uint32_t)aSrc[i]; + } + + for (i = 0; i < 4; ++i) { + aDest[i] = base[(uint32_t)((b32 >> j) & 0x3F)]; + j -= 6; + } +} + +template<typename T> +static void +Encode2to4(const unsigned char* aSrc, T* aDest) +{ + aDest[0] = base[(uint32_t)((aSrc[0] >> 2) & 0x3F)]; + aDest[1] = base[(uint32_t)(((aSrc[0] & 0x03) << 4) | ((aSrc[1] >> 4) & 0x0F))]; + aDest[2] = base[(uint32_t)((aSrc[1] & 0x0F) << 2)]; + aDest[3] = (unsigned char)'='; +} + +template<typename T> +static void +Encode1to4(const unsigned char* aSrc, T* aDest) +{ + aDest[0] = base[(uint32_t)((aSrc[0] >> 2) & 0x3F)]; + aDest[1] = base[(uint32_t)((aSrc[0] & 0x03) << 4)]; + aDest[2] = (unsigned char)'='; + aDest[3] = (unsigned char)'='; +} + +template<typename T> +static void +Encode(const unsigned char* aSrc, uint32_t aSrcLen, T* aDest) +{ + while (aSrcLen >= 3) { + Encode3to4(aSrc, aDest); + aSrc += 3; + aDest += 4; + aSrcLen -= 3; + } + + switch (aSrcLen) { + case 2: + Encode2to4(aSrc, aDest); + break; + case 1: + Encode1to4(aSrc, aDest); + 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> +nsresult +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; + MOZ_ASSERT(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. + MOZ_ASSERT(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); + if (NS_WARN_IF(NS_FAILED(rv))) { + return 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); + + if (!aDest.SetLength(count + aOffset, mozilla::fallible)) { + 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); + } + + if (aDest.Length()) { + // May belong to an nsCString with an unallocated buffer, so only null + // terminate if there is a need to. + *aDest.EndWriting() = '\0'; + } + + return NS_OK; +} + +static const char kBase64URLAlphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +// Maps an encoded character to a value in the Base64 URL alphabet, per +// RFC 4648, Table 2. Invalid input characters map to UINT8_MAX. +static const uint8_t kBase64URLDecodeTable[] = { + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, + 62 /* - */, + 255, 255, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, /* 0 - 9 */ + 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, /* A - Z */ + 255, 255, 255, 255, + 63 /* _ */, + 255, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, /* a - z */ + 255, 255, 255, 255, +}; + +bool +Base64URLCharToValue(char aChar, uint8_t* aValue) { + uint8_t index = static_cast<uint8_t>(aChar); + *aValue = kBase64URLDecodeTable[index & 0x7f]; + return (*aValue != 255) && !(index & ~0x7f); +} + +} // namespace + +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 char* aBinary, uint32_t aBinaryLen, char** aBase64) +{ + // Check for overflow. + if (aBinaryLen > (UINT32_MAX / 4) * 3) { + return NS_ERROR_FAILURE; + } + + // Don't ask PR_Base64Encode to encode empty strings. + if (aBinaryLen == 0) { + *aBase64 = (char*)moz_xmalloc(1); + (*aBase64)[0] = '\0'; + return NS_OK; + } + + *aBase64 = nullptr; + uint32_t base64Len = ((aBinaryLen + 2) / 3) * 4; + + // Add one byte for null termination. + UniqueFreePtr<char[]> base64((char*)malloc(base64Len + 1)); + if (!base64) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!PL_Base64Encode(aBinary, aBinaryLen, base64.get())) { + return NS_ERROR_INVALID_ARG; + } + + // PL_Base64Encode doesn't null terminate the buffer for us when we pass + // the buffer in. Do that manually. + base64[base64Len] = '\0'; + + *aBase64 = base64.release(); + return NS_OK; +} + +nsresult +Base64Encode(const nsACString& aBinary, nsACString& aBase64) +{ + // Check for overflow. + if (aBinary.Length() > (UINT32_MAX / 4) * 3) { + return NS_ERROR_FAILURE; + } + + // Don't ask PR_Base64Encode to encode empty strings. + if (aBinary.IsEmpty()) { + aBase64.Truncate(); + return NS_OK; + } + + uint32_t base64Len = ((aBinary.Length() + 2) / 3) * 4; + + // Add one byte for null termination. + if (!aBase64.SetCapacity(base64Len + 1, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + char* base64 = aBase64.BeginWriting(); + if (!PL_Base64Encode(aBinary.BeginReading(), aBinary.Length(), base64)) { + aBase64.Truncate(); + return NS_ERROR_INVALID_ARG; + } + + // PL_Base64Encode doesn't null terminate the buffer for us when we pass + // the buffer in. Do that manually. + base64[base64Len] = '\0'; + + aBase64.SetLength(base64Len); + return NS_OK; +} + +nsresult +Base64Encode(const nsAString& aBinary, nsAString& aBase64) +{ + NS_LossyConvertUTF16toASCII binary(aBinary); + nsAutoCString base64; + + nsresult rv = Base64Encode(binary, base64); + if (NS_SUCCEEDED(rv)) { + CopyASCIItoUTF16(base64, aBase64); + } else { + aBase64.Truncate(); + } + + return rv; +} + +static nsresult +Base64DecodeHelper(const char* aBase64, uint32_t aBase64Len, char* aBinary, + uint32_t* aBinaryLen) +{ + MOZ_ASSERT(aBinary); + if (!PL_Base64Decode(aBase64, aBase64Len, aBinary)) { + return NS_ERROR_INVALID_ARG; + } + + // 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 (aBase64Len != 0 && aBase64[aBase64Len - 1] == '=') { + if (aBase64Len > 1 && aBase64[aBase64Len - 2] == '=') { + *aBinaryLen -= 2; + } else { + *aBinaryLen -= 1; + } + } + aBinary[*aBinaryLen] = '\0'; + return NS_OK; +} + +nsresult +Base64Decode(const char* aBase64, uint32_t aBase64Len, char** aBinary, + uint32_t* aBinaryLen) +{ + // Check for overflow. + if (aBase64Len > UINT32_MAX / 3) { + return NS_ERROR_FAILURE; + } + + // Don't ask PR_Base64Decode to decode the empty string. + if (aBase64Len == 0) { + *aBinary = (char*)moz_xmalloc(1); + (*aBinary)[0] = '\0'; + *aBinaryLen = 0; + return NS_OK; + } + + *aBinary = nullptr; + *aBinaryLen = (aBase64Len * 3) / 4; + + // Add one byte for null termination. + UniqueFreePtr<char[]> binary((char*)malloc(*aBinaryLen + 1)); + if (!binary) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = + Base64DecodeHelper(aBase64, aBase64Len, binary.get(), aBinaryLen); + if (NS_FAILED(rv)) { + return rv; + } + + *aBinary = binary.release(); + return NS_OK; +} + +nsresult +Base64Decode(const nsACString& aBase64, nsACString& aBinary) +{ + // Check for overflow. + if (aBase64.Length() > UINT32_MAX / 3) { + return NS_ERROR_FAILURE; + } + + // Don't ask PR_Base64Decode to decode the empty string + if (aBase64.IsEmpty()) { + aBinary.Truncate(); + return NS_OK; + } + + uint32_t binaryLen = ((aBase64.Length() * 3) / 4); + + // Add one byte for null termination. + if (!aBinary.SetCapacity(binaryLen + 1, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + char* binary = aBinary.BeginWriting(); + nsresult rv = Base64DecodeHelper(aBase64.BeginReading(), aBase64.Length(), + binary, &binaryLen); + if (NS_FAILED(rv)) { + aBinary.Truncate(); + return rv; + } + + aBinary.SetLength(binaryLen); + return NS_OK; +} + +nsresult +Base64Decode(const nsAString& aBase64, nsAString& aBinary) +{ + NS_LossyConvertUTF16toASCII base64(aBase64); + nsAutoCString binary; + + nsresult rv = Base64Decode(base64, binary); + if (NS_SUCCEEDED(rv)) { + CopyASCIItoUTF16(binary, aBinary); + } else { + aBinary.Truncate(); + } + + return rv; +} + +nsresult +Base64URLDecode(const nsACString& aBase64, + Base64URLDecodePaddingPolicy aPaddingPolicy, + FallibleTArray<uint8_t>& aBinary) +{ + // Don't decode empty strings. + if (aBase64.IsEmpty()) { + aBinary.Clear(); + return NS_OK; + } + + // Check for overflow. + uint32_t base64Len = aBase64.Length(); + if (base64Len > UINT32_MAX / 3) { + return NS_ERROR_FAILURE; + } + const char* base64 = aBase64.BeginReading(); + + // The decoded length may be 1-2 bytes over, depending on the final quantum. + uint32_t binaryLen = (base64Len * 3) / 4; + + // Determine whether to check for and ignore trailing padding. + bool maybePadded = false; + switch (aPaddingPolicy) { + case Base64URLDecodePaddingPolicy::Require: + if (base64Len % 4) { + // Padded input length must be a multiple of 4. + return NS_ERROR_INVALID_ARG; + } + maybePadded = true; + break; + + case Base64URLDecodePaddingPolicy::Ignore: + // Check for padding only if the length is a multiple of 4. + maybePadded = !(base64Len % 4); + break; + + // If we're expecting unpadded input, no need for additional checks. + // `=` isn't in the decode table, so padded strings will fail to decode. + default: + MOZ_FALLTHROUGH_ASSERT("Invalid decode padding policy"); + case Base64URLDecodePaddingPolicy::Reject: + break; + } + if (maybePadded && base64[base64Len - 1] == '=') { + if (base64[base64Len - 2] == '=') { + base64Len -= 2; + } else { + base64Len -= 1; + } + } + + if (NS_WARN_IF(!aBinary.SetCapacity(binaryLen, mozilla::fallible))) { + return NS_ERROR_OUT_OF_MEMORY; + } + aBinary.SetLengthAndRetainStorage(binaryLen); + uint8_t* binary = aBinary.Elements(); + + for (; base64Len >= 4; base64Len -= 4) { + uint8_t w, x, y, z; + if (!Base64URLCharToValue(*base64++, &w) || + !Base64URLCharToValue(*base64++, &x) || + !Base64URLCharToValue(*base64++, &y) || + !Base64URLCharToValue(*base64++, &z)) { + return NS_ERROR_INVALID_ARG; + } + *binary++ = w << 2 | x >> 4; + *binary++ = x << 4 | y >> 2; + *binary++ = y << 6 | z; + } + + if (base64Len == 3) { + uint8_t w, x, y; + if (!Base64URLCharToValue(*base64++, &w) || + !Base64URLCharToValue(*base64++, &x) || + !Base64URLCharToValue(*base64++, &y)) { + return NS_ERROR_INVALID_ARG; + } + *binary++ = w << 2 | x >> 4; + *binary++ = x << 4 | y >> 2; + } else if (base64Len == 2) { + uint8_t w, x; + if (!Base64URLCharToValue(*base64++, &w) || + !Base64URLCharToValue(*base64++, &x)) { + return NS_ERROR_INVALID_ARG; + } + *binary++ = w << 2 | x >> 4; + } else if (base64Len) { + return NS_ERROR_INVALID_ARG; + } + + // Set the length to the actual number of decoded bytes. + aBinary.TruncateLength(binary - aBinary.Elements()); + return NS_OK; +} + +nsresult +Base64URLEncode(uint32_t aBinaryLen, const uint8_t* aBinary, + Base64URLEncodePaddingPolicy aPaddingPolicy, + nsACString& aBase64) +{ + // Don't encode empty strings. + if (aBinaryLen == 0) { + aBase64.Truncate(); + return NS_OK; + } + + // Check for overflow. + if (aBinaryLen > (UINT32_MAX / 4) * 3) { + return NS_ERROR_FAILURE; + } + + // Allocate a buffer large enough to hold the encoded string with padding. + // Add one byte for null termination. + uint32_t base64Len = ((aBinaryLen + 2) / 3) * 4; + if (NS_WARN_IF(!aBase64.SetCapacity(base64Len + 1, fallible))) { + aBase64.Truncate(); + return NS_ERROR_FAILURE; + } + + char* base64 = aBase64.BeginWriting(); + + uint32_t index = 0; + for (; index + 3 <= aBinaryLen; index += 3) { + *base64++ = kBase64URLAlphabet[aBinary[index] >> 2]; + *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4) | + (aBinary[index + 1] >> 4)]; + *base64++ = kBase64URLAlphabet[((aBinary[index + 1] & 0xf) << 2) | + (aBinary[index + 2] >> 6)]; + *base64++ = kBase64URLAlphabet[aBinary[index + 2] & 0x3f]; + } + + uint32_t remaining = aBinaryLen - index; + if (remaining == 1) { + *base64++ = kBase64URLAlphabet[aBinary[index] >> 2]; + *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4)]; + } else if (remaining == 2) { + *base64++ = kBase64URLAlphabet[aBinary[index] >> 2]; + *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4) | + (aBinary[index + 1] >> 4)]; + *base64++ = kBase64URLAlphabet[((aBinary[index + 1] & 0xf) << 2)]; + } + + uint32_t length = base64 - aBase64.BeginWriting(); + if (aPaddingPolicy == Base64URLEncodePaddingPolicy::Include) { + if (length % 4 == 2) { + *base64++ = '='; + *base64++ = '='; + length += 2; + } else if (length % 4 == 3) { + *base64++ = '='; + length += 1; + } + } else { + MOZ_ASSERT(aPaddingPolicy == Base64URLEncodePaddingPolicy::Omit, + "Invalid encode padding policy"); + } + + // Null terminate and truncate to the actual number of characters. + *base64 = '\0'; + aBase64.SetLength(length); + + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/io/Base64.h b/xpcom/io/Base64.h new file mode 100644 index 0000000000..0c3f1e1401 --- /dev/null +++ b/xpcom/io/Base64.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_Base64_h__ +#define mozilla_Base64_h__ + +#include "nsString.h" + +class nsIInputStream; + +namespace mozilla { + +MOZ_MUST_USE nsresult +Base64EncodeInputStream(nsIInputStream* aInputStream, + nsACString& aDest, + uint32_t aCount, + uint32_t aOffset = 0); +MOZ_MUST_USE nsresult +Base64EncodeInputStream(nsIInputStream* aInputStream, + nsAString& aDest, + uint32_t aCount, + uint32_t aOffset = 0); + +MOZ_MUST_USE nsresult +Base64Encode(const char* aBinary, uint32_t aBinaryLen, char** aBase64); +MOZ_MUST_USE nsresult +Base64Encode(const nsACString& aBinary, nsACString& aBase64); +MOZ_MUST_USE nsresult +Base64Encode(const nsAString& aBinary, nsAString& aBase64); + +MOZ_MUST_USE nsresult +Base64Decode(const char* aBase64, uint32_t aBase64Len, char** aBinary, + uint32_t* aBinaryLen); +MOZ_MUST_USE nsresult +Base64Decode(const nsACString& aBase64, nsACString& aBinary); +MOZ_MUST_USE nsresult +Base64Decode(const nsAString& aBase64, nsAString& aBinary); + +enum class Base64URLEncodePaddingPolicy { + Include, + Omit, +}; + +/** + * Converts |aBinary| to an unpadded, Base64 URL-encoded string per RFC 4648. + * Aims to encode the data in constant time. The caller retains ownership + * of |aBinary|. + */ +MOZ_MUST_USE nsresult +Base64URLEncode(uint32_t aBinaryLen, const uint8_t* aBinary, + Base64URLEncodePaddingPolicy aPaddingPolicy, + nsACString& aBase64); + +enum class Base64URLDecodePaddingPolicy { + Require, + Ignore, + Reject, +}; + +/** + * Decodes a Base64 URL-encoded |aBase64| into |aBinary|. + */ +MOZ_MUST_USE nsresult +Base64URLDecode(const nsACString& aBase64, + Base64URLDecodePaddingPolicy aPaddingPolicy, + FallibleTArray<uint8_t>& aBinary); + +} // namespace mozilla + +#endif diff --git a/xpcom/io/CocoaFileUtils.h b/xpcom/io/CocoaFileUtils.h new file mode 100644 index 0000000000..7127cb65da --- /dev/null +++ b/xpcom/io/CocoaFileUtils.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// 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 aUrl); +nsresult OpenURL(CFURLRef aUrl); +nsresult GetFileCreatorCode(CFURLRef aUrl, OSType* aCreatorCode); +nsresult SetFileCreatorCode(CFURLRef aUrl, OSType aCreatorCode); +nsresult GetFileTypeCode(CFURLRef aUrl, OSType* aTypeCode); +nsresult SetFileTypeCode(CFURLRef aUrl, OSType aTypeCode); +void AddOriginMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL); +void AddQuarantineMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL, + const bool isFromWeb); +CFURLRef GetTemporaryFolderCFURLRef(); + +} // namespace CocoaFileUtils + +#endif diff --git a/xpcom/io/CocoaFileUtils.mm b/xpcom/io/CocoaFileUtils.mm new file mode 100644 index 0000000000..a02b82ac1d --- /dev/null +++ b/xpcom/io/CocoaFileUtils.mm @@ -0,0 +1,267 @@ +/* -*- 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 "nsCocoaFeatures.h" +#include "nsCocoaUtils.h" +#include <Cocoa/Cocoa.h> +#include "nsObjCExceptions.h" +#include "nsDebug.h" + +// Need to cope with us using old versions of the SDK and needing this on 10.10+ +#if !defined(MAC_OS_X_VERSION_10_10) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10) +const CFStringRef kCFURLQuarantinePropertiesKey = CFSTR("NSURLQuarantinePropertiesKey"); +#endif + +namespace CocoaFileUtils { + +nsresult RevealFileInFinder(CFURLRef url) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url)) + return NS_ERROR_INVALID_ARG; + + 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; + + if (NS_WARN_IF(!url)) + return NS_ERROR_INVALID_ARG; + + 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; + + if (NS_WARN_IF(!url) || NS_WARN_IF(!creatorCode)) + return NS_ERROR_INVALID_ARG; + + nsAutoreleasePool localPool; + + NSString *resolvedPath = [[(NSURL*)url path] stringByResolvingSymlinksInPath]; + if (!resolvedPath) { + return NS_ERROR_FAILURE; + } + + NSDictionary* dict = [[NSFileManager defaultManager] attributesOfItemAtPath:resolvedPath error:nil]; + if (!dict) { + return NS_ERROR_FAILURE; + } + + NSNumber* creatorNum = (NSNumber*)[dict objectForKey:NSFileHFSCreatorCode]; + if (!creatorNum) { + return NS_ERROR_FAILURE; + } + + *creatorCode = [creatorNum unsignedLongValue]; + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult SetFileCreatorCode(CFURLRef url, OSType creatorCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url)) + return NS_ERROR_INVALID_ARG; + + 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; + + if (NS_WARN_IF(!url) || NS_WARN_IF(!typeCode)) + return NS_ERROR_INVALID_ARG; + + nsAutoreleasePool localPool; + + NSString *resolvedPath = [[(NSURL*)url path] stringByResolvingSymlinksInPath]; + if (!resolvedPath) { + return NS_ERROR_FAILURE; + } + + NSDictionary* dict = [[NSFileManager defaultManager] attributesOfItemAtPath:resolvedPath error:nil]; + if (!dict) { + return NS_ERROR_FAILURE; + } + + NSNumber* typeNum = (NSNumber*)[dict objectForKey:NSFileHFSTypeCode]; + if (!typeNum) { + return NS_ERROR_FAILURE; + } + + *typeCode = [typeNum unsignedLongValue]; + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult SetFileTypeCode(CFURLRef url, OSType typeCode) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (NS_WARN_IF(!url)) + return NS_ERROR_INVALID_ARG; + + 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; +} + +void AddOriginMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL) { + typedef OSStatus (*MDItemSetAttribute_type)(MDItemRef, CFStringRef, CFTypeRef); + static MDItemSetAttribute_type mdItemSetAttributeFunc = NULL; + + static bool did_symbol_lookup = false; + if (!did_symbol_lookup) { + did_symbol_lookup = true; + + CFBundleRef metadata_bundle = ::CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Metadata")); + if (!metadata_bundle) { + return; + } + + mdItemSetAttributeFunc = (MDItemSetAttribute_type) + ::CFBundleGetFunctionPointerForName(metadata_bundle, CFSTR("MDItemSetAttribute")); + } + if (!mdItemSetAttributeFunc) { + return; + } + + MDItemRef mdItem = ::MDItemCreate(NULL, filePath); + if (!mdItem) { + return; + } + + CFMutableArrayRef list = ::CFArrayCreateMutable(kCFAllocatorDefault, 2, NULL); + if (!list) { + ::CFRelease(mdItem); + return; + } + + // The first item in the list is the source URL of the downloaded file. + if (sourceURL) { + ::CFArrayAppendValue(list, ::CFURLGetString(sourceURL)); + } + + // If the referrer is known, store that in the second position. + if (referrerURL) { + ::CFArrayAppendValue(list, ::CFURLGetString(referrerURL)); + } + + mdItemSetAttributeFunc(mdItem, kMDItemWhereFroms, list); + + ::CFRelease(list); + ::CFRelease(mdItem); +} + +void AddQuarantineMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL, + const bool isFromWeb) { + CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault, + filePath, + kCFURLPOSIXPathStyle, + false); + + // The properties key changed in 10.10: + CFStringRef quarantinePropKey; + if (nsCocoaFeatures::OnYosemiteOrLater()) { + quarantinePropKey = kCFURLQuarantinePropertiesKey; + } else { + quarantinePropKey = kLSItemQuarantineProperties; + } + CFDictionaryRef quarantineProps = NULL; + Boolean success = ::CFURLCopyResourcePropertyForKey(fileURL, + quarantinePropKey, + &quarantineProps, + NULL); + + // If there aren't any quarantine properties then the user probably + // set up an exclusion and we don't need to add metadata. + if (!success || !quarantineProps) { + ::CFRelease(fileURL); + return; + } + + // We don't know what to do if the props aren't a dictionary. + if (::CFGetTypeID(quarantineProps) != ::CFDictionaryGetTypeID()) { + ::CFRelease(fileURL); + ::CFRelease(quarantineProps); + return; + } + + // Make a mutable copy of the properties. + CFMutableDictionaryRef mutQuarantineProps = + ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, (CFDictionaryRef)quarantineProps); + ::CFRelease(quarantineProps); + + // Add metadata that the OS couldn't infer. + + if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineTypeKey)) { + CFStringRef type = isFromWeb ? kLSQuarantineTypeWebDownload : kLSQuarantineTypeOtherDownload; + ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineTypeKey, type); + } + + if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineOriginURLKey) && referrerURL) { + ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineOriginURLKey, referrerURL); + } + + if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineDataURLKey) && sourceURL) { + ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineDataURLKey, sourceURL); + } + + // Set quarantine properties on file. + ::CFURLSetResourcePropertyForKey(fileURL, + quarantinePropKey, + mutQuarantineProps, + NULL); + + ::CFRelease(fileURL); + ::CFRelease(mutQuarantineProps); +} + +CFURLRef GetTemporaryFolderCFURLRef() +{ + NSString* tempDir = ::NSTemporaryDirectory(); + return tempDir == nil ? NULL : (CFURLRef)[NSURL fileURLWithPath:tempDir + isDirectory:YES]; +} + +} // namespace CocoaFileUtils diff --git a/xpcom/io/FileUtilsWin.cpp b/xpcom/io/FileUtilsWin.cpp new file mode 100644 index 0000000000..732c074f7a --- /dev/null +++ b/xpcom/io/FileUtilsWin.cpp @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileUtilsWin.h" + +#include <windows.h> +#include <psapi.h> + +#include "mozilla/Unused.h" +#include "nsWindowsHelpers.h" +#include "GeckoProfiler.h" + +namespace { + +// Scoped type used by HandleToFilename +struct ScopedMappedViewTraits +{ + typedef void* type; + static void* empty() + { + return nullptr; + } + static void release(void* aPtr) + { + if (aPtr) { + mozilla::Unused << UnmapViewOfFile(aPtr); + } + } +}; +typedef mozilla::Scoped<ScopedMappedViewTraits> ScopedMappedView; + +} // namespace + +namespace mozilla { + +bool +HandleToFilename(HANDLE aHandle, const LARGE_INTEGER& aOffset, + nsAString& aFilename) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::NETWORK); + + aFilename.Truncate(); + // This implementation is nice because it uses fully documented APIs that + // are available on all Windows versions that we support. + nsAutoHandle fileMapping(CreateFileMapping(aHandle, nullptr, PAGE_READONLY, + 0, 1, nullptr)); + if (!fileMapping) { + return false; + } + ScopedMappedView view(MapViewOfFile(fileMapping, FILE_MAP_READ, + aOffset.HighPart, aOffset.LowPart, 1)); + if (!view) { + return false; + } + nsAutoString mappedFilename; + DWORD len = 0; + SetLastError(ERROR_SUCCESS); + do { + mappedFilename.SetLength(mappedFilename.Length() + MAX_PATH); + len = GetMappedFileNameW(GetCurrentProcess(), view, + wwc(mappedFilename.BeginWriting()), + mappedFilename.Length()); + } while (!len && GetLastError() == ERROR_INSUFFICIENT_BUFFER); + if (!len) { + return false; + } + mappedFilename.Truncate(len); + return NtPathToDosPath(mappedFilename, aFilename); +} + +} // namespace mozilla + diff --git a/xpcom/io/FileUtilsWin.h b/xpcom/io/FileUtilsWin.h new file mode 100644 index 0000000000..e32e9fd6ea --- /dev/null +++ b/xpcom/io/FileUtilsWin.h @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_FileUtilsWin_h +#define mozilla_FileUtilsWin_h + +#include <windows.h> + +#include "mozilla/Scoped.h" +#include "nsStringGlue.h" + +namespace mozilla { + +inline bool +EnsureLongPath(nsAString& aDosPath) +{ + uint32_t aDosPathOriginalLen = aDosPath.Length(); + auto inputPath = PromiseFlatString(aDosPath); + // Try to get the long path, or else get the required length of the long path + DWORD longPathLen = GetLongPathNameW(inputPath.get(), + reinterpret_cast<wchar_t*>(aDosPath.BeginWriting()), + aDosPathOriginalLen); + if (longPathLen == 0) { + return false; + } + aDosPath.SetLength(longPathLen); + if (longPathLen <= aDosPathOriginalLen) { + // Our string happened to be long enough for the first call to succeed. + return true; + } + // Now we have a large enough buffer, get the actual string + longPathLen = GetLongPathNameW(inputPath.get(), + reinterpret_cast<wchar_t*>(aDosPath.BeginWriting()), aDosPath.Length()); + if (longPathLen == 0) { + return false; + } + // This success check should always be less-than because longPathLen excludes + // the null terminator on success, but includes it in the first call that + // returned the required size. + if (longPathLen < aDosPath.Length()) { + aDosPath.SetLength(longPathLen); + return true; + } + // We shouldn't reach this, but if we do then it's a failure! + return false; +} + +inline bool +NtPathToDosPath(const nsAString& aNtPath, nsAString& aDosPath) +{ + aDosPath.Truncate(); + if (aNtPath.IsEmpty()) { + return true; + } + NS_NAMED_LITERAL_STRING(symLinkPrefix, "\\??\\"); + uint32_t ntPathLen = aNtPath.Length(); + uint32_t symLinkPrefixLen = symLinkPrefix.Length(); + if (ntPathLen >= 6 && aNtPath.CharAt(5) == L':' && + ntPathLen >= symLinkPrefixLen && + Substring(aNtPath, 0, symLinkPrefixLen).Equals(symLinkPrefix)) { + // Symbolic link for DOS device. Just strip off the prefix. + aDosPath = aNtPath; + aDosPath.Cut(0, 4); + return true; + } + nsAutoString logicalDrives; + DWORD len = 0; + while (true) { + len = GetLogicalDriveStringsW( + len, reinterpret_cast<wchar_t*>(logicalDrives.BeginWriting())); + if (!len) { + return false; + } else if (len > logicalDrives.Length()) { + logicalDrives.SetLength(len); + } else { + break; + } + } + const char16_t* cur = logicalDrives.BeginReading(); + const char16_t* end = logicalDrives.EndReading(); + nsString targetPath; + targetPath.SetLength(MAX_PATH); + wchar_t driveTemplate[] = L" :"; + do { + // Unfortunately QueryDosDevice doesn't support the idiom for querying the + // output buffer size, so it may require retries. + driveTemplate[0] = *cur; + DWORD targetPathLen = 0; + SetLastError(ERROR_SUCCESS); + while (true) { + targetPathLen = QueryDosDeviceW(driveTemplate, + reinterpret_cast<wchar_t*>(targetPath.BeginWriting()), + targetPath.Length()); + if (targetPathLen || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + break; + } + targetPath.SetLength(targetPath.Length() * 2); + } + if (targetPathLen) { + // Need to use wcslen here because targetPath contains embedded NULL chars + size_t firstTargetPathLen = wcslen(targetPath.get()); + const char16_t* pathComponent = aNtPath.BeginReading() + + firstTargetPathLen; + bool found = _wcsnicmp(char16ptr_t(aNtPath.BeginReading()), targetPath.get(), + firstTargetPathLen) == 0 && + *pathComponent == L'\\'; + if (found) { + aDosPath = driveTemplate; + aDosPath += pathComponent; + return EnsureLongPath(aDosPath); + } + } + // Advance to the next NUL character in logicalDrives + while (*cur++); + } while (cur != end); + // Try to handle UNC paths. NB: This must happen after we've checked drive + // mappings in case a UNC path is mapped to a drive! + NS_NAMED_LITERAL_STRING(uncPrefix, "\\\\"); + NS_NAMED_LITERAL_STRING(deviceMupPrefix, "\\Device\\Mup\\"); + if (StringBeginsWith(aNtPath, deviceMupPrefix)) { + aDosPath = uncPrefix; + aDosPath += Substring(aNtPath, deviceMupPrefix.Length()); + return true; + } + NS_NAMED_LITERAL_STRING(deviceLanmanRedirectorPrefix, + "\\Device\\LanmanRedirector\\"); + if (StringBeginsWith(aNtPath, deviceLanmanRedirectorPrefix)) { + aDosPath = uncPrefix; + aDosPath += Substring(aNtPath, deviceLanmanRedirectorPrefix.Length()); + return true; + } + return false; +} + +bool +HandleToFilename(HANDLE aHandle, const LARGE_INTEGER& aOffset, + nsAString& aFilename); + +} // namespace mozilla + +#endif // mozilla_FileUtilsWin_h diff --git a/xpcom/io/SlicedInputStream.cpp b/xpcom/io/SlicedInputStream.cpp new file mode 100644 index 0000000000..7d5fc2b059 --- /dev/null +++ b/xpcom/io/SlicedInputStream.cpp @@ -0,0 +1,209 @@ +/* -*- 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 "SlicedInputStream.h" +#include "nsISeekableStream.h" +#include "nsStreamUtils.h" + +NS_IMPL_ISUPPORTS(SlicedInputStream, nsIInputStream, + nsICloneableInputStream, nsIAsyncInputStream) + +SlicedInputStream::SlicedInputStream(nsIInputStream* aInputStream, + uint64_t aStart, uint64_t aLength) + : mInputStream(aInputStream) + , mStart(aStart) + , mLength(aLength) + , mCurPos(0) + , mClosed(false) +{ + MOZ_ASSERT(aInputStream); +} + +SlicedInputStream::~SlicedInputStream() +{} + +NS_IMETHODIMP +SlicedInputStream::Close() +{ + mClosed = true; + return NS_OK; +} + +// nsIInputStream interface + +NS_IMETHODIMP +SlicedInputStream::Available(uint64_t* aLength) +{ + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv = mInputStream->Available(aLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Let's remove extra length from the end. + if (*aLength + mCurPos > mStart + mLength) { + *aLength -= XPCOM_MIN(*aLength, (*aLength + mCurPos) - (mStart + mLength)); + } + + // Let's remove extra length from the begin. + if (mCurPos < mStart) { + *aLength -= XPCOM_MIN(*aLength, mStart - mCurPos); + } + + return NS_OK; +} + +NS_IMETHODIMP +SlicedInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aReadCount); +} + +NS_IMETHODIMP +SlicedInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t *aResult) +{ + uint32_t result; + + if (!aResult) { + aResult = &result; + } + + *aResult = 0; + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + if (mCurPos < mStart) { + nsCOMPtr<nsISeekableStream> seekableStream = + do_QueryInterface(mInputStream); + if (seekableStream) { + nsresult rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, + mStart); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurPos = mStart; + } else { + char buf[4096]; + while (mCurPos < mStart) { + uint32_t bytesRead; + uint64_t bufCount = XPCOM_MIN(mStart - mCurPos, (uint64_t)sizeof(buf)); + nsresult rv = mInputStream->Read(buf, bufCount, &bytesRead); + if (NS_WARN_IF(NS_FAILED(rv)) || bytesRead == 0) { + return rv; + } + + mCurPos += bytesRead; + } + } + } + + // Let's reduce aCount in case it's too big. + if (mCurPos + aCount > mStart + mLength) { + aCount = mStart + mLength - mCurPos; + } + + char buf[4096]; + while (mCurPos < mStart + mLength && *aResult < aCount) { + uint32_t bytesRead; + uint64_t bufCount = XPCOM_MIN(aCount - *aResult, (uint32_t)sizeof(buf)); + nsresult rv = mInputStream->Read(buf, bufCount, &bytesRead); + if (NS_WARN_IF(NS_FAILED(rv)) || bytesRead == 0) { + return rv; + } + + mCurPos += bytesRead; + + uint32_t bytesWritten = 0; + while (bytesWritten < bytesRead) { + uint32_t writerCount = 0; + rv = aWriter(this, aClosure, buf + bytesWritten, *aResult, + bytesRead - bytesWritten, &writerCount); + if (NS_FAILED(rv) || writerCount == 0) { + return NS_OK; + } + + MOZ_ASSERT(writerCount <= bytesRead - bytesWritten); + bytesWritten += writerCount; + *aResult += writerCount; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +SlicedInputStream::IsNonBlocking(bool* aNonBlocking) +{ + return mInputStream->IsNonBlocking(aNonBlocking); +} + +// nsICloneableInputStream interface + +NS_IMETHODIMP +SlicedInputStream::GetCloneable(bool* aCloneable) +{ + *aCloneable = true; + return NS_OK; +} + +NS_IMETHODIMP +SlicedInputStream::Clone(nsIInputStream** aResult) +{ + nsCOMPtr<nsIInputStream> clonedStream; + nsCOMPtr<nsIInputStream> replacementStream; + + nsresult rv = NS_CloneInputStream(mInputStream, getter_AddRefs(clonedStream), + getter_AddRefs(replacementStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (replacementStream) { + mInputStream = replacementStream.forget(); + } + + nsCOMPtr<nsIInputStream> sis = + new SlicedInputStream(clonedStream, mStart, mLength); + + sis.forget(aResult); + return NS_OK; +} + +// nsIAsyncInputStream interface + +NS_IMETHODIMP +SlicedInputStream::CloseWithStatus(nsresult aStatus) +{ + nsCOMPtr<nsIAsyncInputStream> asyncStream = + do_QueryInterface(mInputStream); + if (!asyncStream) { + return NS_ERROR_FAILURE; + } + + return asyncStream->CloseWithStatus(aStatus); +} + +NS_IMETHODIMP +SlicedInputStream::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) +{ + nsCOMPtr<nsIAsyncInputStream> asyncStream = + do_QueryInterface(mInputStream); + if (!asyncStream) { + return NS_ERROR_FAILURE; + } + + return asyncStream->AsyncWait(aCallback, aFlags, aRequestedCount, + aEventTarget); +} diff --git a/xpcom/io/SlicedInputStream.h b/xpcom/io/SlicedInputStream.h new file mode 100644 index 0000000000..6c38fc39f2 --- /dev/null +++ b/xpcom/io/SlicedInputStream.h @@ -0,0 +1,50 @@ +/* -*- 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 SlicedInputStream_h +#define SlicedInputStream_h + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsICloneableInputStream.h" + +// A wrapper for a slice of an underlying input stream. + +class SlicedInputStream final : public nsIAsyncInputStream + , public nsICloneableInputStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + // Create an input stream whose data comes from a slice of aInputStream. The + // slice begins at aStart bytes beyond aInputStream's current position, and + // extends for a maximum of aLength bytes. If aInputStream contains fewer + // than aStart bytes, reading from SlicedInputStream returns no data. If + // aInputStream contains more than aStart bytes, but fewer than aStart + + // aLength bytes, reading from SlicedInputStream returns as many bytes as can + // be consumed from aInputStream after reading aLength bytes. + // + // aInputStream should not be read from after constructing a + // SlicedInputStream wrapper around it. + + SlicedInputStream(nsIInputStream* aInputStream, + uint64_t aStart, uint64_t aLength); + +private: + ~SlicedInputStream(); + + nsCOMPtr<nsIInputStream> mInputStream; + uint64_t mStart; + uint64_t mLength; + uint64_t mCurPos; + + bool mClosed; +}; + +#endif // SlicedInputStream_h diff --git a/xpcom/io/SnappyCompressOutputStream.cpp b/xpcom/io/SnappyCompressOutputStream.cpp new file mode 100644 index 0000000000..89b8a08081 --- /dev/null +++ b/xpcom/io/SnappyCompressOutputStream.cpp @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/SnappyCompressOutputStream.h" + +#include <algorithm> +#include "nsStreamUtils.h" +#include "snappy/snappy.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(SnappyCompressOutputStream, nsIOutputStream); + +// static +const size_t +SnappyCompressOutputStream::kMaxBlockSize = snappy::kBlockSize; + +SnappyCompressOutputStream::SnappyCompressOutputStream(nsIOutputStream* aBaseStream, + size_t aBlockSize) + : mBaseStream(aBaseStream) + , mBlockSize(std::min(aBlockSize, kMaxBlockSize)) + , mNextByte(0) + , mCompressedBufferLength(0) + , mStreamIdentifierWritten(false) +{ + MOZ_ASSERT(mBlockSize > 0); + + // This implementation only supports sync base streams. Verify this in debug + // builds. Note, this can be simpler than the check in + // SnappyUncompressInputStream because we don't have to deal with the + // nsStringInputStream oddness of being non-blocking and sync. +#ifdef DEBUG + bool baseNonBlocking; + nsresult rv = mBaseStream->IsNonBlocking(&baseNonBlocking); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + MOZ_ASSERT(!baseNonBlocking); +#endif +} + +size_t +SnappyCompressOutputStream::BlockSize() const +{ + return mBlockSize; +} + +NS_IMETHODIMP +SnappyCompressOutputStream::Close() +{ + if (!mBaseStream) { + return NS_OK; + } + + nsresult rv = Flush(); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + mBaseStream->Close(); + mBaseStream = nullptr; + + mBuffer = nullptr; + mCompressedBuffer = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +SnappyCompressOutputStream::Flush() +{ + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv = FlushToBaseStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + mBaseStream->Flush(); + + return NS_OK; +} + +NS_IMETHODIMP +SnappyCompressOutputStream::Write(const char* aBuf, uint32_t aCount, + uint32_t* aResultOut) +{ + return WriteSegments(NS_CopySegmentToBuffer, const_cast<char*>(aBuf), aCount, + aResultOut); +} + +NS_IMETHODIMP +SnappyCompressOutputStream::WriteFrom(nsIInputStream*, uint32_t, uint32_t*) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +SnappyCompressOutputStream::WriteSegments(nsReadSegmentFun aReader, + void* aClosure, + uint32_t aCount, + uint32_t* aBytesWrittenOut) +{ + *aBytesWrittenOut = 0; + + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + if (!mBuffer) { + mBuffer.reset(new (fallible) char[mBlockSize]); + if (NS_WARN_IF(!mBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + while (aCount > 0) { + // Determine how much space is left in our flat, uncompressed buffer. + MOZ_ASSERT(mNextByte <= mBlockSize); + uint32_t remaining = mBlockSize - mNextByte; + + // If it is full, then compress and flush the data to the base stream. + if (remaining == 0) { + nsresult rv = FlushToBaseStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + // Now the entire buffer should be available for copying. + MOZ_ASSERT(!mNextByte); + remaining = mBlockSize; + } + + uint32_t numToRead = std::min(remaining, aCount); + uint32_t numRead = 0; + + nsresult rv = aReader(this, aClosure, &mBuffer[mNextByte], + *aBytesWrittenOut, numToRead, &numRead); + + // As defined in nsIOutputStream.idl, do not pass reader func errors. + if (NS_FAILED(rv)) { + return NS_OK; + } + + // End-of-file + if (numRead == 0) { + return NS_OK; + } + + mNextByte += numRead; + *aBytesWrittenOut += numRead; + aCount -= numRead; + } + + return NS_OK; +} + +NS_IMETHODIMP +SnappyCompressOutputStream::IsNonBlocking(bool* aNonBlockingOut) +{ + *aNonBlockingOut = false; + return NS_OK; +} + +SnappyCompressOutputStream::~SnappyCompressOutputStream() +{ + Close(); +} + +nsresult +SnappyCompressOutputStream::FlushToBaseStream() +{ + MOZ_ASSERT(mBaseStream); + + // Lazily create the compressed buffer on our first flush. This + // allows us to report OOM during stream operation. This buffer + // will then get re-used until the stream is closed. + if (!mCompressedBuffer) { + mCompressedBufferLength = MaxCompressedBufferLength(mBlockSize); + mCompressedBuffer.reset(new (fallible) char[mCompressedBufferLength]); + if (NS_WARN_IF(!mCompressedBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + // The first chunk must be a StreamIdentifier chunk. Write it out + // if we have not done so already. + nsresult rv = MaybeFlushStreamIdentifier(); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + // Compress the data to our internal compressed buffer. + size_t compressedLength; + rv = WriteCompressedData(mCompressedBuffer.get(), mCompressedBufferLength, + mBuffer.get(), mNextByte, &compressedLength); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + MOZ_ASSERT(compressedLength > 0); + + mNextByte = 0; + + // Write the compressed buffer out to the base stream. + uint32_t numWritten = 0; + rv = WriteAll(mCompressedBuffer.get(), compressedLength, &numWritten); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + MOZ_ASSERT(compressedLength == numWritten); + + return NS_OK; +} + +nsresult +SnappyCompressOutputStream::MaybeFlushStreamIdentifier() +{ + MOZ_ASSERT(mCompressedBuffer); + + if (mStreamIdentifierWritten) { + return NS_OK; + } + + // Build the StreamIdentifier in our compressed buffer. + size_t compressedLength; + nsresult rv = WriteStreamIdentifier(mCompressedBuffer.get(), + mCompressedBufferLength, + &compressedLength); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + // Write the compressed buffer out to the base stream. + uint32_t numWritten = 0; + rv = WriteAll(mCompressedBuffer.get(), compressedLength, &numWritten); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + MOZ_ASSERT(compressedLength == numWritten); + + mStreamIdentifierWritten = true; + + return NS_OK; +} + +nsresult +SnappyCompressOutputStream::WriteAll(const char* aBuf, uint32_t aCount, + uint32_t* aBytesWrittenOut) +{ + *aBytesWrittenOut = 0; + + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + uint32_t offset = 0; + while (aCount > 0) { + uint32_t numWritten = 0; + nsresult rv = mBaseStream->Write(aBuf + offset, aCount, &numWritten); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + offset += numWritten; + aCount -= numWritten; + *aBytesWrittenOut += numWritten; + } + + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/io/SnappyCompressOutputStream.h b/xpcom/io/SnappyCompressOutputStream.h new file mode 100644 index 0000000000..36c47e66e3 --- /dev/null +++ b/xpcom/io/SnappyCompressOutputStream.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_SnappyCompressOutputStream_h__ +#define mozilla_SnappyCompressOutputStream_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsIOutputStream.h" +#include "nsISupportsImpl.h" +#include "SnappyFrameUtils.h" + +namespace mozilla { + +class SnappyCompressOutputStream final : public nsIOutputStream + , protected detail::SnappyFrameUtils +{ +public: + // Maximum compression block size. + static const size_t kMaxBlockSize; + + // Construct a new blocking output stream to compress data to + // the given base stream. The base stream must also be blocking. + // The compression block size may optionally be set to a value + // up to kMaxBlockSize. + explicit SnappyCompressOutputStream(nsIOutputStream* aBaseStream, + size_t aBlockSize = kMaxBlockSize); + + // The compression block size. To optimize stream performance + // try to write to the stream in segments at least this size. + size_t BlockSize() const; + +private: + virtual ~SnappyCompressOutputStream(); + + nsresult FlushToBaseStream(); + nsresult MaybeFlushStreamIdentifier(); + nsresult WriteAll(const char* aBuf, uint32_t aCount, + uint32_t* aBytesWrittenOut); + + nsCOMPtr<nsIOutputStream> mBaseStream; + const size_t mBlockSize; + + // Buffer holding copied uncompressed data. This must be copied here + // so that the compression can be performed on a single flat buffer. + mozilla::UniquePtr<char[]> mBuffer; + + // The next byte in the uncompressed data to copy incoming data to. + size_t mNextByte; + + // Buffer holding the resulting compressed data. + mozilla::UniquePtr<char[]> mCompressedBuffer; + size_t mCompressedBufferLength; + + // The first thing written to the stream must be a stream identifier. + bool mStreamIdentifierWritten; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM +}; + +} // namespace mozilla + +#endif // mozilla_SnappyCompressOutputStream_h__ diff --git a/xpcom/io/SnappyFrameUtils.cpp b/xpcom/io/SnappyFrameUtils.cpp new file mode 100644 index 0000000000..97883a3629 --- /dev/null +++ b/xpcom/io/SnappyFrameUtils.cpp @@ -0,0 +1,258 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/SnappyFrameUtils.h" + +#include "crc32c.h" +#include "mozilla/EndianUtils.h" +#include "nsDebug.h" +#include "snappy/snappy.h" + +namespace { + +using mozilla::detail::SnappyFrameUtils; +using mozilla::NativeEndian; + +SnappyFrameUtils::ChunkType ReadChunkType(uint8_t aByte) +{ + if (aByte == 0xff) { + return SnappyFrameUtils::StreamIdentifier; + } else if (aByte == 0x00) { + return SnappyFrameUtils::CompressedData; + } else if (aByte == 0x01) { + return SnappyFrameUtils::UncompressedData; + } else if (aByte == 0xfe) { + return SnappyFrameUtils::Padding; + } + + return SnappyFrameUtils::Reserved; +} + +void WriteChunkType(char* aDest, SnappyFrameUtils::ChunkType aType) +{ + unsigned char* dest = reinterpret_cast<unsigned char*>(aDest); + if (aType == SnappyFrameUtils::StreamIdentifier) { + *dest = 0xff; + } else if (aType == SnappyFrameUtils::CompressedData) { + *dest = 0x00; + } else if (aType == SnappyFrameUtils::UncompressedData) { + *dest = 0x01; + } else if (aType == SnappyFrameUtils::Padding) { + *dest = 0xfe; + } else { + *dest = 0x02; + } +} + +void WriteUInt24(char* aBuf, uint32_t aVal) +{ + MOZ_ASSERT(!(aVal & 0xff000000)); + uint32_t tmp = NativeEndian::swapToLittleEndian(aVal); + memcpy(aBuf, &tmp, 3); +} + +uint32_t ReadUInt24(const char* aBuf) +{ + uint32_t val = 0; + memcpy(&val, aBuf, 3); + return NativeEndian::swapFromLittleEndian(val); +} + +// This mask is explicitly defined in the snappy framing_format.txt file. +uint32_t MaskChecksum(uint32_t aValue) +{ + return ((aValue >> 15) | (aValue << 17)) + 0xa282ead8; +} + +} // namespace + +namespace mozilla { +namespace detail { + +using mozilla::LittleEndian; + +// static +nsresult +SnappyFrameUtils::WriteStreamIdentifier(char* aDest, size_t aDestLength, + size_t* aBytesWrittenOut) +{ + if (NS_WARN_IF(aDestLength < (kHeaderLength + kStreamIdentifierDataLength))) { + return NS_ERROR_NOT_AVAILABLE; + } + + WriteChunkType(aDest, StreamIdentifier); + aDest[1] = 0x06; // Data length + aDest[2] = 0x00; + aDest[3] = 0x00; + aDest[4] = 0x73; // "sNaPpY" + aDest[5] = 0x4e; + aDest[6] = 0x61; + aDest[7] = 0x50; + aDest[8] = 0x70; + aDest[9] = 0x59; + + static_assert(kHeaderLength + kStreamIdentifierDataLength == 10, + "StreamIdentifier chunk should be exactly 10 bytes long"); + *aBytesWrittenOut = kHeaderLength + kStreamIdentifierDataLength; + + return NS_OK; +} + +// static +nsresult +SnappyFrameUtils::WriteCompressedData(char* aDest, size_t aDestLength, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut) +{ + *aBytesWrittenOut = 0; + + size_t neededLength = MaxCompressedBufferLength(aDataLength); + if (NS_WARN_IF(aDestLength < neededLength)) { + return NS_ERROR_NOT_AVAILABLE; + } + + size_t offset = 0; + + WriteChunkType(aDest, CompressedData); + offset += kChunkTypeLength; + + // Skip length for now and write it out after we know the compressed length. + size_t lengthOffset = offset; + offset += kChunkLengthLength; + + uint32_t crc = ComputeCrc32c(~0, reinterpret_cast<const unsigned char*>(aData), + aDataLength); + uint32_t maskedCrc = MaskChecksum(crc); + LittleEndian::writeUint32(aDest + offset, maskedCrc); + offset += kCRCLength; + + size_t compressedLength; + snappy::RawCompress(aData, aDataLength, aDest + offset, &compressedLength); + + // Go back and write the data length. + size_t dataLength = compressedLength + kCRCLength; + WriteUInt24(aDest + lengthOffset, dataLength); + + *aBytesWrittenOut = kHeaderLength + dataLength; + + return NS_OK; +} + +// static +nsresult +SnappyFrameUtils::ParseHeader(const char* aSource, size_t aSourceLength, + ChunkType* aTypeOut, size_t* aDataLengthOut) +{ + if (NS_WARN_IF(aSourceLength < kHeaderLength)) { + return NS_ERROR_NOT_AVAILABLE; + } + + *aTypeOut = ReadChunkType(aSource[0]); + *aDataLengthOut = ReadUInt24(aSource + kChunkTypeLength); + + return NS_OK; +} + +// static +nsresult +SnappyFrameUtils::ParseData(char* aDest, size_t aDestLength, + ChunkType aType, const char* aData, + size_t aDataLength, + size_t* aBytesWrittenOut, size_t* aBytesReadOut) +{ + switch(aType) { + case StreamIdentifier: + return ParseStreamIdentifier(aDest, aDestLength, aData, aDataLength, + aBytesWrittenOut, aBytesReadOut); + + case CompressedData: + return ParseCompressedData(aDest, aDestLength, aData, aDataLength, + aBytesWrittenOut, aBytesReadOut); + + // TODO: support other snappy chunk types + default: + MOZ_ASSERT_UNREACHABLE("Unsupported snappy framing chunk type."); + return NS_ERROR_NOT_IMPLEMENTED; + } +} + +// static +nsresult +SnappyFrameUtils::ParseStreamIdentifier(char*, size_t, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut, + size_t* aBytesReadOut) +{ + *aBytesWrittenOut = 0; + *aBytesReadOut = 0; + if (NS_WARN_IF(aDataLength != kStreamIdentifierDataLength || + aData[0] != 0x73 || + aData[1] != 0x4e || + aData[2] != 0x61 || + aData[3] != 0x50 || + aData[4] != 0x70 || + aData[5] != 0x59)) { + return NS_ERROR_CORRUPTED_CONTENT; + } + *aBytesReadOut = aDataLength; + return NS_OK; +} + +// static +nsresult +SnappyFrameUtils::ParseCompressedData(char* aDest, size_t aDestLength, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut, + size_t* aBytesReadOut) +{ + *aBytesWrittenOut = 0; + *aBytesReadOut = 0; + size_t offset = 0; + + uint32_t readCrc = LittleEndian::readUint32(aData + offset); + offset += kCRCLength; + + size_t uncompressedLength; + if (NS_WARN_IF(!snappy::GetUncompressedLength(aData + offset, + aDataLength - offset, + &uncompressedLength))) { + return NS_ERROR_CORRUPTED_CONTENT; + } + + if (NS_WARN_IF(aDestLength < uncompressedLength)) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (NS_WARN_IF(!snappy::RawUncompress(aData + offset, aDataLength - offset, + aDest))) { + return NS_ERROR_CORRUPTED_CONTENT; + } + + uint32_t crc = ComputeCrc32c(~0, reinterpret_cast<const unsigned char*>(aDest), + uncompressedLength); + uint32_t maskedCrc = MaskChecksum(crc); + if (NS_WARN_IF(readCrc != maskedCrc)) { + return NS_ERROR_CORRUPTED_CONTENT; + } + + *aBytesWrittenOut = uncompressedLength; + *aBytesReadOut = aDataLength; + + return NS_OK; +} + +// static +size_t +SnappyFrameUtils::MaxCompressedBufferLength(size_t aSourceLength) +{ + size_t neededLength = kHeaderLength; + neededLength += kCRCLength; + neededLength += snappy::MaxCompressedLength(aSourceLength); + return neededLength; +} + +} // namespace detail +} // namespace mozilla diff --git a/xpcom/io/SnappyFrameUtils.h b/xpcom/io/SnappyFrameUtils.h new file mode 100644 index 0000000000..41479b14d6 --- /dev/null +++ b/xpcom/io/SnappyFrameUtils.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_SnappyFrameUtils_h__ +#define mozilla_SnappyFrameUtils_h__ + +#include "mozilla/Attributes.h" +#include "nsError.h" + +namespace mozilla { +namespace detail { + +// +// Utility class providing primitives necessary to build streams based +// on the snappy compressor. This essentially abstracts the framing format +// defined in: +// +// other-licences/snappy/src/framing_format.txt +// +// NOTE: Currently only the StreamIdentifier and CompressedData chunks are +// supported. +// +class SnappyFrameUtils +{ +public: + enum ChunkType + { + Unknown, + StreamIdentifier, + CompressedData, + UncompressedData, + Padding, + Reserved, + ChunkTypeCount + }; + + static const size_t kChunkTypeLength = 1; + static const size_t kChunkLengthLength = 3; + static const size_t kHeaderLength = kChunkTypeLength + kChunkLengthLength; + static const size_t kStreamIdentifierDataLength = 6; + static const size_t kCRCLength = 4; + + static nsresult + WriteStreamIdentifier(char* aDest, size_t aDestLength, + size_t* aBytesWrittenOut); + + static nsresult + WriteCompressedData(char* aDest, size_t aDestLength, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut); + + static nsresult + ParseHeader(const char* aSource, size_t aSourceLength, ChunkType* aTypeOut, + size_t* aDataLengthOut); + + static nsresult + ParseData(char* aDest, size_t aDestLength, + ChunkType aType, const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut, size_t* aBytesReadOut); + + static nsresult + ParseStreamIdentifier(char* aDest, size_t aDestLength, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut, size_t* aBytesReadOut); + + static nsresult + ParseCompressedData(char* aDest, size_t aDestLength, + const char* aData, size_t aDataLength, + size_t* aBytesWrittenOut, size_t* aBytesReadOut); + + static size_t + MaxCompressedBufferLength(size_t aSourceLength); + +protected: + SnappyFrameUtils() { } + virtual ~SnappyFrameUtils() { } +}; + +} // namespace detail +} // namespace mozilla + +#endif // mozilla_SnappyFrameUtils_h__ diff --git a/xpcom/io/SnappyUncompressInputStream.cpp b/xpcom/io/SnappyUncompressInputStream.cpp new file mode 100644 index 0000000000..e0a1b6f4cd --- /dev/null +++ b/xpcom/io/SnappyUncompressInputStream.cpp @@ -0,0 +1,362 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/SnappyUncompressInputStream.h" + +#include <algorithm> +#include "nsIAsyncInputStream.h" +#include "nsStreamUtils.h" +#include "snappy/snappy.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(SnappyUncompressInputStream, + nsIInputStream); + +// Putting kCompressedBufferLength inside a function avoids a static +// constructor. +static size_t CompressedBufferLength() +{ + static size_t kCompressedBufferLength = + detail::SnappyFrameUtils::MaxCompressedBufferLength(snappy::kBlockSize); + + MOZ_ASSERT(kCompressedBufferLength > 0); + return kCompressedBufferLength; +} + +SnappyUncompressInputStream::SnappyUncompressInputStream(nsIInputStream* aBaseStream) + : mBaseStream(aBaseStream) + , mUncompressedBytes(0) + , mNextByte(0) + , mNextChunkType(Unknown) + , mNextChunkDataLength(0) + , mNeedFirstStreamIdentifier(true) +{ + // This implementation only supports sync base streams. Verify this in debug + // builds. Note, this is a bit complicated because the streams we support + // advertise different capabilities: + // - nsFileInputStream - blocking and sync + // - nsStringInputStream - non-blocking and sync + // - nsPipeInputStream - can be blocking, but provides async interface +#ifdef DEBUG + bool baseNonBlocking; + nsresult rv = mBaseStream->IsNonBlocking(&baseNonBlocking); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (baseNonBlocking) { + nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(mBaseStream); + MOZ_ASSERT(!async); + } +#endif +} + +NS_IMETHODIMP +SnappyUncompressInputStream::Close() +{ + if (!mBaseStream) { + return NS_OK; + } + + mBaseStream->Close(); + mBaseStream = nullptr; + + mUncompressedBuffer = nullptr; + mCompressedBuffer = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +SnappyUncompressInputStream::Available(uint64_t* aLengthOut) +{ + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + // If we have uncompressed bytes, then we are done. + *aLengthOut = UncompressedLength(); + if (*aLengthOut > 0) { + return NS_OK; + } + + // Otherwise, attempt to uncompress bytes until we get something or the + // underlying stream is drained. We loop here because some chunks can + // be StreamIdentifiers, padding, etc with no data. + uint32_t bytesRead; + do { + nsresult rv = ParseNextChunk(&bytesRead); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + *aLengthOut = UncompressedLength(); + } while(*aLengthOut == 0 && bytesRead); + + return NS_OK; +} + +NS_IMETHODIMP +SnappyUncompressInputStream::Read(char* aBuf, uint32_t aCount, + uint32_t* aBytesReadOut) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aBytesReadOut); +} + +NS_IMETHODIMP +SnappyUncompressInputStream::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* aBytesReadOut) +{ + *aBytesReadOut = 0; + + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv; + + // Do not try to use the base stream's ReadSegements here. Its very + // unlikely we will get a single buffer that contains all of the compressed + // data and therefore would have to copy into our own buffer anyways. + // Instead, focus on making efficient use of the Read() interface. + + while (aCount > 0) { + // We have some decompressed data in our buffer. Provide it to the + // callers writer function. + if (mUncompressedBytes > 0) { + MOZ_ASSERT(mUncompressedBuffer); + uint32_t remaining = UncompressedLength(); + uint32_t numToWrite = std::min(aCount, remaining); + uint32_t numWritten; + rv = aWriter(this, aClosure, &mUncompressedBuffer[mNextByte], *aBytesReadOut, + numToWrite, &numWritten); + + // As defined in nsIInputputStream.idl, do not pass writer func errors. + if (NS_FAILED(rv)) { + return NS_OK; + } + + // End-of-file + if (numWritten == 0) { + return NS_OK; + } + + *aBytesReadOut += numWritten; + mNextByte += numWritten; + MOZ_ASSERT(mNextByte <= mUncompressedBytes); + + if (mNextByte == mUncompressedBytes) { + mNextByte = 0; + mUncompressedBytes = 0; + } + + aCount -= numWritten; + + continue; + } + + // Otherwise uncompress the next chunk and loop. Any resulting data + // will set mUncompressedBytes which we check at the top of the loop. + uint32_t bytesRead; + rv = ParseNextChunk(&bytesRead); + if (NS_FAILED(rv)) { return rv; } + + // If we couldn't read anything and there is no more data to provide + // to the caller, then this is eof. + if (bytesRead == 0 && mUncompressedBytes == 0) { + return NS_OK; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +SnappyUncompressInputStream::IsNonBlocking(bool* aNonBlockingOut) +{ + *aNonBlockingOut = false; + return NS_OK; +} + +SnappyUncompressInputStream::~SnappyUncompressInputStream() +{ + Close(); +} + +nsresult +SnappyUncompressInputStream::ParseNextChunk(uint32_t* aBytesReadOut) +{ + // There must not be any uncompressed data already in mUncompressedBuffer. + MOZ_ASSERT(mUncompressedBytes == 0); + MOZ_ASSERT(mNextByte == 0); + + nsresult rv; + *aBytesReadOut = 0; + + // Lazily create our two buffers so we can report OOM during stream + // operation. These allocations only happens once. The buffers are reused + // until the stream is closed. + if (!mUncompressedBuffer) { + mUncompressedBuffer.reset(new (fallible) char[snappy::kBlockSize]); + if (NS_WARN_IF(!mUncompressedBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + if (!mCompressedBuffer) { + mCompressedBuffer.reset(new (fallible) char[CompressedBufferLength()]); + if (NS_WARN_IF(!mCompressedBuffer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + // We have no decompressed data and we also have not seen the start of stream + // yet. Read and validate the StreamIdentifier chunk. Also read the next + // header to determine the size of the first real data chunk. + if (mNeedFirstStreamIdentifier) { + const uint32_t firstReadLength = kHeaderLength + + kStreamIdentifierDataLength + + kHeaderLength; + MOZ_ASSERT(firstReadLength <= CompressedBufferLength()); + + rv = ReadAll(mCompressedBuffer.get(), firstReadLength, firstReadLength, + aBytesReadOut); + if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; } + + rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength, + &mNextChunkType, &mNextChunkDataLength); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + if (NS_WARN_IF(mNextChunkType != StreamIdentifier || + mNextChunkDataLength != kStreamIdentifierDataLength)) { + return NS_ERROR_CORRUPTED_CONTENT; + } + size_t offset = kHeaderLength; + + mNeedFirstStreamIdentifier = false; + + size_t numRead; + size_t numWritten; + rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, mNextChunkType, + &mCompressedBuffer[offset], + mNextChunkDataLength, &numWritten, &numRead); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + MOZ_ASSERT(numWritten == 0); + MOZ_ASSERT(numRead == mNextChunkDataLength); + offset += numRead; + + rv = ParseHeader(&mCompressedBuffer[offset], *aBytesReadOut - offset, + &mNextChunkType, &mNextChunkDataLength); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return NS_OK; + } + + // We have no compressed data and we don't know how big the next chunk is. + // This happens when we get an EOF pause in the middle of a stream and also + // at the end of the stream. Simply read the next header and return. The + // chunk body will be read on the next entry into this method. + if (mNextChunkType == Unknown) { + rv = ReadAll(mCompressedBuffer.get(), kHeaderLength, kHeaderLength, + aBytesReadOut); + if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; } + + rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength, + &mNextChunkType, &mNextChunkDataLength); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return NS_OK; + } + + // We have no decompressed data, but we do know the size of the next chunk. + // Read at least that much from the base stream. + uint32_t readLength = mNextChunkDataLength; + MOZ_ASSERT(readLength <= CompressedBufferLength()); + + // However, if there is enough data in the base stream, also read the next + // chunk header. This helps optimize the stream by avoiding many small reads. + uint64_t avail; + rv = mBaseStream->Available(&avail); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + if (avail >= (readLength + kHeaderLength)) { + readLength += kHeaderLength; + MOZ_ASSERT(readLength <= CompressedBufferLength()); + } + + rv = ReadAll(mCompressedBuffer.get(), readLength, mNextChunkDataLength, + aBytesReadOut); + if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; } + + size_t numRead; + size_t numWritten; + rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, mNextChunkType, + mCompressedBuffer.get(), mNextChunkDataLength, + &numWritten, &numRead); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + MOZ_ASSERT(numRead == mNextChunkDataLength); + + mUncompressedBytes = numWritten; + + // If we were unable to directly read the next chunk header, then clear + // our internal state. We will have to perform a small read to get the + // header the next time we enter this method. + if (*aBytesReadOut <= mNextChunkDataLength) { + mNextChunkType = Unknown; + mNextChunkDataLength = 0; + return NS_OK; + } + + // We got the next chunk header. Parse it so that we are ready to for the + // next call into this method. + rv = ParseHeader(&mCompressedBuffer[numRead], *aBytesReadOut - numRead, + &mNextChunkType, &mNextChunkDataLength); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return NS_OK; +} + +nsresult +SnappyUncompressInputStream::ReadAll(char* aBuf, uint32_t aCount, + uint32_t aMinValidCount, + uint32_t* aBytesReadOut) +{ + MOZ_ASSERT(aCount >= aMinValidCount); + + *aBytesReadOut = 0; + + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + uint32_t offset = 0; + while (aCount > 0) { + uint32_t bytesRead = 0; + nsresult rv = mBaseStream->Read(aBuf + offset, aCount, &bytesRead); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + // EOF, but don't immediately return. We need to validate min read bytes + // below. + if (bytesRead == 0) { + break; + } + + *aBytesReadOut += bytesRead; + offset += bytesRead; + aCount -= bytesRead; + } + + // Reading zero bytes is not an error. Its the expected EOF condition. + // Only compare to the minimum valid count if we read at least one byte. + if (*aBytesReadOut != 0 && *aBytesReadOut < aMinValidCount) { + return NS_ERROR_CORRUPTED_CONTENT; + } + + return NS_OK; +} + +size_t +SnappyUncompressInputStream::UncompressedLength() const +{ + MOZ_ASSERT(mNextByte <= mUncompressedBytes); + return mUncompressedBytes - mNextByte; +} + +} // namespace mozilla diff --git a/xpcom/io/SnappyUncompressInputStream.h b/xpcom/io/SnappyUncompressInputStream.h new file mode 100644 index 0000000000..0d24c0d115 --- /dev/null +++ b/xpcom/io/SnappyUncompressInputStream.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_SnappyUncompressInputStream_h__ +#define mozilla_SnappyUncompressInputStream_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsISupportsImpl.h" +#include "SnappyFrameUtils.h" + +namespace mozilla { + +class SnappyUncompressInputStream final : public nsIInputStream + , protected detail::SnappyFrameUtils +{ +public: + // Construct a new blocking stream to uncompress the given base stream. The + // base stream must also be blocking. The base stream does not have to be + // buffered. + explicit SnappyUncompressInputStream(nsIInputStream* aBaseStream); + +private: + virtual ~SnappyUncompressInputStream(); + + // Parse the next chunk of data. This may populate mBuffer and set + // mBufferFillSize. This should not be called when mBuffer already + // contains data. + nsresult ParseNextChunk(uint32_t* aBytesReadOut); + + // Convenience routine to Read() from the base stream until we get + // the given number of bytes or reach EOF. + // + // aBuf - The buffer to write the bytes into. + // aCount - Max number of bytes to read. If the stream closes + // fewer bytes my be read. + // aMinValidCount - A minimum expected number of bytes. If we find + // fewer than this many bytes, then return + // NS_ERROR_CORRUPTED_CONTENT. If nothing was read due + // due to EOF (aBytesReadOut == 0), then NS_OK is returned. + // aBytesReadOut - An out parameter indicating how many bytes were read. + nsresult ReadAll(char* aBuf, uint32_t aCount, uint32_t aMinValidCount, + uint32_t* aBytesReadOut); + + // Convenience routine to determine how many bytes of uncompressed data + // we currently have in our buffer. + size_t UncompressedLength() const; + + nsCOMPtr<nsIInputStream> mBaseStream; + + // Buffer to hold compressed data. Must copy here since we need a large + // flat buffer to run the uncompress process on. Always the same length + // of SnappyFrameUtils::MaxCompressedBufferLength(snappy::kBlockSize) + // bytes long. + mozilla::UniquePtr<char[]> mCompressedBuffer; + + // Buffer storing the resulting uncompressed data. Exactly snappy::kBlockSize + // bytes long. + mozilla::UniquePtr<char[]> mUncompressedBuffer; + + // Number of bytes of uncompressed data in mBuffer. + size_t mUncompressedBytes; + + // Next byte of mBuffer to return in ReadSegments(). Must be less than + // mBufferFillSize + size_t mNextByte; + + // Next chunk in the stream that has been parsed during read-ahead. + ChunkType mNextChunkType; + + // Length of next chunk's length that has been determined during read-ahead. + size_t mNextChunkDataLength; + + // The stream must begin with a StreamIdentifier chunk. Are we still + // expecting it? + bool mNeedFirstStreamIdentifier; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM +}; + +} // namespace mozilla + +#endif // mozilla_SnappyUncompressInputStream_h__ diff --git a/xpcom/io/SpecialSystemDirectory.cpp b/xpcom/io/SpecialSystemDirectory.cpp new file mode 100644 index 0000000000..9ce8eb85b3 --- /dev/null +++ b/xpcom/io/SpecialSystemDirectory.cpp @@ -0,0 +1,830 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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> +#include "mozilla/WindowsVersion.h" + +using mozilla::IsWin7OrLater; + +#elif defined(XP_UNIX) + +#include <limits.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/param.h> +#include "prenv.h" +#if defined(MOZ_WIDGET_COCOA) +#include "CocoaFileUtils.h" +#endif + +#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 = nullptr; +#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* aGuid, nsIFile** aFile) +{ + if (!aGuid || !gGetKnownFolderPath) { + return NS_ERROR_FAILURE; + } + + PWSTR path = nullptr; + gGetKnownFolderPath(*aGuid, 0, nullptr, &path); + + if (!path) { + return NS_ERROR_FAILURE; + } + + nsresult rv = NS_NewLocalFile(nsDependentString(path), + true, + aFile); + + CoTaskMemFree(path); + return rv; +} + +static nsresult +GetWindowsFolder(int aFolder, nsIFile** aFile) +{ + WCHAR path_orig[MAX_PATH + 3]; + WCHAR* path = path_orig + 1; + HRESULT result = SHGetSpecialFolderPathW(nullptr, path, aFolder, 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 = nullptr; + IShellLibrary* plib; + HRESULT hr = CoCreateInstance(CLSID_ShellLibrary, nullptr, + 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. + if (!IsWin7OrLater()) { + return GetWindowsFolder(aFallbackFolderId, aFile); + } + + RefPtr<IShellLibrary> shellLib; + RefPtr<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)))) { + wchar_t* str = nullptr; + if (SUCCEEDED(savePath->GetDisplayName(SIGDN_FILESYSPATH, &str))) { + nsAutoString path; + path.Assign(str); + path.Append('\\'); + 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"), + nullptr, &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) +{ +#if 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* aType) +{ + FILE* file; + char* home_dir; + char* config_home; + char* config_file; + char buffer[512]; + char* user_dir; + char* p; + char* d; + int len; + int relative; + + home_dir = getenv("HOME"); + + if (!home_dir) { + goto error; + } + + config_home = getenv("XDG_CONFIG_HOME"); + if (!config_home || config_home[0] == 0) { + config_file = (char*)malloc(strlen(home_dir) + + strlen("/.config/user-dirs.dirs") + 1); + if (!config_file) { + 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) { + goto error; + } + + strcpy(config_file, config_home); + strcat(config_file, "/user-dirs.dirs"); + } + + file = fopen(config_file, "r"); + free(config_file); + if (!file) { + goto error; + } + + user_dir = nullptr; + 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, aType, strlen(aType)) != 0) { + continue; + } + p += strlen(aType); + 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) { + goto error2; + } + + strcpy(user_dir, home_dir); + strcat(user_dir, "/"); + } else { + user_dir = (char*)malloc(strlen(p) + 1); + if (!user_dir) { + 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 nullptr; +} + +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")); + } 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); +#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] == char16_t(':') && path[2] == char16_t('\\')) { + path[3] = 0; + } + + return NS_NewLocalFile(nsDependentString(path), + 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(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; + } +#if defined(MOZ_CONTENT_SANDBOX) + case Win_LocalAppdataLow: { + // This should only really fail on versions pre-Vista, in which case this + // shouldn't have been used in the first place. + GUID localAppDataLowGuid = FOLDERID_LocalAppDataLow; + return GetKnownFolder(&localAppDataLowGuid, aFile); + } +#endif + 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 + + default: + break; + } + return NS_ERROR_NOT_AVAILABLE; +} + +#if defined (MOZ_WIDGET_COCOA) +nsresult +GetOSXFolderType(short aDomain, OSType aFolderType, nsIFile** aLocalFile) +{ + nsresult rv = NS_ERROR_FAILURE; + + if (aFolderType == kTemporaryFolderType) { + NS_NewLocalFile(EmptyString(), true, aLocalFile); + nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*aLocalFile)); + if (localMacFile) { + rv = localMacFile->InitWithCFURL( + CocoaFileUtils::GetTemporaryFolderCFURLRef()); + } + return rv; + } + + OSErr err; + FSRef fsRef; + err = ::FSFindFolder(aDomain, aFolderType, kCreateFolder, &fsRef); + if (err == noErr) { + NS_NewLocalFile(EmptyString(), true, aLocalFile); + nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*aLocalFile)); + 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 0000000000..dd3d883795 --- /dev/null +++ b/xpcom/io/SpecialSystemDirectory.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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, +#if defined(MOZ_CONTENT_SANDBOX) + Win_LocalAppdataLow = 236, +#endif + + 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 +}; + +nsresult +GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory, + nsIFile** aFile); +#ifdef MOZ_WIDGET_COCOA +nsresult +GetOSXFolderType(short aDomain, OSType aFolderType, nsIFile** aLocalFile); +#endif + +#endif diff --git a/xpcom/io/crc32c.c b/xpcom/io/crc32c.c new file mode 100644 index 0000000000..3494945c0f --- /dev/null +++ b/xpcom/io/crc32c.c @@ -0,0 +1,154 @@ +/* + * Based on file found here: + * + * https://svnweb.freebsd.org/base/stable/10/sys/libkern/crc32.c?revision=256281 + */ + +/*- + * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or + * code or tables extracted from it, as desired without restriction. + */ + +/* + * First, the polynomial itself and its table of feedback terms. The + * polynomial is + * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 + * + * Note that we take it "backwards" and put the highest-order term in + * the lowest-order bit. The X^32 term is "implied"; the LSB is the + * X^31 term, etc. The X^0 term (usually shown as "+1") results in + * the MSB being 1 + * + * Note that the usual hardware shift register implementation, which + * is what we're using (we're merely optimizing it by doing eight-bit + * chunks at a time) shifts bits into the lowest-order term. In our + * implementation, that means shifting towards the right. Why do we + * do it this way? Because the calculated CRC must be transmitted in + * order from highest-order term to lowest-order term. UARTs transmit + * characters in order from LSB to MSB. By storing the CRC this way + * we hand it to the UART in the order low-byte to high-byte; the UART + * sends each low-bit to hight-bit; and the result is transmission bit + * by bit from highest- to lowest-order term without requiring any bit + * shuffling on our part. Reception works similarly + * + * The feedback terms table consists of 256, 32-bit entries. Notes + * + * The table can be generated at runtime if desired; code to do so + * is shown later. It might not be obvious, but the feedback + * terms simply represent the results of eight shift/xor opera + * tions for all combinations of data and CRC register values + * + * The values must be right-shifted by eight bits by the "updcrc + * logic; the shift must be unsigned (bring in zeroes). On some + * hardware you could probably optimize the shift in assembler by + * using byte-swap instructions + * polynomial $edb88320 + * + * + * CRC32 code derived from work by Gary S. Brown. + */ + +#include "crc32c.h" + +/* CRC32C routines, these use a different polynomial */ +/*****************************************************************/ +/* */ +/* CRC LOOKUP TABLE */ +/* ================ */ +/* The following CRC lookup table was generated automagically */ +/* by the Rocksoft^tm Model CRC Algorithm Table Generation */ +/* Program V1.0 using the following model parameters: */ +/* */ +/* Width : 4 bytes. */ +/* Poly : 0x1EDC6F41L */ +/* Reverse : TRUE. */ +/* */ +/* For more information on the Rocksoft^tm Model CRC Algorithm, */ +/* see the document titled "A Painless Guide to CRC Error */ +/* Detection Algorithms" by Ross Williams */ +/* (ross@guest.adelaide.edu.au.). This document is likely to be */ +/* in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft". */ +/* */ +/*****************************************************************/ + +static const uint32_t crc32Table[256] = { + 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, + 0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, + 0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL, + 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, 0x5E133C24L, + 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL, + 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L, + 0x9A879FA0L, 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L, + 0x5D1D08BFL, 0xAF768BBCL, 0xBC267848L, 0x4E4DFB4BL, + 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, 0x33ED7D2AL, + 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L, + 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L, + 0x6DFE410EL, 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL, + 0x30E349B1L, 0xC288CAB2L, 0xD1D83946L, 0x23B3BA45L, + 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, 0xE4292D5AL, + 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL, + 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L, + 0x417B1DBCL, 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L, + 0x86E18AA3L, 0x748A09A0L, 0x67DAFA54L, 0x95B17957L, + 0xCBA24573L, 0x39C9C670L, 0x2A993584L, 0xD8F2B687L, + 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L, + 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L, + 0x96BF4DCCL, 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L, + 0xDBFC821CL, 0x2997011FL, 0x3AC7F2EBL, 0xC8AC71E8L, + 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, 0x0F36E6F7L, + 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L, + 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L, + 0xEB1FCBADL, 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L, + 0x2C855CB2L, 0xDEEEDFB1L, 0xCDBE2C45L, 0x3FD5AF46L, + 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, 0x62C8A7F9L, + 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L, + 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L, + 0x3CDB9BDDL, 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L, + 0x82F63B78L, 0x709DB87BL, 0x63CD4B8FL, 0x91A6C88CL, + 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, 0x563C5F93L, + 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L, + 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL, + 0x92A8FC17L, 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L, + 0x55326B08L, 0xA759E80BL, 0xB4091BFFL, 0x466298FCL, + 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, 0x0B21572CL, + 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L, + 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L, + 0x65D122B9L, 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL, + 0x2892ED69L, 0xDAF96E6AL, 0xC9A99D9EL, 0x3BC21E9DL, + 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, 0xFC588982L, + 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL, + 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L, + 0x38CC2A06L, 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L, + 0xFF56BD19L, 0x0D3D3E1AL, 0x1E6DCDEEL, 0xEC064EEDL, + 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, 0xD0DDD530L, + 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL, + 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL, + 0x8ECEE914L, 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L, + 0xD3D3E1ABL, 0x21B862A8L, 0x32E8915CL, 0xC083125FL, + 0x144976B4L, 0xE622F5B7L, 0xF5720643L, 0x07198540L, + 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L, + 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL, + 0xE330A81AL, 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL, + 0x24AA3F05L, 0xD6C1BC06L, 0xC5914FF2L, 0x37FACCF1L, + 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, 0x7AB90321L, + 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL, + 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L, + 0x34F4F86AL, 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL, + 0x79B737BAL, 0x8BDCB4B9L, 0x988C474DL, 0x6AE7C44EL, + 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, 0xAD7D5351L +}; + +// NOTE: See source URL at top of this file for multitable implementation which +// offers a performance boost at the cost of ~8KB of static tables. + +uint32_t +ComputeCrc32c(uint32_t crc, const void *buf, size_t size) +{ + const uint8_t *p = buf; + + + while (size--) + crc = crc32Table[(crc ^ *p++) & 0xff] ^ (crc >> 8); + + return crc; +} diff --git a/xpcom/io/crc32c.h b/xpcom/io/crc32c.h new file mode 100644 index 0000000000..f7035fa159 --- /dev/null +++ b/xpcom/io/crc32c.h @@ -0,0 +1,23 @@ +#ifndef crc32c_h +#define crc32c_h + +#include <stdint.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// Compute a CRC32c as defined in RFC3720. This is a different polynomial than +// what is used in the crc for zlib, etc. Typical usage to calculate a new CRC: +// +// ComputeCrc32c(~0, buffer, bufferLength); +// +uint32_t +ComputeCrc32c(uint32_t aCrc, const void *aBuf, size_t aSize); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // crc32c_h diff --git a/xpcom/io/moz.build b/xpcom/io/moz.build new file mode 100644 index 0000000000..6f21e0a727 --- /dev/null +++ b/xpcom/io/moz.build @@ -0,0 +1,140 @@ +# -*- Mode: python; 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', + 'nsICloneableInputStream.idl', + 'nsIConverterInputStream.idl', + 'nsIConverterOutputStream.idl', + 'nsIDirectoryEnumerator.idl', + 'nsIDirectoryService.idl', + 'nsIFile.idl', + 'nsIInputStream.idl', + 'nsIInputStreamTee.idl', + 'nsIIOUtil.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', + 'nsIStorageStream.idl', + 'nsIStreamBufferAccess.idl', + 'nsIStringStream.idl', + 'nsIUnicharInputStream.idl', + 'nsIUnicharLineInputStream.idl', + 'nsIUnicharOutputStream.idl', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + XPIDL_SOURCES += [ + 'nsILocalFileMac.idl', + ] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + EXPORTS += ['nsLocalFileWin.h'] + EXPORTS.mozilla += [ + 'FileUtilsWin.h', + ] + SOURCES += [ + 'FileUtilsWin.cpp', + 'nsLocalFileWin.cpp', + ] +else: + EXPORTS += ['nsLocalFileUnix.h'] + SOURCES += [ + 'nsLocalFileUnix.cpp', + ] + +XPIDL_MODULE = 'xpcom_io' + +EXPORTS += [ + 'nsAnonymousTemporaryFile.h', + 'nsAppDirectoryServiceDefs.h', + 'nsDirectoryService.h', + 'nsDirectoryServiceAtomList.h', + 'nsDirectoryServiceDefs.h', + 'nsDirectoryServiceUtils.h', + 'nsEscape.h', + 'nsLinebreakConverter.h', + 'nsLocalFile.h', + 'nsMultiplexInputStream.h', + 'nsNativeCharsetUtils.h', + 'nsScriptableInputStream.h', + 'nsStorageStream.h', + 'nsStreamUtils.h', + 'nsStringStream.h', + 'nsUnicharInputStream.h', + 'nsWildCard.h', + 'SlicedInputStream.h', + 'SpecialSystemDirectory.h', +] + +EXPORTS.mozilla += [ + 'Base64.h', + 'SnappyCompressOutputStream.h', + 'SnappyFrameUtils.h', + 'SnappyUncompressInputStream.h', +] + +UNIFIED_SOURCES += [ + 'Base64.cpp', + 'crc32c.c', + 'nsAnonymousTemporaryFile.cpp', + 'nsAppFileLocationProvider.cpp', + 'nsBinaryStream.cpp', + 'nsDirectoryService.cpp', + 'nsEscape.cpp', + 'nsInputStreamTee.cpp', + 'nsIOUtil.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', + 'SlicedInputStream.cpp', + 'SnappyCompressOutputStream.cpp', + 'SnappyFrameUtils.cpp', + 'SnappyUncompressInputStream.cpp', + 'SpecialSystemDirectory.cpp', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += [ + 'CocoaFileUtils.mm', + ] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +if CONFIG['OS_ARCH'] == 'Linux' and 'lib64' in CONFIG['libdir']: + DEFINES['HAVE_USR_LIB64_DIR'] = True + +LOCAL_INCLUDES += ['!..'] + +if CONFIG['_MSC_VER']: + # This is intended as a temporary hack to support building with VS2015. + # '_snwprintf' : format string '%s' requires an argument of type 'wchar_t *', + # but variadic argument 3 has type 'char16ptr_t' + CXXFLAGS += ['-wd4477'] diff --git a/xpcom/io/nsAnonymousTemporaryFile.cpp b/xpcom/io/nsAnonymousTemporaryFile.cpp new file mode 100644 index 0000000000..586e552c44 --- /dev/null +++ b/xpcom/io/nsAnonymousTemporaryFile.cpp @@ -0,0 +1,314 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "mozilla/dom/ContentChild.h" +#include "mozilla/SyncRunnable.h" +#include "nsAnonymousTemporaryFile.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsXULAppAPI.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsAppDirectoryServiceDefs.h" +#include "prio.h" +#include "private/pprio.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" + +#endif + +using namespace mozilla; + +// 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) +{ + if (NS_WARN_IF(!aTempDir)) { + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsIFile> tmpFile; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return 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")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + + tmpFile.forget(aTempDir); + + return NS_OK; +} + +namespace { + +class nsRemoteAnonymousTemporaryFileRunnable : public Runnable +{ +public: + dom::FileDescOrError *mResultPtr; + explicit nsRemoteAnonymousTemporaryFileRunnable(dom::FileDescOrError *aResultPtr) + : mResultPtr(aResultPtr) + { } + +protected: + NS_IMETHOD Run() override { + dom::ContentChild* child = dom::ContentChild::GetSingleton(); + MOZ_ASSERT(child); + child->SendOpenAnonymousTemporaryFile(mResultPtr); + return NS_OK; + } +}; + +} // namespace + +nsresult +NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc) +{ + if (NS_WARN_IF(!aOutFileDesc)) { + return NS_ERROR_INVALID_ARG; + } + + if (dom::ContentChild* child = dom::ContentChild::GetSingleton()) { + dom::FileDescOrError fd = NS_OK; + if (NS_IsMainThread()) { + child->SendOpenAnonymousTemporaryFile(&fd); + } else { + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); + MOZ_ASSERT(mainThread); + SyncRunnable::DispatchToThread(mainThread, + new nsRemoteAnonymousTemporaryFileRunnable(&fd)); + } + if (fd.type() == dom::FileDescOrError::Tnsresult) { + nsresult rv = fd.get_nsresult(); + MOZ_ASSERT(NS_FAILED(rv)); + return rv; + } + auto rawFD = fd.get_FileDescriptor().ClonePlatformHandle(); + *aOutFileDesc = PR_ImportFile(PROsfd(rawFD.release())); + return NS_OK; + } + + nsresult rv; + nsCOMPtr<nsIFile> tmpFile; + rv = GetTempDir(getter_AddRefs(tmpFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return 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); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700); + if (NS_WARN_IF(NS_FAILED(rv))) { + return 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 final : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + + 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); + if (NS_WARN_IF(!mTimer)) { + return NS_ERROR_FAILURE; + } + nsresult rv = mTimer->Init(this, + SCHEDULE_TIMEOUT_MS, + nsITimer::TYPE_ONE_SHOT); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Register shutdown observer so we can cancel the timer if we shutdown before + // the timer runs. + nsCOMPtr<nsIObserverService> obsSrv = services::GetObserverService(); + if (NS_WARN_IF(!obsSrv)) { + return 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 char16_t* 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)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + // Remove the directory recursively. + tmpDir->Remove(true); + } + +private: + ~nsAnonTempFileRemover() {} + + nsCOMPtr<nsITimer> mTimer; +}; + +NS_IMPL_ISUPPORTS(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_IsParentProcess()) { + return NS_OK; + } + RefPtr<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 0000000000..d2f39528f4 --- /dev/null +++ b/xpcom/io/nsAnonymousTemporaryFile.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 0000000000..aa0a688160 --- /dev/null +++ b/xpcom/io/nsAppDirectoryServiceDefs.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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_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" +#define NS_APP_DISTRIBUTION_SEARCH_DIR_LIST "SrchPluginsDistDL" + +// -------------------------------------------------------------------------------------- +// 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_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_INDEXEDDB_PARENT_DIR "indexedDBPDir" + +#define NS_APP_PERMISSION_PARENT_DIR "permissionDBPDir" + +#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) +// +// NS_APP_CONTENT_PROCESS_TEMP_DIR refers to a directory that is read and +// write accessible from a sandboxed content process. The key may be used in +// either process, but the directory is intended to be used for short-lived +// files that need to be saved to the filesystem by the content process and +// don't need to survive browser restarts. The directory is reset on startup. +// The key is only valid when MOZ_CONTENT_SANDBOX is defined. When +// MOZ_CONTENT_SANDBOX is defined, the directory the key refers to differs +// depending on whether or not content sandboxing is enabled. +// +// When MOZ_CONTENT_SANDBOX is defined and sandboxing is enabled (versus +// manually disabled via prefs), the content process replaces NS_OS_TEMP_DIR +// with NS_APP_CONTENT_PROCESS_TEMP_DIR so that legacy code in content +// attempting to write to NS_OS_TEMP_DIR will write to +// NS_APP_CONTENT_PROCESS_TEMP_DIR instead. When MOZ_CONTENT_SANDBOX is +// defined but sandboxing is disabled, NS_APP_CONTENT_PROCESS_TEMP_DIR +// falls back to NS_OS_TEMP_DIR in both content and chrome processes. +// +// New code should avoid writing to the filesystem from the content process +// and should instead proxy through the parent process whenever possible. +// +// At present, all sandboxed content processes use the same directory for +// NS_APP_CONTENT_PROCESS_TEMP_DIR, but that should not be relied upon. +// +#define NS_APP_CONTENT_PROCESS_TEMP_DIR "ContentTmpD" +#else +// Otherwise NS_APP_CONTENT_PROCESS_TEMP_DIR must match NS_OS_TEMP_DIR. +#define NS_APP_CONTENT_PROCESS_TEMP_DIR "TmpD" +#endif // (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX) + +#endif // nsAppDirectoryServiceDefs_h___ diff --git a/xpcom/io/nsAppFileLocationProvider.cpp b/xpcom/io/nsAppFileLocationProvider.cpp new file mode 100644 index 0000000000..27ccd56dc5 --- /dev/null +++ b/xpcom/io/nsAppFileLocationProvider.cpp @@ -0,0 +1,609 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAppFileLocationProvider.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsEnumeratorUtils.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_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) +#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 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_ISUPPORTS(nsAppFileLocationProvider, + nsIDirectoryServiceProvider, + nsIDirectoryServiceProvider2) + +//***************************************************************************** +// nsAppFileLocationProvider::nsIDirectoryServiceProvider +//***************************************************************************** + +NS_IMETHODIMP +nsAppFileLocationProvider::GetFile(const char* aProp, bool* aPersistent, + nsIFile** aResult) +{ + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIFile> localFile; + nsresult rv = NS_ERROR_FAILURE; + + *aResult = nullptr; + *aPersistent = true; + +#ifdef MOZ_WIDGET_COCOA + FSRef fileRef; + nsCOMPtr<nsILocalFileMac> macFile; +#endif + + if (nsCRT::strcmp(aProp, NS_APP_APPLICATION_REGISTRY_DIR) == 0) { + rv = GetProductDirectory(getter_AddRefs(localFile)); + } else if (nsCRT::strcmp(aProp, 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(aProp, 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(aProp, 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(aProp, NS_APP_USER_PROFILES_ROOT_DIR) == 0) { + rv = GetDefaultUserProfileRoot(getter_AddRefs(localFile)); + } else if (nsCRT::strcmp(aProp, NS_APP_USER_PROFILES_LOCAL_ROOT_DIR) == 0) { + rv = GetDefaultUserProfileRoot(getter_AddRefs(localFile), true); + } else if (nsCRT::strcmp(aProp, NS_APP_RES_DIR) == 0) { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(RES_DIR_NAME); + } + } else if (nsCRT::strcmp(aProp, NS_APP_CHROME_DIR) == 0) { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(CHROME_DIR_NAME); + } + } else if (nsCRT::strcmp(aProp, 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(aProp, 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(aProp, 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(aProp, 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(aProp, 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(aProp, 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(aProp, 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(aProp, NS_APP_SEARCH_DIR) == 0) { + rv = CloneMozBinDirectory(getter_AddRefs(localFile)); + if (NS_SUCCEEDED(rv)) { + rv = localFile->AppendRelativeNativePath(SEARCH_DIR_NAME); + } + } else if (nsCRT::strcmp(aProp, NS_APP_USER_SEARCH_DIR) == 0) { + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, aResult); + if (NS_SUCCEEDED(rv)) { + rv = (*aResult)->AppendNative(SEARCH_DIR_NAME); + } + } else if (nsCRT::strcmp(aProp, 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)) { + localFile.forget(aResult); + return NS_OK; + } + + return rv; +} + + +nsresult +nsAppFileLocationProvider::CloneMozBinDirectory(nsIFile** aLocalFile) +{ + if (NS_WARN_IF(!aLocalFile)) { + return NS_ERROR_INVALID_ARG; + } + 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: +//---------------------------------------------------------------------------------------- +nsresult +nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile, + bool aLocal) +{ + if (NS_WARN_IF(!aLocalFile)) { + return NS_ERROR_INVALID_ARG; + } + + 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_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; + } + + localDir.forget(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: +//---------------------------------------------------------------------------------------- +nsresult +nsAppFileLocationProvider::GetDefaultUserProfileRoot(nsIFile** aLocalFile, + bool aLocal) +{ + if (NS_WARN_IF(!aLocalFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + nsCOMPtr<nsIFile> localDir; + + rv = GetProductDirectory(getter_AddRefs(localDir), aLocal); + if (NS_FAILED(rv)) { + return rv; + } + +#if defined(MOZ_WIDGET_COCOA) || 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 + + localDir.forget(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* aResult) override + { + 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; + } + } + *aResult = mNext != nullptr; + return NS_OK; + } + + NS_IMETHOD GetNext(nsISupports** aResult) override + { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = nullptr; + + bool hasMore; + HasMoreElements(&hasMore); + if (!hasMore) { + return NS_ERROR_FAILURE; + } + + *aResult = mNext; + NS_IF_ADDREF(*aResult); + mNext = nullptr; + + return *aResult ? NS_OK : NS_ERROR_FAILURE; + } + +protected: + nsCOMPtr<nsIDirectoryServiceProvider> mProvider; + const char** mCurrentKey; + nsCOMPtr<nsIFile> mNext; + + // Virtual destructor since subclass nsPathsDirectoryEnumerator + // does not re-implement Release() + virtual ~nsAppDirectoryEnumerator() + { + } +}; + +NS_IMPL_ISUPPORTS(nsAppDirectoryEnumerator, nsISimpleEnumerator) + +/* nsPathsDirectoryEnumerator and PATH_SEPARATOR + * are not used on MacOS/X. */ + +#if defined(XP_WIN) /* Win32 */ +#define PATH_SEPARATOR ';' +#else +#define PATH_SEPARATOR ':' +#endif + +class nsPathsDirectoryEnumerator final + : public nsAppDirectoryEnumerator +{ + ~nsPathsDirectoryEnumerator() {} + +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* aResult) + { + 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) { + *aResult = true; + } else { + nsAppDirectoryEnumerator::HasMoreElements(aResult); + } + + return NS_OK; + } + +protected: + const char* mEndPath; +}; + +NS_IMETHODIMP +nsAppFileLocationProvider::GetFiles(const char* aProp, + nsISimpleEnumerator** aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = nullptr; + nsresult rv = NS_ERROR_FAILURE; + + if (!nsCRT::strcmp(aProp, 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 + }; + *aResult = 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; + } + *aResult = new nsPathsDirectoryEnumerator(this, keys); +#endif + NS_ADDREF(*aResult); + rv = NS_OK; + } + if (!nsCRT::strcmp(aProp, NS_APP_SEARCH_DIR_LIST)) { + static const char* keys[] = { nullptr, 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; + } + *aResult = new nsPathsDirectoryEnumerator(this, keys); + NS_ADDREF(*aResult); + rv = NS_OK; + } + if (!strcmp(aProp, NS_APP_DISTRIBUTION_SEARCH_DIR_LIST)) { + return NS_NewEmptyEnumerator(aResult); + } + 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 0000000000..248c5b828f --- /dev/null +++ b/xpcom/io/nsAppFileLocationProvider.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAppFileLocationProvider_h +#define nsAppFileLocationProvider_h + +#include "nsIDirectoryService.h" +#include "nsIFile.h" +#include "mozilla/Attributes.h" + +class nsIFile; + +//***************************************************************************** +// class nsAppFileLocationProvider +//***************************************************************************** + +class nsAppFileLocationProvider final : public nsIDirectoryServiceProvider2 +{ +public: + nsAppFileLocationProvider(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 + +private: + ~nsAppFileLocationProvider() + { + } + +protected: + nsresult 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) + */ + nsresult GetProductDirectory(nsIFile** aLocalFile, + bool aLocal = false); + nsresult GetDefaultUserProfileRoot(nsIFile** aLocalFile, + bool aLocal = false); + +#if defined(MOZ_WIDGET_COCOA) + static bool IsOSXLeopard(); +#endif + + nsCOMPtr<nsIFile> mMozBinDirectory; +}; + +#endif diff --git a/xpcom/io/nsBinaryStream.cpp b/xpcom/io/nsBinaryStream.cpp new file mode 100644 index 0000000000..82159952e0 --- /dev/null +++ b/xpcom/io/nsBinaryStream.cpp @@ -0,0 +1,1016 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * 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 <algorithm> +#include <string.h> + +#include "nsBinaryStream.h" + +#include "mozilla/EndianUtils.h" +#include "mozilla/PodOperations.h" +#include "mozilla/UniquePtr.h" + +#include "nsCRT.h" +#include "nsString.h" +#include "nsISerializable.h" +#include "nsIClassInfo.h" +#include "nsComponentManagerUtils.h" +#include "nsIURI.h" // for NS_IURI_IID +#include "nsIX509Cert.h" // for NS_IX509CERT_IID + +#include "jsfriendapi.h" + +using mozilla::MakeUnique; +using mozilla::PodCopy; +using mozilla::UniquePtr; + +NS_IMPL_ISUPPORTS(nsBinaryOutputStream, + nsIObjectOutputStream, + nsIBinaryOutputStream, + nsIOutputStream) + +NS_IMETHODIMP +nsBinaryOutputStream::Flush() +{ + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mOutputStream->Flush(); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Close() +{ + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mOutputStream->Close(); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write(const char* aBuf, uint32_t aCount, + uint32_t* aActualBytes) +{ + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mOutputStream->Write(aBuf, aCount, aActualBytes); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteFrom(nsIInputStream* aInStr, uint32_t aCount, + uint32_t* aResult) +{ + NS_NOTREACHED("WriteFrom"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* aResult) +{ + NS_NOTREACHED("WriteSegments"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBinaryOutputStream::IsNonBlocking(bool* aNonBlocking) +{ + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mOutputStream->IsNonBlocking(aNonBlocking); +} + +nsresult +nsBinaryOutputStream::WriteFully(const char* aBuf, uint32_t aCount) +{ + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + + 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) +{ + if (NS_WARN_IF(!aOutputStream)) { + return NS_ERROR_INVALID_ARG; + } + 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 aNum) +{ + aNum = mozilla::NativeEndian::swapToBigEndian(aNum); + return WriteFully((const char*)&aNum, sizeof(aNum)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write32(uint32_t aNum) +{ + aNum = mozilla::NativeEndian::swapToBigEndian(aNum); + return WriteFully((const char*)&aNum, sizeof(aNum)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::Write64(uint64_t aNum) +{ + nsresult rv; + uint32_t bytesWritten; + + aNum = mozilla::NativeEndian::swapToBigEndian(aNum); + rv = Write(reinterpret_cast<char*>(&aNum), sizeof(aNum), &bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesWritten != sizeof(aNum)) { + 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 char16_t* 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(char16_t); + +#ifdef IS_BIG_ENDIAN + rv = WriteBytes(reinterpret_cast<const char*>(aString), byteCount); +#else + // XXX use WriteSegments here to avoid copy! + char16_t* copy; + char16_t temp[64]; + if (length <= 64) { + copy = temp; + } else { + copy = reinterpret_cast<char16_t*>(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) { + free(copy); + } +#endif + + return rv; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteUtf8Z(const char16_t* 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) +{ + nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(aObject); + nsCOMPtr<nsISerializable> serializable = do_QueryInterface(aObject); + + // Can't deal with weak refs + if (NS_WARN_IF(!aIsStrongRef)) { + return NS_ERROR_UNEXPECTED; + } + if (NS_WARN_IF(!classInfo) || NS_WARN_IF(!serializable)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCID cid; + nsresult rv = classInfo->GetClassIDNoAlloc(&cid); + if (NS_SUCCEEDED(rv)) { + rv = WriteID(cid); + } else { + nsCID* cidptr = nullptr; + rv = classInfo->GetClassID(&cidptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = WriteID(*cidptr); + + free(cidptr); + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = WriteID(aIID); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return serializable->Write(this); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteID(const nsIID& aIID) +{ + nsresult rv = Write32(aIID.m0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = Write16(aIID.m1); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = Write16(aIID.m2); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + for (int i = 0; i < 8; ++i) { + rv = Write8(aIID.m3[i]); + if (NS_WARN_IF(NS_FAILED(rv))) { + return 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_ISUPPORTS(nsBinaryInputStream, + nsIObjectInputStream, + nsIBinaryInputStream, + nsIInputStream) + +NS_IMETHODIMP +nsBinaryInputStream::Available(uint64_t* aResult) +{ + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mInputStream->Available(aResult); +} + +NS_IMETHODIMP +nsBinaryInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aNumRead) +{ + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + + // 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 MOZ_STACK_CLASS ReadSegmentsClosure +{ + nsCOMPtr<nsIInputStream> mRealInputStream; + void* mRealClosure; + nsWriteSegmentFun mRealWriter; + nsresult mRealResult; + uint32_t mBytesRead; // to properly implement aToOffset +}; + +// the thunking function +static nsresult +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 aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) +{ + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + + ReadSegmentsClosure thunkClosure = { this, aClosure, aWriter, NS_OK, 0 }; + + // mInputStream might give us short reads, so deal with that. + uint32_t bytesRead; + do { + nsresult rv = mInputStream->ReadSegments(ReadSegmentForwardingThunk, + &thunkClosure, + aCount, &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; + aCount -= bytesRead; + } while (aCount != 0 && bytesRead != 0 && + NS_SUCCEEDED(thunkClosure.mRealResult)); + + *aResult = thunkClosure.mBytesRead; + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::IsNonBlocking(bool* aNonBlocking) +{ + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mInputStream->IsNonBlocking(aNonBlocking); +} + +NS_IMETHODIMP +nsBinaryInputStream::Close() +{ + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mInputStream->Close(); +} + +NS_IMETHODIMP +nsBinaryInputStream::SetInputStream(nsIInputStream* aInputStream) +{ + if (NS_WARN_IF(!aInputStream)) { + return NS_ERROR_INVALID_ARG; + } + 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* aNum) +{ + uint32_t bytesRead; + nsresult rv = Read(reinterpret_cast<char*>(aNum), sizeof(*aNum), &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesRead != sizeof(*aNum)) { + return NS_ERROR_FAILURE; + } + *aNum = mozilla::NativeEndian::swapFromBigEndian(*aNum); + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::Read32(uint32_t* aNum) +{ + uint32_t bytesRead; + nsresult rv = Read(reinterpret_cast<char*>(aNum), sizeof(*aNum), &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesRead != sizeof(*aNum)) { + return NS_ERROR_FAILURE; + } + *aNum = mozilla::NativeEndian::swapFromBigEndian(*aNum); + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::Read64(uint64_t* aNum) +{ + uint32_t bytesRead; + nsresult rv = Read(reinterpret_cast<char*>(aNum), sizeof(*aNum), &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesRead != sizeof(*aNum)) { + return NS_ERROR_FAILURE; + } + *aNum = mozilla::NativeEndian::swapFromBigEndian(*aNum); + 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 nsresult +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 char16_t +struct WriteStringClosure +{ + char16_t* 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 nsresult +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(char16_t) == 2, "We can't handle other sizes!"); + + WriteStringClosure* closure = static_cast<WriteStringClosure*>(aClosure); + char16_t* 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 = *(char16_t*)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 char16_t* unicodeSegment = + reinterpret_cast<const char16_t*>(aFromSegment); + + // calculate number of full characters in segment (aCount could be odd!) + uint32_t segmentLength = aCount / sizeof(char16_t); + + // 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(char16_t)); + char16_t* 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(char16_t) != 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)) { + 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(char16_t), &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + + NS_ASSERTION(!closure.mHasCarryoverByte, "some strange stream corruption!"); + + if (bytesRead != length * sizeof(char16_t)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadBytes(uint32_t aLength, char** aResult) +{ + nsresult rv; + uint32_t bytesRead; + char* s; + + s = reinterpret_cast<char*>(malloc(aLength)); + if (!s) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = Read(s, aLength, &bytesRead); + if (NS_FAILED(rv)) { + free(s); + return rv; + } + if (bytesRead != aLength) { + free(s); + return NS_ERROR_FAILURE; + } + + *aResult = s; + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadByteArray(uint32_t aLength, uint8_t** aResult) +{ + return ReadBytes(aLength, reinterpret_cast<char**>(aResult)); +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadArrayBuffer(uint32_t aLength, + JS::Handle<JS::Value> aBuffer, + JSContext* aCx, uint32_t* aReadLength) +{ + if (!aBuffer.isObject()) { + return NS_ERROR_FAILURE; + } + JS::RootedObject buffer(aCx, &aBuffer.toObject()); + if (!JS_IsArrayBufferObject(buffer)) { + return NS_ERROR_FAILURE; + } + + uint32_t bufferLength = JS_GetArrayBufferByteLength(buffer); + if (bufferLength < aLength) { + return NS_ERROR_FAILURE; + } + + uint32_t bufSize = std::min<uint32_t>(aLength, 4096); + UniquePtr<char[]> buf = MakeUnique<char[]>(bufSize); + + uint32_t pos = 0; + *aReadLength = 0; + do { + // Read data into temporary buffer. + uint32_t bytesRead; + uint32_t amount = std::min(aLength - pos, bufSize); + nsresult rv = Read(buf.get(), amount, &bytesRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + MOZ_ASSERT(bytesRead <= amount); + + if (bytesRead == 0) { + break; + } + + // Copy data into actual buffer. + + JS::AutoCheckCannotGC nogc; + bool isShared; + if (bufferLength != JS_GetArrayBufferByteLength(buffer)) { + return NS_ERROR_FAILURE; + } + + char* data = reinterpret_cast<char*>(JS_GetArrayBufferData(buffer, &isShared, nogc)); + MOZ_ASSERT(!isShared); // Implied by JS_GetArrayBufferData() + if (!data) { + return NS_ERROR_FAILURE; + } + + *aReadLength += bytesRead; + PodCopy(data + pos, buf.get(), bytesRead); + + pos += bytesRead; + } while (pos < aLength); + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadObject(bool aIsStrongRef, nsISupports** aObject) +{ + nsCID cid; + nsIID iid; + nsresult rv = ReadID(&cid); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = ReadID(&iid); + if (NS_WARN_IF(NS_FAILED(rv))) { + return 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 } + }; + + // hackaround for bug 1195415 + static const nsIID oldURIiid4 = { + 0x395fe045, 0x7d18, 0x4adb, + { 0xa3, 0xfd, 0xaf, 0x98, 0xc8, 0xa1, 0xaf, 0x11 } + }; + + if (iid.Equals(oldURIiid) || + iid.Equals(oldURIiid2) || + iid.Equals(oldURIiid3) || + iid.Equals(oldURIiid4)) { + const nsIID newURIiid = NS_IURI_IID; + iid = newURIiid; + } + // END HACK + + // HACK: Service workers store resource security info on disk in the dom + // Cache API. When the uuid of the nsIX509Cert interface changes + // these serialized objects cannot be loaded any more. This hack + // works around this issue. + + // hackaround for bug 1247580 (FF45 to FF46 transition) + static const nsIID oldCertIID = { + 0xf8ed8364, 0xced9, 0x4c6e, + { 0x86, 0xba, 0x48, 0xaf, 0x53, 0xc3, 0x93, 0xe6 } + }; + + if (iid.Equals(oldCertIID)) { + const nsIID newCertIID = NS_IX509CERT_IID; + iid = newCertIID; + } + // END HACK + + nsCOMPtr<nsISupports> object = do_CreateInstance(cid, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsISerializable> serializable = do_QueryInterface(object); + if (NS_WARN_IF(!serializable)) { + return NS_ERROR_UNEXPECTED; + } + + rv = serializable->Read(this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return object->QueryInterface(iid, reinterpret_cast<void**>(aObject)); +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadID(nsID* aResult) +{ + nsresult rv = Read32(&aResult->m0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = Read16(&aResult->m1); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = Read16(&aResult->m2); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + for (int i = 0; i < 8; ++i) { + rv = Read8(&aResult->m3[i]); + if (NS_WARN_IF(NS_FAILED(rv))) { + return 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 0000000000..2520d92018 --- /dev/null +++ b/xpcom/io/nsBinaryStream.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 final : public nsIObjectOutputStream +{ +public: + 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; + +private: + // virtual dtor since subclasses call our Release() + virtual ~nsBinaryOutputStream() + { + } +}; + +#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" + +class nsBinaryInputStream final : public nsIObjectInputStream +{ +public: + 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; + +private: + // virtual dtor since subclasses call our Release() + virtual ~nsBinaryInputStream() + { + } +}; + +#endif // nsBinaryStream_h___ diff --git a/xpcom/io/nsDirectoryService.cpp b/xpcom/io/nsDirectoryService.cpp new file mode 100644 index 0000000000..a4d9623957 --- /dev/null +++ b/xpcom/io/nsDirectoryService.cpp @@ -0,0 +1,766 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.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 +#endif + +#include "SpecialSystemDirectory.h" +#include "nsAppFileLocationProvider.h" + +using namespace mozilla; + +// 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 +#endif + +//---------------------------------------------------------------------------------------- +nsresult +nsDirectoryService::GetCurrentProcessDirectory(nsIFile** aFile) +//---------------------------------------------------------------------------------------- +{ + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + *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> localFile; + dirService->Get(NS_XPCOM_INIT_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(localFile)); + if (localFile) { + localFile.forget(aFile); + return NS_OK; + } + } + + RefPtr<nsLocalFile> localFile = new nsLocalFile; + +#ifdef XP_WIN + wchar_t 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 + wchar_t* lastSlash = wcsrchr(buf, L'\\'); + if (lastSlash) { + *(lastSlash + 1) = L'\0'; + } + + localFile->InitWithPath(nsDependentString(buf)); + localFile.forget(aFile); + return NS_OK; + } + +#elif defined(MOZ_WIDGET_COCOA) + // Works even if we're not bundled. + CFBundleRef appBundle = CFBundleGetMainBundle(); + if (appBundle) { + CFURLRef bundleURL = CFBundleCopyExecutableURL(appBundle); + if (bundleURL) { + 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)) { + localFile.forget(aFile); + } + } + 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)); + localFile.forget(aFile); + 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)); + localFile.forget(aFile); + return NS_OK; + } + +#endif + + NS_ERROR("unable to get current process directory"); + return NS_ERROR_FAILURE; +} // GetCurrentProcessDirectory() + +StaticRefPtr<nsDirectoryService> nsDirectoryService::gService; + +nsDirectoryService::nsDirectoryService() + : mHashtable(128) +{ +} + +nsresult +nsDirectoryService::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + 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!"); + + gService = new nsDirectoryService(); + + NS_RegisterStaticAtoms(directory_atoms); + + // Let the list hold the only reference to the provider. + nsAppFileLocationProvider* defaultProvider = new nsAppFileLocationProvider; + gService->mProviders.AppendElement(defaultProvider); +} + +nsDirectoryService::~nsDirectoryService() +{ +} + +NS_IMPL_ISUPPORTS(nsDirectoryService, + nsIProperties, + nsIDirectoryService, + nsIDirectoryServiceProvider, + nsIDirectoryServiceProvider2) + + +NS_IMETHODIMP +nsDirectoryService::Undefine(const char* aProp) +{ + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + + nsDependentCString key(aProp); + if (!mHashtable.Get(key, nullptr)) { + return NS_ERROR_FAILURE; + } + + mHashtable.Remove(key); + return NS_OK; +} + +NS_IMETHODIMP +nsDirectoryService::GetKeys(uint32_t* aCount, char*** aKeys) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +struct MOZ_STACK_CLASS FileData +{ + FileData(const char* aProperty, const nsIID& aUUID) + : property(aProperty) + , data(nullptr) + , persistent(true) + , uuid(aUUID) + { + } + + const char* property; + nsCOMPtr<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.get(), newFiles); + + if (unionFiles) { + unionFiles.swap(*(nsISimpleEnumerator**)&aData->data); + } + } else { + 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* aProp, const nsIID& aUuid, void** aResult) +{ + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + + nsDependentCString key(aProp); + + nsCOMPtr<nsIFile> cachedFile = mHashtable.Get(key); + + if (cachedFile) { + nsCOMPtr<nsIFile> cloneFile; + cachedFile->Clone(getter_AddRefs(cloneFile)); + return cloneFile->QueryInterface(aUuid, aResult); + } + + // it is not one of our defaults, lets check any providers + FileData fileData(aProp, aUuid); + + for (int32_t i = mProviders.Length() - 1; i >= 0; i--) { + if (!FindProviderFile(mProviders[i], &fileData)) { + break; + } + } + if (fileData.data) { + if (fileData.persistent) { + Set(aProp, static_cast<nsIFile*>(fileData.data.get())); + } + nsresult rv = (fileData.data)->QueryInterface(aUuid, aResult); + fileData.data = nullptr; // AddRef occurs in FindProviderFile() + return rv; + } + + FindProviderFile(static_cast<nsIDirectoryServiceProvider*>(this), &fileData); + if (fileData.data) { + if (fileData.persistent) { + Set(aProp, static_cast<nsIFile*>(fileData.data.get())); + } + nsresult rv = (fileData.data)->QueryInterface(aUuid, aResult); + fileData.data = nullptr; // AddRef occurs in FindProviderFile() + return rv; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDirectoryService::Set(const char* aProp, nsISupports* aValue) +{ + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + + nsDependentCString key(aProp); + if (mHashtable.Get(key, nullptr) || !aValue) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIFile> ourFile = do_QueryInterface(aValue); + 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* aProp, bool* aResult) +{ + if (NS_WARN_IF(!aProp)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = false; + nsCOMPtr<nsIFile> value; + nsresult rv = Get(aProp, NS_GET_IID(nsIFile), getter_AddRefs(value)); + if (NS_FAILED(rv)) { + return NS_OK; + } + + if (value) { + *aResult = true; + } + + return rv; +} + +NS_IMETHODIMP +nsDirectoryService::RegisterProvider(nsIDirectoryServiceProvider* aProv) +{ + if (!aProv) { + return NS_ERROR_FAILURE; + } + + mProviders.AppendElement(aProv); + 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* aProv) +{ + if (!aProv) { + return NS_ERROR_FAILURE; + } + + mProviders.RemoveElement(aProv); + return NS_OK; +} + +#if defined(MOZ_CONTENT_SANDBOX) && defined(XP_WIN) +static nsresult +GetLowIntegrityTempBase(nsIFile** aLowIntegrityTempBase) +{ + nsCOMPtr<nsIFile> localFile; + nsresult rv = GetSpecialSystemDirectory(Win_LocalAppdataLow, + getter_AddRefs(localFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = localFile->Append(NS_LITERAL_STRING(MOZ_USER_DIR)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + localFile.forget(aLowIntegrityTempBase); + return rv; +} +#endif + +// 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* aProp, bool* aPersistent, + nsIFile** aResult) +{ + nsCOMPtr<nsIFile> localFile; + nsresult rv = NS_ERROR_FAILURE; + + *aResult = nullptr; + *aPersistent = true; + + nsCOMPtr<nsIAtom> inAtom = NS_Atomize(aProp); + + // 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 || + inAtom == nsDirectoryService::sGRE_BinDirectory) { + 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)); +#if defined(MOZ_CONTENT_SANDBOX) + } else if (inAtom == nsDirectoryService::sLocalAppdataLow) { + rv = GetSpecialSystemDirectory(Win_LocalAppdataLow, getter_AddRefs(localFile)); + } else if (inAtom == nsDirectoryService::sLowIntegrityTempBase) { + rv = GetLowIntegrityTempBase(getter_AddRefs(localFile)); +#endif + } 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)); + *aPersistent = false; + } else if (inAtom == nsDirectoryService::sXDGDocuments) { + rv = GetSpecialSystemDirectory(Unix_XDG_Documents, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsDirectoryService::sXDGDownload || + inAtom == nsDirectoryService::sDefaultDownloadDirectory) { + rv = GetSpecialSystemDirectory(Unix_XDG_Download, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsDirectoryService::sXDGMusic) { + rv = GetSpecialSystemDirectory(Unix_XDG_Music, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsDirectoryService::sXDGPictures) { + rv = GetSpecialSystemDirectory(Unix_XDG_Pictures, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsDirectoryService::sXDGPublicShare) { + rv = GetSpecialSystemDirectory(Unix_XDG_PublicShare, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsDirectoryService::sXDGTemplates) { + rv = GetSpecialSystemDirectory(Unix_XDG_Templates, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsDirectoryService::sXDGVideos) { + rv = GetSpecialSystemDirectory(Unix_XDG_Videos, getter_AddRefs(localFile)); + *aPersistent = false; + } +#endif + + if (NS_FAILED(rv)) { + return rv; + } + + if (!localFile) { + return NS_ERROR_FAILURE; + } + + localFile.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsDirectoryService::GetFiles(const char* aProp, nsISimpleEnumerator** aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = nullptr; + + return NS_ERROR_FAILURE; +} diff --git a/xpcom/io/nsDirectoryService.h b/xpcom/io/nsDirectoryService.h new file mode 100644 index 0000000000..d0f92b75a2 --- /dev/null +++ b/xpcom/io/nsDirectoryService.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDirectoryService_h___ +#define nsDirectoryService_h___ + +#include "nsIDirectoryService.h" +#include "nsInterfaceHashtable.h" +#include "nsIFile.h" +#include "nsIAtom.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" +#include "mozilla/StaticPtr.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 final + : public nsIDirectoryService + , public nsIProperties + , public nsIDirectoryServiceProvider2 +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIPROPERTIES + + NS_DECL_NSIDIRECTORYSERVICE + + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + + NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 + + nsDirectoryService(); + + static void RealInit(); + void RegisterCategoryProviders(); + + static nsresult + Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + + static mozilla::StaticRefPtr<nsDirectoryService> gService; + +private: + ~nsDirectoryService(); + + nsresult GetCurrentProcessDirectory(nsIFile** aFile); + + nsInterfaceHashtable<nsCStringHashKey, nsIFile> 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 0000000000..38a2f0e9d6 --- /dev/null +++ b/xpcom/io/nsDirectoryServiceAtomList.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +DIR_ATOM(sCurrentProcess, NS_XPCOM_CURRENT_PROCESS_DIR) +DIR_ATOM(sGRE_Directory, NS_GRE_DIR) +DIR_ATOM(sGRE_BinDirectory, NS_GRE_BIN_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) +#if defined(MOZ_CONTENT_SANDBOX) +DIR_ATOM(sLocalAppdataLow, NS_WIN_LOCAL_APPDATA_LOW_DIR) +DIR_ATOM(sLowIntegrityTempBase, NS_WIN_LOW_INTEGRITY_TEMP_BASE) +#endif +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) +#endif diff --git a/xpcom/io/nsDirectoryServiceDefs.h b/xpcom/io/nsDirectoryServiceDefs.h new file mode 100644 index 0000000000..0bdc5e3905 --- /dev/null +++ b/xpcom/io/nsDirectoryServiceDefs.h @@ -0,0 +1,168 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * 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 GRE directory. + * On OSX, this typically points to Contents/Resources in the app bundle. + * If no GRE is used, this propery will behave like + * NS_XPCOM_CURRENT_PROCESS_DIR. + */ +#define NS_GRE_DIR "GreD" + +/* Property will return the current location of the GRE-binaries directory. + * On OSX, this typically points to Contents/MacOS in the app bundle. On + * all other platforms, this will be identical to NS_GRE_DIR. + * Since this property is based on the NS_GRE_DIR, if no GRE is used, this + * propery will behave like NS_XPCOM_CURRENT_PROCESS_DIR. + */ +#define NS_GRE_BIN_DIR "GreBinD" + +/* 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" +#if defined(MOZ_CONTENT_SANDBOX) + #define NS_WIN_LOCAL_APPDATA_LOW_DIR "LocalAppDataLow" + #define NS_WIN_LOW_INTEGRITY_TEMP_BASE "LowTmpDBase" +#endif + #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" +#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 0000000000..6100e75652 --- /dev/null +++ b/xpcom/io/nsDirectoryServiceUtils.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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* aSpecialDirName, nsIFile** aResult) +{ + nsresult rv; + nsCOMPtr<nsIProperties> serv(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, + &rv)); + if (NS_FAILED(rv)) { + return rv; + } + + return serv->Get(aSpecialDirName, NS_GET_IID(nsIFile), + reinterpret_cast<void**>(aResult)); +} + +#endif diff --git a/xpcom/io/nsEscape.cpp b/xpcom/io/nsEscape.cpp new file mode 100644 index 0000000000..f16edc4ce7 --- /dev/null +++ b/xpcom/io/nsEscape.cpp @@ -0,0 +1,633 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsEscape.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/BinarySearch.h" +#include "nsTArray.h" +#include "nsCRT.h" +#include "plstr.h" + +static const char hexCharsUpper[] = "0123456789ABCDEF"; +static const char hexCharsUpperLower[] = "0123456789ABCDEFabcdef"; + +static 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))] & (aFlags)) +#define HEX_ESCAPE '%' + +static const uint32_t ENCODE_MAX_LEN = 6; // %uABCD + +static uint32_t +AppendPercentHex(char* aBuffer, unsigned char aChar) +{ + uint32_t i = 0; + aBuffer[i++] = '%'; + aBuffer[i++] = hexCharsUpper[aChar >> 4]; // high nibble + aBuffer[i++] = hexCharsUpper[aChar & 0xF]; // low nibble + return i; +} + +static uint32_t +AppendPercentHex(char16_t* aBuffer, char16_t aChar) +{ + uint32_t i = 0; + aBuffer[i++] = '%'; + if (aChar & 0xff00) { + aBuffer[i++] = 'u'; + aBuffer[i++] = hexCharsUpper[aChar >> 12]; // high-byte high nibble + aBuffer[i++] = hexCharsUpper[(aChar >> 8) & 0xF]; // high-byte low nibble + } + aBuffer[i++] = hexCharsUpper[(aChar >> 4) & 0xF]; // low-byte high nibble + aBuffer[i++] = hexCharsUpper[aChar & 0xF]; // low-byte low nibble + return i; +} + +//---------------------------------------------------------------------------------------- +char* +nsEscape(const char* aStr, size_t aLength, size_t* aOutputLength, + nsEscapeMask aFlags) +//---------------------------------------------------------------------------------------- +{ + if (!aStr) { + return nullptr; + } + + size_t charsToEscape = 0; + + const unsigned char* src = (const unsigned char*)aStr; + for (size_t i = 0; i < aLength; ++i) { + if (!IS_OK(src[i])) { + 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 = aLength + 1 + charsToEscape; + if (dstSize <= aLength) { + return nullptr; + } + dstSize += charsToEscape; + if (dstSize < aLength) { + return nullptr; + } + + // fail if we need more than 4GB + if (dstSize > UINT32_MAX) { + return nullptr; + } + + char* result = (char*)moz_xmalloc(dstSize); + if (!result) { + return nullptr; + } + + unsigned char* dst = (unsigned char*)result; + src = (const unsigned char*)aStr; + if (aFlags == url_XPAlphas) { + for (size_t i = 0; i < aLength; ++i) { + unsigned char c = *src++; + if (IS_OK(c)) { + *dst++ = c; + } else if (c == ' ') { + *dst++ = '+'; /* convert spaces to pluses */ + } else { + *dst++ = HEX_ESCAPE; + *dst++ = hexCharsUpper[c >> 4]; /* high nibble */ + *dst++ = hexCharsUpper[c & 0x0f]; /* low nibble */ + } + } + } else { + for (size_t i = 0; i < aLength; ++i) { + unsigned char c = *src++; + if (IS_OK(c)) { + *dst++ = c; + } else { + *dst++ = HEX_ESCAPE; + *dst++ = hexCharsUpper[c >> 4]; /* high nibble */ + *dst++ = hexCharsUpper[c & 0x0f]; /* low nibble */ + } + } + } + + *dst = '\0'; /* tack on eos */ + if (aOutputLength) { + *aOutputLength = dst - (unsigned char*)result; + } + + return result; +} + +//---------------------------------------------------------------------------------------- +char* +nsUnescape(char* aStr) +//---------------------------------------------------------------------------------------- +{ + nsUnescapeCount(aStr); + return aStr; +} + +//---------------------------------------------------------------------------------------- +int32_t +nsUnescapeCount(char* aStr) +//---------------------------------------------------------------------------------------- +{ + char* src = aStr; + char* dst = aStr; + + char c1[] = " "; + char c2[] = " "; + char* const pc1 = c1; + char* const pc2 = c2; + + if (!*src) { + // A null string was passed in. Nothing to escape. + // Returns early as the string might not actually be mutable with + // length 0. + return 0; + } + + 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, hexCharsUpperLower) == 0 || + PL_strpbrk(pc2, hexCharsUpperLower) == 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 - aStr); + +} /* NET_UnEscapeCnt */ + + +char* +nsEscapeHTML(const char* aString) +{ + char* rv = nullptr; + /* XXX Hardcoded max entity len. The +1 is for the trailing null. */ + uint32_t len = strlen(aString); + if (len >= (UINT32_MAX / 6)) { + return nullptr; + } + + rv = (char*)moz_xmalloc((6 * len) + 1); + char* ptr = rv; + + if (rv) { + for (; *aString != '\0'; ++aString) { + if (*aString == '<') { + *ptr++ = '&'; + *ptr++ = 'l'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if (*aString == '>') { + *ptr++ = '&'; + *ptr++ = 'g'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if (*aString == '&') { + *ptr++ = '&'; + *ptr++ = 'a'; + *ptr++ = 'm'; + *ptr++ = 'p'; + *ptr++ = ';'; + } else if (*aString == '"') { + *ptr++ = '&'; + *ptr++ = 'q'; + *ptr++ = 'u'; + *ptr++ = 'o'; + *ptr++ = 't'; + *ptr++ = ';'; + } else if (*aString == '\'') { + *ptr++ = '&'; + *ptr++ = '#'; + *ptr++ = '3'; + *ptr++ = '9'; + *ptr++ = ';'; + } else { + *ptr++ = *aString; + } + } + *ptr = '\0'; + } + + return rv; +} + +char16_t* +nsEscapeHTML2(const char16_t* 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(char16_t)) / (6 * sizeof(char16_t)))) { + return nullptr; + } + + char16_t* resultBuffer = (char16_t*)moz_xmalloc( + aSourceBufferLen * 6 * sizeof(char16_t) + sizeof(char16_t('\0'))); + char16_t* 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; +} + +//---------------------------------------------------------------------------------------- +// +// The following table encodes which characters needs to be escaped for which +// parts of an URL. The bits are the "url components" in the enum EscapeMask, +// see nsEscape.h. +// +// 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 + +static const uint32_t 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, 112,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,1008, 896,1008, 896,1023, // 5x PQRSTUVWXYZ[\]^_ + 384,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{|}~ DEL + 0 // 80 to FF are zero +}; + +static uint16_t dontNeedEscape(unsigned char aChar, uint32_t aFlags) +{ + return EscapeChars[(uint32_t)aChar] & aFlags; +} +static uint16_t dontNeedEscape(uint16_t aChar, uint32_t aFlags) +{ + return aChar < mozilla::ArrayLength(EscapeChars) ? + (EscapeChars[(uint32_t)aChar] & aFlags) : 0; +} + +//---------------------------------------------------------------------------------------- + +/** + * Templated helper for URL escaping a portion of a string. + * + * @param aPart The pointer to the beginning of the portion of the string to + * escape. + * @param aPartLen The length of the string to escape. + * @param aFlags Flags used to configure escaping. @see EscapeMask + * @param aResult String that has the URL escaped portion appended to. Only + * altered if the string is URL escaped or |esc_AlwaysCopy| is specified. + * @param aDidAppend Indicates whether or not data was appended to |aResult|. + * @return NS_ERROR_INVALID_ARG, NS_ERROR_OUT_OF_MEMORY on failure. + */ +template<class T> +static nsresult +T_EscapeURL(const typename T::char_type* aPart, size_t aPartLen, + uint32_t aFlags, T& aResult, bool& aDidAppend) +{ + typedef nsCharTraits<typename T::char_type> traits; + typedef typename traits::unsigned_char_type unsigned_char_type; + static_assert(sizeof(*aPart) == 1 || sizeof(*aPart) == 2, + "unexpected char type"); + + if (!aPart) { + NS_NOTREACHED("null pointer"); + return NS_ERROR_INVALID_ARG; + } + + bool forced = !!(aFlags & esc_Forced); + bool ignoreNonAscii = !!(aFlags & esc_OnlyASCII); + bool ignoreAscii = !!(aFlags & esc_OnlyNonASCII); + bool writing = !!(aFlags & esc_AlwaysCopy); + bool colon = !!(aFlags & esc_Colon); + + auto src = reinterpret_cast<const unsigned_char_type*>(aPart); + + typename T::char_type tempBuffer[100]; + unsigned int tempBufferPos = 0; + + bool previousIsNonASCII = false; + for (size_t i = 0; i < aPartLen; ++i) { + unsigned_char_type 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 aPart 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 ((dontNeedEscape(c, aFlags) || (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) { + if (!aResult.Append(aPart, i, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + writing = true; + } + uint32_t len = ::AppendPercentHex(tempBuffer + tempBufferPos, c); + tempBufferPos += len; + MOZ_ASSERT(len <= ENCODE_MAX_LEN, "potential buffer overflow"); + } + + // Flush the temp buffer if it doesnt't have room for another encoded char. + if (tempBufferPos >= mozilla::ArrayLength(tempBuffer) - ENCODE_MAX_LEN) { + NS_ASSERTION(writing, "should be writing"); + if (!aResult.Append(tempBuffer, tempBufferPos, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + tempBufferPos = 0; + } + + previousIsNonASCII = (c > 0x7f); + } + if (writing) { + if (!aResult.Append(tempBuffer, tempBufferPos, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + aDidAppend = writing; + return NS_OK; +} + +bool +NS_EscapeURL(const char* aPart, int32_t aPartLen, uint32_t aFlags, + nsACString& aResult) +{ + if (aPartLen < 0) { + aPartLen = strlen(aPart); + } + + bool result = false; + nsresult rv = T_EscapeURL(aPart, aPartLen, aFlags, aResult, result); + if (NS_FAILED(rv)) { + ::NS_ABORT_OOM(aResult.Length() * sizeof(nsACString::char_type)); + } + + return result; +} + +nsresult +NS_EscapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult, + const mozilla::fallible_t&) +{ + bool appended = false; + nsresult rv = T_EscapeURL(aStr.Data(), aStr.Length(), aFlags, aResult, appended); + if (NS_FAILED(rv)) { + aResult.Truncate(); + return rv; + } + + if (!appended) { + aResult = aStr; + } + + return rv; +} + +const nsSubstring& +NS_EscapeURL(const nsSubstring& aStr, uint32_t aFlags, nsSubstring& aResult) +{ + bool result = false; + nsresult rv = T_EscapeURL<nsSubstring>(aStr.Data(), aStr.Length(), aFlags, aResult, result); + + if (NS_FAILED(rv)) { + ::NS_ABORT_OOM(aResult.Length() * sizeof(nsSubstring::char_type)); + } + + if (result) { + return aResult; + } + return aStr; +} + +// Starting at aStr[aStart] find the first index in aStr that matches any +// character in aForbidden. Return false if not found. +static bool +FindFirstMatchFrom(const nsAFlatString& aStr, size_t aStart, + const nsTArray<char16_t>& aForbidden, size_t* aIndex) +{ + const size_t len = aForbidden.Length(); + for (size_t j = aStart, l = aStr.Length(); j < l; ++j) { + size_t unused; + if (mozilla::BinarySearch(aForbidden, 0, len, aStr[j], &unused)) { + *aIndex = j; + return true; + } + } + return false; +} + +const nsSubstring& +NS_EscapeURL(const nsAFlatString& aStr, const nsTArray<char16_t>& aForbidden, + nsSubstring& aResult) +{ + bool didEscape = false; + for (size_t i = 0, strLen = aStr.Length(); i < strLen; ) { + size_t j; + if (MOZ_UNLIKELY(FindFirstMatchFrom(aStr, i, aForbidden, &j))) { + if (i == 0) { + didEscape = true; + aResult.Truncate(); + aResult.SetCapacity(aStr.Length()); + } + if (j != i) { + // The substring from 'i' up to 'j' that needs no escaping. + aResult.Append(nsDependentSubstring(aStr, i, j - i)); + } + char16_t buffer[ENCODE_MAX_LEN]; + uint32_t bufferLen = ::AppendPercentHex(buffer, aStr[j]); + MOZ_ASSERT(bufferLen <= ENCODE_MAX_LEN, "buffer overflow"); + aResult.Append(buffer, bufferLen); + i = j + 1; + } else { + if (MOZ_UNLIKELY(didEscape)) { + // The tail of the string that needs no escaping. + aResult.Append(nsDependentSubstring(aStr, i, strLen - i)); + } + break; + } + } + if (MOZ_UNLIKELY(didEscape)) { + return aResult; + } + return aStr; +} + +#define ISHEX(c) memchr(hexCharsUpperLower, c, sizeof(hexCharsUpperLower)-1) + +bool +NS_UnescapeURL(const char* aStr, int32_t aLen, uint32_t aFlags, + nsACString& aResult) +{ + if (!aStr) { + NS_NOTREACHED("null pointer"); + return false; + } + + MOZ_ASSERT(aResult.IsEmpty(), + "Passing a non-empty string as an out parameter!"); + + if (aLen < 0) { + aLen = strlen(aStr); + } + + bool ignoreNonAscii = !!(aFlags & esc_OnlyASCII); + bool ignoreAscii = !!(aFlags & esc_OnlyNonASCII); + bool writing = !!(aFlags & esc_AlwaysCopy); + bool skipControl = !!(aFlags & esc_SkipControl); + bool skipInvalidHostChar = !!(aFlags & esc_Host); + + if (writing) { + aResult.SetCapacity(aLen); + } + + const char* last = aStr; + const char* p = aStr; + + for (int i = 0; i < aLen; ++i, ++p) { + if (*p == HEX_ESCAPE && i < aLen - 2) { + unsigned char c1 = *((unsigned char*)p + 1); + unsigned char c2 = *((unsigned char*)p + 2); + unsigned char u = (UNHEX(c1) << 4) + UNHEX(c2); + if (ISHEX(c1) && ISHEX(c2) && + (!skipInvalidHostChar || dontNeedEscape(u, aFlags) || c1 >= '8') && + ((c1 < '8' && !ignoreAscii) || (c1 >= '8' && !ignoreNonAscii)) && + !(skipControl && + (c1 < '2' || (c1 == '7' && (c2 == 'f' || c2 == 'F'))))) { + if (!writing) { + writing = true; + aResult.SetCapacity(aLen); + } + if (p > last) { + aResult.Append(last, p - last); + last = p; + } + aResult.Append(u); + i += 2; + p += 2; + last += 3; + } + } + } + if (writing && last < aStr + aLen) { + aResult.Append(last, aStr + aLen - last); + } + + return writing; +} diff --git a/xpcom/io/nsEscape.h b/xpcom/io/nsEscape.h new file mode 100644 index 0000000000..bf89b737a8 --- /dev/null +++ b/xpcom/io/nsEscape.h @@ -0,0 +1,224 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* First checked in on 98/12/03 by John R. McMullen, derived from net.h/mkparse.c. */ + +#ifndef _ESCAPE_H_ +#define _ESCAPE_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 aSstr The string to escape + * @param aLength The length of the string to escape + * @param aOutputLen A pointer that will be used to store the length of the + * output string, if not null + * @param aMask How to escape the string + * @return A newly allocated escaped string that must be free'd with + * nsCRT::free, or null on failure + * @note: Please, don't use this function. Use NS_Escape instead! + */ +char* nsEscape(const char* aStr, size_t aLength, size_t* aOutputLen, + nsEscapeMask aMask); + +char* nsUnescape(char* aStr); +/* decode % escaped hex codes into character values, + * modifies the parameter, returns the same buffer + */ + +int32_t nsUnescapeCount(char* aStr); +/* 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* aString); + +char16_t* +nsEscapeHTML2(const char16_t* 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, unless aFlags has the esc_Forced bit in which + * case % will also be escaped. Iff some part of aStr is escaped is the + * final result appended to aResult. You can also request that aStr is + * always appended to aResult with esc_AlwaysCopy. + * + * @param aStr url segment string + * @param aLen url segment string length (-1 if unknown) + * @param aFlags url segment type flag (see EscapeMask above) + * @param aResult result buffer, untouched if aStr is already escaped unless + * aFlags has esc_AlwaysCopy + * + * @return true if aResult was written to (i.e. at least one character was + * escaped or esc_AlwaysCopy was requested), false otherwise. + */ +bool NS_EscapeURL(const char* aStr, + int32_t aLen, + uint32_t aFlags, + nsACString& aResult); + +/** + * Expands URL escape sequences... beware embedded null bytes! + * + * @param aStr url string to unescape + * @param aLen length of aStr + * @param aFlags only esc_OnlyNonASCII, esc_SkipControl and esc_AlwaysCopy + * are recognized + * @param aResult result buffer, untouched if aStr is already unescaped unless + * aFlags has esc_AlwaysCopy + * + * @return true if aResult was written to (i.e. at least one character was + * unescaped or esc_AlwaysCopy was requested), false otherwise. + */ +bool NS_UnescapeURL(const char* aStr, + int32_t aLen, + uint32_t aFlags, + nsACString& aResult); + +/** returns resultant string length **/ +inline int32_t +NS_UnescapeURL(char* aStr) +{ + return nsUnescapeCount(aStr); +} + +/** + * String friendly versions... + */ +inline const nsCSubstring& +NS_EscapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult) +{ + if (NS_EscapeURL(aStr.Data(), aStr.Length(), aFlags, aResult)) { + return aResult; + } + return aStr; +} + +/** + * Fallible version of NS_EscapeURL. On success aResult will point to either + * the original string or an escaped copy. + */ +nsresult +NS_EscapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult, + const mozilla::fallible_t&); + +inline const nsCSubstring& +NS_UnescapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult) +{ + if (NS_UnescapeURL(aStr.Data(), aStr.Length(), aFlags, aResult)) { + return aResult; + } + return aStr; +} +const nsSubstring& +NS_EscapeURL(const nsSubstring& aStr, uint32_t aFlags, nsSubstring& aResult); + +/** + * Percent-escapes all characters in aStr that occurs in aForbidden. + * @param aStr the input URL string + * @param aForbidden the characters that should be escaped if found in aStr + * @note that aForbidden MUST be sorted (low to high) + * @param aResult the result if some characters were escaped + * @return aResult if some characters were escaped, or aStr otherwise (aResult + * is unmodified in that case) + */ +const nsSubstring& +NS_EscapeURL(const nsAFlatString& aStr, const nsTArray<char16_t>& aForbidden, + nsSubstring& aResult); + +/** + * 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 nsACString& aOriginal, nsACString& aEscaped, + nsEscapeMask aMask) +{ + size_t escLen = 0; + char* esc = nsEscape(aOriginal.BeginReading(), aOriginal.Length(), &escLen, + aMask); + if (! esc) { + return false; + } + aEscaped.Adopt(esc, escLen); + return true; +} + +/** + * Inline unescape of mutable string object. + */ +inline nsACString& +NS_UnescapeURL(nsACString& aStr) +{ + aStr.SetLength(nsUnescapeCount(aStr.BeginWriting())); + return aStr; +} + +#endif // _ESCAPE_H_ diff --git a/xpcom/io/nsIAsyncInputStream.idl b/xpcom/io/nsIAsyncInputStream.idl new file mode 100644 index 0000000000..5570817dd1 --- /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 0000000000..7e74579c6c --- /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. + */ +[function, 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 0000000000..c3a27e203f --- /dev/null +++ b/xpcom/io/nsIBinaryInputStream.idl @@ -0,0 +1,119 @@ +/* -*- 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" + +/** + * 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(899b826b-2eb3-469c-8b31-4c29f5d341a6)] +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. + * + * @return The number of bytes actually read into aArrayBuffer. + */ + [implicit_jscontext] + unsigned long 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 0000000000..4d426d580e --- /dev/null +++ b/xpcom/io/nsIBinaryOutputStream.idl @@ -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/. */ + +#include "nsIOutputStream.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 char16_t* 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/nsICloneableInputStream.idl b/xpcom/io/nsICloneableInputStream.idl new file mode 100644 index 0000000000..4fd9a74f7a --- /dev/null +++ b/xpcom/io/nsICloneableInputStream.idl @@ -0,0 +1,22 @@ +/* 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" + +[scriptable, builtinclass, uuid(8149be1f-44d3-4f14-8b65-a57a5fbbeb97)] +interface nsICloneableInputStream : nsISupports +{ + // Allow streams that implement the interface to determine if cloning + // possible at runtime. For example, this allows wrappers to check if + // their base stream supports cloning. + [infallible] readonly attribute boolean cloneable; + + // Produce a copy of the current stream in the most efficient way possible. + // In this case "copy" means that both the original and cloned streams + // should produce the same bytes for all future reads. Bytes that have + // already been consumed from the original stream are not copied to the + // clone. Operations on the two streams should be completely independent + // after the clone() occurs. + nsIInputStream clone(); +}; diff --git a/xpcom/io/nsIConverterInputStream.idl b/xpcom/io/nsIConverterInputStream.idl new file mode 100644 index 0000000000..7f35d8c5ce --- /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 char16_t 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 char16_t aReplacementChar); +}; + diff --git a/xpcom/io/nsIConverterOutputStream.idl b/xpcom/io/nsIConverterOutputStream.idl new file mode 100644 index 0000000000..a93d3cfa6d --- /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 char16_t aReplacementCharacter); +}; diff --git a/xpcom/io/nsIDirectoryEnumerator.idl b/xpcom/io/nsIDirectoryEnumerator.idl new file mode 100644 index 0000000000..7a1135fda9 --- /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 0000000000..6f58e37b9c --- /dev/null +++ b/xpcom/io/nsIDirectoryService.idl @@ -0,0 +1,103 @@ +/* -*- 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 nsIFile; +interface nsISimpleEnumerator; + +/** + * 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 0000000000..fc07106b90 --- /dev/null +++ b/xpcom/io/nsIFile.idl @@ -0,0 +1,521 @@ +/* -*- 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++ +struct PRFileDesc; +struct PRLibrary; +#include <stdio.h> +%} + +[ptr] native PRFileDescStar(PRFileDesc); +[ptr] native PRLibraryStar(PRLibrary); +[ptr] native FILE(FILE); + +interface nsISimpleEnumerator; + +/** + * An nsIFile is an abstract representation of a filename. It manages + * filename encoding issues, pathname component separators ('/' vs. '\\' + * vs. ':') and weird stuff like differing volumes with identical names, as + * on pre-Darwin Macintoshes. + * + * This file has long introduced itself to new hackers with this opening + * paragraph: + * + * 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. + * + * While taking the pose struck here to heart would be uncalled for, one + * may safely conclude that writing cross-platform code is an embittering + * experience. + * + * 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, main_process_scriptable_only, uuid(2fa6884a-ae65-412a-9d4c-ce6e34544ba1), 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. + */ + [must_use] 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); + + /** + * renameTo + * + * This method is identical to moveTo except that if this file or directory + * is moved to a a different volume, it fails and returns an error + * (NS_ERROR_FILE_ACCESS_DENIED). + * This object will still point to the old location after renaming. + */ + void renameTo(in nsIFile newParentDir, in AString newName); + [noscript] void renameToNative(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. + */ + [must_use] + 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. + * This routine looks in subdirectories too. + */ + boolean contains(in nsIFile inFile); + + /** + * 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. On success, the + * returned PRFileDescr must be non-null. + * + * @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, must_use] 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. On success, the + * returned FILE pointer must be non-null. + */ + [noscript, must_use] 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, must_use] PRLibraryStar load(); + + // number of bytes available on disk to non-superuser + [must_use] 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! + */ + [must_use] 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. + */ + [must_use] 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. + */ + [must_use] 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. + * Throws if fromFile is null. + */ + [must_use] 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 + */ + [must_use] + void setRelativeDescriptor(in nsIFile fromFile, in ACString relativeDesc); + + /** + * getRelativePath + * + * Returns a relative file from 'fromFile' to this file as a UTF-8 string. + * Going up the directory tree is represented via "../". '/' is used as + * the path segment separator. This is not a native path, since it's UTF-8 + * encoded. + * + * @param fromFile + * the file from which the path is relative. + * Throws if fromFile is null. + */ + [must_use] AUTF8String getRelativePath(in nsIFile fromFile); + + /** + * setRelativePath + * + * Initializes the file to the location relative to fromFile using + * a string returned by getRelativePath. + * + * @param fromFile + * the file from which the path is relative + * @param relative + * the relative path obtained from getRelativePath + */ + [must_use] + void setRelativePath(in nsIFile fromFile, in AUTF8String 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 0000000000..577ba40467 --- /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 0000000000..b2f1652746 --- /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 nsresult (*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 0000000000..953be7a7c4 --- /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 0000000000..4a8eff42b2 --- /dev/null +++ b/xpcom/io/nsILineInputStream.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(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 0000000000..cead3e3f44 --- /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(7ba8c6ba-2ce2-48b1-bd60-4c32aac35f9c)] +interface nsILocalFile : nsIFile +{ +}; + diff --git a/xpcom/io/nsILocalFileMac.idl b/xpcom/io/nsILocalFileMac.idl new file mode 100644 index 0000000000..d8655449bb --- /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(623eca5b-c25d-4e27-be5a-789a66c4b2f7)] +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/nsILocalFileWin.idl b/xpcom/io/nsILocalFileWin.idl new file mode 100644 index 0000000000..c036cb96b4 --- /dev/null +++ b/xpcom/io/nsILocalFileWin.idl @@ -0,0 +1,121 @@ +/* -*- 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" + +%{C++ +struct PRFileDesc; +%} + +[ptr] native PRFileDescStar(PRFileDesc); + +[scriptable, builtinclass, uuid(e7a3a954-384b-4aeb-a5f7-55626b0de9be)] +interface nsILocalFileWin : nsILocalFile +{ + /** + * initWithCommandLine + * + * Initialize this object based on the main app path of a commandline + * handler. + * + * @param aCommandLine + * the commandline to parse an app path out of. + */ + void initWithCommandLine(in AString aCommandLine); + /** + * 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); + + /** + * Identical to nsIFile::openNSPRFileDesc except it also uses the + * FILE_SHARE_DELETE flag. + */ + [noscript] PRFileDescStar openNSPRFileDescShareDelete(in long flags, + in long mode); +}; + diff --git a/xpcom/io/nsIMultiplexInputStream.idl b/xpcom/io/nsIMultiplexInputStream.idl new file mode 100644 index 0000000000..d42adcbd40 --- /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 0000000000..d583dd75bd --- /dev/null +++ b/xpcom/io/nsIOUtil.cpp @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIOUtil.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsStreamUtils.h" + +NS_IMPL_ISUPPORTS(nsIOUtil, nsIIOUtil) + +NS_IMETHODIMP +nsIOUtil::InputStreamIsBuffered(nsIInputStream* aStream, bool* aResult) +{ + if (NS_WARN_IF(!aStream)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = NS_InputStreamIsBuffered(aStream); + return NS_OK; +} + +NS_IMETHODIMP +nsIOUtil::OutputStreamIsBuffered(nsIOutputStream* aStream, bool* aResult) +{ + if (NS_WARN_IF(!aStream)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = NS_OutputStreamIsBuffered(aStream); + return NS_OK; +} diff --git a/xpcom/io/nsIOUtil.h b/xpcom/io/nsIOUtil.h new file mode 100644 index 0000000000..47b02a02e8 --- /dev/null +++ b/xpcom/io/nsIOUtil.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 final : public nsIIOUtil +{ + ~nsIOUtil() {} + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIOUTIL +}; + +#endif /* nsIOUtil_h__ */ diff --git a/xpcom/io/nsIObjectInputStream.idl b/xpcom/io/nsIObjectInputStream.idl new file mode 100644 index 0000000000..c482d3b89f --- /dev/null +++ b/xpcom/io/nsIObjectInputStream.idl @@ -0,0 +1,53 @@ +/* -*- 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" + +/** + * @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 0000000000..3ef6711d49 --- /dev/null +++ b/xpcom/io/nsIObjectOutputStream.idl @@ -0,0 +1,97 @@ +/* -*- 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" + +/** + * @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 0000000000..0e04a3910e --- /dev/null +++ b/xpcom/io/nsIOutputStream.idl @@ -0,0 +1,145 @@ +/* -*- 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 nsresult (*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). This failure + * means no bytes were transferred. + * @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). This failure + * means no bytes were transferred. + * @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 0000000000..596be92e9f --- /dev/null +++ b/xpcom/io/nsIPipe.idl @@ -0,0 +1,171 @@ +/* -*- 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 nsIAsyncInputStream; +interface nsIAsyncOutputStream; + +/** + * 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(25d0de93-685e-4ea4-95d3-d884e31df63c)] +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. + */ + [must_use] void init(in boolean nonBlockingInput, + in boolean nonBlockingOutput, + in unsigned long segmentSize, + in unsigned long segmentCount); + + /** + * The pipe's input end, which also implements nsISearchableInputStream. + * Getting fails if the pipe hasn't been initialized. + */ + [must_use] readonly attribute nsIAsyncInputStream inputStream; + + /** + * The pipe's output end. Getting fails if the pipe hasn't been + * initialized. + */ + [must_use] 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++ + +class nsIInputStream; +class nsIOutputStream; + +/** + * 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. + */ +extern MOZ_MUST_USE nsresult +NS_NewPipe2(nsIAsyncInputStream **pipeIn, + nsIAsyncOutputStream **pipeOut, + bool nonBlockingInput = false, + bool nonBlockingOutput = false, + uint32_t segmentSize = 0, + uint32_t segmentCount = 0); + +/** + * 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 + */ +extern MOZ_MUST_USE nsresult +NS_NewPipe(nsIInputStream **pipeIn, + nsIOutputStream **pipeOut, + uint32_t segmentSize = 0, + uint32_t maxSize = 0, + bool nonBlockingInput = false, + bool nonBlockingOutput = false); + +%} diff --git a/xpcom/io/nsISafeOutputStream.idl b/xpcom/io/nsISafeOutputStream.idl new file mode 100644 index 0000000000..c62d85f071 --- /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 0000000000..6414518056 --- /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 0000000000..d9248f4258 --- /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 0000000000..1be7ea60c6 --- /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/nsIStorageStream.idl b/xpcom/io/nsIStorageStream.idl new file mode 100644 index 0000000000..7d5061e230 --- /dev/null +++ b/xpcom/io/nsIStorageStream.idl @@ -0,0 +1,69 @@ +/* -*- 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 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(44a200fe-6c2b-4b41-b4e3-63e8c14e7c0d)] +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. + */ + void init(in uint32_t segmentSize, in uint32_t maxSize); + + /** + * 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 0000000000..c37afd8dc2 --- /dev/null +++ b/xpcom/io/nsIStreamBufferAccess.idl @@ -0,0 +1,88 @@ +/* -*- 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" + +/** + * 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 0000000000..d43200382b --- /dev/null +++ b/xpcom/io/nsIStringStream.idl @@ -0,0 +1,66 @@ +/* -*- 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" + +%{C++ +#include "mozilla/MemoryReporting.h" +%} + +native MallocSizeOf(mozilla::MallocSizeOf); + +/** + * nsIStringInputStream + * + * Provides scriptable and specialized C++-only methods for initializing a + * nsIInputStream implementation with a simple character array. + */ +[scriptable, builtinclass, 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 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); + + [noscript, notxpcom] + size_t SizeOfIncludingThis(in MallocSizeOf aMallocSizeOf); +}; diff --git a/xpcom/io/nsIUnicharInputStream.idl b/xpcom/io/nsIUnicharInputStream.idl new file mode 100644 index 0000000000..8d0459fedb --- /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 nsresult (*nsWriteUnicharSegmentFun)(nsIUnicharInputStream *aInStream, + void *aClosure, + const char16_t *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 char16_t 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 0000000000..34a67f0992 --- /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 0000000000..599224dd0a --- /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 char16_t 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 0000000000..8f01cb87e1 --- /dev/null +++ b/xpcom/io/nsInputStreamTee.cpp @@ -0,0 +1,366 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdlib.h> +#include "mozilla/Logging.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 LOG +#undef LOG +#endif + +static LazyLogModule sTeeLog("nsInputStreamTee"); +#define LOG(args) MOZ_LOG(sTeeLog, mozilla::LogLevel::Debug, args) + +class nsInputStreamTee final : public nsIInputStreamTee +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIINPUTSTREAMTEE + + nsInputStreamTee(); + bool SinkIsValid(); + void InvalidateSink(); + +private: + ~nsInputStreamTee() + { + } + + nsresult TeeSegment(const char* aBuf, uint32_t aCount); + + static nsresult 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 Runnable +{ +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() override + { + if (!mBuf) { + NS_WARNING("nsInputStreamTeeWriteEvent::Run() " + "memory not allocated\n"); + return NS_OK; + } + MOZ_ASSERT(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 + RefPtr<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* aBuf, uint32_t aCount) +{ + 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 + } + nsCOMPtr<nsIRunnable> event = + new nsInputStreamTeeWriteEvent(aBuf, aCount, mSink, this); + LOG(("nsInputStreamTee::TeeSegment [%p] dispatching write %u bytes\n", + this, aCount)); + 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 (aCount) { + uint32_t bytesWritten = 0; + rv = mSink->Write(aBuf + totalBytesWritten, aCount, &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 = nullptr; + break; + } + totalBytesWritten += bytesWritten; + NS_ASSERTION(bytesWritten <= aCount, "wrote too much"); + aCount -= bytesWritten; + } + return NS_OK; + } +} + +nsresult +nsInputStreamTee::WriteSegmentFun(nsIInputStream* aIn, void* aClosure, + const char* aFromSegment, uint32_t aOffset, + uint32_t aCount, uint32_t* aWriteCount) +{ + nsInputStreamTee* tee = reinterpret_cast<nsInputStreamTee*>(aClosure); + nsresult rv = tee->mWriter(aIn, tee->mClosure, aFromSegment, aOffset, + aCount, aWriteCount); + if (NS_FAILED(rv) || (*aWriteCount == 0)) { + NS_ASSERTION((NS_FAILED(rv) ? (*aWriteCount == 0) : true), + "writer returned an error with non-zero writeCount"); + return rv; + } + + return tee->TeeSegment(aFromSegment, *aWriteCount); +} + +NS_IMPL_ISUPPORTS(nsInputStreamTee, + nsIInputStreamTee, + nsIInputStream) +NS_IMETHODIMP +nsInputStreamTee::Close() +{ + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + nsresult rv = mSource->Close(); + mSource = nullptr; + mSink = nullptr; + return rv; +} + +NS_IMETHODIMP +nsInputStreamTee::Available(uint64_t* aAvail) +{ + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mSource->Available(aAvail); +} + +NS_IMETHODIMP +nsInputStreamTee::Read(char* aBuf, uint32_t aCount, uint32_t* aBytesRead) +{ + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv = mSource->Read(aBuf, aCount, aBytesRead); + if (NS_FAILED(rv) || (*aBytesRead == 0)) { + return rv; + } + + return TeeSegment(aBuf, *aBytesRead); +} + +NS_IMETHODIMP +nsInputStreamTee::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, + uint32_t aCount, + uint32_t* aBytesRead) +{ + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + + mWriter = aWriter; + mClosure = aClosure; + + return mSource->ReadSegments(WriteSegmentFun, this, aCount, aBytesRead); +} + +NS_IMETHODIMP +nsInputStreamTee::IsNonBlocking(bool* aResult) +{ + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mSource->IsNonBlocking(aResult); +} + +NS_IMETHODIMP +nsInputStreamTee::SetSource(nsIInputStream* aSource) +{ + mSource = aSource; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::GetSource(nsIInputStream** aSource) +{ + NS_IF_ADDREF(*aSource = mSource); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::SetSink(nsIOutputStream* aSink) +{ +#ifdef DEBUG + if (aSink) { + bool nonBlocking; + nsresult rv = aSink->IsNonBlocking(&nonBlocking); + if (NS_FAILED(rv) || nonBlocking) { + NS_ERROR("aSink should be a blocking stream"); + } + } +#endif + mSink = aSink; + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::GetSink(nsIOutputStream** aSink) +{ + NS_IF_ADDREF(*aSink = mSink); + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::SetEventTarget(nsIEventTarget* aEventTarget) +{ + mEventTarget = aEventTarget; + if (mEventTarget) { + // Only need synchronization if this is an async tee + mLock = new Mutex("nsInputStreamTee.mLock"); + } + return NS_OK; +} + +NS_IMETHODIMP +nsInputStreamTee::GetEventTarget(nsIEventTarget** aEventTarget) +{ + NS_IF_ADDREF(*aEventTarget = mEventTarget); + return NS_OK; +} + + +nsresult +NS_NewInputStreamTeeAsync(nsIInputStream** aResult, + nsIInputStream* aSource, + nsIOutputStream* aSink, + nsIEventTarget* aEventTarget) +{ + nsresult rv; + + nsCOMPtr<nsIInputStreamTee> tee = new nsInputStreamTee(); + rv = tee->SetSource(aSource); + if (NS_FAILED(rv)) { + return rv; + } + + rv = tee->SetSink(aSink); + if (NS_FAILED(rv)) { + return rv; + } + + rv = tee->SetEventTarget(aEventTarget); + if (NS_FAILED(rv)) { + return rv; + } + + tee.forget(aResult); + return rv; +} + +nsresult +NS_NewInputStreamTee(nsIInputStream** aResult, + nsIInputStream* aSource, + nsIOutputStream* aSink) +{ + return NS_NewInputStreamTeeAsync(aResult, aSource, aSink, nullptr); +} + +#undef LOG diff --git a/xpcom/io/nsLinebreakConverter.cpp b/xpcom/io/nsLinebreakConverter.cpp new file mode 100644 index 0000000000..007685a6aa --- /dev/null +++ b/xpcom/io/nsLinebreakConverter.cpp @@ -0,0 +1,488 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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*& aIoDest, const char* aLineBreakStr) +{ + *aIoDest++ = *aLineBreakStr; + + if (aLineBreakStr[1]) { + *aIoDest++ = aLineBreakStr[1]; + } +} + +/*---------------------------------------------------------------------------- + CountChars + + Counts occurrences of breakStr in aSrc +----------------------------------------------------------------------------*/ +template<class T> +int32_t +CountLinebreaks(const T* aSrc, int32_t aInLen, const char* aBreakStr) +{ + const T* src = aSrc; + const T* srcEnd = aSrc + aInLen; + int32_t theCount = 0; + + while (src < srcEnd) { + if (*src == *aBreakStr) { + src++; + + if (aBreakStr[1]) { + if (src < srcEnd && *src == aBreakStr[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* aInSrc, int32_t& aIoLen, const char* aSrcBreak, + const char* aDestBreak) +{ + NS_ASSERTION(aInSrc && aSrcBreak && aDestBreak, "Got a null string"); + + T* resultString = nullptr; + + // handle the no conversion case + if (nsCRT::strcmp(aSrcBreak, aDestBreak) == 0) { + resultString = (T*)malloc(sizeof(T) * aIoLen); + if (!resultString) { + return nullptr; + } + memcpy(resultString, aInSrc, sizeof(T) * aIoLen); // includes the null, if any + return resultString; + } + + int32_t srcBreakLen = strlen(aSrcBreak); + int32_t destBreakLen = strlen(aDestBreak); + + // 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*)malloc(sizeof(T) * aIoLen); + if (!resultString) { + return nullptr; + } + + const T* src = aInSrc; + const T* srcEnd = aInSrc + aIoLen; // includes null, if any + T* dst = resultString; + + char srcBreakChar = *aSrcBreak; // we know it's one char long already + char dstBreakChar = *aDestBreak; + + while (src < srcEnd) { + if (*src == srcBreakChar) { + *dst++ = dstBreakChar; + src++; + } else { + *dst++ = *src++; + } + } + + // aIoLen 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(aInSrc, aIoLen, aSrcBreak); + + int32_t newBufLen = + aIoLen - (numLinebreaks * srcBreakLen) + (numLinebreaks * destBreakLen); + resultString = (T*)malloc(sizeof(T) * newBufLen); + if (!resultString) { + return nullptr; + } + + const T* src = aInSrc; + const T* srcEnd = aInSrc + aIoLen; // includes null, if any + T* dst = resultString; + + while (src < srcEnd) { + if (*src == *aSrcBreak) { + *dst++ = *aDestBreak; + if (aDestBreak[1]) { + *dst++ = aDestBreak[1]; + } + + src++; + if (src < srcEnd && aSrcBreak[1] && *src == aSrcBreak[1]) { + src++; + } + } else { + *dst++ = *src++; + } + } + + aIoLen = 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* aInSrc, int32_t aInLen, char aSrcBreak, char aDestBreak) +{ + T* src = aInSrc; + T* srcEnd = aInSrc + aInLen; + + while (src < srcEnd) { + if (*src == aSrcBreak) { + *src = aDestBreak; + } + + 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* aInSrc, int32_t& aIoLen, const char* aDestBreak) +{ + const T* src = aInSrc; + const T* srcEnd = aInSrc + aIoLen; // includes null, if any + + int32_t destBreakLen = strlen(aDestBreak); + 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*)malloc(sizeof(T) * finalLen); + if (!resultString) { + return nullptr; + } + + src = aInSrc; + srcEnd = aInSrc + aIoLen; // includes null, if any + + T* dst = resultString; + + while (src < srcEnd) { + if (*src == nsCRT::CR) { + if (src < srcEnd && src[1] == nsCRT::LF) { + // CRLF + AppendLinebreak(dst, aDestBreak); + src++; + } else { + // Lone CR + AppendLinebreak(dst, aDestBreak); + } + } else if (*src == nsCRT::LF) { + // Lone LF + AppendLinebreak(dst, aDestBreak); + } else { + *dst++ = *src; + } + src++; + } + + aIoLen = finalLen; + return resultString; +} + + +/*---------------------------------------------------------------------------- + ConvertLineBreaks + +----------------------------------------------------------------------------*/ +char* +nsLinebreakConverter::ConvertLineBreaks(const char* aSrc, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks, + int32_t aSrcLen, int32_t* aOutLen) +{ + 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 (aOutLen) { + *aOutLen = sourceLen; + } + return resultString; +} + + +/*---------------------------------------------------------------------------- + ConvertLineBreaksInSitu + +----------------------------------------------------------------------------*/ +nsresult +nsLinebreakConverter::ConvertLineBreaksInSitu(char** aIoBuffer, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks, + int32_t aSrcLen, int32_t* aOutLen) +{ + NS_ASSERTION(aIoBuffer && *aIoBuffer, "Null pointer passed"); + if (!aIoBuffer || !*aIoBuffer) { + return NS_ERROR_NULL_POINTER; + } + + NS_ASSERTION(aDestBreaks != eLinebreakAny && + aSrcBreaks != eLinebreakSpace, "Invalid parameter"); + + int32_t sourceLen = (aSrcLen == kIgnoreLen) ? strlen(*aIoBuffer) + 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(*aIoBuffer, sourceLen, *srcBreaks, *dstBreaks); + if (aOutLen) { + *aOutLen = sourceLen; + } + } else { + char* destBuffer; + + if (aSrcBreaks == eLinebreakAny) { + destBuffer = ConvertUnknownBreaks(*aIoBuffer, sourceLen, dstBreaks); + } else { + destBuffer = ConvertBreaks(*aIoBuffer, sourceLen, srcBreaks, dstBreaks); + } + + if (!destBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + *aIoBuffer = destBuffer; + if (aOutLen) { + *aOutLen = sourceLen; + } + } + + return NS_OK; +} + + +/*---------------------------------------------------------------------------- + ConvertUnicharLineBreaks + +----------------------------------------------------------------------------*/ +char16_t* +nsLinebreakConverter::ConvertUnicharLineBreaks(const char16_t* aSrc, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks, + int32_t aSrcLen, + int32_t* aOutLen) +{ + NS_ASSERTION(aDestBreaks != eLinebreakAny && + aSrcBreaks != eLinebreakSpace, "Invalid parameter"); + if (!aSrc) { + return nullptr; + } + + int32_t bufLen = (aSrcLen == kIgnoreLen) ? NS_strlen(aSrc) + 1 : aSrcLen; + + char16_t* resultString; + if (aSrcBreaks == eLinebreakAny) { + resultString = ConvertUnknownBreaks(aSrc, bufLen, + GetLinebreakString(aDestBreaks)); + } else + resultString = ConvertBreaks(aSrc, bufLen, GetLinebreakString(aSrcBreaks), + GetLinebreakString(aDestBreaks)); + + if (aOutLen) { + *aOutLen = bufLen; + } + return resultString; +} + + +/*---------------------------------------------------------------------------- + ConvertStringLineBreaks + +----------------------------------------------------------------------------*/ +nsresult +nsLinebreakConverter::ConvertUnicharLineBreaksInSitu( + char16_t** aIoBuffer, ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks, + int32_t aSrcLen, int32_t* aOutLen) +{ + NS_ASSERTION(aIoBuffer && *aIoBuffer, "Null pointer passed"); + if (!aIoBuffer || !*aIoBuffer) { + return NS_ERROR_NULL_POINTER; + } + NS_ASSERTION(aDestBreaks != eLinebreakAny && + aSrcBreaks != eLinebreakSpace, "Invalid parameter"); + + int32_t sourceLen = + (aSrcLen == kIgnoreLen) ? NS_strlen(*aIoBuffer) + 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(*aIoBuffer, sourceLen, *srcBreaks, *dstBreaks); + if (aOutLen) { + *aOutLen = sourceLen; + } + } else { + char16_t* destBuffer; + + if (aSrcBreaks == eLinebreakAny) { + destBuffer = ConvertUnknownBreaks(*aIoBuffer, sourceLen, dstBreaks); + } else { + destBuffer = ConvertBreaks(*aIoBuffer, sourceLen, srcBreaks, dstBreaks); + } + + if (!destBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + *aIoBuffer = destBuffer; + if (aOutLen) { + *aOutLen = sourceLen; + } + } + + return NS_OK; +} + +/*---------------------------------------------------------------------------- + ConvertStringLineBreaks + +----------------------------------------------------------------------------*/ +nsresult +nsLinebreakConverter::ConvertStringLineBreaks(nsString& aIoString, + ELinebreakType aSrcBreaks, + ELinebreakType aDestBreaks) +{ + + NS_ASSERTION(aDestBreaks != eLinebreakAny && + aSrcBreaks != eLinebreakSpace, "Invalid parameter"); + + // nothing to do + if (aIoString.IsEmpty()) { + return NS_OK; + } + + nsresult rv; + + // remember the old buffer in case + // we blow it away later + nsString::char_iterator stringBuf; + if (!aIoString.BeginWriting(stringBuf, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + int32_t newLen; + + rv = ConvertUnicharLineBreaksInSitu(&stringBuf, + aSrcBreaks, aDestBreaks, + aIoString.Length() + 1, &newLen); + if (NS_FAILED(rv)) { + return rv; + } + + if (stringBuf != aIoString.get()) { + aIoString.Adopt(stringBuf, newLen - 1); + } + + return NS_OK; +} + + + diff --git a/xpcom/io/nsLinebreakConverter.h b/xpcom/io/nsLinebreakConverter.h new file mode 100644 index 0000000000..a1678ef2d1 --- /dev/null +++ b/xpcom/io/nsLinebreakConverter.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 char16_t* ConvertUnicharLineBreaks(const char16_t* 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& aIoString, + 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** aIoBuffer, + 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(char16_t** aIoBuffer, + 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 0000000000..f7bdb86f7e --- /dev/null +++ b/xpcom/io/nsLocalFile.h @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * 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 char16_t *aNode); \ + nsresult GetUnicodeLeafName(char16_t **aLeafName); \ + nsresult SetUnicodeLeafName(const char16_t *aLeafName); \ + nsresult CopyToUnicode(nsIFile *aNewParentDir, const char16_t *aNewLeafName); \ + nsresult CopyToFollowingLinksUnicode(nsIFile *aNewParentDir, const char16_t *aNewLeafName); \ + nsresult MoveToUnicode(nsIFile *aNewParentDir, const char16_t *aNewLeafName); \ + nsresult GetUnicodeTarget(char16_t **aTarget); \ + nsresult GetUnicodePath(char16_t **aPath); \ + nsresult InitWithUnicodePath(const char16_t *aPath); \ + nsresult AppendRelativeUnicodePath(const char16_t *aRelativePath); + +// XPCOMInit 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" +#else +#error NOT_IMPLEMENTED +#endif + +#define NSRESULT_FOR_RETURN(ret) (((ret) < 0) ? NSRESULT_FOR_ERRNO() : NS_OK) + +inline nsresult +nsresultForErrno(int aErr) +{ + switch (aErr) { + case 0: + return NS_OK; +#ifdef EDQUOT + case EDQUOT: /* Quota exceeded */ + // FALLTHROUGH to return NS_ERROR_FILE_DISK_FULL +#endif + case ENOSPC: + return NS_ERROR_FILE_DISK_FULL; +#ifdef EISDIR + case EISDIR: /* Is a directory. */ + return NS_ERROR_FILE_IS_DIRECTORY; +#endif + case ENAMETOOLONG: + return NS_ERROR_FILE_NAME_TOO_LONG; + case ENOEXEC: /* Executable file format error. */ + return NS_ERROR_FILE_EXECUTION_FAILED; + case ENOENT: + return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST; + case ENOTDIR: + return NS_ERROR_FILE_DESTINATION_NOT_DIR; +#ifdef ELOOP + case ELOOP: + return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; +#endif /* ELOOP */ +#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; +#ifdef EROFS + case EROFS: /* Read-only file system. */ + return NS_ERROR_FILE_READ_ONLY; +#endif + /* + * 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 */ + /* Note that nsIFile.createUnique() returns + NS_ERROR_FILE_TOO_BIG when it cannot create a temporary + file with a unique filename. + See https://developer.mozilla.org/en-US/docs/Table_Of_Errors + Other usages of NS_ERROR_FILE_TOO_BIG in the source tree + are in line with the POSIX semantics of EFBIG. + So this is a reasonably good approximation. + */ + case EFBIG: /* File too large. */ + return NS_ERROR_FILE_TOO_BIG; + + 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 0000000000..8fbb7227d3 --- /dev/null +++ b/xpcom/io/nsLocalFileCommon.cpp @@ -0,0 +1,328 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIServiceManager.h" + +#include "nsLocalFile.h" // includes platform-specific headers + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsReadableUtils.h" +#include "nsPrintfCString.h" +#include "nsCRT.h" +#include "nsNativeCharsetUtils.h" +#include "nsUTF8Utils.h" + +#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) +{ + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString path; + aFile->GetNativePath(path); + if (path.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + return InitWithNativePath(path); +} +#endif + +#define kMaxFilenameLength 255 +#define kMaxExtensionLength 100 +#define kMaxSequenceNumberLength 5 // "-9999" +// requirement: kMaxExtensionLength < kMaxFilenameLength - kMaxSequenceNumberLength + +NS_IMETHODIMP +nsLocalFile::CreateUnique(uint32_t aType, uint32_t aAttributes) +{ + nsresult rv; + bool longName; + +#ifdef XP_WIN + nsAutoString pathName, leafName, rootName, suffix; + rv = GetPath(pathName); +#else + nsAutoCString pathName, leafName, rootName, suffix; + rv = GetNativePath(pathName); +#endif + if (NS_FAILED(rv)) { + return rv; + } + + longName = (pathName.Length() + kMaxSequenceNumberLength > + kMaxFilenameLength); + if (!longName) { + rv = Create(aType, aAttributes); + 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(char16_t('.')); +#else + rv = GetNativeLeafName(leafName); + if (NS_FAILED(rv)) { + return rv; + } + + const int32_t lastDot = leafName.RFindChar('.'); +#endif + + if (lastDot == kNotFound) { + rootName = leafName; + } else { + suffix = Substring(leafName, lastDot); // include '.' + rootName = Substring(leafName, 0, lastDot); // strip suffix and dot + } + + if (longName) { + int32_t maxRootLength = (kMaxFilenameLength - + (pathName.Length() - leafName.Length()) - + suffix.Length() - kMaxSequenceNumberLength); + + // We cannot create an item inside a directory whose name is too long. + // Also, ensure that at least one character remains after we truncate + // the root name, as we don't want to end up with an empty leaf name. + if (maxRootLength < 2) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + +#ifdef XP_WIN + // ensure that we don't cut the name in mid-UTF16-character + rootName.SetLength(NS_IS_LOW_SURROGATE(rootName[maxRootLength]) ? + maxRootLength - 1 : maxRootLength); + SetLeafName(rootName + suffix); +#else + if (NS_IsNativeUTF8()) { + // ensure that we don't cut the name in mid-UTF8-character + // (assume the name is valid UTF8 to begin with) + while (UTF8traits::isInSeq(rootName[maxRootLength])) { + --maxRootLength; + } + + // Another check to avoid ending up with an empty leaf name. + if (maxRootLength == 0 && suffix.IsEmpty()) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + } + + rootName.SetLength(maxRootLength); + SetNativeLeafName(rootName + suffix); +#endif + nsresult rvCreate = Create(aType, aAttributes); + if (rvCreate != NS_ERROR_FILE_ALREADY_EXISTS) { + return rvCreate; + } + } + + for (int indx = 1; indx < 10000; ++indx) { + // start with "Picture-1.jpg" after "Picture.jpg" exists +#ifdef XP_WIN + SetLeafName(rootName + + NS_ConvertASCIItoUTF16(nsPrintfCString("-%d", indx)) + + suffix); +#else + SetNativeLeafName(rootName + nsPrintfCString("-%d", indx) + suffix); +#endif + rv = Create(aType, aAttributes); + if (NS_SUCCEEDED(rv) || rv != NS_ERROR_FILE_ALREADY_EXISTS) { + return rv; + } + } + + // The disk is full, sort of + return NS_ERROR_FILE_TOO_BIG; +} + +#if defined(XP_WIN) +static const char16_t kPathSeparatorChar = '\\'; +#elif defined(XP_UNIX) +static const char16_t kPathSeparatorChar = '/'; +#else +#error Need to define file path separator for your platform +#endif + +static void +SplitPath(char16_t* aPath, nsTArray<char16_t*>& aNodeArray) +{ + if (*aPath == 0) { + return; + } + + if (*aPath == kPathSeparatorChar) { + aPath++; + } + aNodeArray.AppendElement(aPath); + + for (char16_t* cp = aPath; *cp != 0; ++cp) { + if (*cp == kPathSeparatorChar) { + *cp++ = 0; + if (*cp == 0) { + break; + } + aNodeArray.AppendElement(cp); + } + } +} + + +NS_IMETHODIMP +nsLocalFile::GetRelativeDescriptor(nsIFile* aFromFile, nsACString& aResult) +{ + if (NS_WARN_IF(!aFromFile)) { + return NS_ERROR_INVALID_ARG; + } + + // + // aResult will be UTF-8 encoded + // + + nsresult rv; + aResult.Truncate(0); + + nsAutoString thisPath, fromPath; + AutoTArray<char16_t*, 32> thisNodes; + AutoTArray<char16_t*, 32> fromNodes; + + rv = GetPath(thisPath); + if (NS_FAILED(rv)) { + return rv; + } + rv = aFromFile->GetPath(fromPath); + if (NS_FAILED(rv)) { + return rv; + } + + // get raw pointer to mutable string buffer + char16_t* thisPathPtr; + thisPath.BeginWriting(thisPathPtr); + char16_t* fromPathPtr; + fromPath.BeginWriting(fromPathPtr); + + SplitPath(thisPathPtr, thisNodes); + SplitPath(fromPathPtr, fromNodes); + + size_t nodeIndex; + for (nodeIndex = 0; + nodeIndex < thisNodes.Length() && nodeIndex < fromNodes.Length(); + ++nodeIndex) { +#ifdef XP_WIN + if (_wcsicmp(char16ptr_t(thisNodes[nodeIndex]), + char16ptr_t(fromNodes[nodeIndex]))) { + break; + } +#else + if (nsCRT::strcmp(thisNodes[nodeIndex], fromNodes[nodeIndex])) { + break; + } +#endif + } + + size_t branchIndex = nodeIndex; + for (nodeIndex = branchIndex; nodeIndex < fromNodes.Length(); ++nodeIndex) { + aResult.AppendLiteral("../"); + } + for (nodeIndex = branchIndex; nodeIndex < thisNodes.Length(); ++nodeIndex) { + NS_ConvertUTF16toUTF8 nodeStr(thisNodes[nodeIndex]); + aResult.Append(nodeStr); + if (nodeIndex + 1 < thisNodes.Length()) { + aResult.Append('/'); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetRelativeDescriptor(nsIFile* aFromFile, + const nsACString& aRelativeDesc) +{ + NS_NAMED_LITERAL_CSTRING(kParentDirStr, "../"); + + nsCOMPtr<nsIFile> targetFile; + nsresult rv = aFromFile->Clone(getter_AddRefs(targetFile)); + if (NS_FAILED(rv)) { + return rv; + } + + // + // aRelativeDesc is UTF-8 encoded + // + + nsCString::const_iterator strBegin, strEnd; + aRelativeDesc.BeginReading(strBegin); + aRelativeDesc.EndReading(strEnd); + + nsCString::const_iterator nodeBegin(strBegin), nodeEnd(strEnd); + nsCString::const_iterator pos(strBegin); + + nsCOMPtr<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); +} + +NS_IMETHODIMP +nsLocalFile::GetRelativePath(nsIFile* aFromFile, nsACString& aResult) +{ + return GetRelativeDescriptor(aFromFile, aResult); +} + +NS_IMETHODIMP +nsLocalFile::SetRelativePath(nsIFile* aFromFile, + const nsACString& aRelativePath) +{ + return SetRelativeDescriptor(aFromFile, aRelativePath); +} diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp new file mode 100644 index 0000000000..194e5835e7 --- /dev/null +++ b/xpcom/io/nsLocalFileUnix.cpp @@ -0,0 +1,2715 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Implementation of nsIFile for "unixy" systems. + */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/Sprintf.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(HAVE_SYS_QUOTA_H) && defined(HAVE_LINUX_QUOTA_H) +#define USE_LINUX_QUOTACTL +#include <sys/mount.h> +#include <sys/quota.h> +#include <sys/sysmacros.h> +#ifndef BLOCK_SIZE +#define BLOCK_SIZE 1024 /* kernel block size */ +#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" +#include "prlink.h" + +#ifdef MOZ_WIDGET_GTK +#include "nsIGIOService.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 + +#ifdef MOZ_WIDGET_ANDROID +#include "GeneratedJNIWrappers.h" +#include "nsIMIMEService.h" +#include <linux/magic.h> +#endif + +#ifdef MOZ_ENABLE_CONTENTACTION +#include <contentaction/contentaction.h> +#endif + +#include "nsNativeCharsetUtils.h" +#include "nsTraceRefcnt.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 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* aParent, bool aIgnored); + +private: + ~nsDirEnumeratorUnix(); + +protected: + NS_IMETHOD GetNextEntry(); + + DIR* mDir; + struct dirent* mEntry; + nsCString mParentPath; +}; + +nsDirEnumeratorUnix::nsDirEnumeratorUnix() : + mDir(nullptr), + mEntry(nullptr) +{ +} + +nsDirEnumeratorUnix::~nsDirEnumeratorUnix() +{ + Close(); +} + +NS_IMPL_ISUPPORTS(nsDirEnumeratorUnix, nsISimpleEnumerator, + nsIDirectoryEnumerator) + +NS_IMETHODIMP +nsDirEnumeratorUnix::Init(nsLocalFile* aParent, + bool aResolveSymlinks /*ignored*/) +{ + nsAutoCString dirPath; + if (NS_FAILED(aParent->GetNativePath(dirPath)) || + dirPath.IsEmpty()) { + return NS_ERROR_FILE_INVALID_PATH; + } + + if (NS_FAILED(aParent->GetNativePath(mParentPath))) { + return NS_ERROR_FAILURE; + } + + mDir = opendir(dirPath.get()); + if (!mDir) { + return NSRESULT_FOR_ERRNO(); + } + return GetNextEntry(); +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::HasMoreElements(bool* aResult) +{ + *aResult = mDir && mEntry; + if (!*aResult) { + Close(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::GetNext(nsISupports** aResult) +{ + nsCOMPtr<nsIFile> file; + nsresult rv = GetNextFile(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + NS_IF_ADDREF(*aResult = 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** aResult) +{ + nsresult rv; + if (!mDir || !mEntry) { + *aResult = nullptr; + return NS_OK; + } + + nsCOMPtr<nsIFile> file = new nsLocalFile(); + + if (NS_FAILED(rv = file->InitWithNativePath(mParentPath)) || + NS_FAILED(rv = file->AppendNative(nsDependentCString(mEntry->d_name)))) { + return rv; + } + + file.forget(aResult); + return GetNextEntry(); +} + +NS_IMETHODIMP +nsDirEnumeratorUnix::Close() +{ + if (mDir) { + closedir(mDir); + mDir = nullptr; + } + return NS_OK; +} + +nsLocalFile::nsLocalFile() +{ +} + +nsLocalFile::nsLocalFile(const nsLocalFile& aOther) + : mPath(aOther.mPath) +{ +} + +#ifdef MOZ_WIDGET_COCOA +NS_IMPL_ISUPPORTS(nsLocalFile, + nsILocalFileMac, + nsILocalFile, + nsIFile, + nsIHashable) +#else +NS_IMPL_ISUPPORTS(nsLocalFile, + nsILocalFile, + nsIFile, + nsIHashable) +#endif + +nsresult +nsLocalFile::nsLocalFileConstructor(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr) +{ + if (NS_WARN_IF(!aInstancePtr)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + *aInstancePtr = nullptr; + + nsCOMPtr<nsIFile> inst = new nsLocalFile(); + 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** aFile) +{ + // Just copy-construct ourselves + RefPtr<nsLocalFile> copy = new nsLocalFile(*this); + copy.forget(aFile); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::InitWithNativePath(const nsACString& aFilePath) +{ + if (aFilePath.EqualsLiteral("~") || + Substring(aFilePath, 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 (aFilePath.Length() > 2) { + mPath.Append(Substring(aFilePath, 1, aFilePath.Length() - 1)); + } + } else { + if (aFilePath.IsEmpty() || aFilePath.First() != '/') { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + mPath = aFilePath; + } + + // 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 aPermissions) +{ + // <jband> I promise to play nice + char* buffer = mPath.BeginWriting(); + char* 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, aPermissions); + 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 aFlags, int32_t aMode, + PRFileDesc** aResult) +{ + *aResult = PR_Open(mPath.get(), aFlags, aMode); + if (!*aResult) { + return NS_ErrorAccordingToNSPR(); + } + + if (aFlags & DELETE_ON_CLOSE) { + PR_Delete(mPath.get()); + } + +#if defined(HAVE_POSIX_FADVISE) + if (aFlags & OS_READAHEAD) { + posix_fadvise(PR_FileDesc2NativeHandle(*aResult), 0, 0, + POSIX_FADV_SEQUENTIAL); + } +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult) +{ + *aResult = fopen(mPath.get(), aMode); + if (!*aResult) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +static int +do_create(const char* aPath, int aFlags, mode_t aMode, PRFileDesc** aResult) +{ + *aResult = PR_Open(aPath, aFlags, aMode); + return *aResult ? 0 : -1; +} + +static int +do_mkdir(const char* aPath, int aFlags, mode_t aMode, PRFileDesc** aResult) +{ + *aResult = nullptr; + return mkdir(aPath, aMode); +} + +nsresult +nsLocalFile::CreateAndKeepOpen(uint32_t aType, int aFlags, + uint32_t aPermissions, PRFileDesc** aResult) +{ + if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) { + return NS_ERROR_FILE_UNKNOWN_TYPE; + } + + int (*createFunc)(const char*, int, mode_t, PRFileDesc**) = + (aType == NORMAL_FILE_TYPE) ? do_create : do_mkdir; + + int result = createFunc(mPath.get(), aFlags, aPermissions, aResult); + 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 = aPermissions; + if (aPermissions & S_IRUSR) { + dirperm |= S_IXUSR; + } + if (aPermissions & S_IRGRP) { + dirperm |= S_IXGRP; + } + if (aPermissions & S_IROTH) { + dirperm |= S_IXOTH; + } + +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: perm = %o, dirperm = %o\n", aPermissions, + 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(), aFlags, aPermissions, aResult); + } + return NSRESULT_FOR_RETURN(result); +} + +NS_IMETHODIMP +nsLocalFile::Create(uint32_t aType, uint32_t aPermissions) +{ + PRFileDesc* junk = nullptr; + nsresult rv = CreateAndKeepOpen(aType, + PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE | + PR_EXCL, + aPermissions, + &junk); + if (junk) { + PR_Close(junk); + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::AppendNative(const nsACString& aFragment) +{ + if (aFragment.IsEmpty()) { + return NS_OK; + } + + // only one component of path can be appended + nsACString::const_iterator begin, end; + if (FindCharInReadable('/', aFragment.BeginReading(begin), + aFragment.EndReading(end))) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + return AppendRelativeNativePath(aFragment); +} + +NS_IMETHODIMP +nsLocalFile::AppendRelativeNativePath(const nsACString& aFragment) +{ + if (aFragment.IsEmpty()) { + return NS_OK; + } + + // No leading '/' + if (aFragment.First() == '/') { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + if (!mPath.EqualsLiteral("/")) { + mPath.Append('/'); + } + mPath.Append(aFragment); + + 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& aBegin, + nsACString::const_iterator& aEnd) +{ + // XXX perhaps we should cache this?? + + mPath.BeginReading(aBegin); + mPath.EndReading(aEnd); + + nsACString::const_iterator it = aEnd; + nsACString::const_iterator stop = aBegin; + --stop; + while (--it != stop) { + if (*it == '/') { + aBegin = ++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& aResult) +{ + aResult = mPath; + return NS_OK; +} + +nsresult +nsLocalFile::GetNativeTargetPathName(nsIFile* aNewParent, + const nsACString& aNewName, + nsACString& aResult) +{ + nsresult rv; + nsCOMPtr<nsIFile> oldParent; + + if (!aNewParent) { + if (NS_FAILED(rv = GetParent(getter_AddRefs(oldParent)))) { + return rv; + } + aNewParent = oldParent.get(); + } else { + // check to see if our target directory exists + bool targetExists; + if (NS_FAILED(rv = aNewParent->Exists(&targetExists))) { + return rv; + } + + if (!targetExists) { + // XXX create the new directory with some permissions + rv = aNewParent->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 = aNewParent->IsDirectory(&targetIsDirectory))) { + return rv; + } + if (!targetIsDirectory) { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + } + } + + nsACString::const_iterator nameBegin, nameEnd; + if (!aNewName.IsEmpty()) { + aNewName.BeginReading(nameBegin); + aNewName.EndReading(nameEnd); + } else { + LocateNativeLeafName(nameBegin, nameEnd); + } + + nsAutoCString dirName; + if (NS_FAILED(rv = aNewParent->GetNativePath(dirName))) { + return rv; + } + + aResult = dirName + NS_LITERAL_CSTRING("/") + Substring(nameBegin, nameEnd); + return NS_OK; +} + +nsresult +nsLocalFile::CopyDirectoryTo(nsIFile* aNewParent) +{ + 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(aNewParent, EmptyCString()); + } + + if (NS_FAILED(rv = Equals(aNewParent, &dirCheck))) { + return rv; + } + if (dirCheck) { + // can't copy dir to itself + return NS_ERROR_INVALID_ARG; + } + + if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) { + return rv; + } + // get the dirs old permissions + if (NS_FAILED(rv = GetPermissions(&oldPerms))) { + return rv; + } + if (!dirCheck) { + if (NS_FAILED(rv = aNewParent->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 = aNewParent->AppendNative(leafName))) { + return rv; + } + if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) { + return rv; + } + if (dirCheck) { + return NS_ERROR_FILE_ALREADY_EXISTS; // dest exists + } + if (NS_FAILED(rv = aNewParent->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<nsISupports> supports; + nsCOMPtr<nsIFile> entry; + rv = dirIterator->GetNext(getter_AddRefs(supports)); + entry = do_QueryInterface(supports); + if (NS_FAILED(rv) || !entry) { + 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 = aNewParent->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(aNewParent, 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* aNewParent, const nsACString& aNewName) +{ + nsresult rv; + // check to make sure that this has been initialized properly + CHECK_mPath(); + + // we copy the parent here so 'aNewParent' remains immutable + nsCOMPtr <nsIFile> workParent; + if (aNewParent) { + if (NS_FAILED(rv = aNewParent->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 (!aNewName.IsEmpty()) { + if (NS_FAILED(rv = workParent->AppendNative(aNewName))) { + 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, aNewName, 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(); + 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; + + // record PR_Write() error for better error message later. + nsresult saved_write_error = NS_OK; + nsresult saved_read_error = NS_OK; + nsresult saved_read_close_error = NS_OK; + nsresult saved_write_close_error = NS_OK; + + // DONE: Does PR_Read() return bytesRead < 0 for error? + // Yes., The errors from PR_Read are not so common and + // the value may not have correspondence in NS_ERROR_*, but + // we do catch it still, immediately after while() loop. + // We can differentiate errors pf PR_Read and PR_Write by + // looking at saved_write_error value. If PR_Write error occurs (and not + // PR_Read() error), save_write_error is not NS_OK. + + 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) { + saved_write_error = NSRESULT_FOR_ERRNO(); + bytesRead = -1; + break; + } + NS_ASSERTION(bytesWritten == bytesRead, "short PR_Write?"); + +#ifdef DEBUG_blizzard + totalWritten += bytesWritten; +#endif + } + + // TODO/FIXME: If CIFS (and NFS?) may force read/write to return EINTR, + // we are better off to prepare for retrying. But we need confirmation if + // EINTR is returned. + + // Record error if PR_Read() failed. + // Must be done before any other I/O which may reset errno. + if (bytesRead < 0 && saved_write_error == NS_OK) { + saved_read_error = NSRESULT_FOR_ERRNO(); + } + +#ifdef DEBUG_blizzard + printf("read %d bytes, wrote %d bytes\n", + totalRead, totalWritten); +#endif + + // DONE: Errors of close can occur. Read man page of + // close(2); + // This is likely to happen if the file system is remote file + // system (NFS, CIFS, etc.) and network outage occurs. + // At least, we should tell the user that filesystem/disk is + // hosed (possibly due to network error, hard disk failure, + // etc.) so that users can take remedial action. + + // close the files + if (PR_Close(newFD) < 0) { + saved_write_close_error = NSRESULT_FOR_ERRNO(); +#if DEBUG + // This error merits printing. + fprintf(stderr, "ERROR: PR_Close(newFD) returned error. errno = %d\n", errno); +#endif + } + + if (PR_Close(oldFD) < 0) { + saved_read_close_error = NSRESULT_FOR_ERRNO(); +#if DEBUG + fprintf(stderr, "ERROR: PR_Close(oldFD) returned error. errno = %d\n", errno); +#endif + } + + // Let us report the failure to write and read. + // check for write/read error after cleaning up + if (bytesRead < 0) { + if (saved_write_error != NS_OK) { + return saved_write_error; + } else if (saved_read_error != NS_OK) { + return saved_read_error; + } +#if DEBUG + else { // sanity check. Die and debug. + MOZ_ASSERT(0); + } +#endif + } + + if (saved_write_close_error != NS_OK) { + return saved_write_close_error; + } + if (saved_read_close_error != NS_OK) { + return saved_read_close_error; + } + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParent, + const nsACString& aNewName) +{ + return CopyToNative(aNewParent, aNewName); +} + +NS_IMETHODIMP +nsLocalFile::MoveToNative(nsIFile* aNewParent, const nsACString& aNewName) +{ + 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(aNewParent, aNewName, newPathName); + if (NS_FAILED(rv)) { + return rv; + } + + // try for atomic rename, falling back to copy/delete + if (rename(mPath.get(), newPathName.get()) < 0) { + if (errno == EXDEV) { + rv = CopyToNative(aNewParent, aNewName); + 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 aRecursive) +{ + 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 (aRecursive) { + nsDirEnumeratorUnix* dir = new nsDirEnumeratorUnix(); + + 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(aRecursive); + +#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(); + if (NS_WARN_IF(!aLastModTime)) { + return NS_ERROR_INVALID_ARG; + } + + 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(); + if (NS_WARN_IF(!aLastModTimeOfLink)) { + return NS_ERROR_INVALID_ARG; + } + + 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) +{ + if (NS_WARN_IF(!aPermissions)) { + return NS_ERROR_INVALID_ARG; + } + ENSURE_STAT_CACHE(); + *aPermissions = NORMALIZE_PERMS(mCachedStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissionsOfLink) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aPermissionsOfLink)) { + return NS_ERROR_INVALID_ARG; + } + + 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) +{ + if (NS_WARN_IF(!aFileSize)) { + return NS_ERROR_INVALID_ARG; + } + *aFileSize = 0; + ENSURE_STAT_CACHE(); + + 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(); + if (NS_WARN_IF(!aFileSize)) { + return NS_ERROR_INVALID_ARG; + } + + 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 aDeviceMajor, int aDeviceMinor, nsACString& aDeviceName) +{ + bool ret = false; + + const int kMountInfoLineLength = 200; + const int kMountInfoDevPosition = 6; + + char mountinfoLine[kMountInfoLineLength]; + char deviceNum[kMountInfoLineLength]; + + SprintfLiteral(deviceNum, "%d:%d", aDeviceMajor, aDeviceMinor); + + 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(mountinfoLine, kMountInfoLineLength, f)) { + char* p_dev = strstr(mountinfoLine, deviceNum); + + for (int i = 0; i < kMountInfoDevPosition && p_dev; ++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'; + aDeviceName.Assign(p_dev); + ret = true; + break; + } + } + } + + fclose(f); + return ret; +} +#endif + +NS_IMETHODIMP +nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) +{ + if (NS_WARN_IF(!aDiskSpaceAvailable)) { + return NS_ERROR_INVALID_ARG; + } + + // 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; + // dqb_bhardlimit is count of BLOCK_SIZE blocks, dqb_curspace is bytes + if ((BLOCK_SIZE * dq.dqb_bhardlimit) > dq.dqb_curspace) + QuotaSpaceAvailable = int64_t(BLOCK_SIZE * 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(); + if (NS_WARN_IF(!aParent)) { + return NS_ERROR_INVALID_ARG; + } + *aParent = nullptr; + + // if '/' we are at the top of the volume, return null + if (mPath.EqualsLiteral("/")) { + return NS_OK; + } + + // <brendan, after jband> I promise to play nice + char* buffer = mPath.BeginWriting(); + // find the last significant slash in buffer + char* 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_FAILED(rv)) { + return rv; + } + + localFile.forget(aParent); + return NS_OK; +} + +/* + * The results of Exists, isWritable and isReadable are not cached. + */ + + +NS_IMETHODIMP +nsLocalFile::Exists(bool* aResult) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = (access(mPath.get(), F_OK) == 0); + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::IsWritable(bool* aResult) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = (access(mPath.get(), W_OK) == 0); + if (*aResult || errno == EACCES) { + return NS_OK; + } + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::IsReadable(bool* aResult) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = (access(mPath.get(), R_OK) == 0); + if (*aResult || errno == EACCES) { + return NS_OK; + } + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::IsExecutable(bool* aResult) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + // 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(char16_t('.')); + if (dotIdx != kNotFound) { + // Convert extension to lower case. + char16_t* 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. + *aResult = 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) { + *aResult = true; + return NS_OK; + } + } +#endif + + // Then check the execute bit. + *aResult = (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 (*aResult) { + struct STAT buf; + + *aResult = (STAT(mPath.get(), &buf) == 0); + if (*aResult || errno == EACCES) { + *aResult = *aResult && (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)); + return NS_OK; + } + + return NSRESULT_FOR_ERRNO(); + } +#endif + if (*aResult || errno == EACCES) { + return NS_OK; + } + return NSRESULT_FOR_ERRNO(); +} + +NS_IMETHODIMP +nsLocalFile::IsDirectory(bool* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + ENSURE_STAT_CACHE(); + *aResult = S_ISDIR(mCachedStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsFile(bool* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + ENSURE_STAT_CACHE(); + *aResult = S_ISREG(mCachedStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsHidden(bool* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + nsACString::const_iterator begin, end; + LocateNativeLeafName(begin, end); + *aResult = (*begin == '.'); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSymlink(bool* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + CHECK_mPath(); + + struct STAT symStat; + if (LSTAT(mPath.get(), &symStat) == -1) { + return NSRESULT_FOR_ERRNO(); + } + *aResult = S_ISLNK(symStat.st_mode); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSpecial(bool* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + ENSURE_STAT_CACHE(); + *aResult = 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* aInFile, bool* aResult) +{ + if (NS_WARN_IF(!aInFile)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + + nsAutoCString inPath; + nsresult rv = aInFile->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. + *aResult = !strcmp(inPath.get(), mPath.get()); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Contains(nsIFile* aInFile, bool* aResult) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aInFile)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString inPath; + nsresult rv; + + if (NS_FAILED(rv = aInFile->GetNativePath(inPath))) { + return rv; + } + + *aResult = false; + + ssize_t len = mPath.Length(); + if (strncmp(mPath.get(), inPath.get(), len) == 0) { + // Now make sure that the |aInFile|'s path has a separator at len, + // which implies that it has more components after len. + if (inPath[len] == '/') { + *aResult = true; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetNativeTarget(nsACString& aResult) +{ + CHECK_mPath(); + aResult.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*)moz_xmalloc(size + 1); + if (!target) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (readlink(mPath.get(), target, (size_t)size) < 0) { + 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(aResult))) { + break; + } + self = parent; + } else { + aResult = target; + } + + const nsPromiseFlatCString& flatRetval = PromiseFlatCString(aResult); + + // 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*)moz_xrealloc(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'; + } + + free(target); + + if (NS_FAILED(rv)) { + aResult.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** aEntries) +{ + RefPtr<nsDirEnumeratorUnix> dir = new nsDirEnumeratorUnix(); + + nsresult rv = dir->Init(this, false); + if (NS_FAILED(rv)) { + *aEntries = nullptr; + } else { + dir.forget(aEntries); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::Load(PRLibrary** aResult) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcnt::SetActivityIsLegal(false); +#endif + + *aResult = PR_LoadLibrary(mPath.get()); + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcnt::SetActivityIsLegal(true); +#endif + + if (!*aResult) { + 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); + if (!giovfs) { + return NS_ERROR_FAILURE; + } + + bool isDirectory; + if (NS_FAILED(IsDirectory(&isDirectory))) { + return NS_ERROR_FAILURE; + } + + if (isDirectory) { + return giovfs->ShowURIForInput(mPath); + } else if (NS_SUCCEEDED(giovfs->OrgFreedesktopFileManager1ShowItems(mPath))) { + return NS_OK; + } 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; + } + + return giovfs->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 + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (!giovfs) { + return NS_ERROR_FAILURE; + } + + return giovfs->ShowURIForInput(mPath); +#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); + } + + nsAutoCString fileUri = NS_LITERAL_CSTRING("file://") + mPath; + return java::GeckoAppShell::OpenUriExternal( + NS_ConvertUTF8toUTF16(fileUri), + NS_ConvertUTF8toUTF16(type), + EmptyString(), + EmptyString(), + EmptyString(), + EmptyString()) ? 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& aPath, bool aFollowSymlinks, + nsIFile** aResult) +{ + RefPtr<nsLocalFile> file = new nsLocalFile(); + + file->SetFollowLinks(aFollowSymlinks); + + if (!aPath.IsEmpty()) { + nsresult rv = file->InitWithNativePath(aPath); + if (NS_FAILED(rv)) { + return rv; + } + } + file.forget(aResult); + 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& aFilePath) +{ + SET_UCS(InitWithNativePath, aFilePath); +} +nsresult +nsLocalFile::Append(const nsAString& aNode) +{ + SET_UCS(AppendNative, aNode); +} +nsresult +nsLocalFile::AppendRelativePath(const nsAString& aNode) +{ + SET_UCS(AppendRelativeNativePath, aNode); +} +nsresult +nsLocalFile::GetLeafName(nsAString& aLeafName) +{ + GET_UCS(GetNativeLeafName, aLeafName); +} +nsresult +nsLocalFile::SetLeafName(const nsAString& aLeafName) +{ + SET_UCS(SetNativeLeafName, aLeafName); +} +nsresult +nsLocalFile::GetPath(nsAString& aResult) +{ + return NS_CopyNativeToUnicode(mPath, aResult); +} +nsresult +nsLocalFile::CopyTo(nsIFile* aNewParentDir, const nsAString& aNewName) +{ + SET_UCS_2ARGS_2(CopyToNative , aNewParentDir, aNewName); +} +nsresult +nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir, + const nsAString& aNewName) +{ + SET_UCS_2ARGS_2(CopyToFollowingLinksNative , aNewParentDir, aNewName); +} +nsresult +nsLocalFile::MoveTo(nsIFile* aNewParentDir, const nsAString& aNewName) +{ + SET_UCS_2ARGS_2(MoveToNative, aNewParentDir, aNewName); +} + +NS_IMETHODIMP +nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName) +{ + SET_UCS_2ARGS_2(RenameToNative, aNewParentDir, aNewName); +} + +NS_IMETHODIMP +nsLocalFile::RenameToNative(nsIFile* aNewParentDir, const nsACString& aNewName) +{ + 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(aNewParentDir, aNewName, newPathName); + if (NS_FAILED(rv)) { + return rv; + } + + // try for atomic rename + if (rename(mPath.get(), newPathName.get()) < 0) { + if (errno == EXDEV) { + rv = NS_ERROR_FILE_ACCESS_DENIED; + } else { + rv = NSRESULT_FOR_ERRNO(); + } + } + + return rv; +} + +nsresult +nsLocalFile::GetTarget(nsAString& aResult) +{ + GET_UCS(GetNativeTarget, aResult); +} + +// 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& aPath, bool aFollowLinks, nsIFile** aResult) +{ + nsAutoCString buf; + nsresult rv = NS_CopyUnicodeToNative(aPath, buf); + if (NS_FAILED(rv)) { + return rv; + } + return NS_NewNativeLocalFile(buf, aFollowLinks, aResult); +} + +//----------------------------------------------------------------------------- +// 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, + nullptr, 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, true, path, PATH_MAX)) { + nsDependentCString nativePath((char*)path); + return InitWithNativePath(nativePath); + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::InitWithFSRef(const FSRef* aFSRef) +{ + if (NS_WARN_IF(!aFSRef)) { + return NS_ERROR_INVALID_ARG; + } + + CFURLRef newURLRef = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aFSRef); + if (newURLRef) { + nsresult rv = InitWithCFURL(newURLRef); + ::CFRelease(newURLRef); + return rv; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsLocalFile::GetCFURL(CFURLRef* aResult) +{ + CHECK_mPath(); + + bool isDir; + IsDirectory(&isDir); + *aResult = ::CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, + (UInt8*)mPath.get(), + mPath.Length(), + isDir); + + return (*aResult ? NS_OK : NS_ERROR_FAILURE); +} + +NS_IMETHODIMP +nsLocalFile::GetFSRef(FSRef* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = NS_ERROR_FAILURE; + + CFURLRef url = nullptr; + if (NS_SUCCEEDED(GetCFURL(&url))) { + if (::CFURLGetFSRef(url, aResult)) { + rv = NS_OK; + } + ::CFRelease(url); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetFSSpec(FSSpec* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + FSRef fsRef; + nsresult rv = GetFSRef(&fsRef); + if (NS_SUCCEEDED(rv)) { + OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNone, nullptr, nullptr, + aResult, nullptr); + return MacErrorMapper(err); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetFileSizeWithResFork(int64_t* aFileSizeWithResFork) +{ + if (NS_WARN_IF(!aFileSizeWithResFork)) { + return NS_ERROR_INVALID_ARG; + } + + 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, nullptr); + 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, nullptr); + 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, nullptr); + if (err != noErr) { + return MacErrorMapper(err); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsPackage(bool* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = 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; + } + + *aResult = !!(info.flags & kLSItemInfoIsPackage); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetBundleDisplayName(nsAString& aOutBundleName) +{ + 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" + aOutBundleName = Substring(name, 0, length - 4); + } else { + aOutBundleName = name; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetBundleIdentifier(nsACString& aOutBundleIdentifier) +{ + nsresult rv = NS_ERROR_FAILURE; + + CFURLRef urlRef; + if (NS_SUCCEEDED(GetCFURL(&urlRef))) { + CFBundleRef bundle = ::CFBundleCreate(nullptr, urlRef); + if (bundle) { + CFStringRef bundleIdentifier = ::CFBundleGetIdentifier(bundle); + if (bundleIdentifier) { + rv = CFStringReftoUTF8(bundleIdentifier, aOutBundleIdentifier); + } + ::CFRelease(bundle); + } + ::CFRelease(urlRef); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetBundleContentsLastModifiedTime(int64_t* aLastModTime) +{ + CHECK_mPath(); + if (NS_WARN_IF(!aLastModTime)) { + return NS_ERROR_INVALID_ARG; + } + + 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) +{ + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + + 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** aResult) +{ + RefPtr<nsLocalFile> file = new nsLocalFile(); + + file->SetFollowLinks(aFollowLinks); + + nsresult rv = file->InitWithFSRef(aFSRef); + if (NS_FAILED(rv)) { + return rv; + } + file.forget(aResult); + return NS_OK; +} + +nsresult +NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowLinks, + nsILocalFileMac** aResult) +{ + RefPtr<nsLocalFile> file = new nsLocalFile(); + + file->SetFollowLinks(aFollowLinks); + + nsresult rv = file->InitWithCFURL(aURL); + if (NS_FAILED(rv)) { + return rv; + } + file.forget(aResult); + return NS_OK; +} + +#endif diff --git a/xpcom/io/nsLocalFileUnix.h b/xpcom/io/nsLocalFileUnix.h new file mode 100644 index 0000000000..9a3e7d6afe --- /dev/null +++ b/xpcom/io/nsLocalFileUnix.h @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * 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 + +// stat64 and lstat64 are deprecated on OS X. Normal stat and lstat are +// 64-bit by default on OS X 10.6+. +#if defined(HAVE_STAT64) && defined(HAVE_LSTAT64) && !defined(XP_DARWIN) + #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 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* aOuter, + const nsIID& aIID, + void** aInstancePtr); + + NS_DECL_THREADSAFE_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& aOther); + ~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* aNewParent); + nsresult CreateAllAncestors(uint32_t aPermissions); + nsresult GetNativeTargetPathName(nsIFile* aNewParent, + const nsACString& aNewName, + nsACString& aResult); + + bool FillStatCache(); + + nsresult CreateAndKeepOpen(uint32_t aType, int aFlags, + uint32_t aPermissions, PRFileDesc** aResult); +}; + +#endif /* _nsLocalFileUNIX_H_ */ diff --git a/xpcom/io/nsLocalFileWin.cpp b/xpcom/io/nsLocalFileWin.cpp new file mode 100644 index 0000000000..3a7e570f56 --- /dev/null +++ b/xpcom/io/nsLocalFileWin.cpp @@ -0,0 +1,3787 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/WindowsVersion.h" + +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsMemory.h" +#include "GeckoProfiler.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 "prlink.h" + +#include "mozilla/Mutex.h" +#include "SpecialSystemDirectory.h" + +#include "nsTraceRefcnt.h" +#include "nsXPCOMCIDInternal.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +#include "nsIWindowMediator.h" +#include "mozIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsIWidget.h" +#include "mozilla/WidgetUtils.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 + +static HWND +GetMostRecentNavigatorHWND() +{ + nsresult rv; + nsCOMPtr<nsIWindowMediator> winMediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + return nullptr; + } + + nsCOMPtr<mozIDOMWindowProxy> navWin; + rv = winMediator->GetMostRecentWindow(u"navigator:browser", + getter_AddRefs(navWin)); + if (NS_FAILED(rv) || !navWin) { + return nullptr; + } + + nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin); + nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(win); + if (!widget) { + return nullptr; + } + + return reinterpret_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW)); +} + + +/** + * A runnable to dispatch back to the main thread when + * AsyncRevealOperation completes. +*/ +class AsyncLocalFileWinDone : public Runnable +{ +public: + AsyncLocalFileWinDone() : + mWorkerThread(do_GetCurrentThread()) + { + // Objects of this type must only be created on worker threads + MOZ_ASSERT(!NS_IsMainThread()); + } + + NS_IMETHOD Run() override + { + // 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 AsyncRevealOperation : public Runnable +{ +public: + explicit AsyncRevealOperation(const nsAString& aResolvedPath) + : mResolvedPath(aResolvedPath) + { + } + + NS_IMETHOD Run() override + { + MOZ_ASSERT(!NS_IsMainThread(), + "AsyncRevealOperation should not be run on the main thread!"); + + bool doCoUninitialize = SUCCEEDED( + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)); + Reveal(); + if (doCoUninitialize) { + CoUninitialize(); + } + + // Send the result back to the main thread so that this thread can be + // cleanly shut down + 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. + LPITEMIDLIST dir = ILCreateFromPathW(mResolvedPath.get()); + if (!dir) { + return NS_ERROR_FAILURE; + } + + LPCITEMIDLIST 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. + LPITEMIDLIST dir = ILCreateFromPathW(parentDirectoryPath); + if (!dir) { + return NS_ERROR_FAILURE; + } + + // Set the item in the directory to select to the file we want to reveal. + LPITEMIDLIST item = ILCreateFromPathW(mResolvedPath.get()); + if (!item) { + CoTaskMemFree(dir); + return NS_ERROR_FAILURE; + } + + LPCITEMIDLIST 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; + } + + // Stores the path to perform the operation on + nsString mResolvedPath; +}; + +class nsDriveEnumerator : public nsISimpleEnumerator +{ +public: + nsDriveEnumerator(); + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + nsresult Init(); +private: + virtual ~nsDriveEnumerator(); + + /* 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* aIn, WCHAR* aOut); + nsresult SetShortcut(bool aUpdateExisting, + const WCHAR* aShortcutPath, + const WCHAR* aTargetPath, + const WCHAR* aWorkingDir, + const WCHAR* aArgs, + const WCHAR* aDescription, + const WCHAR* aIconFile, + int32_t aIconIndex); + +private: + Mutex mLock; + RefPtr<IPersistFile> mPersistFile; + RefPtr<IShellLinkW> mShellLink; +}; + +ShortcutResolver::ShortcutResolver() : + mLock("ShortcutResolver.mLock") +{ + CoInitialize(nullptr); +} + +ShortcutResolver::~ShortcutResolver() +{ + CoUninitialize(); +} + +nsresult +ShortcutResolver::Init() +{ + // Get a pointer to the IPersistFile interface. + if (FAILED(CoCreateInstance(CLSID_ShellLink, + nullptr, + 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* aIn, WCHAR* aOut) +{ + if (!mShellLink) { + return NS_ERROR_FAILURE; + } + + MutexAutoLock lock(mLock); + + if (FAILED(mPersistFile->Load(aIn, STGM_READ)) || + FAILED(mShellLink->Resolve(nullptr, SLR_NO_UI)) || + FAILED(mShellLink->GetPath(aOut, MAX_PATH, nullptr, SLGP_UNCPRIORITY))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult +ShortcutResolver::SetShortcut(bool aUpdateExisting, + const WCHAR* aShortcutPath, + const WCHAR* aTargetPath, + const WCHAR* aWorkingDir, + const WCHAR* aArgs, + const WCHAR* aDescription, + const WCHAR* aIconPath, + int32_t aIconIndex) +{ + if (!mShellLink) { + return NS_ERROR_FAILURE; + } + + if (!aShortcutPath) { + return NS_ERROR_FAILURE; + } + + MutexAutoLock lock(mLock); + + if (aUpdateExisting) { + if (FAILED(mPersistFile->Load(aShortcutPath, STGM_READWRITE))) { + return NS_ERROR_FAILURE; + } + } else { + if (!aTargetPath) { + 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 (aTargetPath && FAILED(mShellLink->SetPath(aTargetPath))) { + return NS_ERROR_FAILURE; + } + + if (aWorkingDir && FAILED(mShellLink->SetWorkingDirectory(aWorkingDir))) { + return NS_ERROR_FAILURE; + } + + if (aArgs && FAILED(mShellLink->SetArguments(aArgs))) { + return NS_ERROR_FAILURE; + } + + if (aDescription && FAILED(mShellLink->SetDescription(aDescription))) { + return NS_ERROR_FAILURE; + } + + if (aIconPath && FAILED(mShellLink->SetIconLocation(aIconPath, aIconIndex))) { + return NS_ERROR_FAILURE; + } + + if (FAILED(mPersistFile->Save(aShortcutPath, + 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(); + 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 aWinErr) +{ + nsresult rv; + + switch (aWinErr) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + case ERROR_NOT_READY: + 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& aPath) +{ + // 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. + MOZ_ASSERT(!aPath.IsEmpty(), "don't pass an empty string"); + int32_t len = aPath.Length(); + return len >= 4 && (StringTail(aPath, 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 +nsresult +OpenFile(const nsAFlatString& aName, + int aOsflags, + int aMode, + bool aShareDelete, + PRFileDesc** aFd) +{ + int32_t access = 0; + + int32_t shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + int32_t disposition = 0; + int32_t attributes = 0; + + if (aShareDelete) { + shareMode |= FILE_SHARE_DELETE; + } + + if (aOsflags & PR_SYNC) { + attributes = FILE_FLAG_WRITE_THROUGH; + } + if (aOsflags & PR_RDONLY || aOsflags & PR_RDWR) { + access |= GENERIC_READ; + } + if (aOsflags & PR_WRONLY || aOsflags & PR_RDWR) { + access |= GENERIC_WRITE; + } + + if (aOsflags & PR_CREATE_FILE && aOsflags & PR_EXCL) { + disposition = CREATE_NEW; + } else if (aOsflags & PR_CREATE_FILE) { + if (aOsflags & PR_TRUNCATE) { + disposition = CREATE_ALWAYS; + } else { + disposition = OPEN_ALWAYS; + } + } else { + if (aOsflags & PR_TRUNCATE) { + disposition = TRUNCATE_EXISTING; + } else { + disposition = OPEN_EXISTING; + } + } + + if (aOsflags & nsIFile::DELETE_ON_CLOSE) { + attributes |= FILE_FLAG_DELETE_ON_CLOSE; + } + + if (aOsflags & 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 (!(aMode & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) && + disposition != OPEN_EXISTING) { + attributes |= FILE_ATTRIBUTE_READONLY; + } + + HANDLE file = ::CreateFileW(aName.get(), access, shareMode, + nullptr, disposition, attributes, nullptr); + + if (file == INVALID_HANDLE_VALUE) { + *aFd = nullptr; + return ConvertWinError(GetLastError()); + } + + *aFd = PR_ImportFile((PROsfd) file); + if (*aFd) { + // 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) + (*aFd)->secret->appendMode = (PR_APPEND & aOsflags) ? 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* aFiletime, PRTime* aPrtm) +{ +#ifdef __GNUC__ + const PRTime _pr_filetime_offset = 116444736000000000LL; +#else + const PRTime _pr_filetime_offset = 116444736000000000i64; +#endif + + MOZ_ASSERT(sizeof(FILETIME) == sizeof(PRTime)); + ::CopyMemory(aPrtm, aFiletime, sizeof(PRTime)); +#ifdef __GNUC__ + *aPrtm = (*aPrtm - _pr_filetime_offset) / 10LL; +#else + *aPrtm = (*aPrtm - _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& aName, PRFileInfo64* aInfo) +{ + WIN32_FILE_ATTRIBUTE_DATA fileData; + + if (aName.IsEmpty() || aName.FindCharInSet(u"?*") != kNotFound) { + return NS_ERROR_INVALID_ARG; + } + + if (!::GetFileAttributesExW(aName.get(), GetFileExInfoStandard, &fileData)) { + return ConvertWinError(GetLastError()); + } + + if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + aInfo->type = PR_FILE_DIRECTORY; + } else { + aInfo->type = PR_FILE_FILE; + } + + aInfo->size = fileData.nFileSizeHigh; + aInfo->size = (aInfo->size << 32) + fileData.nFileSizeLow; + + FileTimeToPRTime(&fileData.ftLastWriteTime, &aInfo->modifyTime); + + if (0 == fileData.ftCreationTime.dwLowDateTime && + 0 == fileData.ftCreationTime.dwHighDateTime) { + aInfo->creationTime = aInfo->modifyTime; + } else { + FileTimeToPRTime(&fileData.ftCreationTime, &aInfo->creationTime); + } + + return NS_OK; +} + +struct nsDir +{ + HANDLE handle; + WIN32_FIND_DATAW data; + bool firstEntry; +}; + +static nsresult +OpenDir(const nsAFlatString& aName, nsDir** aDir) +{ + if (NS_WARN_IF(!aDir)) { + return NS_ERROR_INVALID_ARG; + } + + *aDir = nullptr; + if (aName.Length() + 3 >= MAX_PATH) { + return NS_ERROR_FILE_NAME_TOO_LONG; + } + + nsDir* d = new nsDir(); + nsAutoString filename(aName); + + // If |aName| 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) { + delete d; + return ConvertWinError(GetLastError()); + } + d->firstEntry = true; + + *aDir = d; + return NS_OK; +} + +static nsresult +ReadDir(nsDir* aDir, PRDirFlags aFlags, nsString& aName) +{ + aName.Truncate(); + if (NS_WARN_IF(!aDir)) { + return NS_ERROR_INVALID_ARG; + } + + while (1) { + BOOL rv; + if (aDir->firstEntry) { + aDir->firstEntry = false; + rv = 1; + } else { + rv = ::FindNextFileW(aDir->handle, &(aDir->data)); + } + + if (rv == 0) { + break; + } + + const wchar_t* fileName; + fileName = (aDir)->data.cFileName; + + if ((aFlags & PR_SKIP_DOT) && + (fileName[0] == L'.') && (fileName[1] == L'\0')) { + continue; + } + if ((aFlags & PR_SKIP_DOT_DOT) && + (fileName[0] == L'.') && (fileName[1] == L'.') && + (fileName[2] == L'\0')) { + continue; + } + + DWORD attrib = aDir->data.dwFileAttributes; + if ((aFlags & PR_SKIP_HIDDEN) && (attrib & FILE_ATTRIBUTE_HIDDEN)) { + continue; + } + + aName = fileName; + return NS_OK; + } + + DWORD err = GetLastError(); + return err == ERROR_NO_MORE_FILES ? NS_OK : ConvertWinError(err); +} + +static nsresult +CloseDir(nsDir*& aDir) +{ + if (NS_WARN_IF(!aDir)) { + return NS_ERROR_INVALID_ARG; + } + + BOOL isOk = FindClose(aDir->handle); + delete aDir; + aDir = nullptr; + return isOk ? NS_OK : ConvertWinError(GetLastError()); +} + +//----------------------------------------------------------------------------- +// nsDirEnumerator +//----------------------------------------------------------------------------- + +class nsDirEnumerator final + : public nsISimpleEnumerator + , public nsIDirectoryEnumerator +{ +private: + ~nsDirEnumerator() + { + Close(); + } + +public: + NS_DECL_ISUPPORTS + + nsDirEnumerator() : mDir(nullptr) + { + } + + nsresult Init(nsIFile* aParent) + { + nsAutoString filepath; + aParent->GetTarget(filepath); + + if (filepath.IsEmpty()) { + aParent->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 = aParent; + return NS_OK; + } + + NS_IMETHOD HasMoreElements(bool* aResult) + { + nsresult rv; + if (!mNext && 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; + } + + *aResult = 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); + } + *aResult = mNext != nullptr; + if (!*aResult) { + Close(); + } + return NS_OK; + } + + NS_IMETHOD GetNext(nsISupports** aResult) + { + nsresult rv; + bool hasMore; + rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv)) { + return rv; + } + + *aResult = mNext; // might return nullptr + NS_IF_ADDREF(*aResult); + + mNext = nullptr; + return NS_OK; + } + + NS_IMETHOD GetNextFile(nsIFile** aResult) + { + *aResult = nullptr; + bool hasMore = false; + nsresult rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv) || !hasMore) { + return rv; + } + *aResult = mNext; + NS_IF_ADDREF(*aResult); + 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; + } + +protected: + nsDir* mDir; + nsCOMPtr<nsIFile> mParent; + nsCOMPtr<nsIFile> mNext; +}; + +NS_IMPL_ISUPPORTS(nsDirEnumerator, nsISimpleEnumerator, nsIDirectoryEnumerator) + + +//----------------------------------------------------------------------------- +// nsLocalFile <public> +//----------------------------------------------------------------------------- + +nsLocalFile::nsLocalFile() + : mDirty(true) + , mResolveDirty(true) + , mFollowSymlinks(false) +{ +} + +nsresult +nsLocalFile::nsLocalFileConstructor(nsISupports* aOuter, const nsIID& aIID, + void** aInstancePtr) +{ + if (NS_WARN_IF(!aInstancePtr)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + nsLocalFile* inst = new nsLocalFile(); + nsresult rv = inst->QueryInterface(aIID, aInstancePtr); + if (NS_FAILED(rv)) { + delete inst; + return rv; + } + return NS_OK; +} + + +//----------------------------------------------------------------------------- +// nsLocalFile::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsLocalFile, + nsILocalFile, + nsIFile, + nsILocalFileWin, + nsIHashable) + + +//----------------------------------------------------------------------------- +// nsLocalFile <private> +//----------------------------------------------------------------------------- + +nsLocalFile::nsLocalFile(const nsLocalFile& aOther) + : mDirty(true) + , mResolveDirty(true) + , mFollowSymlinks(aOther.mFollowSymlinks) + , mWorkingPath(aOther.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; + } + + wchar_t* resolvedPath = wwc(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; + } + + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + // 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** aFile) +{ + // Just copy-construct ourselves + RefPtr<nsLocalFile> file = new nsLocalFile(*this); + file.forget(aFile); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::InitWithFile(nsIFile* aFile) +{ + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoString path; + aFile->GetPath(path); + if (path.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + return InitWithPath(path); +} + +NS_IMETHODIMP +nsLocalFile::InitWithPath(const nsAString& aFilePath) +{ + MakeDirty(); + + nsAString::const_iterator begin, end; + aFilePath.BeginReading(begin); + aFilePath.EndReading(end); + + // input string must not be empty + if (begin == end) { + return NS_ERROR_FAILURE; + } + + char16_t firstChar = *begin; + char16_t 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(aFilePath.Data()) == -1) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + } + + mWorkingPath = aFilePath; + // kill any trailing '\' + if (mWorkingPath.Last() == L'\\') { + mWorkingPath.Truncate(mWorkingPath.Length() - 1); + } + + return NS_OK; + +} + +// Strip a handler command string of its quotes and parameters. +static void +CleanupHandlerPath(nsString& aPath) +{ + // Example command strings passed into this routine: + + // 1) C:\Program Files\Company\some.exe -foo -bar + // 2) C:\Program Files\Company\some.dll + // 3) C:\Windows\some.dll,-foo -bar + // 4) C:\Windows\some.cpl,-foo -bar + + int32_t lastCommaPos = aPath.RFindChar(','); + if (lastCommaPos != kNotFound) + aPath.Truncate(lastCommaPos); + + aPath.Append(' '); + + // case insensitive + uint32_t index = aPath.Find(".exe ", true); + if (index == kNotFound) + index = aPath.Find(".dll ", true); + if (index == kNotFound) + index = aPath.Find(".cpl ", true); + + if (index != kNotFound) + aPath.Truncate(index + 4); + aPath.Trim(" ", true, true); +} + +// Strip the windows host process bootstrap executable rundll32.exe +// from a handler's command string if it exists. +static void +StripRundll32(nsString& aCommandString) +{ + // Example rundll formats: + // C:\Windows\System32\rundll32.exe "path to dll" + // rundll32.exe "path to dll" + // C:\Windows\System32\rundll32.exe "path to dll", var var + // rundll32.exe "path to dll", var var + + NS_NAMED_LITERAL_STRING(rundllSegment, "rundll32.exe "); + NS_NAMED_LITERAL_STRING(rundllSegmentShort, "rundll32 "); + + // case insensitive + int32_t strLen = rundllSegment.Length(); + int32_t index = aCommandString.Find(rundllSegment, true); + if (index == kNotFound) { + strLen = rundllSegmentShort.Length(); + index = aCommandString.Find(rundllSegmentShort, true); + } + + if (index != kNotFound) { + uint32_t rundllSegmentLength = index + strLen; + aCommandString.Cut(0, rundllSegmentLength); + } +} + +// Returns the fully qualified path to an application handler based on +// a parameterized command string. Note this routine should not be used +// to launch the associated application as it strips parameters and +// rundll.exe from the string. Designed for retrieving display information +// on a particular handler. +/* static */ bool +nsLocalFile::CleanupCmdHandlerPath(nsAString& aCommandHandler) +{ + nsAutoString handlerCommand(aCommandHandler); + + // Straight command path: + // + // %SystemRoot%\system32\NOTEPAD.EXE var + // "C:\Program Files\iTunes\iTunes.exe" var var + // C:\Program Files\iTunes\iTunes.exe var var + // + // Example rundll handlers: + // + // rundll32.exe "%ProgramFiles%\Win...ery\PhotoViewer.dll", var var + // rundll32.exe "%ProgramFiles%\Windows Photo Gallery\PhotoViewer.dll" + // C:\Windows\System32\rundll32.exe "path to dll", var var + // %SystemRoot%\System32\rundll32.exe "%ProgramFiles%\Win...ery\Photo + // Viewer.dll", var var + + // Expand environment variables so we have full path strings. + uint32_t bufLength = ::ExpandEnvironmentStringsW(handlerCommand.get(), + L"", 0); + if (bufLength == 0) // Error + return false; + + auto destination = mozilla::MakeUniqueFallible<wchar_t[]>(bufLength); + if (!destination) + return false; + if (!::ExpandEnvironmentStringsW(handlerCommand.get(), destination.get(), + bufLength)) + return false; + + handlerCommand.Assign(destination.get()); + + // Remove quotes around paths + handlerCommand.StripChars("\""); + + // Strip windows host process bootstrap so we can get to the actual + // handler. + StripRundll32(handlerCommand); + + // Trim any command parameters so that we have a native path we can + // initialize a local file with. + CleanupHandlerPath(handlerCommand); + + aCommandHandler.Assign(handlerCommand); + return true; +} + + +NS_IMETHODIMP +nsLocalFile::InitWithCommandLine(const nsAString& aCommandLine) +{ + nsAutoString commandLine(aCommandLine); + if (!CleanupCmdHandlerPath(commandLine)) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + return InitWithPath(commandLine); +} + +NS_IMETHODIMP +nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode, + PRFileDesc** aResult) +{ + nsresult rv = OpenNSPRFileDescMaybeShareDelete(aFlags, aMode, false, aResult); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult) +{ + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) { + return rv; + } + + *aResult = _wfopen(mResolvedPath.get(), NS_ConvertASCIItoUTF16(aMode).get()); + if (*aResult) { + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + + + +NS_IMETHODIMP +nsLocalFile::Create(uint32_t aType, uint32_t aAttributes) +{ + if (aType != NORMAL_FILE_TYPE && aType != 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. + + wchar_t* path = wwc(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 + wchar_t* 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(), nullptr)) { + 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 (rv != NS_ERROR_FILE_ALREADY_EXISTS && + rv != NS_ERROR_FILE_ACCESS_DENIED) { + return rv; + } + + directoryCreateError = rv; + } + *slash = L'\\'; + ++slash; + slash = wcschr(slash, L'\\'); + } + } + + if (aType == NORMAL_FILE_TYPE) { + PRFileDesc* file; + rv = OpenFile(mResolvedPath, + PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL, + aAttributes, false, &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 (aType == DIRECTORY_TYPE) { + if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) { + 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; + } + return rv; + } + return NS_OK; + } + + return NS_ERROR_FILE_UNKNOWN_TYPE; +} + + +NS_IMETHODIMP +nsLocalFile::Append(const nsAString& aNode) +{ + // append this path, multiple components are not permitted + return AppendInternal(PromiseFlatString(aNode), false); +} + +NS_IMETHODIMP +nsLocalFile::AppendRelativePath(const nsAString& aNode) +{ + // append this path, multiple components are permitted + return AppendInternal(PromiseFlatString(aNode), true); +} + + +nsresult +nsLocalFile::AppendInternal(const nsAFlatString& aNode, + bool aMultipleComponents) +{ + if (aNode.IsEmpty()) { + return NS_OK; + } + + // check the relative path for validity + if (aNode.First() == L'\\' || // can't start with an '\' + aNode.Contains(L'/') || // can't contain / + aNode.EqualsASCII("..")) { // can't be .. + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + if (aMultipleComponents) { + // 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; + aNode.BeginReading(start); + aNode.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(aNode, NS_LITERAL_STRING("..\\"))) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + } + // single components can't contain '\' + else if (aNode.Contains(L'\\')) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + MakeDirty(); + + mWorkingPath.Append('\\'); + mWorkingPath.Append(aNode); + + return NS_OK; +} + +nsresult +nsLocalFile::OpenNSPRFileDescMaybeShareDelete(int32_t aFlags, + int32_t aMode, + bool aShareDelete, + PRFileDesc** aResult) +{ + nsresult rv = Resolve(); + if (NS_FAILED(rv)) { + return rv; + } + + return OpenFile(mResolvedPath, aFlags, aMode, aShareDelete, aResult); +} + +#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 char16_t* 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& aResult) +{ + aResult = 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& aResult) +{ + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + rv = NS_ERROR_FAILURE; + + const WCHAR* path = + mFollowSymlinks ? mResolvedPath.get() : mWorkingPath.get(); + + DWORD dummy; + DWORD size = ::GetFileVersionInfoSizeW(path, &dummy); + if (!size) { + return rv; + } + + void* ver = moz_xcalloc(size, 1); + 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) { + wchar_t 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) { + aResult.Assign(static_cast<char16_t*>(value)); + if (!aResult.IsEmpty()) { + rv = NS_OK; + break; + } + } + } + } + } + free(ver); + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::SetShortcut(nsIFile* aTargetFile, + nsIFile* aWorkingDir, + const char16_t* aArgs, + const char16_t* aDescription, + nsIFile* aIconFile, + int32_t aIconIndex) +{ + bool exists; + nsresult rv = this->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + + const WCHAR* targetFilePath = nullptr; + const WCHAR* workingDirPath = nullptr; + const WCHAR* iconFilePath = nullptr; + + nsAutoString targetFilePathAuto; + if (aTargetFile) { + rv = aTargetFile->GetPath(targetFilePathAuto); + if (NS_FAILED(rv)) { + return rv; + } + targetFilePath = targetFilePathAuto.get(); + } + + nsAutoString workingDirPathAuto; + if (aWorkingDir) { + rv = aWorkingDir->GetPath(workingDirPathAuto); + if (NS_FAILED(rv)) { + return rv; + } + workingDirPath = workingDirPathAuto.get(); + } + + nsAutoString iconPathAuto; + if (aIconFile) { + rv = aIconFile->GetPath(iconPathAuto); + if (NS_FAILED(rv)) { + return rv; + } + iconFilePath = iconPathAuto.get(); + } + + rv = gResolver->SetShortcut(exists, + mWorkingPath.get(), + targetFilePath, + workingDirPath, + char16ptr_t(aArgs), + char16ptr_t(aDescription), + iconFilePath, + iconFilePath ? aIconIndex : 0); + if (targetFilePath && NS_SUCCEEDED(rv)) { + MakeDirty(); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::OpenNSPRFileDescShareDelete(int32_t aFlags, + int32_t aMode, + PRFileDesc** aResult) +{ + nsresult rv = OpenNSPRFileDescMaybeShareDelete(aFlags, aMode, true, aResult); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +/** + * 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 aPath, bool& aRemote) +{ + // Obtain the parent directory path and make sure it ends with + // a trailing backslash. + WCHAR dirPath[MAX_PATH + 1] = { 0 }; + wcsncpy(dirPath, aPath, 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); + aRemote = driveType == DRIVE_REMOTE; + return true; +} + +nsresult +nsLocalFile::CopySingleFile(nsIFile* aSourceFile, nsIFile* aDestParent, + const nsAString& aNewName, uint32_t aOptions) +{ + nsresult rv = NS_OK; + nsAutoString filePath; + + bool move = aOptions & (Move | Rename); + + // 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; + aDestParent->GetTarget(destPath); + + destPath.Append('\\'); + + if (aNewName.IsEmpty()) { + nsAutoString aFileName; + aSourceFile->GetLeafName(aFileName); + destPath.Append(aFileName); + } else { + destPath.Append(aNewName); + } + + + if (aOptions & FollowSymlinks) { + rv = aSourceFile->GetTarget(filePath); + if (filePath.IsEmpty()) { + rv = aSourceFile->GetPath(filePath); + } + } else { + rv = aSourceFile->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 dwCopyFlags = COPY_FILE_ALLOW_DECRYPTED_DESTINATION; + if (IsVistaOrLater()) { + 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(), nullptr, + nullptr, nullptr, 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) { + if (aOptions & Rename) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + copyOK = CopyFileExW(filePath.get(), destPath.get(), nullptr, + nullptr, nullptr, dwCopyFlags); + + if (copyOK) { + DeleteFileW(filePath.get()); + } + } + } + + if (!copyOK) { // CopyFileEx and MoveFileEx return zero at failure. + rv = ConvertWinError(GetLastError()); + } else if (move && !(aOptions & SkipNtfsAclReset)) { + // Set security permissions to inherit from parent. + // Note: propagates to all children: slow for big file trees + PACL pOldDACL = nullptr; + PSECURITY_DESCRIPTOR pSD = nullptr; + ::GetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, + nullptr, nullptr, &pOldDACL, nullptr, &pSD); + if (pOldDACL) + ::SetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | + UNPROTECTED_DACL_SECURITY_INFORMATION, + nullptr, nullptr, pOldDACL, nullptr); + if (pSD) { + LocalFree((HLOCAL)pSD); + } + } + + return rv; +} + +nsresult +nsLocalFile::CopyMove(nsIFile* aParentDir, const nsAString& aNewName, + uint32_t aOptions) +{ + bool move = aOptions & (Move | Rename); + bool followSymlinks = aOptions & FollowSymlinks; + + 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| option. + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + if (!newParentDir) { + // no parent was specified. We must rename. + if (aNewName.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(); + rv = realDest->InitWithPath(target); + + if (NS_FAILED(rv)) { + return rv; + } + + return CopyMove(realDest, aNewName, aOptions); + } + } 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 + if (!aParentDir) { + aOptions |= SkipNtfsAclReset; + } + rv = CopySingleFile(this, newParentDir, aNewName, aOptions); + 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 (aNewName.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 = aNewName; + } + + 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; + } + } + + RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator(); + + 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()); + if (NS_FAILED(rv)) { + return rv; + } + } else { + if (followSymlinks) { + rv = file->CopyToFollowingLinks(target, EmptyString()); + } else { + rv = file->CopyTo(target, EmptyString()); + } + if (NS_FAILED(rv)) { + return 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 */); + if (NS_FAILED(rv)) { + return 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 (aNewName.IsEmpty()) { + nsAutoString aFileName; + GetLeafName(aFileName); + + InitWithPath(newParentPath); + Append(aFileName); + } else { + InitWithPath(newParentPath); + Append(aNewName); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::CopyTo(nsIFile* aNewParentDir, const nsAString& aNewName) +{ + return CopyMove(aNewParentDir, aNewName, 0); +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir, + const nsAString& aNewName) +{ + return CopyMove(aNewParentDir, aNewName, FollowSymlinks); +} + +NS_IMETHODIMP +nsLocalFile::MoveTo(nsIFile* aNewParentDir, const nsAString& aNewName) +{ + return CopyMove(aNewParentDir, aNewName, Move); +} + +NS_IMETHODIMP +nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName) +{ + nsCOMPtr<nsIFile> targetParentDir = aNewParentDir; + // 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 (!targetParentDir) { + // no parent was specified. We must rename. + if (aNewName.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + rv = GetParent(getter_AddRefs(targetParentDir)); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (!targetParentDir) { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + + // make sure it exists and is a directory. Create it if not there. + bool exists; + targetParentDir->Exists(&exists); + if (!exists) { + rv = targetParentDir->Create(DIRECTORY_TYPE, 0644); + if (NS_FAILED(rv)) { + return rv; + } + } else { + bool isDir; + targetParentDir->IsDirectory(&isDir); + if (!isDir) { + return NS_ERROR_FILE_DESTINATION_NOT_DIR; + } + } + + uint32_t options = Rename; + if (!aNewParentDir) { + options |= SkipNtfsAclReset; + } + // Move single file, or move a directory + return CopySingleFile(this, targetParentDir, aNewName, options); +} + +NS_IMETHODIMP +nsLocalFile::RenameToNative(nsIFile* aNewParentDir, const nsACString& aNewName) +{ + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return RenameTo(aNewParentDir, tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::Load(PRLibrary** aResult) +{ + // 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 + nsTraceRefcnt::SetActivityIsLegal(false); +#endif + + PRLibSpec libSpec; + libSpec.value.pathname_u = mResolvedPath.get(); + libSpec.type = PR_LibSpec_PathnameU; + *aResult = PR_LoadLibraryWithFlags(libSpec, 0); + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcnt::SetActivityIsLegal(true); +#endif + + if (*aResult) { + return NS_OK; + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsLocalFile::Remove(bool aRecursive) +{ + // 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 (aRecursive) { + RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator(); + + 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(aRecursive); + } + } + } + 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(); + + if (NS_WARN_IF(!aLastModifiedTime)) { + return NS_ERROR_INVALID_ARG; + } + + // 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(); + + if (NS_WARN_IF(!aLastModifiedTime)) { + return NS_ERROR_INVALID_ARG; + } + + // 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 wchar_t* aFilePath) +{ + // The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the + // modification time for directories. + HANDLE file = ::CreateFileW(aFilePath, // pointer to name of the file + GENERIC_WRITE, // access (write) mode + 0, // share mode + nullptr, // pointer to security attributes + OPEN_EXISTING, // how to create + FILE_FLAG_BACKUP_SEMANTICS, // file attributes + nullptr); + + 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, nullptr, &ft, &ft) != 0)) { + rv = ConvertWinError(GetLastError()); + } + + CloseHandle(file); + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetPermissions(uint32_t* aPermissions) +{ + if (NS_WARN_IF(!aPermissions)) { + return NS_ERROR_INVALID_ARG; + } + + // 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(); + + if (NS_WARN_IF(!aPermissions)) { + return NS_ERROR_INVALID_ARG; + } + + // 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) +{ + if (NS_WARN_IF(!aFileSize)) { + return NS_ERROR_INVALID_ARG; + } + + 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(); + + if (NS_WARN_IF(!aFileSize)) { + return NS_ERROR_INVALID_ARG; + } + + // 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 + nullptr, // pointer to security attributes + OPEN_EXISTING, // how to create + FILE_ATTRIBUTE_NORMAL, // file attributes + nullptr); + 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(); + + if (NS_WARN_IF(!aDiskSpaceAvailable)) { + return NS_ERROR_INVALID_ARG; + } + + 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, nullptr)) { + *aDiskSpaceAvailable = liFreeBytesAvailableToCaller.QuadPart; + return NS_OK; + } + *aDiskSpaceAvailable = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetParent(nsIFile** aParent) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aParent)) { + return NS_ERROR_INVALID_ARG; + } + + // 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(char16_t('\\')); + // 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_FAILED(rv)) { + return rv; + } + + localFile.forget(aParent); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Exists(bool* aResult) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + + MakeDirty(); + nsresult rv = ResolveAndStat(); + *aResult = 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, false, &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* aResult) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = false; + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + *aResult = true; + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::IsExecutable(bool* aResult) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + *aResult = 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(char16_t('.')); + if (dotIdx != kNotFound) { + // Convert extension to lower case. + char16_t* 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. + *aResult = true; + break; + } + } + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::IsDirectory(bool* aResult) +{ + return HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, aResult); +} + +NS_IMETHODIMP +nsLocalFile::IsFile(bool* aResult) +{ + nsresult rv = HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, aResult); + if (NS_SUCCEEDED(rv)) { + *aResult = !*aResult; + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::IsHidden(bool* aResult) +{ + return HasFileAttribute(FILE_ATTRIBUTE_HIDDEN, aResult); +} + +nsresult +nsLocalFile::HasFileAttribute(DWORD aFileAttrib, bool* aResult) +{ + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = Resolve(); + if (NS_FAILED(rv)) { + return rv; + } + + DWORD attributes = GetFileAttributesW(mResolvedPath.get()); + if (INVALID_FILE_ATTRIBUTES == attributes) { + return ConvertWinError(GetLastError()); + } + + *aResult = ((attributes & aFileAttrib) != 0); + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSymlink(bool* aResult) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + // unless it is a valid shortcut path it's not a symlink + if (!IsShortcutPath(mWorkingPath)) { + *aResult = 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! + *aResult = true; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsSpecial(bool* aResult) +{ + return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM, aResult); +} + +NS_IMETHODIMP +nsLocalFile::Equals(nsIFile* aInFile, bool* aResult) +{ + if (NS_WARN_IF(!aInFile)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + EnsureShortPath(); + + nsCOMPtr<nsILocalFileWin> lf(do_QueryInterface(aInFile)); + if (!lf) { + *aResult = false; + return NS_OK; + } + + nsAutoString inFilePath; + lf->GetCanonicalPath(inFilePath); + + // Ok : Win9x + *aResult = _wcsicmp(mShortWorkingPath.get(), inFilePath.get()) == 0; + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::Contains(nsIFile* aInFile, bool* aResult) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + *aResult = false; + + nsAutoString myFilePath; + if (NS_FAILED(GetTarget(myFilePath))) { + GetPath(myFilePath); + } + + uint32_t myFilePathLen = myFilePath.Length(); + + nsAutoString inFilePath; + if (NS_FAILED(aInFile->GetTarget(inFilePath))) { + aInFile->GetPath(inFilePath); + } + + // make sure that the |aInFile|'s path has a trailing separator. + if (inFilePath.Length() >= myFilePathLen && + inFilePath[myFilePathLen] == L'\\') { + if (_wcsnicmp(myFilePath.get(), inFilePath.get(), myFilePathLen) == 0) { + *aResult = true; + } + + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsLocalFile::GetTarget(nsAString& aResult) +{ + aResult.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(); + + aResult = mResolvedPath; + return NS_OK; +} + + +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** aEntries) +{ + nsresult rv; + + *aEntries = nullptr; + if (mWorkingPath.EqualsLiteral("\\\\.")) { + RefPtr<nsDriveEnumerator> drives = new nsDriveEnumerator; + rv = drives->Init(); + if (NS_FAILED(rv)) { + return rv; + } + drives.forget(aEntries); + return NS_OK; + } + + RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator(); + rv = dirEnum->Init(this); + if (NS_FAILED(rv)) { + return rv; + } + + dirEnum.forget(aEntries); + + 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); + } +} + +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 AsyncRevealOperation(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; + } + + // use the app registry name to launch a shell execute.... + SHELLEXECUTEINFOW seinfo; + memset(&seinfo, 0, sizeof(seinfo)); + seinfo.cbSize = sizeof(SHELLEXECUTEINFOW); + seinfo.fMask = SEE_MASK_ASYNCOK; + seinfo.hwnd = GetMostRecentNavigatorHWND(); + seinfo.lpVerb = nullptr; + seinfo.lpFile = mResolvedPath.get(); + seinfo.lpParameters = nullptr; + seinfo.lpDirectory = nullptr; + 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.AssignLiteral(u"shell32.dll,OpenAs_RunDLL "); + shellArg.Append(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; +} + +nsresult +NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks, nsIFile** aResult) +{ + RefPtr<nsLocalFile> file = new nsLocalFile(); + + file->SetFollowLinks(aFollowLinks); + + if (!aPath.IsEmpty()) { + nsresult rv = file->InitWithPath(aPath); + if (NS_FAILED(rv)) { + return rv; + } + } + + file.forget(aResult); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// Native (lossy) interface +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsLocalFile::InitWithNativePath(const nsACString& aFilePath) +{ + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aFilePath, tmp); + if (NS_SUCCEEDED(rv)) { + return InitWithPath(tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::AppendNative(const nsACString& aNode) +{ + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNode, tmp); + if (NS_SUCCEEDED(rv)) { + return Append(tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::AppendRelativeNativePath(const nsACString& aNode) +{ + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNode, 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& aResult) +{ + //NS_WARNING("This API is lossy. Use GetPath !"); + nsAutoString tmp; + nsresult rv = GetPath(tmp); + if (NS_SUCCEEDED(rv)) { + rv = NS_CopyUnicodeToNative(tmp, aResult); + } + + 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* aNewParentDir, const nsACString& aNewName) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (aNewName.IsEmpty()) { + return CopyTo(aNewParentDir, EmptyString()); + } + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return CopyTo(aNewParentDir, tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParentDir, + const nsACString& aNewName) +{ + if (aNewName.IsEmpty()) { + return CopyToFollowingLinks(aNewParentDir, EmptyString()); + } + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return CopyToFollowingLinks(aNewParentDir, tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::MoveToNative(nsIFile* aNewParentDir, const nsACString& aNewName) +{ + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (aNewName.IsEmpty()) { + return MoveTo(aNewParentDir, EmptyString()); + } + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return MoveTo(aNewParentDir, tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetNativeTarget(nsACString& aResult) +{ + // 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, aResult); + } + + return rv; +} + +nsresult +NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowLinks, + nsIFile** aResult) +{ + nsAutoString buf; + nsresult rv = NS_CopyNativeToUnicode(aPath, buf); + if (NS_FAILED(rv)) { + *aResult = nullptr; + return rv; + } + return NS_NewLocalFile(buf, aFollowLinks, aResult); +} + +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_ISUPPORTS(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)) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (!GetLogicalDriveStringsW(length, wwc(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 0000000000..abef2c1064 --- /dev/null +++ b/xpcom/io/nsLocalFileWin.h @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 "prio.h" + +#include "mozilla/Attributes.h" + +#include "windows.h" +#include "shlobj.h" + +#include <sys/stat.h> + +class nsLocalFile final + : public nsILocalFileWin + , public nsIHashable +{ +public: + NS_DEFINE_STATIC_CID_ACCESSOR(NS_LOCAL_FILE_CID) + + nsLocalFile(); + + static nsresult nsLocalFileConstructor(nsISupports* aOuter, + const nsIID& aIID, + void** aInstancePtr); + + // nsISupports interface + NS_DECL_THREADSAFE_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(); + + // Removes registry command handler parameters, quotes, and expands environment strings. + static bool CleanupCmdHandlerPath(nsAString& aCommandHandler); + +private: + // CopyMove and CopySingleFile constants for |options| parameter: + enum CopyFileOption { + FollowSymlinks = 1u << 0, + Move = 1u << 1, + SkipNtfsAclReset = 1u << 2, + Rename = 1u << 3 + }; + + nsLocalFile(const nsLocalFile& aOther); + ~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* aNewParentDir, const nsAString& aNewName, + uint32_t aOptions); + nsresult CopySingleFile(nsIFile* aSource, nsIFile* aDest, + const nsAString& aNewName, uint32_t aOptions); + + nsresult SetModDate(int64_t aLastModifiedTime, const wchar_t* aFilePath); + nsresult HasFileAttribute(DWORD aFileAttrib, bool* aResult); + nsresult AppendInternal(const nsAFlatString& aNode, + bool aMultipleComponents); + + nsresult OpenNSPRFileDescMaybeShareDelete(int32_t aFlags, + int32_t aMode, + bool aShareDelete, + PRFileDesc** aResult); +}; + +#endif diff --git a/xpcom/io/nsMultiplexInputStream.cpp b/xpcom/io/nsMultiplexInputStream.cpp new file mode 100644 index 0000000000..4aa397c37d --- /dev/null +++ b/xpcom/io/nsMultiplexInputStream.cpp @@ -0,0 +1,835 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * The multiplex stream concatenates a list of input streams into a single + * stream. + */ + +#include "mozilla/Attributes.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Mutex.h" + +#include "base/basictypes.h" + +#include "nsMultiplexInputStream.h" +#include "nsICloneableInputStream.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; +using namespace mozilla::ipc; + +using mozilla::DeprecatedAbs; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; + +class nsMultiplexInputStream final + : public nsIMultiplexInputStream + , public nsISeekableStream + , public nsIIPCSerializableInputStream + , public nsICloneableInputStream +{ +public: + nsMultiplexInputStream(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIMULTIPLEXINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + +private: + ~nsMultiplexInputStream() + { + } + + struct MOZ_STACK_CLASS ReadSegmentsState + { + nsCOMPtr<nsIInputStream> mThisStream; + uint32_t mOffset; + nsWriteSegmentFun mWriter; + void* mClosure; + bool mDone; + }; + + static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount); + + Mutex mLock; // Protects access to all data members. + nsTArray<nsCOMPtr<nsIInputStream>> mStreams; + uint32_t mCurrentStream; + bool mStartedReadingCurrent; + nsresult mStatus; +}; + +NS_IMPL_ADDREF(nsMultiplexInputStream) +NS_IMPL_RELEASE(nsMultiplexInputStream) + +NS_IMPL_CLASSINFO(nsMultiplexInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_MULTIPLEXINPUTSTREAM_CID) + +NS_IMPL_QUERY_INTERFACE_CI(nsMultiplexInputStream, + nsIMultiplexInputStream, + nsIInputStream, + nsISeekableStream, + nsIIPCSerializableInputStream, + nsICloneableInputStream) +NS_IMPL_CI_INTERFACE_GETTER(nsMultiplexInputStream, + nsIMultiplexInputStream, + nsIInputStream, + nsISeekableStream) + +static nsresult +AvailableMaybeSeek(nsIInputStream* aStream, uint64_t* aResult) +{ + nsresult rv = aStream->Available(aResult); + if (rv == NS_BASE_STREAM_CLOSED) { + // Blindly seek to the current position if Available() returns + // NS_BASE_STREAM_CLOSED. + // If nsIFileInputStream is closed in Read() due to CLOSE_ON_EOF flag, + // Seek() could reopen the file if REOPEN_ON_REWIND flag is set. + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aStream); + if (seekable) { + nsresult rvSeek = seekable->Seek(nsISeekableStream::NS_SEEK_CUR, 0); + if (NS_SUCCEEDED(rvSeek)) { + rv = aStream->Available(aResult); + } + } + } + return rv; +} + +static nsresult +TellMaybeSeek(nsISeekableStream* aSeekable, int64_t* aResult) +{ + nsresult rv = aSeekable->Tell(aResult); + if (rv == NS_BASE_STREAM_CLOSED) { + // Blindly seek to the current position if Tell() returns + // NS_BASE_STREAM_CLOSED. + // If nsIFileInputStream is closed in Read() due to CLOSE_ON_EOF flag, + // Seek() could reopen the file if REOPEN_ON_REWIND flag is set. + nsresult rvSeek = aSeekable->Seek(nsISeekableStream::NS_SEEK_CUR, 0); + if (NS_SUCCEEDED(rvSeek)) { + rv = aSeekable->Tell(aResult); + } + } + return rv; +} + +nsMultiplexInputStream::nsMultiplexInputStream() + : mLock("nsMultiplexInputStream lock"), + mCurrentStream(0), + mStartedReadingCurrent(false), + mStatus(NS_OK) +{ +} + +NS_IMETHODIMP +nsMultiplexInputStream::GetCount(uint32_t* aCount) +{ + MutexAutoLock lock(mLock); + *aCount = mStreams.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::AppendStream(nsIInputStream* aStream) +{ + MutexAutoLock lock(mLock); + return mStreams.AppendElement(aStream) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsMultiplexInputStream::InsertStream(nsIInputStream* aStream, uint32_t aIndex) +{ + MutexAutoLock lock(mLock); + mStreams.InsertElementAt(aIndex, aStream); + if (mCurrentStream > aIndex || + (mCurrentStream == aIndex && mStartedReadingCurrent)) { + ++mCurrentStream; + } + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::RemoveStream(uint32_t aIndex) +{ + MutexAutoLock lock(mLock); + mStreams.RemoveElementAt(aIndex); + if (mCurrentStream > aIndex) { + --mCurrentStream; + } else if (mCurrentStream == aIndex) { + mStartedReadingCurrent = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::GetStream(uint32_t aIndex, nsIInputStream** aResult) +{ + MutexAutoLock lock(mLock); + *aResult = mStreams.SafeElementAt(aIndex, nullptr); + if (NS_WARN_IF(!*aResult)) { + return NS_ERROR_NOT_AVAILABLE; + } + + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Close() +{ + MutexAutoLock lock(mLock); + 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; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Available(uint64_t* aResult) +{ + MutexAutoLock lock(mLock); + if (NS_FAILED(mStatus)) { + return mStatus; + } + + uint64_t avail = 0; + + uint32_t len = mStreams.Length(); + for (uint32_t i = mCurrentStream; i < len; i++) { + uint64_t streamAvail; + mStatus = AvailableMaybeSeek(mStreams[i], &streamAvail); + if (NS_WARN_IF(NS_FAILED(mStatus))) { + return mStatus; + } + avail += streamAvail; + } + *aResult = avail; + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult) +{ + MutexAutoLock lock(mLock); + // 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). + + *aResult = 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"); + *aResult += read; + aCount -= read; + aBuf += read; + mStartedReadingCurrent = true; + } + } + return *aResult ? NS_OK : rv; +} + +NS_IMETHODIMP +nsMultiplexInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) +{ + MutexAutoLock lock(mLock); + + if (mStatus == NS_BASE_STREAM_CLOSED) { + *aResult = 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. + *aResult = state.mOffset; + return state.mOffset ? NS_OK : rv; +} + +nsresult +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; +} + +NS_IMETHODIMP +nsMultiplexInputStream::IsNonBlocking(bool* aNonBlocking) +{ + MutexAutoLock lock(mLock); + + 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); + if (NS_WARN_IF(NS_FAILED(rv))) { + return 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; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + MutexAutoLock lock(mLock); + + 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); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + continue; + } else { + break; + } + } + + // Get position in current stream + int64_t streamPos; + if (i > oldCurrentStream || + (i == oldCurrentStream && !oldStartedReadingCurrent)) { + streamPos = 0; + } else { + rv = TellMaybeSeek(stream, &streamPos); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // See if we need to seek current stream forward or backward + if (remaining < streamPos) { + rv = stream->Seek(NS_SEEK_SET, remaining); + if (NS_WARN_IF(NS_FAILED(rv))) { + return 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 = AvailableMaybeSeek(mStreams[i], &avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t newPos = XPCOM_MIN(remaining, streamPos + (int64_t)avail); + + rv = stream->Seek(NS_SEEK_SET, newPos); + if (NS_WARN_IF(NS_FAILED(rv))) { + return 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 = AvailableMaybeSeek(mStreams[i], &avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t seek = XPCOM_MIN((int64_t)avail, remaining); + + rv = stream->Seek(NS_SEEK_CUR, seek); + if (NS_WARN_IF(NS_FAILED(rv))) { + return 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 = TellMaybeSeek(stream, &pos); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t seek = XPCOM_MIN(pos, remaining); + + rv = stream->Seek(NS_SEEK_CUR, -seek); + if (NS_WARN_IF(NS_FAILED(rv))) { + return 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); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + break; + } + } + + // Get position in current stream + int64_t streamPos; + if (i < oldCurrentStream) { + streamPos = 0; + } else { + uint64_t avail; + rv = AvailableMaybeSeek(mStreams[i], &avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + streamPos = avail; + } + + // See if we have enough data in the current stream. + if (DeprecatedAbs(remaining) < streamPos) { + rv = stream->Seek(NS_SEEK_END, remaining); + if (NS_WARN_IF(NS_FAILED(rv))) { + return 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 = TellMaybeSeek(stream, &avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t newPos = streamPos + XPCOM_MIN(avail, DeprecatedAbs(remaining)); + + rv = stream->Seek(NS_SEEK_END, -newPos); + if (NS_WARN_IF(NS_FAILED(rv))) { + return 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; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Tell(int64_t* aResult) +{ + MutexAutoLock lock(mLock); + + 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]); + if (NS_WARN_IF(!stream)) { + return NS_ERROR_NO_INTERFACE; + } + + int64_t pos; + rv = TellMaybeSeek(stream, &pos); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + ret64 += pos; + } + *aResult = ret64; + + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::SetEOF() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsMultiplexInputStreamConstructor(nsISupports* aOuter, + REFNSIID aIID, + void** aResult) +{ + *aResult = nullptr; + + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + RefPtr<nsMultiplexInputStream> inst = new nsMultiplexInputStream(); + + return inst->QueryInterface(aIID, aResult); +} + +void +nsMultiplexInputStream::Serialize(InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors) +{ + MutexAutoLock lock(mLock); + + 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++) { + InputStreamParams childStreamParams; + SerializeInputStream(mStreams[index], childStreamParams, + aFileDescriptors); + + streams.AppendElement(childStreamParams); + } + } + + params.currentStream() = mCurrentStream; + params.status() = mStatus; + params.startedReadingCurrent() = mStartedReadingCurrent; + + aParams = params; +} + +bool +nsMultiplexInputStream::Deserialize(const InputStreamParams& aParams, + const FileDescriptorArray& aFileDescriptors) +{ + 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], aFileDescriptors); + 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; +} + +Maybe<uint64_t> +nsMultiplexInputStream::ExpectedSerializedLength() +{ + MutexAutoLock lock(mLock); + + bool lengthValueExists = false; + uint64_t expectedLength = 0; + uint32_t streamCount = mStreams.Length(); + for (uint32_t index = 0; index < streamCount; index++) { + nsCOMPtr<nsIIPCSerializableInputStream> stream = do_QueryInterface(mStreams[index]); + if (!stream) { + continue; + } + Maybe<uint64_t> length = stream->ExpectedSerializedLength(); + if (length.isNothing()) { + continue; + } + lengthValueExists = true; + expectedLength += length.value(); + } + return lengthValueExists ? Some(expectedLength) : Nothing(); +} + +NS_IMETHODIMP +nsMultiplexInputStream::GetCloneable(bool* aCloneable) +{ + MutexAutoLock lock(mLock); + //XXXnsm Cloning a multiplex stream which has started reading is not permitted + //right now. + if (mCurrentStream > 0 || mStartedReadingCurrent) { + *aCloneable = false; + return NS_OK; + } + + uint32_t len = mStreams.Length(); + for (uint32_t i = 0; i < len; ++i) { + nsCOMPtr<nsICloneableInputStream> cis = do_QueryInterface(mStreams[i]); + if (!cis || !cis->GetCloneable()) { + *aCloneable = false; + return NS_OK; + } + } + + *aCloneable = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Clone(nsIInputStream** aClone) +{ + MutexAutoLock lock(mLock); + + //XXXnsm Cloning a multiplex stream which has started reading is not permitted + //right now. + if (mCurrentStream > 0 || mStartedReadingCurrent) { + return NS_ERROR_FAILURE; + } + + RefPtr<nsMultiplexInputStream> clone = new nsMultiplexInputStream(); + + nsresult rv; + uint32_t len = mStreams.Length(); + for (uint32_t i = 0; i < len; ++i) { + nsCOMPtr<nsICloneableInputStream> substream = do_QueryInterface(mStreams[i]); + if (NS_WARN_IF(!substream)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIInputStream> clonedSubstream; + rv = substream->Clone(getter_AddRefs(clonedSubstream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = clone->AppendStream(clonedSubstream); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + clone.forget(aClone); + return NS_OK; +} diff --git a/xpcom/io/nsMultiplexInputStream.h b/xpcom/io/nsMultiplexInputStream.h new file mode 100644 index 0000000000..381051dca7 --- /dev/null +++ b/xpcom/io/nsMultiplexInputStream.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * 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* aOuter, + REFNSIID aIID, + void** aResult); + +#endif // _nsMultiplexInputStream_h_ diff --git a/xpcom/io/nsNativeCharsetUtils.cpp b/xpcom/io/nsNativeCharsetUtils.cpp new file mode 100644 index 0000000000..e53307af56 --- /dev/null +++ b/xpcom/io/nsNativeCharsetUtils.cpp @@ -0,0 +1,1044 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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& aInput, nsAString& aOutput) +{ + CopyUTF8toUTF16(aInput, aOutput); + return NS_OK; +} + +nsresult +NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput) +{ + CopyUTF16toUTF8(aInput, aOutput); + 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** aInput, uint32_t* aInputLeft, + char16_t** aOutput, uint32_t* aOutputLeft) +{ + while (*aInputLeft && *aOutputLeft) { + **aOutput = (unsigned char)** aInput; + (*aInput)++; + (*aInputLeft)--; + (*aOutput)++; + (*aOutputLeft)--; + } +} + +static void +utf16_to_isolatin1(const char16_t** aInput, uint32_t* aInputLeft, + char** aOutput, uint32_t* aOutputLeft) +{ + while (*aInputLeft && *aOutputLeft) { + **aOutput = (unsigned char)**aInput; + (*aInput)++; + (*aInputLeft)--; + (*aOutput)++; + (*aOutputLeft)--; + } +} + +//----------------------------------------------------------------------------- +// 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** aInput, size_t* aInputLeft, + char** aOutput, size_t* aOutputLeft) +{ + size_t res, outputAvail = *aOutputLeft; + res = iconv(converter, ICONV_INPUT(aInput), aInputLeft, aOutput, aOutputLeft); + 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) && (*aOutputLeft < 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 nullptr + // 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 = nullptr; + char* zero_char_out_ptr = nullptr; + size_t zero_size_in = 0; + size_t 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; +} + +/* + * char16_t[] 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", + nullptr +}; + +#if defined(ENABLE_UTF8_FALLBACK_SUPPORT) +static const char* UTF_8_NAMES[] = { + "UTF-8", + "UTF8", + "UTF_8", + "utf-8", + "utf8", + "utf_8", + nullptr +}; +#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 + nullptr +}; + +class nsNativeCharsetConverter +{ +public: + nsNativeCharsetConverter(); + ~nsNativeCharsetConverter(); + + nsresult NativeToUnicode(const char** aInput, uint32_t* aInputLeft, + char16_t** aOutput, uint32_t* aOutputLeft); + nsresult UnicodeToNative(const char16_t** aInput, uint32_t* aInputLeft, + char** aOutput, uint32_t* aOutputLeft); + + 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[] = { "", nullptr }; + const char** native_charset_list = blank_list; + const char* native_charset = nl_langinfo(CODESET); + if (!native_charset) { + 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() +{ + 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** aInput, + uint32_t* aInputLeft, + char16_t** aOutput, + uint32_t* aOutputLeft) +{ + size_t res = 0; + size_t inLeft = (size_t)*aInputLeft; + size_t outLeft = (size_t)*aOutputLeft * 2; + + if (gNativeToUnicode != INVALID_ICONV_T) { + + res = xp_iconv(gNativeToUnicode, aInput, &inLeft, (char**)aOutput, &outLeft); + + *aInputLeft = inLeft; + *aOutputLeft = 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 = *aInput; + + char ubuf[1024]; + + // we assume we're always called with enough space in |aOutput|, + // 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**)aOutput, &outLeft); + if (res == (size_t)-1) { + NS_ERROR("conversion from utf-8 to utf-16 failed"); + break; + } + } + + (*aInput) += (*aInputLeft - inLeft); + *aInputLeft = inLeft; + *aOutputLeft = 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(aInput, aInputLeft, aOutput, aOutputLeft); + + return NS_OK; +} + +nsresult +nsNativeCharsetConverter::UnicodeToNative(const char16_t** aInput, + uint32_t* aInputLeft, + char** aOutput, + uint32_t* aOutputLeft) +{ + size_t res = 0; + size_t inLeft = (size_t)*aInputLeft * 2; + size_t outLeft = (size_t)*aOutputLeft; + + if (gUnicodeToNative != INVALID_ICONV_T) { + res = xp_iconv(gUnicodeToNative, (const char**)aInput, &inLeft, + aOutput, &outLeft); + + *aInputLeft = inLeft / 2; + *aOutputLeft = 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*)*aInput; + + 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(char16_t); + 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, aOutput, &outLeft); + if (res == (size_t)-1) { + if (errno == E2BIG) { + // not enough room for last uchar... back up and return. + in -= sizeof(char16_t); + res = 0; + } else { + NS_ERROR("conversion from utf-8 to native failed"); + } + break; + } + inLeft -= sizeof(char16_t); + } + + (*aInput) += (*aInputLeft - inLeft / 2); + *aInputLeft = inLeft / 2; + *aOutputLeft = 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(aInput, aInputLeft, aOutput, aOutputLeft); + + 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** aInput, uint32_t* aInputLeft, + char16_t** aOutput, uint32_t* aOutputLeft); + nsresult UnicodeToNative(const char16_t** aInput, uint32_t* aInputLeft, + char** aOutput, uint32_t* aOutputLeft); + + 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** aInput, + uint32_t* aInputLeft, + char16_t** aOutput, + uint32_t* aOutputLeft) +{ + 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 (*aInputLeft && *aOutputLeft) { +#ifdef HAVE_MBRTOWC + incr = (int)mbrtowc((wchar_t*)&tmp, *aInput, *aInputLeft, &ps); +#else + // XXX is this thread-safe? + incr = (int)mbtowc((wchar_t*)&tmp, *aInput, *aInputLeft); +#endif + if (incr < 0) { + NS_WARNING("mbtowc failed: possible charset mismatch"); + // zero-pad and hope for the best + tmp = (unsigned char)**aInput; + incr = 1; + } + ** aOutput = (char16_t)tmp; + (*aInput) += incr; + (*aInputLeft) -= incr; + (*aOutput)++; + (*aOutputLeft)--; + } + } else { + // wchar_t isn't unicode, so the best we can do is treat the + // input as if it is isolatin1 :( + isolatin1_to_utf16(aInput, aInputLeft, aOutput, aOutputLeft); + } + + return NS_OK; +} + +nsresult +nsNativeCharsetConverter::UnicodeToNative(const char16_t** aInput, + uint32_t* aInputLeft, + char** aOutput, + uint32_t* aOutputLeft) +{ + if (gWCharIsUnicode) { + int incr; + + while (*aInputLeft && *aOutputLeft >= MB_CUR_MAX) { +#ifdef HAVE_WCRTOMB + incr = (int)wcrtomb(*aOutput, (wchar_t)**aInput, &ps); +#else + // XXX is this thread-safe? + incr = (int)wctomb(*aOutput, (wchar_t)**aInput); +#endif + if (incr < 0) { + NS_WARNING("mbtowc failed: possible charset mismatch"); + ** aOutput = (unsigned char)**aInput; // truncate + incr = 1; + } + // most likely we're dead anyways if this assertion should fire + NS_ASSERTION(uint32_t(incr) <= *aOutputLeft, "wrote beyond end of string"); + (*aOutput) += incr; + (*aOutputLeft) -= incr; + (*aInput)++; + (*aInputLeft)--; + } + } else { + // wchar_t isn't unicode, so the best we can do is treat the + // input as if it is isolatin1 :( + utf16_to_isolatin1(aInput, aInputLeft, aOutput, aOutputLeft); + } + + return NS_OK; +} + +// XXX : for now, return false +bool +nsNativeCharsetConverter::IsNativeUTF8() +{ + return false; +} + +#endif // USE_STDCONV + +//----------------------------------------------------------------------------- +// API implementation +//----------------------------------------------------------------------------- + +nsresult +NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput) +{ + aOutput.Truncate(); + + uint32_t inputLen = aInput.Length(); + + nsACString::const_iterator iter; + aInput.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 (!aOutput.SetLength(inputLen, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsAString::iterator out_iter; + aOutput.BeginWriting(out_iter); + + char16_t* 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"); + aOutput.SetLength(inputLen - resultLeft); + } + return rv; +} + +nsresult +NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput) +{ + aOutput.Truncate(); + + nsAString::const_iterator iter, end; + aInput.BeginReading(iter); + aInput.EndReading(end); + + // cannot easily avoid intermediate buffer copy. + char temp[4096]; + + nsNativeCharsetConverter conv; + + const char16_t* 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)) { + aOutput.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 "nsString.h" +#include "nsAString.h" +#include "nsReadableUtils.h" + +using namespace mozilla; + +nsresult +NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput) +{ + uint32_t inputLen = aInput.Length(); + + nsACString::const_iterator iter; + aInput.BeginReading(iter); + + const char* buf = iter.get(); + + // determine length of result + uint32_t resultLen = 0; + int n = ::MultiByteToWideChar(CP_ACP, 0, buf, inputLen, nullptr, 0); + if (n > 0) { + resultLen += n; + } + + // allocate sufficient space + if (!aOutput.SetLength(resultLen, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (resultLen > 0) { + nsAString::iterator out_iter; + aOutput.BeginWriting(out_iter); + + char16_t* result = out_iter.get(); + + ::MultiByteToWideChar(CP_ACP, 0, buf, inputLen, wwc(result), resultLen); + } + return NS_OK; +} + +nsresult +NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput) +{ + uint32_t inputLen = aInput.Length(); + + nsAString::const_iterator iter; + aInput.BeginReading(iter); + + char16ptr_t buf = iter.get(); + + // determine length of result + uint32_t resultLen = 0; + + int n = ::WideCharToMultiByte(CP_ACP, 0, buf, inputLen, nullptr, 0, + nullptr, nullptr); + if (n > 0) { + resultLen += n; + } + + // allocate sufficient space + if (!aOutput.SetLength(resultLen, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (resultLen > 0) { + nsACString::iterator out_iter; + aOutput.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, nullptr); + } + return NS_OK; +} + +// moved from widget/windows/nsToolkit.cpp +int32_t +NS_ConvertAtoW(const char* aStrInA, int aBufferSize, char16_t* aStrOutW) +{ + return MultiByteToWideChar(CP_ACP, 0, aStrInA, -1, wwc(aStrOutW), aBufferSize); +} + +int32_t +NS_ConvertWtoA(const char16_t* aStrInW, int aBufferSizeOut, + char* aStrOutA, const char* aDefault) +{ + if ((!aStrInW) || (!aStrOutA) || (aBufferSizeOut <= 0)) { + return 0; + } + + int numCharsConverted = WideCharToMultiByte(CP_ACP, 0, char16ptr_t(aStrInW), -1, + aStrOutA, aBufferSizeOut, + aDefault, nullptr); + + 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; +} + +#else + +#include "nsReadableUtils.h" + +nsresult +NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput) +{ + CopyASCIItoUTF16(aInput, aOutput); + return NS_OK; +} + +nsresult +NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput) +{ + LossyCopyUTF16toASCII(aInput, aOutput); + 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 0000000000..5c1e670d5e --- /dev/null +++ b/xpcom/io/nsNativeCharsetUtils.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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& aInput, nsAString& aOutput); +nsresult NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput); + +/* + * 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 0000000000..29ce0ce96d --- /dev/null +++ b/xpcom/io/nsPipe.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 +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 0000000000..56932adfc8 --- /dev/null +++ b/xpcom/io/nsPipe3.cpp @@ -0,0 +1,2007 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <algorithm> +#include "mozilla/Attributes.h" +#include "mozilla/ReentrantMonitor.h" +#include "nsIBufferedStreams.h" +#include "nsICloneableInputStream.h" +#include "nsIPipe.h" +#include "nsIEventTarget.h" +#include "nsISeekableStream.h" +#include "mozilla/RefPtr.h" +#include "nsSegmentedBuffer.h" +#include "nsStreamUtils.h" +#include "nsCOMPtr.h" +#include "nsCRT.h" +#include "mozilla/Logging.h" +#include "nsIClassInfoImpl.h" +#include "nsAlgorithm.h" +#include "nsMemory.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" + +using namespace mozilla; + +#ifdef LOG +#undef LOG +#endif +// +// set MOZ_LOG=nsPipe:5 +// +static LazyLogModule sPipeLog("nsPipe"); +#define LOG(args) MOZ_LOG(sPipeLog, mozilla::LogLevel::Debug, args) + +#define DEFAULT_SEGMENT_SIZE 4096 +#define DEFAULT_SEGMENT_COUNT 16 + +class nsPipe; +class nsPipeEvents; +class nsPipeInputStream; +class nsPipeOutputStream; +class AutoReadSegment; + +namespace { + +enum MonitorAction +{ + DoNotNotifyMonitor, + NotifyMonitor +}; + +enum SegmentChangeResult +{ + SegmentNotChanged, + SegmentAdvanceBufferRead +}; + +} // namespace + +//----------------------------------------------------------------------------- + +// 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* aStream, + nsIInputStreamCallback* aCallback) + { + mInputList.AppendElement(InputEntry(aStream, aCallback)); + } + + inline void NotifyOutputReady(nsIAsyncOutputStream* aStream, + nsIOutputStreamCallback* aCallback) + { + NS_ASSERTION(!mOutputCallback, "already have an output event"); + mOutputStream = aStream; + mOutputCallback = aCallback; + } + +private: + struct InputEntry + { + InputEntry(nsIAsyncInputStream* aStream, nsIInputStreamCallback* aCallback) + : mStream(aStream) + , mCallback(aCallback) + { + MOZ_ASSERT(mStream); + MOZ_ASSERT(mCallback); + } + + nsCOMPtr<nsIAsyncInputStream> mStream; + nsCOMPtr<nsIInputStreamCallback> mCallback; + }; + + nsTArray<InputEntry> mInputList; + + nsCOMPtr<nsIAsyncOutputStream> mOutputStream; + nsCOMPtr<nsIOutputStreamCallback> mOutputCallback; +}; + +//----------------------------------------------------------------------------- + +// This class is used to maintain input stream state. Its broken out from the +// nsPipeInputStream class because generally the nsPipe should be modifying +// this state and not the input stream itself. +struct nsPipeReadState +{ + nsPipeReadState() + : mReadCursor(nullptr) + , mReadLimit(nullptr) + , mSegment(0) + , mAvailable(0) + , mActiveRead(false) + , mNeedDrain(false) + { } + + char* mReadCursor; + char* mReadLimit; + int32_t mSegment; + uint32_t mAvailable; + + // This flag is managed using the AutoReadSegment RAII stack class. + bool mActiveRead; + + // Set to indicate that the input stream has closed and should be drained, + // but that drain has been delayed due to an active read. When the read + // completes, this flag indicate the drain should then be performed. + bool mNeedDrain; +}; + +//----------------------------------------------------------------------------- + +// an input end of a pipe (maintained as a list of refs within the pipe) +class nsPipeInputStream final + : public nsIAsyncInputStream + , public nsISeekableStream + , public nsISearchableInputStream + , public nsICloneableInputStream + , public nsIClassInfo + , public nsIBufferedInputStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSISEARCHABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSICLASSINFO + NS_DECL_NSIBUFFEREDINPUTSTREAM + + explicit nsPipeInputStream(nsPipe* aPipe) + : mPipe(aPipe) + , mLogicalOffset(0) + , mInputStatus(NS_OK) + , mBlocking(true) + , mBlocked(false) + , mCallbackFlags(0) + { } + + explicit nsPipeInputStream(const nsPipeInputStream& aOther) + : mPipe(aOther.mPipe) + , mLogicalOffset(aOther.mLogicalOffset) + , mInputStatus(aOther.mInputStatus) + , mBlocking(aOther.mBlocking) + , mBlocked(false) + , mCallbackFlags(0) + , mReadState(aOther.mReadState) + { } + + nsresult Fill(); + void SetNonBlocking(bool aNonBlocking) + { + mBlocking = !aNonBlocking; + } + + uint32_t Available(); + + // synchronously wait for the pipe to become readable. + nsresult Wait(); + + // These two don't acquire the monitor themselves. Instead they + // expect their caller to have done so and to pass the monitor as + // evidence. + MonitorAction OnInputReadable(uint32_t aBytesWritten, nsPipeEvents&, + const ReentrantMonitorAutoEnter& ev); + MonitorAction OnInputException(nsresult, nsPipeEvents&, + const ReentrantMonitorAutoEnter& ev); + + nsPipeReadState& ReadState() + { + return mReadState; + } + + const nsPipeReadState& ReadState() const + { + return mReadState; + } + + nsresult Status() const; + + // A version of Status() that doesn't acquire the monitor. + nsresult Status(const ReentrantMonitorAutoEnter& ev) const; + +private: + virtual ~nsPipeInputStream(); + + RefPtr<nsPipe> mPipe; + + int64_t mLogicalOffset; + // Individual input streams can be closed without effecting the rest of the + // pipe. So track individual input stream status separately. |mInputStatus| + // is protected by |mPipe->mReentrantMonitor|. + nsresult mInputStatus; + bool mBlocking; + + // these variables can only be accessed while inside the pipe's monitor + bool mBlocked; + nsCOMPtr<nsIInputStreamCallback> mCallback; + uint32_t mCallbackFlags; + + // requires pipe's monitor; usually treat as an opaque token to pass to nsPipe + nsPipeReadState mReadState; +}; + +//----------------------------------------------------------------------------- + +// 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 + + explicit nsPipeOutputStream(nsPipe* aPipe) + : mPipe(aPipe) + , mWriterRefCnt(0) + , mLogicalOffset(0) + , mBlocking(true) + , mBlocked(false) + , mWritable(true) + , mCallbackFlags(0) + { } + + void SetNonBlocking(bool aNonBlocking) + { + mBlocking = !aNonBlocking; + } + void SetWritable(bool aWritable) + { + mWritable = aWritable; + } + + // synchronously wait for the pipe to become writable. + nsresult Wait(); + + MonitorAction OnOutputWritable(nsPipeEvents&); + MonitorAction OnOutputException(nsresult, nsPipeEvents&); + +private: + nsPipe* mPipe; + + // separate refcnt so that we know when to close the producer + mozilla::ThreadSafeAutoRefCnt 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 final : public nsIPipe +{ +public: + friend class nsPipeInputStream; + friend class nsPipeOutputStream; + friend class AutoReadSegment; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPIPE + + // nsPipe methods: + nsPipe(); + +private: + ~nsPipe(); + + // + // Methods below may only be called while inside the pipe's monitor. Some + // of these methods require passing a ReentrantMonitorAutoEnter to prove the + // monitor is held. + // + + void PeekSegment(const nsPipeReadState& aReadState, uint32_t aIndex, + char*& aCursor, char*& aLimit); + SegmentChangeResult AdvanceReadSegment(nsPipeReadState& aReadState, + const ReentrantMonitorAutoEnter &ev); + bool ReadSegmentBeingWritten(nsPipeReadState& aReadState); + uint32_t CountSegmentReferences(int32_t aSegment); + void SetAllNullReadCursors(); + bool AllReadCursorsMatchWriteCursor(); + void RollBackAllReadCursors(char* aWriteCursor); + void UpdateAllReadCursors(char* aWriteCursor); + void ValidateAllReadCursors(); + uint32_t GetBufferSegmentCount(const nsPipeReadState& aReadState, + const ReentrantMonitorAutoEnter& ev) const; + bool IsAdvanceBufferFull(const ReentrantMonitorAutoEnter& ev) const; + + // + // methods below may be called while outside the pipe's monitor + // + + void DrainInputStream(nsPipeReadState& aReadState, nsPipeEvents& aEvents); + nsresult GetWriteSegment(char*& aSegment, uint32_t& aSegmentLen); + void AdvanceWriteCursor(uint32_t aCount); + + void OnInputStreamException(nsPipeInputStream* aStream, nsresult aReason); + void OnPipeException(nsresult aReason, bool aOutputOnly = false); + + nsresult CloneInputStream(nsPipeInputStream* aOriginal, + nsIInputStream** aCloneOut); + + // methods below should only be called by AutoReadSegment + nsresult GetReadSegment(nsPipeReadState& aReadState, const char*& aSegment, + uint32_t& aLength); + void ReleaseReadSegment(nsPipeReadState& aReadState, + nsPipeEvents& aEvents); + void AdvanceReadCursor(nsPipeReadState& aReadState, uint32_t aCount); + + // 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. + nsPipeOutputStream mOutput; + + // Since the input stream can be cloned, we may have more than one. Use + // a weak reference as the streams will clear their entry here in their + // destructor. Using a strong reference would create a reference cycle. + // Only usable while mReentrantMonitor is locked. + nsTArray<nsPipeInputStream*> mInputList; + + // But hold a strong ref to our original input stream. For backward + // compatibility we need to be able to consistently return this same + // object from GetInputStream(). Note, mOriginalInput is also stored + // in mInputList as a weak ref. + RefPtr<nsPipeInputStream> mOriginalInput; + + ReentrantMonitor mReentrantMonitor; + nsSegmentedBuffer mBuffer; + + // The maximum number of segments to allow to be buffered in advance + // of the fastest reader. This is collection of segments is called + // the "advance buffer". + uint32_t mMaxAdvanceBufferSegmentCount; + + int32_t mWriteSegment; + char* mWriteCursor; + char* mWriteLimit; + + // |mStatus| is protected by |mReentrantMonitor|. + nsresult mStatus; + bool mInited; +}; + +//----------------------------------------------------------------------------- + +// RAII class representing an active read segment. When it goes out of scope +// it automatically updates the read cursor and releases the read segment. +class MOZ_STACK_CLASS AutoReadSegment final +{ +public: + AutoReadSegment(nsPipe* aPipe, nsPipeReadState& aReadState, + uint32_t aMaxLength) + : mPipe(aPipe) + , mReadState(aReadState) + , mStatus(NS_ERROR_FAILURE) + , mSegment(nullptr) + , mLength(0) + , mOffset(0) + { + MOZ_ASSERT(mPipe); + MOZ_ASSERT(!mReadState.mActiveRead); + mStatus = mPipe->GetReadSegment(mReadState, mSegment, mLength); + if (NS_SUCCEEDED(mStatus)) { + MOZ_ASSERT(mReadState.mActiveRead); + MOZ_ASSERT(mSegment); + mLength = std::min(mLength, aMaxLength); + MOZ_ASSERT(mLength); + } + } + + ~AutoReadSegment() + { + if (NS_SUCCEEDED(mStatus)) { + if (mOffset) { + mPipe->AdvanceReadCursor(mReadState, mOffset); + } else { + nsPipeEvents events; + mPipe->ReleaseReadSegment(mReadState, events); + } + } + MOZ_ASSERT(!mReadState.mActiveRead); + } + + nsresult Status() const + { + return mStatus; + } + + const char* Data() const + { + MOZ_ASSERT(NS_SUCCEEDED(mStatus)); + MOZ_ASSERT(mSegment); + return mSegment + mOffset; + } + + uint32_t Length() const + { + MOZ_ASSERT(NS_SUCCEEDED(mStatus)); + MOZ_ASSERT(mLength >= mOffset); + return mLength - mOffset; + } + + void + Advance(uint32_t aCount) + { + MOZ_ASSERT(NS_SUCCEEDED(mStatus)); + MOZ_ASSERT(aCount <= (mLength - mOffset)); + mOffset += aCount; + } + + nsPipeReadState& + ReadState() const + { + return mReadState; + } + +private: + // guaranteed to remain alive due to limited stack lifetime of AutoReadSegment + nsPipe* mPipe; + nsPipeReadState& mReadState; + nsresult mStatus; + const char* mSegment; + uint32_t mLength; + uint32_t mOffset; +}; + +// +// NOTES on buffer architecture: +// +// +-----------------+ - - mBuffer.GetSegment(0) +// | | +// + - - - - - - - - + - - nsPipeReadState.mReadCursor +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// +-----------------+ - - nsPipeReadState.mReadLimit +// | +// +-----------------+ +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// |/////////////////| +// +-----------------+ +// | +// +-----------------+ - - mBuffer.GetSegment(mWriteSegment) +// |/////////////////| +// |/////////////////| +// |/////////////////| +// + - - - - - - - - + - - mWriteCursor +// | | +// | | +// +-----------------+ - - mWriteLimit +// +// (shaded region contains data) +// +// NOTE: Each input stream produced by the nsPipe contains its own, separate +// nsPipeReadState. This means there are multiple mReadCursor and +// mReadLimit values in play. The pipe cannot discard old data until +// all mReadCursors have moved beyond that point in the stream. +// +// Likewise, each input stream reader will have it's own amount of +// buffered data. The pipe size threshold, however, is only applied +// to the input stream that is being read fastest. We call this +// the "advance buffer" in that its in advance of all readers. We +// allow slower input streams to buffer more data so that we don't +// stall processing of the faster input stream. +// +// 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() + : mOutput(this) + , mOriginalInput(new nsPipeInputStream(this)) + , mReentrantMonitor("nsPipe.mReentrantMonitor") + , mMaxAdvanceBufferSegmentCount(0) + , mWriteSegment(-1) + , mWriteCursor(nullptr) + , mWriteLimit(nullptr) + , mStatus(NS_OK) + , mInited(false) +{ + mInputList.AppendElement(mOriginalInput); +} + +nsPipe::~nsPipe() +{ +} + +NS_IMPL_ADDREF(nsPipe) +NS_IMPL_QUERY_INTERFACE(nsPipe, nsIPipe) + +NS_IMETHODIMP_(MozExternalRefCountType) +nsPipe::Release() +{ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "nsPipe"); + if (count == 0) { + delete (this); + return 0; + } + // Avoid racing on |mOriginalInput| by only looking at it when + // the refcount is 1, that is, we are the only pointer (hence only + // thread) to access it. + if (count == 1 && mOriginalInput) { + mOriginalInput = nullptr; + return 1; + } + return count; +} + +NS_IMETHODIMP +nsPipe::Init(bool aNonBlockingIn, + bool aNonBlockingOut, + uint32_t aSegmentSize, + uint32_t aSegmentCount) +{ + mInited = true; + + if (aSegmentSize == 0) { + aSegmentSize = DEFAULT_SEGMENT_SIZE; + } + if (aSegmentCount == 0) { + aSegmentCount = DEFAULT_SEGMENT_COUNT; + } + + // protect against overflow + uint32_t maxCount = uint32_t(-1) / aSegmentSize; + if (aSegmentCount > maxCount) { + aSegmentCount = maxCount; + } + + // The internal buffer is always "infinite" so that we can allow + // the size to expand when cloned streams are read at different + // rates. We enforce a limit on how much data can be buffered + // ahead of the fastest reader in GetWriteSegment(). + nsresult rv = mBuffer.Init(aSegmentSize, UINT32_MAX); + if (NS_FAILED(rv)) { + return rv; + } + + mMaxAdvanceBufferSegmentCount = aSegmentCount; + + mOutput.SetNonBlocking(aNonBlockingOut); + mOriginalInput->SetNonBlocking(aNonBlockingIn); + + return NS_OK; +} + +NS_IMETHODIMP +nsPipe::GetInputStream(nsIAsyncInputStream** aInputStream) +{ + if (NS_WARN_IF(!mInited)) { + return NS_ERROR_NOT_INITIALIZED; + } + RefPtr<nsPipeInputStream> ref = mOriginalInput; + ref.forget(aInputStream); + return NS_OK; +} + +NS_IMETHODIMP +nsPipe::GetOutputStream(nsIAsyncOutputStream** aOutputStream) +{ + if (NS_WARN_IF(!mInited)) { + return NS_ERROR_NOT_INITIALIZED; + } + NS_ADDREF(*aOutputStream = &mOutput); + return NS_OK; +} + +void +nsPipe::PeekSegment(const nsPipeReadState& aReadState, uint32_t aIndex, + char*& aCursor, char*& aLimit) +{ + if (aIndex == 0) { + NS_ASSERTION(!aReadState.mReadCursor || mBuffer.GetSegmentCount(), + "unexpected state"); + aCursor = aReadState.mReadCursor; + aLimit = aReadState.mReadLimit; + } else { + uint32_t absoluteIndex = aReadState.mSegment + aIndex; + uint32_t numSegments = mBuffer.GetSegmentCount(); + if (absoluteIndex >= numSegments) { + aCursor = aLimit = nullptr; + } else { + aCursor = mBuffer.GetSegment(absoluteIndex); + if (mWriteSegment == (int32_t)absoluteIndex) { + aLimit = mWriteCursor; + } else { + aLimit = aCursor + mBuffer.GetSegmentSize(); + } + } + } +} + +nsresult +nsPipe::GetReadSegment(nsPipeReadState& aReadState, const char*& aSegment, + uint32_t& aLength) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + if (aReadState.mReadCursor == aReadState.mReadLimit) { + return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_WOULD_BLOCK; + } + + // The input stream locks the pipe while getting the buffer to read from, + // but then unlocks while actual data copying is taking place. In + // order to avoid deleting the buffer out from under this lockless read + // set a flag to indicate a read is active. This flag is only modified + // while the lock is held. + MOZ_ASSERT(!aReadState.mActiveRead); + aReadState.mActiveRead = true; + + aSegment = aReadState.mReadCursor; + aLength = aReadState.mReadLimit - aReadState.mReadCursor; + + return NS_OK; +} + +void +nsPipe::ReleaseReadSegment(nsPipeReadState& aReadState, nsPipeEvents& aEvents) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + MOZ_ASSERT(aReadState.mActiveRead); + aReadState.mActiveRead = false; + + // When a read completes and releases the mActiveRead flag, we may have blocked + // a drain from completing. This occurs when the input stream is closed during + // the read. In these cases, we need to complete the drain as soon as the + // active read completes. + if (aReadState.mNeedDrain) { + aReadState.mNeedDrain = false; + DrainInputStream(aReadState, aEvents); + } +} + +void +nsPipe::AdvanceReadCursor(nsPipeReadState& aReadState, uint32_t aBytesRead) +{ + NS_ASSERTION(aBytesRead, "don't call if no bytes read"); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + LOG(("III advancing read cursor by %u\n", aBytesRead)); + NS_ASSERTION(aBytesRead <= mBuffer.GetSegmentSize(), "read too much"); + + aReadState.mReadCursor += aBytesRead; + NS_ASSERTION(aReadState.mReadCursor <= aReadState.mReadLimit, + "read cursor exceeds limit"); + + MOZ_ASSERT(aReadState.mAvailable >= aBytesRead); + aReadState.mAvailable -= aBytesRead; + + // Check to see if we're at the end of the available read data. If we + // are, and this segment is not still being written, then we can possibly + // free up the segment. + if (aReadState.mReadCursor == aReadState.mReadLimit && + !ReadSegmentBeingWritten(aReadState)) { + + // Advance the segment position. If we have read any segments from the + // advance buffer then we can potentially notify blocked writers. + if (AdvanceReadSegment(aReadState, mon) == SegmentAdvanceBufferRead && + mOutput.OnOutputWritable(events) == NotifyMonitor) { + mon.NotifyAll(); + } + } + + ReleaseReadSegment(aReadState, events); + } +} + +SegmentChangeResult +nsPipe::AdvanceReadSegment(nsPipeReadState& aReadState, + const ReentrantMonitorAutoEnter &ev) +{ + // Calculate how many segments are buffered for this stream to start. + uint32_t startBufferSegments = GetBufferSegmentCount(aReadState, ev); + + int32_t currentSegment = aReadState.mSegment; + + // Move to the next segment to read + aReadState.mSegment += 1; + + // If this was the last reference to the first segment, then remove it. + if (currentSegment == 0 && CountSegmentReferences(currentSegment) == 0) { + + // shift write and read segment index (-1 indicates an empty buffer). + mWriteSegment -= 1; + + // Directly modify the current read state. If the associated input + // stream is closed simultaneous with reading, then it may not be + // in the mInputList any more. + aReadState.mSegment -= 1; + + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + // Skip the current read state structure since we modify it manually + // before entering this loop. + if (&mInputList[i]->ReadState() == &aReadState) { + continue; + } + mInputList[i]->ReadState().mSegment -= 1; + } + + // done with this segment + mBuffer.DeleteFirstSegment(); + LOG(("III deleting first segment\n")); + } + + if (mWriteSegment < aReadState.mSegment) { + // read cursor has hit the end of written data, so reset it + MOZ_ASSERT(mWriteSegment == (aReadState.mSegment - 1)); + aReadState.mReadCursor = nullptr; + aReadState.mReadLimit = nullptr; + // also, the buffer is completely empty, so reset the write cursor + if (mWriteSegment == -1) { + mWriteCursor = nullptr; + mWriteLimit = nullptr; + } + } else { + // advance read cursor and limit to next buffer segment + aReadState.mReadCursor = mBuffer.GetSegment(aReadState.mSegment); + if (mWriteSegment == aReadState.mSegment) { + aReadState.mReadLimit = mWriteCursor; + } else { + aReadState.mReadLimit = aReadState.mReadCursor + mBuffer.GetSegmentSize(); + } + } + + // Calculate how many segments are buffered for the stream after + // reading. + uint32_t endBufferSegments = GetBufferSegmentCount(aReadState, ev); + + // If the stream has read a segment out of the set of advanced buffer + // segments, then the writer may advance. + if (startBufferSegments >= mMaxAdvanceBufferSegmentCount && + endBufferSegments < mMaxAdvanceBufferSegmentCount) { + return SegmentAdvanceBufferRead; + } + + // Otherwise there are no significant changes to the segment structure. + return SegmentNotChanged; +} + +void +nsPipe::DrainInputStream(nsPipeReadState& aReadState, nsPipeEvents& aEvents) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + // If a segment is actively being read in ReadSegments() for this input + // stream, then we cannot drain the stream. This can happen because + // ReadSegments() does not hold the lock while copying from the buffer. + // If we detect this condition, simply note that we need a drain once + // the read completes and return immediately. + if (aReadState.mActiveRead) { + MOZ_ASSERT(!aReadState.mNeedDrain); + aReadState.mNeedDrain = true; + return; + } + + aReadState.mAvailable = 0; + + while(mWriteSegment >= aReadState.mSegment) { + + // If the last segment to free is still being written to, we're done + // draining. We can't free any more. + if (ReadSegmentBeingWritten(aReadState)) { + break; + } + + // Don't bother checking if this results in an advance buffer segment + // read. Since we are draining the entire stream we will read an + // advance buffer segment no matter what. + AdvanceReadSegment(aReadState, mon); + } + + // If we have read any segments from the advance buffer then we can + // potentially notify blocked writers. + if (!IsAdvanceBufferFull(mon) && + mOutput.OnOutputWritable(aEvents) == NotifyMonitor) { + mon.NotifyAll(); + } +} + +bool +nsPipe::ReadSegmentBeingWritten(nsPipeReadState& aReadState) +{ + mReentrantMonitor.AssertCurrentThreadIn(); + bool beingWritten = mWriteSegment == aReadState.mSegment && + mWriteLimit > mWriteCursor; + NS_ASSERTION(!beingWritten || aReadState.mReadLimit == mWriteCursor, + "unexpected state"); + return beingWritten; +} + +nsresult +nsPipe::GetWriteSegment(char*& aSegment, uint32_t& aSegmentLen) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + if (NS_FAILED(mStatus)) { + return mStatus; + } + + // write cursor and limit may both be null indicating an empty buffer. + if (mWriteCursor == mWriteLimit) { + // The pipe is full if we have hit our limit on advance data buffering. + // This means the fastest reader is still reading slower than data is + // being written into the pipe. + if (IsAdvanceBufferFull(mon)) { + return NS_BASE_STREAM_WOULD_BLOCK; + } + + // The nsSegmentedBuffer is configured to be "infinite", so this + // should never return nullptr here. + char* seg = mBuffer.AppendNewSegment(); + if (!seg) { + return NS_ERROR_OUT_OF_MEMORY; + } + + LOG(("OOO appended new segment\n")); + mWriteCursor = seg; + mWriteLimit = mWriteCursor + mBuffer.GetSegmentSize(); + ++mWriteSegment; + } + + // make sure read cursor is initialized + SetAllNullReadCursors(); + + // 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 (mWriteSegment == 0 && AllReadCursorsMatchWriteCursor()) { + char* head = mBuffer.GetSegment(0); + LOG(("OOO rolling back write cursor %u bytes\n", mWriteCursor - head)); + RollBackAllReadCursors(head); + mWriteCursor = head; + } + + aSegment = mWriteCursor; + aSegmentLen = mWriteLimit - mWriteCursor; + return NS_OK; +} + +void +nsPipe::AdvanceWriteCursor(uint32_t aBytesWritten) +{ + NS_ASSERTION(aBytesWritten, "don't call if no bytes written"); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + LOG(("OOO advancing write cursor by %u\n", aBytesWritten)); + + char* newWriteCursor = mWriteCursor + aBytesWritten; + NS_ASSERTION(newWriteCursor <= mWriteLimit, "write cursor exceeds limit"); + + // update read limit if reading in the same segment + UpdateAllReadCursors(newWriteCursor); + + mWriteCursor = newWriteCursor; + + ValidateAllReadCursors(); + + // update the writable flag on the output stream + if (mWriteCursor == mWriteLimit) { + mOutput.SetWritable(!IsAdvanceBufferFull(mon)); + } + + // notify input stream that pipe now contains additional data + bool needNotify = false; + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + if (mInputList[i]->OnInputReadable(aBytesWritten, events, mon) + == NotifyMonitor) { + needNotify = true; + } + } + + if (needNotify) { + mon.NotifyAll(); + } + } +} + +void +nsPipe::OnInputStreamException(nsPipeInputStream* aStream, nsresult aReason) +{ + MOZ_ASSERT(NS_FAILED(aReason)); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + // Its possible to re-enter this method when we call OnPipeException() or + // OnInputExection() below. If there is a caller stuck in our synchronous + // Wait() method, then they will get woken up with a failure code which + // re-enters this method. Therefore, gracefully handle unknown streams + // here. + + // If we only have one stream open and it is the given stream, then shut + // down the entire pipe. + if (mInputList.Length() == 1) { + if (mInputList[0] == aStream) { + OnPipeException(aReason); + } + return; + } + + // Otherwise just close the particular stream that hit an exception. + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + if (mInputList[i] != aStream) { + continue; + } + + MonitorAction action = mInputList[i]->OnInputException(aReason, events, + mon); + mInputList.RemoveElementAt(i); + + // Notify after element is removed in case we re-enter as a result. + if (action == NotifyMonitor) { + mon.NotifyAll(); + } + + return; + } + } +} + +void +nsPipe::OnPipeException(nsresult aReason, bool aOutputOnly) +{ + LOG(("PPP nsPipe::OnPipeException [reason=%x output-only=%d]\n", + aReason, aOutputOnly)); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + // if we've already hit an exception, then ignore this one. + if (NS_FAILED(mStatus)) { + return; + } + + mStatus = aReason; + + bool needNotify = false; + + nsTArray<nsPipeInputStream*> tmpInputList; + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + // an output-only exception applies to the input end if the pipe has + // zero bytes available. + if (aOutputOnly && mInputList[i]->Available()) { + tmpInputList.AppendElement(mInputList[i]); + continue; + } + + if (mInputList[i]->OnInputException(aReason, events, mon) + == NotifyMonitor) { + needNotify = true; + } + } + mInputList = tmpInputList; + + if (mOutput.OnOutputException(aReason, events) == NotifyMonitor) { + needNotify = true; + } + + // Notify after we have removed any input streams from mInputList + if (needNotify) { + mon.NotifyAll(); + } + } +} + +nsresult +nsPipe::CloneInputStream(nsPipeInputStream* aOriginal, + nsIInputStream** aCloneOut) +{ + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + RefPtr<nsPipeInputStream> ref = new nsPipeInputStream(*aOriginal); + mInputList.AppendElement(ref); + nsCOMPtr<nsIAsyncInputStream> downcast = ref.forget(); + downcast.forget(aCloneOut); + return NS_OK; +} + +uint32_t +nsPipe::CountSegmentReferences(int32_t aSegment) +{ + mReentrantMonitor.AssertCurrentThreadIn(); + uint32_t count = 0; + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + if (aSegment >= mInputList[i]->ReadState().mSegment) { + count += 1; + } + } + return count; +} + +void +nsPipe::SetAllNullReadCursors() +{ + mReentrantMonitor.AssertCurrentThreadIn(); + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + nsPipeReadState& readState = mInputList[i]->ReadState(); + if (!readState.mReadCursor) { + NS_ASSERTION(mWriteSegment == readState.mSegment, + "unexpected null read cursor"); + readState.mReadCursor = readState.mReadLimit = mWriteCursor; + } + } +} + +bool +nsPipe::AllReadCursorsMatchWriteCursor() +{ + mReentrantMonitor.AssertCurrentThreadIn(); + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + const nsPipeReadState& readState = mInputList[i]->ReadState(); + if (readState.mSegment != mWriteSegment || + readState.mReadCursor != mWriteCursor) { + return false; + } + } + return true; +} + +void +nsPipe::RollBackAllReadCursors(char* aWriteCursor) +{ + mReentrantMonitor.AssertCurrentThreadIn(); + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + nsPipeReadState& readState = mInputList[i]->ReadState(); + MOZ_ASSERT(mWriteSegment == readState.mSegment); + MOZ_ASSERT(mWriteCursor == readState.mReadCursor); + MOZ_ASSERT(mWriteCursor == readState.mReadLimit); + readState.mReadCursor = aWriteCursor; + readState.mReadLimit = aWriteCursor; + } +} + +void +nsPipe::UpdateAllReadCursors(char* aWriteCursor) +{ + mReentrantMonitor.AssertCurrentThreadIn(); + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + nsPipeReadState& readState = mInputList[i]->ReadState(); + if (mWriteSegment == readState.mSegment && + readState.mReadLimit == mWriteCursor) { + readState.mReadLimit = aWriteCursor; + } + } +} + +void +nsPipe::ValidateAllReadCursors() +{ + mReentrantMonitor.AssertCurrentThreadIn(); + // 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.) +#ifdef DEBUG + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + const nsPipeReadState& state = mInputList[i]->ReadState(); + NS_ASSERTION(state.mReadCursor != mWriteCursor || + (mBuffer.GetSegment(state.mSegment) == state.mReadCursor && + mWriteCursor == mWriteLimit), + "read cursor is bad"); + } +#endif +} + +uint32_t +nsPipe::GetBufferSegmentCount(const nsPipeReadState& aReadState, + const ReentrantMonitorAutoEnter& ev) const +{ + // The write segment can be smaller than the current reader position + // in some cases. For example, when the first write segment has not + // been allocated yet mWriteSegment is negative. In these cases + // the stream is effectively using zero segments. + if (mWriteSegment < aReadState.mSegment) { + return 0; + } + + MOZ_ASSERT(mWriteSegment >= 0); + MOZ_ASSERT(aReadState.mSegment >= 0); + + // Otherwise at least one segment is being used. We add one here + // since a single segment is being used when the write and read + // segment indices are the same. + return 1 + mWriteSegment - aReadState.mSegment; +} + +bool +nsPipe::IsAdvanceBufferFull(const ReentrantMonitorAutoEnter& ev) const +{ + // If we have fewer total segments than the limit we can immediately + // determine we are not full. Note, we must add one to mWriteSegment + // to convert from a index to a count. + MOZ_DIAGNOSTIC_ASSERT(mWriteSegment >= -1); + MOZ_DIAGNOSTIC_ASSERT(mWriteSegment < INT32_MAX); + uint32_t totalWriteSegments = mWriteSegment + 1; + if (totalWriteSegments < mMaxAdvanceBufferSegmentCount) { + return false; + } + + // Otherwise we must inspect all of our reader streams. We need + // to determine the buffer depth of the fastest reader. + uint32_t minBufferSegments = UINT32_MAX; + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + // Only count buffer segments from input streams that are open. + if (NS_FAILED(mInputList[i]->Status(ev))) { + continue; + } + const nsPipeReadState& state = mInputList[i]->ReadState(); + uint32_t bufferSegments = GetBufferSegmentCount(state, ev); + minBufferSegments = std::min(minBufferSegments, bufferSegments); + // We only care if any reader has fewer segments buffered than + // our threshold. We can stop once we hit that threshold. + if (minBufferSegments < mMaxAdvanceBufferSegmentCount) { + return false; + } + } + + // Note, its possible for minBufferSegments to exceed our + // mMaxAdvanceBufferSegmentCount here. This happens when a cloned + // reader gets far behind, but then the fastest reader stream is + // closed. This leaves us with a single stream that is buffered + // beyond our max. Naturally we continue to indicate the pipe + // is full at this point. + + return true; +} + +//----------------------------------------------------------------------------- +// nsPipeEvents methods: +//----------------------------------------------------------------------------- + +nsPipeEvents::~nsPipeEvents() +{ + // dispatch any pending events + + for (uint32_t i = 0; i < mInputList.Length(); ++i) { + mInputList[i].mCallback->OnInputStreamReady(mInputList[i].mStream); + } + mInputList.Clear(); + + if (mOutputCallback) { + mOutputCallback->OnOutputStreamReady(mOutputStream); + mOutputCallback = nullptr; + mOutputStream = nullptr; + } +} + +//----------------------------------------------------------------------------- +// nsPipeInputStream methods: +//----------------------------------------------------------------------------- + +NS_IMPL_ADDREF(nsPipeInputStream); +NS_IMPL_RELEASE(nsPipeInputStream); + +NS_INTERFACE_TABLE_HEAD(nsPipeInputStream) + NS_INTERFACE_TABLE_BEGIN + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIAsyncInputStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsISeekableStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsISearchableInputStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsICloneableInputStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIBufferedInputStream) + NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIClassInfo) + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsPipeInputStream, nsIInputStream, + nsIAsyncInputStream) + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsPipeInputStream, nsISupports, + nsIAsyncInputStream) + NS_INTERFACE_TABLE_END +NS_INTERFACE_TABLE_TAIL + +NS_IMPL_CI_INTERFACE_GETTER(nsPipeInputStream, + nsIInputStream, + nsIAsyncInputStream, + nsISeekableStream, + nsISearchableInputStream, + nsICloneableInputStream, + nsIBufferedInputStream) + +NS_IMPL_THREADSAFE_CI(nsPipeInputStream) + +NS_IMETHODIMP +nsPipeInputStream::Init(nsIInputStream*, uint32_t) +{ + MOZ_CRASH("nsPipeInputStream should never be initialized with " + "nsIBufferedInputStream::Init!\n"); +} + +uint32_t +nsPipeInputStream::Available() +{ + mPipe->mReentrantMonitor.AssertCurrentThreadIn(); + return mReadState.mAvailable; +} + +nsresult +nsPipeInputStream::Wait() +{ + NS_ASSERTION(mBlocking, "wait on non-blocking pipe input stream"); + + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + while (NS_SUCCEEDED(Status(mon)) && (mReadState.mAvailable == 0)) { + LOG(("III pipe input: waiting for data\n")); + + mBlocked = true; + mon.Wait(); + mBlocked = false; + + LOG(("III pipe input: woke up [status=%x available=%u]\n", + Status(mon), mReadState.mAvailable)); + } + + return Status(mon) == NS_BASE_STREAM_CLOSED ? NS_OK : Status(mon); +} + +MonitorAction +nsPipeInputStream::OnInputReadable(uint32_t aBytesWritten, + nsPipeEvents& aEvents, + const ReentrantMonitorAutoEnter& ev) +{ + MonitorAction result = DoNotNotifyMonitor; + + mPipe->mReentrantMonitor.AssertCurrentThreadIn(); + mReadState.mAvailable += aBytesWritten; + + if (mCallback && !(mCallbackFlags & WAIT_CLOSURE_ONLY)) { + aEvents.NotifyInputReady(this, mCallback); + mCallback = nullptr; + mCallbackFlags = 0; + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + +MonitorAction +nsPipeInputStream::OnInputException(nsresult aReason, nsPipeEvents& aEvents, + const ReentrantMonitorAutoEnter& ev) +{ + LOG(("nsPipeInputStream::OnInputException [this=%x reason=%x]\n", + this, aReason)); + + MonitorAction result = DoNotNotifyMonitor; + + NS_ASSERTION(NS_FAILED(aReason), "huh? successful exception"); + + if (NS_SUCCEEDED(mInputStatus)) { + mInputStatus = aReason; + } + + // force count of available bytes to zero. + mPipe->DrainInputStream(mReadState, aEvents); + + if (mCallback) { + aEvents.NotifyInputReady(this, mCallback); + mCallback = nullptr; + mCallbackFlags = 0; + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + +NS_IMETHODIMP +nsPipeInputStream::CloseWithStatus(nsresult aReason) +{ + LOG(("III CloseWithStatus [this=%x reason=%x]\n", this, aReason)); + + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + if (NS_FAILED(mInputStatus)) { + return NS_OK; + } + + if (NS_SUCCEEDED(aReason)) { + aReason = NS_BASE_STREAM_CLOSED; + } + + mPipe->OnInputStreamException(this, aReason); + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::Close() +{ + return CloseWithStatus(NS_BASE_STREAM_CLOSED); +} + +NS_IMETHODIMP +nsPipeInputStream::Available(uint64_t* aResult) +{ + // nsPipeInputStream supports under 4GB stream only + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // return error if closed + if (!mReadState.mAvailable && NS_FAILED(Status(mon))) { + return Status(mon); + } + + *aResult = (uint64_t)mReadState.mAvailable; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, + uint32_t aCount, + uint32_t* aReadCount) +{ + LOG(("III ReadSegments [this=%x count=%u]\n", this, aCount)); + + nsresult rv = NS_OK; + + *aReadCount = 0; + while (aCount) { + AutoReadSegment segment(mPipe, mReadState, aCount); + rv = segment.Status(); + if (NS_FAILED(rv)) { + // ignore this error if we've already read something. + if (*aReadCount > 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->OnInputStreamException(this, rv); + break; + } + + uint32_t writeCount; + while (segment.Length()) { + writeCount = 0; + + rv = aWriter(static_cast<nsIAsyncInputStream*>(this), aClosure, + segment.Data(), *aReadCount, segment.Length(), &writeCount); + + if (NS_FAILED(rv) || writeCount == 0) { + aCount = 0; + // any errors returned from the writer end here: do not + // propagate to the caller of ReadSegments. + rv = NS_OK; + break; + } + + NS_ASSERTION(writeCount <= segment.Length(), "wrote more than expected"); + segment.Advance(writeCount); + aCount -= writeCount; + *aReadCount += writeCount; + mLogicalOffset += writeCount; + } + } + + return rv; +} + +NS_IMETHODIMP +nsPipeInputStream::Read(char* aToBuf, uint32_t aBufLen, uint32_t* aReadCount) +{ + return ReadSegments(NS_CopySegmentToBuffer, aToBuf, aBufLen, aReadCount); +} + +NS_IMETHODIMP +nsPipeInputStream::IsNonBlocking(bool* aNonBlocking) +{ + *aNonBlocking = !mBlocking; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aTarget) +{ + LOG(("III AsyncWait [this=%x]\n", this)); + + nsPipeEvents pipeEvents; + { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // replace a pending callback + mCallback = nullptr; + mCallbackFlags = 0; + + if (!aCallback) { + return NS_OK; + } + + nsCOMPtr<nsIInputStreamCallback> proxy; + if (aTarget) { + proxy = NS_NewInputStreamReadyEvent(aCallback, aTarget); + aCallback = proxy; + } + + if (NS_FAILED(Status(mon)) || + (mReadState.mAvailable && !(aFlags & WAIT_CLOSURE_ONLY))) { + // stream is already closed or readable; post event. + pipeEvents.NotifyInputReady(this, aCallback); + } else { + // queue up callback object to be notified when data becomes available + mCallback = aCallback; + mCallbackFlags = aFlags; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + NS_NOTREACHED("nsPipeInputStream::Seek"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPipeInputStream::Tell(int64_t* aOffset) +{ + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // return error if closed + if (!mReadState.mAvailable && NS_FAILED(Status(mon))) { + return Status(mon); + } + + *aOffset = mLogicalOffset; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::SetEOF() +{ + NS_NOTREACHED("nsPipeInputStream::SetEOF"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +static bool strings_equal(bool aIgnoreCase, + const char* aS1, const char* aS2, uint32_t aLen) +{ + return aIgnoreCase + ? !nsCRT::strncasecmp(aS1, aS2, aLen) : !nsCRT::strncmp(aS1, aS2, aLen); +} + +NS_IMETHODIMP +nsPipeInputStream::Search(const char* aForString, + bool aIgnoreCase, + bool* aFound, + uint32_t* aOffsetSearchedTo) +{ + LOG(("III Search [for=%s ic=%u]\n", aForString, aIgnoreCase)); + + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + char* cursor1; + char* limit1; + uint32_t index = 0, offset = 0; + uint32_t strLen = strlen(aForString); + + mPipe->PeekSegment(mReadState, 0, cursor1, limit1); + if (cursor1 == limit1) { + *aFound = false; + *aOffsetSearchedTo = 0; + LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); + 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 (strings_equal(aIgnoreCase, &cursor1[i], aForString, strLen)) { + *aFound = true; + *aOffsetSearchedTo = offset + i; + LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); + return NS_OK; + } + } + + // get the next segment + char* cursor2; + char* limit2; + uint32_t len2; + + index++; + offset += len1; + + mPipe->PeekSegment(mReadState, index, cursor2, limit2); + if (cursor2 == limit2) { + *aFound = false; + *aOffsetSearchedTo = offset - strLen + 1; + LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); + 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 = &aForString[strLen - strPart2Len]; + uint32_t bufSeg1Offset = len1 - strPart1Len; + if (strings_equal(aIgnoreCase, &cursor1[bufSeg1Offset], aForString, strPart1Len) && + strings_equal(aIgnoreCase, cursor2, strPart2, strPart2Len)) { + *aFound = true; + *aOffsetSearchedTo = offset - strPart1Len; + LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo)); + 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 +} + +NS_IMETHODIMP +nsPipeInputStream::GetCloneable(bool* aCloneableOut) +{ + *aCloneableOut = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::Clone(nsIInputStream** aCloneOut) +{ + return mPipe->CloneInputStream(this, aCloneOut); +} + +nsresult +nsPipeInputStream::Status(const ReentrantMonitorAutoEnter& ev) const +{ + if (NS_FAILED(mInputStatus)) { + return mInputStatus; + } + + if (mReadState.mAvailable) { + // Still something to read and this input stream state is OK. + return NS_OK; + } + + // Nothing to read, just fall through to the pipe's state that + // may reflect state of its output stream side (already closed). + return mPipe->mStatus; +} + +nsresult +nsPipeInputStream::Status() const +{ + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + return Status(mon); +} + +nsPipeInputStream::~nsPipeInputStream() +{ + Close(); +} + +//----------------------------------------------------------------------------- +// nsPipeOutputStream methods: +//----------------------------------------------------------------------------- + +NS_IMPL_QUERY_INTERFACE(nsPipeOutputStream, + nsIOutputStream, + nsIAsyncOutputStream, + nsIClassInfo) + +NS_IMPL_CI_INTERFACE_GETTER(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; +} + +MonitorAction +nsPipeOutputStream::OnOutputWritable(nsPipeEvents& aEvents) +{ + MonitorAction result = DoNotNotifyMonitor; + + mWritable = true; + + if (mCallback && !(mCallbackFlags & WAIT_CLOSURE_ONLY)) { + aEvents.NotifyOutputReady(this, mCallback); + mCallback = nullptr; + mCallbackFlags = 0; + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + +MonitorAction +nsPipeOutputStream::OnOutputException(nsresult aReason, nsPipeEvents& aEvents) +{ + LOG(("nsPipeOutputStream::OnOutputException [this=%x reason=%x]\n", + this, aReason)); + + MonitorAction result = DoNotNotifyMonitor; + + NS_ASSERTION(NS_FAILED(aReason), "huh? successful exception"); + mWritable = false; + + if (mCallback) { + aEvents.NotifyOutputReady(this, mCallback); + mCallback = nullptr; + mCallbackFlags = 0; + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + + +NS_IMETHODIMP_(MozExternalRefCountType) +nsPipeOutputStream::AddRef() +{ + ++mWriterRefCnt; + return mPipe->AddRef(); +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsPipeOutputStream::Release() +{ + if (--mWriterRefCnt == 0) { + Close(); + } + return mPipe->Release(); +} + +NS_IMETHODIMP +nsPipeOutputStream::CloseWithStatus(nsresult aReason) +{ + LOG(("OOO CloseWithStatus [this=%x reason=%x]\n", this, aReason)); + + if (NS_SUCCEEDED(aReason)) { + aReason = NS_BASE_STREAM_CLOSED; + } + + // input stream may remain open + mPipe->OnPipeException(aReason, true); + return NS_OK; +} + +NS_IMETHODIMP +nsPipeOutputStream::Close() +{ + return CloseWithStatus(NS_BASE_STREAM_CLOSED); +} + +NS_IMETHODIMP +nsPipeOutputStream::WriteSegments(nsReadSegmentFun aReader, + void* aClosure, + uint32_t aCount, + uint32_t* aWriteCount) +{ + LOG(("OOO WriteSegments [this=%x count=%u]\n", this, aCount)); + + nsresult rv = NS_OK; + + char* segment; + uint32_t segmentLen; + + *aWriteCount = 0; + while (aCount) { + 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 (*aWriteCount > 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 aCount + if (segmentLen > aCount) { + segmentLen = aCount; + } + + uint32_t readCount, originalLen = segmentLen; + while (segmentLen) { + readCount = 0; + + rv = aReader(this, aClosure, segment, *aWriteCount, segmentLen, &readCount); + + if (NS_FAILED(rv) || readCount == 0) { + aCount = 0; + // any errors returned from the aReader 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; + aCount -= readCount; + *aWriteCount += readCount; + mLogicalOffset += readCount; + } + + if (segmentLen < originalLen) { + mPipe->AdvanceWriteCursor(originalLen - segmentLen); + } + } + + return rv; +} + +static nsresult +nsReadFromRawBuffer(nsIOutputStream* aOutStr, + void* aClosure, + char* aToRawSegment, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aReadCount) +{ + const char* fromBuf = (const char*)aClosure; + memcpy(aToRawSegment, &fromBuf[aOffset], aCount); + *aReadCount = aCount; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeOutputStream::Write(const char* aFromBuf, + uint32_t aBufLen, + uint32_t* aWriteCount) +{ + return WriteSegments(nsReadFromRawBuffer, (void*)aFromBuf, aBufLen, aWriteCount); +} + +NS_IMETHODIMP +nsPipeOutputStream::Flush(void) +{ + // nothing to do + return NS_OK; +} + +static nsresult +nsReadFromInputStream(nsIOutputStream* aOutStr, + void* aClosure, + char* aToRawSegment, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aReadCount) +{ + nsIInputStream* fromStream = (nsIInputStream*)aClosure; + return fromStream->Read(aToRawSegment, aCount, aReadCount); +} + +NS_IMETHODIMP +nsPipeOutputStream::WriteFrom(nsIInputStream* aFromStream, + uint32_t aCount, + uint32_t* aWriteCount) +{ + return WriteSegments(nsReadFromInputStream, aFromStream, aCount, aWriteCount); +} + +NS_IMETHODIMP +nsPipeOutputStream::IsNonBlocking(bool* aNonBlocking) +{ + *aNonBlocking = !mBlocking; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeOutputStream::AsyncWait(nsIOutputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aTarget) +{ + LOG(("OOO AsyncWait [this=%x]\n", this)); + + nsPipeEvents pipeEvents; + { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // replace a pending callback + mCallback = nullptr; + mCallbackFlags = 0; + + if (!aCallback) { + return NS_OK; + } + + nsCOMPtr<nsIOutputStreamCallback> proxy; + if (aTarget) { + proxy = NS_NewOutputStreamReadyEvent(aCallback, aTarget); + aCallback = proxy; + } + + if (NS_FAILED(mPipe->mStatus) || + (mWritable && !(aFlags & WAIT_CLOSURE_ONLY))) { + // stream is already closed or writable; post event. + pipeEvents.NotifyOutputReady(this, aCallback); + } else { + // queue up callback object to be notified when data becomes available + mCallback = aCallback; + mCallbackFlags = aFlags; + } + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +nsresult +NS_NewPipe(nsIInputStream** aPipeIn, + nsIOutputStream** aPipeOut, + uint32_t aSegmentSize, + uint32_t aMaxSize, + bool aNonBlockingInput, + bool aNonBlockingOutput) +{ + if (aSegmentSize == 0) { + aSegmentSize = DEFAULT_SEGMENT_SIZE; + } + + // Handle aMaxSize of UINT32_MAX as a special case + uint32_t segmentCount; + if (aMaxSize == UINT32_MAX) { + segmentCount = UINT32_MAX; + } else { + segmentCount = aMaxSize / aSegmentSize; + } + + nsIAsyncInputStream* in; + nsIAsyncOutputStream* out; + nsresult rv = NS_NewPipe2(&in, &out, aNonBlockingInput, aNonBlockingOutput, + aSegmentSize, segmentCount); + if (NS_FAILED(rv)) { + return rv; + } + + *aPipeIn = in; + *aPipeOut = out; + return NS_OK; +} + +nsresult +NS_NewPipe2(nsIAsyncInputStream** aPipeIn, + nsIAsyncOutputStream** aPipeOut, + bool aNonBlockingInput, + bool aNonBlockingOutput, + uint32_t aSegmentSize, + uint32_t aSegmentCount) +{ + nsPipe* pipe = new nsPipe(); + nsresult rv = pipe->Init(aNonBlockingInput, + aNonBlockingOutput, + aSegmentSize, + aSegmentCount); + if (NS_FAILED(rv)) { + NS_ADDREF(pipe); + NS_RELEASE(pipe); + return rv; + } + + // These always succeed because the pipe is initialized above. + MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(aPipeIn)); + MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(aPipeOut)); + return NS_OK; +} + +nsresult +nsPipeConstructor(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + nsPipe* pipe = new nsPipe(); + NS_ADDREF(pipe); + nsresult rv = pipe->QueryInterface(aIID, aResult); + NS_RELEASE(pipe); + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/io/nsScriptableBase64Encoder.cpp b/xpcom/io/nsScriptableBase64Encoder.cpp new file mode 100644 index 0000000000..a8ffce8cd0 --- /dev/null +++ b/xpcom/io/nsScriptableBase64Encoder.cpp @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsScriptableBase64Encoder.h" +#include "mozilla/Base64.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsScriptableBase64Encoder, nsIScriptableBase64Encoder) + +NS_IMETHODIMP +nsScriptableBase64Encoder::EncodeToCString(nsIInputStream* aStream, + uint32_t aLength, + nsACString& aResult) +{ + return Base64EncodeInputStream(aStream, aResult, aLength); +} + +NS_IMETHODIMP +nsScriptableBase64Encoder::EncodeToString(nsIInputStream* aStream, + uint32_t aLength, + nsAString& aResult) +{ + return Base64EncodeInputStream(aStream, aResult, aLength); +} diff --git a/xpcom/io/nsScriptableBase64Encoder.h b/xpcom/io/nsScriptableBase64Encoder.h new file mode 100644 index 0000000000..d9121e5de2 --- /dev/null +++ b/xpcom/io/nsScriptableBase64Encoder.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 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 0000000000..6f8022238e --- /dev/null +++ b/xpcom/io/nsScriptableInputStream.cpp @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsScriptableInputStream.h" +#include "nsMemory.h" +#include "nsString.h" + +NS_IMPL_ISUPPORTS(nsScriptableInputStream, nsIScriptableInputStream) + +// nsIScriptableInputStream methods +NS_IMETHODIMP +nsScriptableInputStream::Close() +{ + 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* aResult) +{ + if (!mInputStream) { + return NS_ERROR_NOT_INITIALIZED; + } + return mInputStream->Available(aResult); +} + +NS_IMETHODIMP +nsScriptableInputStream::Read(uint32_t aCount, char** aResult) +{ + 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*)malloc(count + 1); // make room for '\0' + if (!buffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = ReadHelper(buffer, count); + if (NS_FAILED(rv)) { + free(buffer); + return rv; + } + + buffer[count] = '\0'; + *aResult = buffer; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptableInputStream::ReadBytes(uint32_t aCount, nsACString& aResult) +{ + if (!mInputStream) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (!aResult.SetLength(aCount, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + MOZ_ASSERT(aResult.Length() == aCount); + char* ptr = aResult.BeginWriting(); + nsresult rv = ReadHelper(ptr, aCount); + if (NS_FAILED(rv)) { + aResult.Truncate(); + } + return rv; +} + +nsresult +nsScriptableInputStream::ReadHelper(char* aBuffer, uint32_t aCount) +{ + uint32_t totalBytesRead = 0; + while (1) { + uint32_t bytesRead; + nsresult rv = mInputStream->Read(aBuffer + 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) { + return NS_ERROR_FAILURE; + } + + } + return NS_OK; +} + +nsresult +nsScriptableInputStream::Create(nsISupports* aOuter, REFNSIID aIID, + void** aResult) +{ + if (aOuter) { + return NS_ERROR_NO_AGGREGATION; + } + + RefPtr<nsScriptableInputStream> sis = new nsScriptableInputStream(); + return sis->QueryInterface(aIID, aResult); +} diff --git a/xpcom/io/nsScriptableInputStream.h b/xpcom/io/nsScriptableInputStream.h new file mode 100644 index 0000000000..a84facd94c --- /dev/null +++ b/xpcom/io/nsScriptableInputStream.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 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() + { + } + + nsresult ReadHelper(char* aBuffer, uint32_t aCount); + + nsCOMPtr<nsIInputStream> mInputStream; +}; + +#endif // ___nsscriptableinputstream___h_ diff --git a/xpcom/io/nsSegmentedBuffer.cpp b/xpcom/io/nsSegmentedBuffer.cpp new file mode 100644 index 0000000000..ab42a73c72 --- /dev/null +++ b/xpcom/io/nsSegmentedBuffer.cpp @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSegmentedBuffer.h" +#include "nsMemory.h" + +nsresult +nsSegmentedBuffer::Init(uint32_t aSegmentSize, uint32_t aMaxSize) +{ + if (mSegmentArrayCount != 0) { + return NS_ERROR_FAILURE; // initialized more than once + } + mSegmentSize = aSegmentSize; + mMaxSize = aMaxSize; +#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) { + uint32_t bytes = mSegmentArrayCount * sizeof(char*); + mSegmentArray = (char**)moz_xmalloc(bytes); + if (!mSegmentArray) { + return nullptr; + } + memset(mSegmentArray, 0, bytes); + } + + if (IsFull()) { + uint32_t newArraySize = mSegmentArrayCount * 2; + uint32_t bytes = newArraySize * sizeof(char*); + char** newSegArray = (char**)moz_xrealloc(mSegmentArray, bytes); + if (!newSegArray) { + 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*)malloc(mSegmentSize); + if (!seg) { + return nullptr; + } + mSegmentArray[mLastSegmentIndex] = seg; + mLastSegmentIndex = ModSegArraySize(mLastSegmentIndex + 1); + return seg; +} + +bool +nsSegmentedBuffer::DeleteFirstSegment() +{ + NS_ASSERTION(mSegmentArray[mFirstSegmentIndex] != nullptr, "deleting bad segment"); + 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"); + free(mSegmentArray[last]); + mSegmentArray[last] = nullptr; + mLastSegmentIndex = last; + return (bool)(mLastSegmentIndex == mFirstSegmentIndex); +} + +bool +nsSegmentedBuffer::ReallocLastSegment(size_t aNewSize) +{ + int32_t last = ModSegArraySize(mLastSegmentIndex - 1); + NS_ASSERTION(mSegmentArray[last] != nullptr, "realloc'ing bad segment"); + char* newSegment = (char*)realloc(mSegmentArray[last], aNewSize); + if (newSegment) { + mSegmentArray[last] = newSegment; + return true; + } + return false; +} + +void +nsSegmentedBuffer::Empty() +{ + if (mSegmentArray) { + for (uint32_t i = 0; i < mSegmentArrayCount; i++) { + if (mSegmentArray[i]) { + free(mSegmentArray[i]); + } + } + 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 0000000000..37b3a8e4fc --- /dev/null +++ b/xpcom/io/nsSegmentedBuffer.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSegmentedBuffer_h__ +#define nsSegmentedBuffer_h__ + +#include "nsIMemory.h" + +class nsSegmentedBuffer +{ +public: + nsSegmentedBuffer() + : mSegmentSize(0) + , mMaxSize(0) + , mSegmentArray(nullptr) + , mSegmentArrayCount(0) + , mFirstSegmentIndex(0) + , mLastSegmentIndex(0) + { + } + + ~nsSegmentedBuffer() + { + Empty(); + } + + + nsresult Init(uint32_t aSegmentSize, uint32_t aMaxSize); + + 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 aNewSize); + + 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 aIndex) + { + NS_ASSERTION(aIndex < GetSegmentCount(), "index out of bounds"); + int32_t i = ModSegArraySize(mFirstSegmentIndex + (int32_t)aIndex); + return mSegmentArray[i]; + } + +protected: + inline int32_t ModSegArraySize(int32_t aIndex) + { + uint32_t result = aIndex & (mSegmentArrayCount - 1); + NS_ASSERTION(result == aIndex % mSegmentArrayCount, + "non-power-of-2 mSegmentArrayCount"); + return result; + } + + inline bool IsFull() + { + return ModSegArraySize(mLastSegmentIndex + 1) == mFirstSegmentIndex; + } + +protected: + uint32_t mSegmentSize; + uint32_t mMaxSize; + 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 0000000000..3af7bb8532 --- /dev/null +++ b/xpcom/io/nsStorageStream.cpp @@ -0,0 +1,648 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * 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 "nsICloneableInputStream.h" +#include "nsIInputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsISeekableStream.h" +#include "mozilla/Logging.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/ipc/InputStreamUtils.h" + +using mozilla::ipc::InputStreamParams; +using mozilla::ipc::StringInputStreamParams; +using mozilla::Maybe; +using mozilla::Some; + +// +// Log module for StorageStream logging... +// +// To enable logging (see prlog.h for full details): +// +// set MOZ_LOG=StorageStreamLog:5 +// set MOZ_LOG_FILE=storage.log +// +// This enables LogLevel::Debug level information and places all output in +// the file storage.log. +// +static LazyLogModule sStorageStreamLog("nsStorageStream"); +#ifdef LOG +#undef LOG +#endif +#define LOG(args) MOZ_LOG(sStorageStreamLog, mozilla::LogLevel::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_ISUPPORTS(nsStorageStream, + nsIStorageStream, + nsIOutputStream) + +NS_IMETHODIMP +nsStorageStream::Init(uint32_t aSegmentSize, uint32_t aMaxSize) +{ + mSegmentedBuffer = new nsSegmentedBuffer(); + mSegmentSize = aSegmentSize; + mSegmentSizeLog2 = mozilla::FloorLog2(aSegmentSize); + + // Segment size must be a power of two + if (mSegmentSize != ((uint32_t)1 << mSegmentSizeLog2)) { + return NS_ERROR_INVALID_ARG; + } + + return mSegmentedBuffer->Init(aSegmentSize, aMaxSize); +} + +NS_IMETHODIMP +nsStorageStream::GetOutputStream(int32_t aStartingOffset, + nsIOutputStream** aOutputStream) +{ + if (NS_WARN_IF(!aOutputStream)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!mSegmentedBuffer)) { + return 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() +{ + if (NS_WARN_IF(!mSegmentedBuffer)) { + return 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) +{ + if (NS_WARN_IF(!aNumWritten) || NS_WARN_IF(!aBuffer)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + const char* readCursor; + uint32_t count, availableInSegment, remaining; + nsresult rv = NS_OK; + + 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* aInStr, uint32_t aCount, + uint32_t* aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsStorageStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsStorageStream::IsNonBlocking(bool* aNonBlocking) +{ + *aNonBlocking = false; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::GetLength(uint32_t* aLength) +{ + *aLength = mLogicalLength; + return NS_OK; +} + +// Truncate the buffer by deleting the end segments +NS_IMETHODIMP +nsStorageStream::SetLength(uint32_t aLength) +{ + if (NS_WARN_IF(!mSegmentedBuffer)) { + return 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) +{ + *aWriteInProgress = mWriteInProgress; + return NS_OK; +} + +nsresult +nsStorageStream::Seek(int32_t aPosition) +{ + if (NS_WARN_IF(!mSegmentedBuffer)) { + return 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 final + : public nsIInputStream + , public nsISeekableStream + , public nsIIPCSerializableInputStream + , public nsICloneableInputStream +{ +public: + nsStorageInputStream(nsStorageStream* aStorageStream, uint32_t aSegmentSize) + : mStorageStream(aStorageStream), mReadCursor(0), + mSegmentEnd(0), mSegmentNum(0), + mSegmentSize(aSegmentSize), mLogicalCursor(0), + mStatus(NS_OK) + { + } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + +private: + ~nsStorageInputStream() + { + } + +protected: + nsresult Seek(uint32_t aPosition); + + friend class nsStorageStream; + +private: + RefPtr<nsStorageStream> mStorageStream; + uint32_t mReadCursor; // Next memory location to read byte, or 0 + 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_ISUPPORTS(nsStorageInputStream, + nsIInputStream, + nsISeekableStream, + nsIIPCSerializableInputStream, + nsICloneableInputStream) + +NS_IMETHODIMP +nsStorageStream::NewInputStream(int32_t aStartingOffset, + nsIInputStream** aInputStream) +{ + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + RefPtr<nsStorageInputStream> inputStream = + new nsStorageInputStream(this, mSegmentSize); + + nsresult rv = inputStream->Seek(aStartingOffset); + if (NS_FAILED(rv)) { + return rv; + } + + inputStream.forget(aInputStream); + 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 aWriter, void* aClosure, + 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; + } + + // We have data in the stream, but if mSegmentEnd is zero, then we + // were likely constructed prior to any data being written into + // the stream. Therefore, if mSegmentEnd is non-zero, we should + // move into the next segment; otherwise, we should stay in this + // segment so our input state can be updated and we can properly + // perform the initial read. + if (mSegmentEnd > 0) { + mSegmentNum++; + } + mReadCursor = 0; + mSegmentEnd = XPCOM_MIN(mSegmentSize, available); + availableInSegment = mSegmentEnd; + } + const char* cur = mStorageStream->mSegmentedBuffer->GetSegment(mSegmentNum); + + count = XPCOM_MIN(availableInSegment, remainingCapacity); + rv = aWriter(this, aClosure, 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; +} + +nsresult +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; +} + +void +nsStorageInputStream::Serialize(InputStreamParams& aParams, FileDescriptorArray&) +{ + nsCString combined; + int64_t offset; + mozilla::DebugOnly<nsresult> rv = Tell(&offset); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + uint64_t remaining; + rv = Available(&remaining); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + combined.SetCapacity(remaining); + uint32_t numRead = 0; + + rv = Read(combined.BeginWriting(), remaining, &numRead); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + MOZ_ASSERT(numRead == remaining); + combined.SetLength(numRead); + + rv = Seek(NS_SEEK_SET, offset); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + StringInputStreamParams params; + params.data() = combined; + aParams = params; +} + +Maybe<uint64_t> +nsStorageInputStream::ExpectedSerializedLength() +{ + uint64_t remaining = 0; + DebugOnly<nsresult> rv = Available(&remaining); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return Some(remaining); +} + +bool +nsStorageInputStream::Deserialize(const InputStreamParams& aParams, + const FileDescriptorArray&) +{ + NS_NOTREACHED("We should never attempt to deserialize a storage input stream."); + return false; +} + +NS_IMETHODIMP +nsStorageInputStream::GetCloneable(bool* aCloneableOut) +{ + *aCloneableOut = true; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::Clone(nsIInputStream** aCloneOut) +{ + return mStorageStream->NewInputStream(mLogicalCursor, aCloneOut); +} + +nsresult +NS_NewStorageStream(uint32_t aSegmentSize, uint32_t aMaxSize, + nsIStorageStream** aResult) +{ + RefPtr<nsStorageStream> storageStream = new nsStorageStream(); + nsresult rv = storageStream->Init(aSegmentSize, aMaxSize); + if (NS_FAILED(rv)) { + return rv; + } + storageStream.forget(aResult); + return NS_OK; +} + +// Undefine LOG, so that other .cpp files (or their includes) won't complain +// about it already being defined, when we build in unified mode. +#undef LOG diff --git a/xpcom/io/nsStorageStream.h b/xpcom/io/nsStorageStream.h new file mode 100644 index 0000000000..7fddd683ed --- /dev/null +++ b/xpcom/io/nsStorageStream.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * 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 final + : public nsIStorageStream + , public nsIOutputStream +{ +public: + nsStorageStream(); + + NS_DECL_THREADSAFE_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 + + nsresult 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 0000000000..891dad59fb --- /dev/null +++ b/xpcom/io/nsStreamUtils.cpp @@ -0,0 +1,957 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Mutex.h" +#include "mozilla/Attributes.h" +#include "nsStreamUtils.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsIPipe.h" +#include "nsICloneableInputStream.h" +#include "nsIEventTarget.h" +#include "nsICancelableRunnable.h" +#include "nsISafeOutputStream.h" +#include "nsString.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIBufferedStreams.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +//----------------------------------------------------------------------------- + +// This is a nsICancelableRunnable because we can dispatch it to Workers and +// those can be shut down at any time, and in these cases, Cancel() is called +// instead of Run(). +class nsInputStreamReadyEvent final + : public CancelableRunnable + , public nsIInputStreamCallback +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + nsInputStreamReadyEvent(nsIInputStreamCallback* aCallback, + nsIEventTarget* aTarget) + : mCallback(aCallback) + , mTarget(aTarget) + { + } + +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* aStream) override + { + mStream = aStream; + + 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() override + { + if (mCallback) { + if (mStream) { + mCallback->OnInputStreamReady(mStream); + } + mCallback = nullptr; + } + return NS_OK; + } + + nsresult Cancel() override + { + mCallback = nullptr; + return NS_OK; + } + +private: + nsCOMPtr<nsIAsyncInputStream> mStream; + nsCOMPtr<nsIInputStreamCallback> mCallback; + nsCOMPtr<nsIEventTarget> mTarget; +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsInputStreamReadyEvent, CancelableRunnable, + nsIInputStreamCallback) + +//----------------------------------------------------------------------------- + +// This is a nsICancelableRunnable because we can dispatch it to Workers and +// those can be shut down at any time, and in these cases, Cancel() is called +// instead of Run(). +class nsOutputStreamReadyEvent final + : public CancelableRunnable + , public nsIOutputStreamCallback +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + nsOutputStreamReadyEvent(nsIOutputStreamCallback* aCallback, + nsIEventTarget* aTarget) + : mCallback(aCallback) + , mTarget(aTarget) + { + } + +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* aStream) override + { + mStream = aStream; + + 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() override + { + if (mCallback) { + if (mStream) { + mCallback->OnOutputStreamReady(mStream); + } + mCallback = nullptr; + } + return NS_OK; + } + + nsresult Cancel() override + { + mCallback = nullptr; + return NS_OK; + } + +private: + nsCOMPtr<nsIAsyncOutputStream> mStream; + nsCOMPtr<nsIOutputStreamCallback> mCallback; + nsCOMPtr<nsIEventTarget> mTarget; +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsOutputStreamReadyEvent, CancelableRunnable, + nsIOutputStreamCallback) + +//----------------------------------------------------------------------------- + +already_AddRefed<nsIInputStreamCallback> +NS_NewInputStreamReadyEvent(nsIInputStreamCallback* aCallback, + nsIEventTarget* aTarget) +{ + NS_ASSERTION(aCallback, "null callback"); + NS_ASSERTION(aTarget, "null target"); + RefPtr<nsInputStreamReadyEvent> ev = + new nsInputStreamReadyEvent(aCallback, aTarget); + return ev.forget(); +} + +already_AddRefed<nsIOutputStreamCallback> +NS_NewOutputStreamReadyEvent(nsIOutputStreamCallback* aCallback, + nsIEventTarget* aTarget) +{ + NS_ASSERTION(aCallback, "null callback"); + NS_ASSERTION(aTarget, "null target"); + RefPtr<nsOutputStreamReadyEvent> ev = + new nsOutputStreamReadyEvent(aCallback, aTarget); + return ev.forget(); +} + +//----------------------------------------------------------------------------- +// NS_AsyncCopy implementation + +// abstract stream copier... +class nsAStreamCopier + : public nsIInputStreamCallback + , public nsIOutputStreamCallback + , public CancelableRunnable +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + 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) + { + } + + // kick off the async copy... + nsresult Start(nsIInputStream* aSource, + nsIOutputStream* aSink, + nsIEventTarget* aTarget, + nsAsyncCopyCallbackFun aCallback, + void* aClosure, + uint32_t aChunksize, + bool aCloseSource, + bool aCloseSink, + nsAsyncCopyProgressFun aProgressCallback) + { + mSource = aSource; + mSink = aSink; + mTarget = aTarget; + mCallback = aCallback; + mClosure = aClosure; + mChunkSize = aChunksize; + mCloseSource = aCloseSource; + mCloseSink = aCloseSink; + mProgressCallback = aProgressCallback; + + 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* aSourceCondition, + nsresult* aSinkCondition) = 0; + + void Process() + { + if (!mSource || !mSink) { + return; + } + + nsresult cancelStatus; + bool canceled; + { + MutexAutoLock lock(mLock); + canceled = mCanceled; + cancelStatus = mCancelStatus; + } + + // If the copy was canceled before Process() was even called, then + // sourceCondition and sinkCondition should be set to error results to + // ensure we don't call Finish() on a canceled nsISafeOutputStream. + MOZ_ASSERT(NS_FAILED(cancelStatus) == canceled, "cancel needs an error"); + nsresult sourceCondition = cancelStatus; + nsresult sinkCondition = cancelStatus; + + // 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* aSource) override + { + PostContinuationEvent(); + return NS_OK; + } + + NS_IMETHOD OnOutputStreamReady(nsIAsyncOutputStream* aSink) override + { + PostContinuationEvent(); + return NS_OK; + } + + // continuation event handler + NS_IMETHOD Run() override + { + 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 Cancel() MOZ_MUST_OVERRIDE override = 0; + + 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; + + // virtual since subclasses call superclass Release() + virtual ~nsAStreamCopier() + { + } +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsAStreamCopier, + CancelableRunnable, + nsIInputStreamCallback, + nsIOutputStreamCallback) + +class nsStreamCopierIB final : public nsAStreamCopier +{ +public: + nsStreamCopierIB() : nsAStreamCopier() + { + } + virtual ~nsStreamCopierIB() + { + } + + struct MOZ_STACK_CLASS ReadSegmentsState + { + // the nsIOutputStream will outlive the ReadSegmentsState on the stack + nsIOutputStream* MOZ_NON_OWNING_REF mSink; + nsresult mSinkCondition; + }; + + static nsresult ConsumeInputBuffer(nsIInputStream* aInStr, + void* aClosure, + const char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountWritten) + { + ReadSegmentsState* state = (ReadSegmentsState*)aClosure; + + nsresult rv = state->mSink->Write(aBuffer, aCount, aCountWritten); + if (NS_FAILED(rv)) { + state->mSinkCondition = rv; + } else if (*aCountWritten == 0) { + state->mSinkCondition = NS_BASE_STREAM_CLOSED; + } + + return state->mSinkCondition; + } + + uint32_t DoCopy(nsresult* aSourceCondition, + nsresult* aSinkCondition) override + { + ReadSegmentsState state; + state.mSink = mSink; + state.mSinkCondition = NS_OK; + + uint32_t n; + *aSourceCondition = + mSource->ReadSegments(ConsumeInputBuffer, &state, mChunkSize, &n); + *aSinkCondition = state.mSinkCondition; + return n; + } + + nsresult Cancel() override + { + return NS_OK; + } +}; + +class nsStreamCopierOB final : public nsAStreamCopier +{ +public: + nsStreamCopierOB() : nsAStreamCopier() + { + } + virtual ~nsStreamCopierOB() + { + } + + struct MOZ_STACK_CLASS WriteSegmentsState + { + // the nsIInputStream will outlive the WriteSegmentsState on the stack + nsIInputStream* MOZ_NON_OWNING_REF mSource; + nsresult mSourceCondition; + }; + + static nsresult FillOutputBuffer(nsIOutputStream* aOutStr, + void* aClosure, + char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountRead) + { + WriteSegmentsState* state = (WriteSegmentsState*)aClosure; + + nsresult rv = state->mSource->Read(aBuffer, aCount, aCountRead); + if (NS_FAILED(rv)) { + state->mSourceCondition = rv; + } else if (*aCountRead == 0) { + state->mSourceCondition = NS_BASE_STREAM_CLOSED; + } + + return state->mSourceCondition; + } + + uint32_t DoCopy(nsresult* aSourceCondition, + nsresult* aSinkCondition) override + { + WriteSegmentsState state; + state.mSource = mSource; + state.mSourceCondition = NS_OK; + + uint32_t n; + *aSinkCondition = + mSink->WriteSegments(FillOutputBuffer, &state, mChunkSize, &n); + *aSourceCondition = state.mSourceCondition; + return n; + } + + nsresult Cancel() override + { + return NS_OK; + } +}; + +//----------------------------------------------------------------------------- + +nsresult +NS_AsyncCopy(nsIInputStream* aSource, + nsIOutputStream* aSink, + nsIEventTarget* aTarget, + nsAsyncCopyMode aMode, + uint32_t aChunkSize, + nsAsyncCopyCallbackFun aCallback, + void* aClosure, + bool aCloseSource, + bool aCloseSink, + nsISupports** aCopierCtx, + nsAsyncCopyProgressFun aProgressCallback) +{ + NS_ASSERTION(aTarget, "non-null target required"); + + nsresult rv; + nsAStreamCopier* copier; + + if (aMode == NS_ASYNCCOPY_VIA_READSEGMENTS) { + copier = new nsStreamCopierIB(); + } else { + copier = new nsStreamCopierOB(); + } + + // Start() takes an owning ref to the copier... + NS_ADDREF(copier); + rv = copier->Start(aSource, aSink, aTarget, aCallback, aClosure, aChunkSize, + aCloseSource, aCloseSink, aProgressCallback); + + 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* aStream, uint32_t aMaxCount, + nsACString& aResult) +{ + nsresult rv = NS_OK; + aResult.Truncate(); + + while (aMaxCount) { + uint64_t avail64; + rv = aStream->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, aMaxCount); + + // resize aResult buffer + uint32_t length = aResult.Length(); + if (avail > UINT32_MAX - length) { + return NS_ERROR_FILE_TOO_BIG; + } + + aResult.SetLength(length + avail); + if (aResult.Length() != (length + avail)) { + return NS_ERROR_OUT_OF_MEMORY; + } + char* buf = aResult.BeginWriting() + length; + + uint32_t n; + rv = aStream->Read(buf, avail, &n); + if (NS_FAILED(rv)) { + break; + } + if (n != avail) { + aResult.SetLength(length + n); + } + if (n == 0) { + break; + } + aMaxCount -= n; + } + + return rv; +} + +//----------------------------------------------------------------------------- + +static nsresult +TestInputStream(nsIInputStream* aInStr, + void* aClosure, + const char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountWritten) +{ + bool* result = static_cast<bool*>(aClosure); + *result = true; + return NS_ERROR_ABORT; // don't call me anymore +} + +bool +NS_InputStreamIsBuffered(nsIInputStream* aStream) +{ + nsCOMPtr<nsIBufferedInputStream> bufferedIn = do_QueryInterface(aStream); + if (bufferedIn) { + return true; + } + + bool result = false; + uint32_t n; + nsresult rv = aStream->ReadSegments(TestInputStream, &result, 1, &n); + return result || NS_SUCCEEDED(rv); +} + +static nsresult +TestOutputStream(nsIOutputStream* aOutStr, + void* aClosure, + char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountRead) +{ + bool* result = static_cast<bool*>(aClosure); + *result = true; + return NS_ERROR_ABORT; // don't call me anymore +} + +bool +NS_OutputStreamIsBuffered(nsIOutputStream* aStream) +{ + nsCOMPtr<nsIBufferedOutputStream> bufferedOut = do_QueryInterface(aStream); + if (bufferedOut) { + return true; + } + + bool result = false; + uint32_t n; + aStream->WriteSegments(TestOutputStream, &result, 1, &n); + return result; +} + +//----------------------------------------------------------------------------- + +nsresult +NS_CopySegmentToStream(nsIInputStream* aInStr, + void* aClosure, + const char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountWritten) +{ + nsIOutputStream* outStr = static_cast<nsIOutputStream*>(aClosure); + *aCountWritten = 0; + while (aCount) { + uint32_t n; + nsresult rv = outStr->Write(aBuffer, aCount, &n); + if (NS_FAILED(rv)) { + return rv; + } + aBuffer += n; + aCount -= n; + *aCountWritten += n; + } + return NS_OK; +} + +nsresult +NS_CopySegmentToBuffer(nsIInputStream* aInStr, + void* aClosure, + const char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountWritten) +{ + char* toBuf = static_cast<char*>(aClosure); + memcpy(&toBuf[aOffset], aBuffer, aCount); + *aCountWritten = aCount; + return NS_OK; +} + +nsresult +NS_CopySegmentToBuffer(nsIOutputStream* aOutStr, + void* aClosure, + char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountRead) +{ + const char* fromBuf = static_cast<const char*>(aClosure); + memcpy(aBuffer, &fromBuf[aOffset], aCount); + *aCountRead = aCount; + return NS_OK; +} + +nsresult +NS_DiscardSegment(nsIInputStream* aInStr, + void* aClosure, + const char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountWritten) +{ + *aCountWritten = aCount; + return NS_OK; +} + +//----------------------------------------------------------------------------- + +nsresult +NS_WriteSegmentThunk(nsIInputStream* aInStr, + void* aClosure, + const char* aBuffer, + uint32_t aOffset, + uint32_t aCount, + uint32_t* aCountWritten) +{ + nsWriteSegmentThunk* thunk = static_cast<nsWriteSegmentThunk*>(aClosure); + return thunk->mFun(thunk->mStream, thunk->mClosure, aBuffer, aOffset, aCount, + aCountWritten); +} + +nsresult +NS_FillArray(FallibleTArray<char>& aDest, nsIInputStream* aInput, + uint32_t aKeep, uint32_t* aNewBytes) +{ + MOZ_ASSERT(aInput, "null stream"); + MOZ_ASSERT(aKeep <= aDest.Length(), "illegal keep count"); + + char* aBuffer = aDest.Elements(); + int64_t keepOffset = int64_t(aDest.Length()) - aKeep; + if (aKeep != 0 && keepOffset > 0) { + memmove(aBuffer, aBuffer + keepOffset, aKeep); + } + + nsresult rv = + aInput->Read(aBuffer + aKeep, aDest.Capacity() - aKeep, aNewBytes); + if (NS_FAILED(rv)) { + *aNewBytes = 0; + } + // NOTE: we rely on the fact that the new slots are NOT initialized by + // SetLengthAndRetainStorage here, see nsTArrayElementTraits::Construct() + // in nsTArray.h: + aDest.SetLengthAndRetainStorage(aKeep + *aNewBytes); + + MOZ_ASSERT(aDest.Length() <= aDest.Capacity(), "buffer overflow"); + return rv; +} + +bool +NS_InputStreamIsCloneable(nsIInputStream* aSource) +{ + if (!aSource) { + return false; + } + + nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(aSource); + return cloneable && cloneable->GetCloneable(); +} + +nsresult +NS_CloneInputStream(nsIInputStream* aSource, nsIInputStream** aCloneOut, + nsIInputStream** aReplacementOut) +{ + if (NS_WARN_IF(!aSource)) { + return NS_ERROR_FAILURE; + } + + // Attempt to perform the clone directly on the source stream + nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(aSource); + if (cloneable && cloneable->GetCloneable()) { + if (aReplacementOut) { + *aReplacementOut = nullptr; + } + return cloneable->Clone(aCloneOut); + } + + // If we failed the clone and the caller does not want to replace their + // original stream, then we are done. Return error. + if (!aReplacementOut) { + return NS_ERROR_FAILURE; + } + + // The caller has opted-in to the fallback clone support that replaces + // the original stream. Copy the data to a pipe and return two cloned + // input streams. + + nsCOMPtr<nsIInputStream> reader; + nsCOMPtr<nsIInputStream> readerClone; + nsCOMPtr<nsIOutputStream> writer; + + nsresult rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), + 0, 0, // default segment size and max size + true, true); // non-blocking + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + cloneable = do_QueryInterface(reader); + MOZ_ASSERT(cloneable && cloneable->GetCloneable()); + + rv = cloneable->Clone(getter_AddRefs(readerClone)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = NS_AsyncCopy(aSource, writer, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + readerClone.forget(aCloneOut); + reader.forget(aReplacementOut); + + return NS_OK; +} diff --git a/xpcom/io/nsStreamUtils.h b/xpcom/io/nsStreamUtils.h new file mode 100644 index 0000000000..957eb2713b --- /dev/null +++ b/xpcom/io/nsStreamUtils.h @@ -0,0 +1,295 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsStreamUtils_h__ +#define nsStreamUtils_h__ + +#include "nsCOMPtr.h" +#include "nsStringFwd.h" +#include "nsIInputStream.h" +#include "nsTArray.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 + * 1/ check whether the input stream implements nsIBufferedInputStream; + * 2/ if not, 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. + * + * PERFORMANCE NOTE: If the stream does not implement nsIBufferedInputStream, + * calling readSegments may cause I/O. Therefore, you should avoid calling + * this function from the main thread. + * + * @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: + * 1/ check whether the output stream implements nsIBufferedOutputStream; + * 2/ if not, 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. + * + * PERFORMANCE NOTE: If the stream does not implement nsIBufferedOutputStream, + * calling writeSegments may cause I/O. Therefore, you should avoid calling + * this function from the main thread. + * + * @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 nsresult +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 nsresult +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 nsresult +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 nsresult +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 nsresult +NS_WriteSegmentThunk(nsIInputStream* aInputStream, void* aClosure, + const char* aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount); + +struct MOZ_STACK_CLASS nsWriteSegmentThunk +{ + nsCOMPtr<nsIInputStream> mStream; + nsWriteSegmentFun mFun; + void* mClosure; +}; + +/** + * Read data from aInput and store in aDest. A non-zero aKeep will keep that + * many bytes from aDest (from the end). New data is appended after the kept + * bytes (if any). aDest's new length on returning from this function is + * aKeep + aNewBytes and is guaranteed to be less than or equal to aDest's + * current capacity. + * @param aDest the array to fill + * @param aInput the stream to read from + * @param aKeep number of bytes to keep (0 <= aKeep <= aDest.Length()) + * @param aNewBytes (out) number of bytes read from aInput or zero if Read() + * failed + * @return the result from aInput->Read(...) + */ +extern nsresult +NS_FillArray(FallibleTArray<char>& aDest, nsIInputStream* aInput, + uint32_t aKeep, uint32_t* aNewBytes); + +/** + * Return true if the given stream can be directly cloned. + */ +extern bool +NS_InputStreamIsCloneable(nsIInputStream* aSource); + +/** + * Clone the provided source stream in the most efficient way possible. This + * first attempts to QI to nsICloneableInputStream to use Clone(). If that is + * not supported or its cloneable attribute is false, then a fallback clone is + * provided by copying the source to a pipe. In this case the caller must + * replace the source stream with the resulting replacement stream. The clone + * and the replacement stream are then cloneable using nsICloneableInputStream + * without duplicating memory. This fallback clone using the pipe is only + * performed if a replacement stream parameter is also passed in. + * @param aSource The input stream to clone. + * @param aCloneOut Required out parameter to hold resulting clone. + * @param aReplacementOut Optional out parameter to hold stream to replace + * original source stream after clone. If not + * provided then the fallback clone process is not + * supported and a non-cloneable source will result + * in failure. Replacement streams are non-blocking. + * @return NS_OK on successful clone. Error otherwise. + */ +extern nsresult +NS_CloneInputStream(nsIInputStream* aSource, nsIInputStream** aCloneOut, + nsIInputStream** aReplacementOut = nullptr); + +#endif // !nsStreamUtils_h__ diff --git a/xpcom/io/nsStringStream.cpp b/xpcom/io/nsStringStream.cpp new file mode 100644 index 0000000000..b65242c143 --- /dev/null +++ b/xpcom/io/nsStringStream.cpp @@ -0,0 +1,452 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Based on original code from nsIStringStream.cpp + */ + +#include "ipc/IPCMessageUtils.h" + +#include "nsStringStream.h" +#include "nsStreamUtils.h" +#include "nsReadableUtils.h" +#include "nsICloneableInputStream.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; +using mozilla::Maybe; +using mozilla::Some; + +//----------------------------------------------------------------------------- +// nsIStringInputStream implementation +//----------------------------------------------------------------------------- + +class nsStringInputStream final + : public nsIStringInputStream + , public nsISeekableStream + , public nsISupportsCString + , public nsIIPCSerializableInputStream + , public nsICloneableInputStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISTRINGINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCSTRING + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + + nsStringInputStream() + { + Clear(); + } + + explicit nsStringInputStream(const nsStringInputStream& aOther) + : mOffset(aOther.mOffset) + { + // Use Assign() here because we don't want the life of the clone to be + // dependent on the life of the original stream. + mData.Assign(aOther.mData); + } + +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_ADDREF(nsStringInputStream) +NS_IMPL_RELEASE(nsStringInputStream) + +NS_IMPL_CLASSINFO(nsStringInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_STRINGINPUTSTREAM_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsStringInputStream, + nsIStringInputStream, + nsIInputStream, + nsISupportsCString, + nsISeekableStream, + nsIIPCSerializableInputStream, + nsICloneableInputStream) +NS_IMPL_CI_INTERFACE_GETTER(nsStringInputStream, + nsIStringInputStream, + nsIInputStream, + nsISupportsCString, + nsISeekableStream, + nsICloneableInputStream) + +///////// +// nsISupportsCString implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::GetType(uint16_t* aType) +{ + *aType = 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. + if (NS_WARN_IF(Closed())) { + return NS_BASE_STREAM_CLOSED; + } + + data.Assign(mData); + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::SetData(const nsACString& aData) +{ + mData.Assign(aData); + mOffset = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::ToString(char** aResult) +{ + // 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* aData, int32_t aDataLen) +{ + if (NS_WARN_IF(!aData)) { + return NS_ERROR_INVALID_ARG; + } + mData.Assign(aData, aDataLen); + mOffset = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::AdoptData(char* aData, int32_t aDataLen) +{ + if (NS_WARN_IF(!aData)) { + return NS_ERROR_INVALID_ARG; + } + mData.Adopt(aData, aDataLen); + mOffset = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::ShareData(const char* aData, int32_t aDataLen) +{ + if (NS_WARN_IF(!aData)) { + return NS_ERROR_INVALID_ARG; + } + + if (aDataLen < 0) { + aDataLen = strlen(aData); + } + + mData.Rebind(aData, aDataLen); + mOffset = 0; + return NS_OK; +} + +NS_IMETHODIMP_(size_t) +nsStringInputStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) +{ + size_t n = aMallocSizeOf(this); + n += mData.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + return n; +} + +///////// +// 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 aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) +{ + NS_ASSERTION(aResult, "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) { + *aResult = 0; + return NS_OK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + nsresult rv = aWriter(this, aClosure, mData.BeginReading() + mOffset, 0, + aCount, aResult); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(*aResult <= aCount, + "writer should not write more than we asked it to write"); + mOffset += *aResult; + } + + // 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 aWhence, int64_t aOffset) +{ + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + // Compute new stream position. The given offset may be a negative value. + + int64_t newPos = aOffset; + switch (aWhence) { + case NS_SEEK_SET: + break; + case NS_SEEK_CUR: + newPos += mOffset; + break; + case NS_SEEK_END: + newPos += Length(); + break; + default: + NS_ERROR("invalid aWhence"); + return NS_ERROR_INVALID_ARG; + } + + if (NS_WARN_IF(newPos < 0) || NS_WARN_IF(newPos > Length())) { + return NS_ERROR_INVALID_ARG; + } + + mOffset = (uint32_t)newPos; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::Tell(int64_t* aOutWhere) +{ + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + *aOutWhere = mOffset; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::SetEOF() +{ + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + mOffset = Length(); + return NS_OK; +} + +///////// +// nsIIPCSerializableInputStream implementation +///////// + +void +nsStringInputStream::Serialize(InputStreamParams& aParams, + FileDescriptorArray& /* aFDs */) +{ + StringInputStreamParams params; + params.data() = PromiseFlatCString(mData); + aParams = params; +} + +bool +nsStringInputStream::Deserialize(const InputStreamParams& aParams, + const FileDescriptorArray& /* aFDs */) +{ + 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; +} + +Maybe<uint64_t> +nsStringInputStream::ExpectedSerializedLength() +{ + return Some(static_cast<uint64_t>(Length())); +} + +///////// +// nsICloneableInputStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::GetCloneable(bool* aCloneableOut) +{ + *aCloneableOut = true; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::Clone(nsIInputStream** aCloneOut) +{ + RefPtr<nsIInputStream> ref = new nsStringInputStream(*this); + ref.forget(aCloneOut); + return NS_OK; +} + +nsresult +NS_NewByteInputStream(nsIInputStream** aStreamResult, + const char* aStringToRead, int32_t aLength, + nsAssignmentType aAssignment) +{ + NS_PRECONDITION(aStreamResult, "null out ptr"); + + RefPtr<nsStringInputStream> stream = new nsStringInputStream(); + + 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)) { + return rv; + } + + stream.forget(aStreamResult); + return NS_OK; +} + +nsresult +NS_NewCStringInputStream(nsIInputStream** aStreamResult, + const nsACString& aStringToRead) +{ + NS_PRECONDITION(aStreamResult, "null out ptr"); + + RefPtr<nsStringInputStream> stream = new nsStringInputStream(); + + stream->SetData(aStringToRead); + + stream.forget(aStreamResult); + return NS_OK; +} + +// factory method for constructing a nsStringInputStream object +nsresult +nsStringInputStreamConstructor(nsISupports* aOuter, REFNSIID aIID, + void** aResult) +{ + *aResult = nullptr; + + if (NS_WARN_IF(aOuter)) { + return NS_ERROR_NO_AGGREGATION; + } + + RefPtr<nsStringInputStream> inst = new nsStringInputStream(); + return inst->QueryInterface(aIID, aResult); +} diff --git a/xpcom/io/nsStringStream.h b/xpcom/io/nsStringStream.h new file mode 100644 index 0000000000..8c09530ebf --- /dev/null +++ b/xpcom/io/nsStringStream.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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 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 0000000000..27c074c092 --- /dev/null +++ b/xpcom/io/nsUnicharInputStream.cpp @@ -0,0 +1,398 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsUnicharInputStream.h" +#include "nsIInputStream.h" +#include "nsIServiceManager.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsAutoPtr.h" +#include "nsCRT.h" +#include "nsStreamUtils.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 final : public nsIUnicharInputStream +{ +public: + explicit 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(char16_t* aBuf, + uint32_t aCount, + uint32_t* aReadCount) +{ + if (mPos >= mLen) { + *aReadCount = 0; + return NS_OK; + } + nsAString::const_iterator iter; + mString.BeginReading(iter); + const char16_t* us = iter.get(); + uint32_t amount = mLen - mPos; + if (amount > aCount) { + amount = aCount; + } + memcpy(aBuf, us + mPos, sizeof(char16_t) * 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_ISUPPORTS(StringUnicharInputStream, nsIUnicharInputStream) + +//---------------------------------------------------------------------- + +class UTF8InputStream 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; + FallibleTArray<char> mByteData; + FallibleTArray<char16_t> mUnicharData; + + uint32_t mByteDataOffset; + uint32_t mUnicharDataOffset; + uint32_t mUnicharDataLength; +}; + +UTF8InputStream::UTF8InputStream() : + mByteDataOffset(0), + mUnicharDataOffset(0), + mUnicharDataLength(0) +{ +} + +nsresult +UTF8InputStream::Init(nsIInputStream* aStream) +{ + if (!mByteData.SetCapacity(STRING_BUFFER_SIZE, mozilla::fallible) || + !mUnicharData.SetCapacity(STRING_BUFFER_SIZE, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + mInput = aStream; + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(UTF8InputStream, nsIUnicharInputStream) + +UTF8InputStream::~UTF8InputStream() +{ + Close(); +} + +nsresult +UTF8InputStream::Close() +{ + mInput = nullptr; + mByteData.Clear(); + mUnicharData.Clear(); + return NS_OK; +} + +nsresult +UTF8InputStream::Read(char16_t* 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.Elements() + mUnicharDataOffset, + readCount * sizeof(char16_t)); + 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.Elements() + 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 char16_t* buf = mUnicharData.Elements() + mUnicharDataOffset; + aString.Assign(buf, readCount); + + mUnicharDataOffset += readCount; + *aReadCount = readCount; + return NS_OK; +} + +int32_t +UTF8InputStream::Fill(nsresult* aErrorCode) +{ + if (!mInput) { + // We already closed the stream! + *aErrorCode = NS_BASE_STREAM_CLOSED; + return -1; + } + + NS_ASSERTION(mByteData.Length() >= mByteDataOffset, "unsigned madness"); + uint32_t remainder = mByteData.Length() - mByteDataOffset; + mByteDataOffset = remainder; + uint32_t nb; + *aErrorCode = NS_FillArray(mByteData, mInput, remainder, &nb); + 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.Length(), "bad nb"); + + // Now convert as much of the byte buffer to unicode as possible + uint32_t srcLen, dstLen; + CountValidUTF8Bytes(mByteData.Elements(), 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(dstLen <= mUnicharData.Capacity(), + "Ouch. I would overflow my buffer if I wasn't so careful."); + if (dstLen > mUnicharData.Capacity()) { + return 0; + } + + ConvertUTF8toUTF16 converter(mUnicharData.Elements()); + + nsASingleFragmentCString::const_char_iterator start = mByteData.Elements(); + nsASingleFragmentCString::const_char_iterator end = mByteData.Elements() + 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; +} + +nsresult +NS_NewUnicharInputStream(nsIInputStream* aStreamToWrap, + nsIUnicharInputStream** aResult) +{ + *aResult = nullptr; + + // Create converter input stream + RefPtr<UTF8InputStream> it = new UTF8InputStream(); + nsresult rv = it->Init(aStreamToWrap); + if (NS_FAILED(rv)) { + return rv; + } + + it.forget(aResult); + return NS_OK; +} diff --git a/xpcom/io/nsUnicharInputStream.h b/xpcom/io/nsUnicharInputStream.h new file mode 100644 index 0000000000..d4631af7ed --- /dev/null +++ b/xpcom/io/nsUnicharInputStream.h @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsUnicharInputStream_h__ +#define nsUnicharInputStream_h__ + +#include "nsIUnicharInputStream.h" + +nsresult NS_NewUnicharInputStream(nsIInputStream* aStreamToWrap, + nsIUnicharInputStream** aResult); + +#endif // nsUnicharInputStream_h__ diff --git a/xpcom/io/nsWildCard.cpp b/xpcom/io/nsWildCard.cpp new file mode 100644 index 0000000000..9125cbbb8f --- /dev/null +++ b/xpcom/io/nsWildCard.cpp @@ -0,0 +1,481 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* * + * + * + * 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 aChar) +{ + return ('a' <= aChar && aChar <= 'z') || + ('A' <= aChar && aChar <= 'Z'); +} + +template<class T> +static int +alphanumeric(T aChar) +{ + return ('0' <= aChar && aChar <= '9') || ::alpha(aChar); +} + +template<class T> +static int +lower(T aChar) +{ + return ('A' <= aChar && aChar <= 'Z') ? aChar + ('a' - 'A') : aChar; +} + +template<class T> +static int +upper(T aChar) +{ + return ('a' <= aChar && aChar <= 'z') ? aChar - ('a' - 'A') : aChar; +} + +/* ----------------------------- _valid_subexp ---------------------------- */ + +template<class T> +static int +_valid_subexp(const T* aExpr, T aStop1, T aStop2) +{ + 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; aExpr[x] && (aExpr[x] != aStop1) && (aExpr[x] != aStop2); ++x) { + switch (aExpr[x]) { + case '~': + if (tld) { /* at most one exclusion */ + return INVALID_SXP; + } + if (aStop1) { /* no exclusions within unions */ + return INVALID_SXP; + } + if (!aExpr[x + 1]) { /* exclusion cannot be last character */ + return INVALID_SXP; + } + if (!x) { /* exclusion cannot be first character */ + return INVALID_SXP; + } + ++tld; + MOZ_FALLTHROUGH; + case '*': + case '?': + case '$': + ++nsc; + break; + case '[': + ++nsc; + if ((!aExpr[++x]) || (aExpr[x] == ']')) { + return INVALID_SXP; + } + for (; aExpr[x] && (aExpr[x] != ']'); ++x) { + if (aExpr[x] == '\\' && !aExpr[++x]) { + return INVALID_SXP; + } + } + if (!aExpr[x]) { + return INVALID_SXP; + } + break; + case '(': + ++nsc; + if (aStop1) { /* no nested unions */ + return INVALID_SXP; + } + np = -1; + do { + int t = ::_valid_subexp(&aExpr[++x], T(')'), T('|')); + if (t == 0 || t == INVALID_SXP) { + return INVALID_SXP; + } + x += t; + if (!aExpr[x]) { + return INVALID_SXP; + } + ++np; + } while (aExpr[x] == '|'); + if (np < 1) { /* must be at least one pipe */ + return INVALID_SXP; + } + break; + case ')': + case ']': + case '|': + return INVALID_SXP; + case '\\': + ++nsc; + if (!aExpr[++x]) { + return INVALID_SXP; + } + break; + default: + break; + } + } + if (!aStop1 && !nsc) { /* must be at least one special character */ + return NON_SXP; + } + return ((aExpr[x] == aStop1 || aExpr[x] == aStop2) ? x : INVALID_SXP); +} + + +template<class T> +int +NS_WildCardValid_(const T* aExpr) +{ + int x = ::_valid_subexp(aExpr, T('\0'), T('\0')); + return (x < 0 ? x : VALID_SXP); +} + +int +NS_WildCardValid(const char* aExpr) +{ + return NS_WildCardValid_(aExpr); +} + +int +NS_WildCardValid(const char16_t* aExpr) +{ + return NS_WildCardValid_(aExpr); +} + +/* ----------------------------- _shexp_match ----------------------------- */ + + +#define MATCH 0 +#define NOMATCH 1 +#define ABORTED -1 + +template<class T> +static int +_shexp_match(const T* aStr, const T* aExpr, bool aCaseInsensitive, + unsigned int aLevel); + +/** + * 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-nullptr, copy counted characters to it and null terminate. + */ +template<class T> +static int +_scan_and_copy(const T* aExpr, T aStop1, T aStop2, T* aDest) +{ + int sx; /* source index */ + T cc; + + for (sx = 0; (cc = aExpr[sx]) && cc != aStop1 && cc != aStop2; ++sx) { + if (cc == '\\') { + if (!aExpr[++sx]) { + return ABORTED; /* should be impossible */ + } + } else if (cc == '[') { + while ((cc = aExpr[++sx]) && cc != ']') { + if (cc == '\\' && !aExpr[++sx]) { + return ABORTED; + } + } + if (!cc) { + return ABORTED; /* should be impossible */ + } + } + } + if (aDest && sx) { + /* Copy all but the closing delimiter. */ + memcpy(aDest, aExpr, sx * sizeof(T)); + aDest[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* aStr, const T* aExpr, bool aCaseInsensitive, + unsigned int aLevel) +{ + 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(aExpr, T(')'), T('\0'), static_cast<T*>(nullptr)); + if (cp == ABORTED || cp < 4) { /* must be at least "(a|b" before ')' */ + return ABORTED; + } + ++cp; /* now index of char after closing parenthesis */ + e2 = (T*)moz_xmalloc((1 + nsCharTraits<T>::length(aExpr)) * sizeof(T)); + if (!e2) { + return ABORTED; + } + for (sx = 1; ; ++sx) { + /* Here, aExpr[sx] is one character past the preceding '(' or '|'. */ + /* Copy everything up to the next delimiter to e2 */ + count = ::_scan_and_copy(aExpr + 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, aExpr + cp, + nsCharTraits<T>::length(aExpr + cp) + 1); + ret = ::_shexp_match(aStr, e2, aCaseInsensitive, aLevel + 1); + if (ret != NOMATCH || !aExpr[sx] || aExpr[sx] == ')') { + break; + } + } + 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 aStart, unsigned char aEnd, unsigned char aVal) +{ + char map[256]; + memset(map, 0, sizeof(map)); + while (aStart <= aEnd) { + map[lower(aStart++)] = 1; + } + return map[lower(aVal)]; +} + +template<class T> +static int +_shexp_match(const T* aStr, const T* aExpr, bool aCaseInsensitive, + unsigned int aLevel) +{ + int x; /* input string index */ + int y; /* expression index */ + int ret, neg; + + if (aLevel > 20) { /* Don't let the stack get too deep. */ + return ABORTED; + } + for (x = 0, y = 0; aExpr[y]; ++y, ++x) { + if (!aStr[x] && aExpr[y] != '$' && aExpr[y] != '*') { + return NOMATCH; + } + switch (aExpr[y]) { + case '$': + if (aStr[x]) { + return NOMATCH; + } + --x; /* we don't want loop to increment x */ + break; + case '*': + while (aExpr[++y] == '*') { + } + if (!aExpr[y]) { + return MATCH; + } + while (aStr[x]) { + ret = ::_shexp_match(&aStr[x++], &aExpr[y], aCaseInsensitive, + aLevel + 1); + switch (ret) { + case NOMATCH: + continue; + case ABORTED: + return ABORTED; + default: + return MATCH; + } + } + if (aExpr[y] == '$' && aExpr[y + 1] == '\0' && !aStr[x]) { + return MATCH; + } else { + return NOMATCH; + } + case '[': { + T start, end = 0; + int i; + ++y; + neg = (aExpr[y] == '^' && aExpr[y + 1] != ']'); + if (neg) { + ++y; + } + i = y; + start = aExpr[i++]; + if (start == '\\') { + start = aExpr[i++]; + } + if (::alphanumeric(start) && aExpr[i++] == '-') { + end = aExpr[i++]; + if (end == '\\') { + end = aExpr[i++]; + } + } + if (::alphanumeric(end) && aExpr[i] == ']') { + /* This is a range form: a-b */ + T val = aStr[x]; + if (end < start) { /* swap them */ + T tmp = end; + end = start; + start = tmp; + } + if (aCaseInsensitive && ::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 (; aExpr[y] != ']'; ++y) { + if (aExpr[y] == '\\') { + ++y; + } + if (aCaseInsensitive) { + matched |= (::upper(aStr[x]) == ::upper(aExpr[y])); + } else { + matched |= (aStr[x] == aExpr[y]); + } + } + if (neg == matched) { + return NOMATCH; + } + } + } + break; + case '(': + if (!aExpr[y + 1]) { + return ABORTED; + } + return ::_handle_union(&aStr[x], &aExpr[y], aCaseInsensitive, + aLevel + 1); + case '?': + break; + case ')': + case ']': + case '|': + return ABORTED; + case '\\': + ++y; + MOZ_FALLTHROUGH; + default: + if (aCaseInsensitive) { + if (::upper(aStr[x]) != ::upper(aExpr[y])) { + return NOMATCH; + } + } else { + if (aStr[x] != aExpr[y]) { + return NOMATCH; + } + } + break; + } + } + return (aStr[x] ? NOMATCH : MATCH); +} + +template<class T> +static int +ns_WildCardMatch(const T* aStr, const T* aXp, bool aCaseInsensitive) +{ + T* expr = nullptr; + int ret = MATCH; + + if (!nsCharTraits<T>::find(aXp, nsCharTraits<T>::length(aXp), T('~'))) { + return ::_shexp_match(aStr, aXp, aCaseInsensitive, 0); + } + + expr = (T*)moz_xmalloc((nsCharTraits<T>::length(aXp) + 1) * sizeof(T)); + if (!expr) { + return NOMATCH; + } + memcpy(expr, aXp, (nsCharTraits<T>::length(aXp) + 1) * sizeof(T)); + + int x = ::_scan_and_copy(expr, T('~'), T('\0'), static_cast<T*>(nullptr)); + if (x != ABORTED && expr[x] == '~') { + expr[x++] = '\0'; + ret = ::_shexp_match(aStr, &expr[x], aCaseInsensitive, 0); + switch (ret) { + case NOMATCH: + ret = MATCH; + break; + case MATCH: + ret = NOMATCH; + break; + default: + break; + } + } + if (ret == MATCH) { + ret = ::_shexp_match(aStr, expr, aCaseInsensitive, 0); + } + + free(expr); + return ret; +} + +template<class T> +int +NS_WildCardMatch_(const T* aStr, const T* aExpr, bool aCaseInsensitive) +{ + int is_valid = NS_WildCardValid(aExpr); + switch (is_valid) { + case INVALID_SXP: + return -1; + default: + return ::ns_WildCardMatch(aStr, aExpr, aCaseInsensitive); + } +} + +int +NS_WildCardMatch(const char* aStr, const char* aXp, bool aCaseInsensitive) +{ + return NS_WildCardMatch_(aStr, aXp, aCaseInsensitive); +} + +int +NS_WildCardMatch(const char16_t* aStr, const char16_t* aXp, + bool aCaseInsensitive) +{ + return NS_WildCardMatch_(aStr, aXp, aCaseInsensitive); +} diff --git a/xpcom/io/nsWildCard.h b/xpcom/io/nsWildCard.h new file mode 100644 index 0000000000..a077382bb3 --- /dev/null +++ b/xpcom/io/nsWildCard.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * 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 "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* aExpr); + +int NS_WildCardValid(const char16_t* aExpr); + +/* 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* aStr, const char* aExpr, + bool aCaseInsensitive); + +int NS_WildCardMatch(const char16_t* aStr, const char16_t* aExpr, + bool aCaseInsensitive); + +#endif /* nsWildCard_h__ */ |