diff options
Diffstat (limited to 'xpcom/io')
118 files changed, 29744 insertions, 0 deletions
diff --git a/xpcom/io/Base64.cpp b/xpcom/io/Base64.cpp new file mode 100644 index 0000000000..4bd0394ecb --- /dev/null +++ b/xpcom/io/Base64.cpp @@ -0,0 +1,780 @@ +/* -*- 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/ArrayUtils.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* const base = + (unsigned char*)"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +// The Base64 encoder assumes all characters are less than 256; for 16-bit +// strings, that means assuming that all characters are within range, and +// masking off high bits if necessary. +template <typename T> +uint8_t CharTo8Bit(T aChar) { + return uint8_t(aChar); +} + +template <typename SrcT, typename DestT> +static void Encode3to4(const SrcT* aSrc, DestT* aDest) { + uint32_t b32 = (uint32_t)0; + int i, j = 18; + + for (i = 0; i < 3; ++i) { + b32 <<= 8; + b32 |= CharTo8Bit(aSrc[i]); + } + + for (i = 0; i < 4; ++i) { + aDest[i] = base[(uint32_t)((b32 >> j) & 0x3F)]; + j -= 6; + } +} + +template <typename SrcT, typename DestT> +static void Encode2to4(const SrcT* aSrc, DestT* aDest) { + uint8_t src0 = CharTo8Bit(aSrc[0]); + uint8_t src1 = CharTo8Bit(aSrc[1]); + aDest[0] = base[(uint32_t)((src0 >> 2) & 0x3F)]; + aDest[1] = base[(uint32_t)(((src0 & 0x03) << 4) | ((src1 >> 4) & 0x0F))]; + aDest[2] = base[(uint32_t)((src1 & 0x0F) << 2)]; + aDest[3] = DestT('='); +} + +template <typename SrcT, typename DestT> +static void Encode1to4(const SrcT* aSrc, DestT* aDest) { + uint8_t src0 = CharTo8Bit(aSrc[0]); + aDest[0] = base[(uint32_t)((src0 >> 2) & 0x3F)]; + aDest[1] = base[(uint32_t)((src0 & 0x03) << 4)]; + aDest[2] = DestT('='); + aDest[3] = DestT('='); +} + +template <typename SrcT, typename DestT> +static void Encode(const SrcT* aSrc, uint32_t aSrcLen, DestT* 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: + MOZ_ASSERT_UNREACHABLE("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) { + MOZ_ASSERT(aCount > 0, "Er, what?"); + + EncodeInputStream_State<T>* state = + static_cast<EncodeInputStream_State<T>*>(aClosure); + + // We consume the whole data always. + *aWriteCount = aCount; + + // 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) { + MOZ_ASSERT(state->charsOnStack == 1 || state->charsOnStack == 2); + + // Not enough data to compose a triple. + if (state->charsOnStack == 1 && countRemaining == 1) { + state->charsOnStack = 2; + state->c[1] = src[0]; + return NS_OK; + } + + uint32_t consumed = 0; + unsigned char firstSet[4]; + if (state->charsOnStack == 1) { + firstSet[0] = state->c[0]; + firstSet[1] = src[0]; + firstSet[2] = src[1]; + firstSet[3] = '\0'; + consumed = 2; + } else /* state->charsOnStack == 2 */ { + firstSet[0] = state->c[0]; + firstSet[1] = state->c[1]; + firstSet[2] = src[0]; + firstSet[3] = '\0'; + consumed = 1; + } + + Encode(firstSet, 3, state->buffer); + state->buffer += 4; + countRemaining -= consumed; + src += consumed; + state->charsOnStack = 0; + + // Nothing is left. + if (!countRemaining) { + return NS_OK; + } + } + + // Encode as many full triplets as possible. + 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; + + 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; +} + +mozilla::Result<uint32_t, nsresult> CalculateBase64EncodedLength( + const size_t aBinaryLen, const uint32_t aPrefixLen = 0) { + mozilla::CheckedUint32 res = aBinaryLen; + // base 64 encoded length is 4/3rds the length of the input data, rounded up + res += 2; + res /= 3; + res *= 4; + res += aPrefixLen; + if (!res.isValid()) { + return mozilla::Err(NS_ERROR_FAILURE); + } + return res.value(); +} + +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; + } + + const auto base64LenOrErr = CalculateBase64EncodedLength(count64, aOffset); + if (base64LenOrErr.isErr()) { + // XXX For some reason, it was NS_ERROR_OUT_OF_MEMORY here instead of + // NS_ERROR_FAILURE, so we keep that. + return NS_ERROR_OUT_OF_MEMORY; + } + + auto handleOrErr = aDest.BulkWrite(base64LenOrErr.inspect(), aOffset, false); + if (handleOrErr.isErr()) { + return handleOrErr.unwrapErr(); + } + + auto handle = handleOrErr.unwrap(); + + EncodeInputStream_State<T> state{ + .c = {'\0', '\0', '\0'}, + .charsOnStack = 0, + .buffer = handle.Elements() + aOffset, + }; + + while (aCount > 0) { + 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) { + MOZ_CRASH("Not implemented for async streams!"); + } + if (rv == NS_ERROR_NOT_IMPLEMENTED) { + MOZ_CRASH("Requires a stream that implements ReadSegments!"); + } + return rv; + } + + if (!read) { + break; + } + + aCount -= read; + } + + // Finish encoding if anything is left + if (state.charsOnStack) { + Encode(state.c, state.charsOnStack, state.buffer); + state.buffer += 4; + } + + // If we encountered EOF before reading aCount bytes, the resulting string + // could be shorter than predicted, so determine the length from the state. + size_t trueLength = state.buffer - handle.Elements(); + handle.Finish(trueLength, false); + + return NS_OK; +} + +// Maps an encoded character to a value in the Base64 alphabet, per +// RFC 4648, Table 1. Invalid input characters map to UINT8_MAX. + +static const uint8_t kBase64DecodeTable[] = { + // clang-format off + /* 0 */ 255, 255, 255, 255, 255, 255, 255, 255, + /* 8 */ 255, 255, 255, 255, 255, 255, 255, 255, + /* 16 */ 255, 255, 255, 255, 255, 255, 255, 255, + /* 24 */ 255, 255, 255, 255, 255, 255, 255, 255, + /* 32 */ 255, 255, 255, 255, 255, 255, 255, 255, + /* 40 */ 255, 255, 255, + 62 /* + */, + 255, 255, 255, + 63 /* / */, + + /* 48 */ /* 0 - 9 */ 52, 53, 54, 55, 56, 57, 58, 59, + /* 56 */ 60, 61, 255, 255, 255, 255, 255, 255, + + /* 64 */ 255, /* A - Z */ 0, 1, 2, 3, 4, 5, 6, + /* 72 */ 7, 8, 9, 10, 11, 12, 13, 14, + /* 80 */ 15, 16, 17, 18, 19, 20, 21, 22, + /* 88 */ 23, 24, 25, 255, 255, 255, 255, 255, + /* 96 */ 255, /* a - z */ 26, 27, 28, 29, 30, 31, 32, + /* 104 */ 33, 34, 35, 36, 37, 38, 39, 40, + /* 112 */ 41, 42, 43, 44, 45, 46, 47, 48, + /* 120 */ 49, 50, 51, 255, 255, 255, 255, 255, +}; +static_assert(mozilla::ArrayLength(kBase64DecodeTable) == 0x80); +// clang-format on + +template <typename T> +[[nodiscard]] bool Base64CharToValue(T aChar, uint8_t* aValue) { + size_t index = static_cast<uint8_t>(aChar); + if (index >= mozilla::ArrayLength(kBase64DecodeTable)) { + *aValue = 255; + return false; + } + *aValue = kBase64DecodeTable[index]; + return *aValue != 255; +} + +static const char kBase64URLAlphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +static_assert(mozilla::ArrayLength(kBase64URLAlphabet) == 0x41); + +// 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[] = { + // clang-format off + 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, 255, +}; +static_assert(mozilla::ArrayLength(kBase64URLDecodeTable) == 0x80); +// clang-format on + +bool Base64URLCharToValue(char aChar, uint8_t* aValue) { + uint8_t index = static_cast<uint8_t>(aChar); + if (index >= mozilla::ArrayLength(kBase64URLDecodeTable)) { + *aValue = 255; + return false; + } + *aValue = kBase64URLDecodeTable[index]; + return *aValue != 255; +} + +} // 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) { + if (aBinaryLen == 0) { + *aBase64 = (char*)moz_xmalloc(1); + (*aBase64)[0] = '\0'; + return NS_OK; + } + + const auto base64LenOrErr = CalculateBase64EncodedLength(aBinaryLen); + if (base64LenOrErr.isErr()) { + return base64LenOrErr.inspectErr(); + } + const uint32_t base64Len = base64LenOrErr.inspect(); + + *aBase64 = nullptr; + + // Add one byte for null termination. + UniqueFreePtr<char[]> base64((char*)malloc(base64Len + 1)); + if (!base64) { + return NS_ERROR_OUT_OF_MEMORY; + } + + Encode(aBinary, aBinaryLen, base64.get()); + base64[base64Len] = '\0'; + + *aBase64 = base64.release(); + return NS_OK; +} + +template <bool Append = false, typename T, typename U> +static nsresult Base64EncodeHelper(const T* const aBinary, + const size_t aBinaryLen, U& aBase64) { + if (aBinaryLen == 0) { + if (!Append) { + aBase64.Truncate(); + } + return NS_OK; + } + + const uint32_t prefixLen = Append ? aBase64.Length() : 0; + const auto base64LenOrErr = + CalculateBase64EncodedLength(aBinaryLen, prefixLen); + if (base64LenOrErr.isErr()) { + return base64LenOrErr.inspectErr(); + } + const uint32_t base64Len = base64LenOrErr.inspect(); + + auto handleOrErr = aBase64.BulkWrite(base64Len, prefixLen, false); + if (handleOrErr.isErr()) { + return handleOrErr.unwrapErr(); + } + + auto handle = handleOrErr.unwrap(); + + Encode(aBinary, aBinaryLen, handle.Elements() + prefixLen); + handle.Finish(base64Len, false); + return NS_OK; +} + +nsresult Base64EncodeAppend(const char* aBinary, uint32_t aBinaryLen, + nsAString& aBase64) { + return Base64EncodeHelper<true>(aBinary, aBinaryLen, aBase64); +} + +nsresult Base64EncodeAppend(const char* aBinary, uint32_t aBinaryLen, + nsACString& aBase64) { + return Base64EncodeHelper<true>(aBinary, aBinaryLen, aBase64); +} + +nsresult Base64EncodeAppend(const nsACString& aBinary, nsACString& aBase64) { + return Base64EncodeHelper<true>(aBinary.BeginReading(), aBinary.Length(), + aBase64); +} + +nsresult Base64EncodeAppend(const nsACString& aBinary, nsAString& aBase64) { + return Base64EncodeHelper<true>(aBinary.BeginReading(), aBinary.Length(), + aBase64); +} + +nsresult Base64Encode(const char* aBinary, uint32_t aBinaryLen, + nsACString& aBase64) { + return Base64EncodeHelper(aBinary, aBinaryLen, aBase64); +} + +nsresult Base64Encode(const char* aBinary, uint32_t aBinaryLen, + nsAString& aBase64) { + return Base64EncodeHelper(aBinary, aBinaryLen, aBase64); +} + +nsresult Base64Encode(const nsACString& aBinary, nsACString& aBase64) { + return Base64EncodeHelper(aBinary.BeginReading(), aBinary.Length(), aBase64); +} + +nsresult Base64Encode(const nsACString& aBinary, nsAString& aBase64) { + return Base64EncodeHelper(aBinary.BeginReading(), aBinary.Length(), aBase64); +} + +nsresult Base64Encode(const nsAString& aBinary, nsAString& aBase64) { + return Base64EncodeHelper(aBinary.BeginReading(), aBinary.Length(), aBase64); +} + +template <typename T, typename U, typename Decoder> +static bool Decode4to3(const T* aSrc, U* aDest, Decoder aToVal) { + uint8_t w, x, y, z; + if (!aToVal(aSrc[0], &w) || !aToVal(aSrc[1], &x) || !aToVal(aSrc[2], &y) || + !aToVal(aSrc[3], &z)) { + return false; + } + aDest[0] = U(uint8_t(w << 2 | x >> 4)); + aDest[1] = U(uint8_t(x << 4 | y >> 2)); + aDest[2] = U(uint8_t(y << 6 | z)); + return true; +} + +template <typename T, typename U, typename Decoder> +static bool Decode3to2(const T* aSrc, U* aDest, Decoder aToVal) { + uint8_t w, x, y; + if (!aToVal(aSrc[0], &w) || !aToVal(aSrc[1], &x) || !aToVal(aSrc[2], &y)) { + return false; + } + aDest[0] = U(uint8_t(w << 2 | x >> 4)); + aDest[1] = U(uint8_t(x << 4 | y >> 2)); + return true; +} + +template <typename T, typename U, typename Decoder> +static bool Decode2to1(const T* aSrc, U* aDest, Decoder aToVal) { + uint8_t w, x; + if (!aToVal(aSrc[0], &w) || !aToVal(aSrc[1], &x)) { + return false; + } + aDest[0] = U(uint8_t(w << 2 | x >> 4)); + return true; +} + +template <typename SrcT, typename DestT> +static nsresult Base64DecodeHelper(const SrcT* aBase64, uint32_t aBase64Len, + DestT* aBinary, uint32_t* aBinaryLen) { + MOZ_ASSERT(aBinary); + + const SrcT* input = aBase64; + uint32_t inputLength = aBase64Len; + DestT* binary = aBinary; + uint32_t binaryLength = 0; + + // Handle trailing '=' characters. + if (inputLength && (inputLength % 4 == 0)) { + if (aBase64[inputLength - 1] == SrcT('=')) { + if (aBase64[inputLength - 2] == SrcT('=')) { + inputLength -= 2; + } else { + inputLength -= 1; + } + } + } + + while (inputLength >= 4) { + if (!Decode4to3(input, binary, Base64CharToValue<SrcT>)) { + return NS_ERROR_INVALID_ARG; + } + + input += 4; + inputLength -= 4; + binary += 3; + binaryLength += 3; + } + + switch (inputLength) { + case 3: + if (!Decode3to2(input, binary, Base64CharToValue<SrcT>)) { + return NS_ERROR_INVALID_ARG; + } + binaryLength += 2; + break; + case 2: + if (!Decode2to1(input, binary, Base64CharToValue<SrcT>)) { + return NS_ERROR_INVALID_ARG; + } + binaryLength += 1; + break; + case 1: + return NS_ERROR_INVALID_ARG; + case 0: + break; + default: + MOZ_CRASH("Too many characters leftover"); + } + + aBinary[binaryLength] = DestT('\0'); + *aBinaryLen = binaryLength; + + 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; +} + +template <typename T, typename U> +static nsresult Base64DecodeString(const T& aBase64, U& aBinary) { + aBinary.Truncate(); + + // Check for overflow. + if (aBase64.Length() > UINT32_MAX / 3) { + return NS_ERROR_FAILURE; + } + + // Don't decode the empty string + if (aBase64.IsEmpty()) { + return NS_OK; + } + + uint32_t binaryLen = ((aBase64.Length() * 3) / 4); + + auto handleOrErr = aBinary.BulkWrite(binaryLen, 0, false); + if (handleOrErr.isErr()) { + // Must not touch the handle if failing here, but we + // already truncated the string at the top, so it's + // unchanged. + return handleOrErr.unwrapErr(); + } + + auto handle = handleOrErr.unwrap(); + + nsresult rv = Base64DecodeHelper(aBase64.BeginReading(), aBase64.Length(), + handle.Elements(), &binaryLen); + if (NS_FAILED(rv)) { + // Retruncate to match old semantics of this method. + handle.Finish(0, true); + return rv; + } + + handle.Finish(binaryLen, true); + return NS_OK; +} + +nsresult Base64Decode(const nsACString& aBase64, nsACString& aBinary) { + return Base64DecodeString(aBase64, aBinary); +} + +nsresult Base64Decode(const nsAString& aBase64, nsAString& aBinary) { + return Base64DecodeString(aBase64, aBinary); +} + +nsresult Base64Decode(const nsAString& aBase64, nsACString& aBinary) { + return Base64DecodeString(aBase64, aBinary); +} + +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) { + if (!Decode4to3(base64, binary, Base64URLCharToValue)) { + return NS_ERROR_INVALID_ARG; + } + base64 += 4; + binary += 3; + } + + if (base64Len == 3) { + if (!Decode3to2(base64, binary, Base64URLCharToValue)) { + return NS_ERROR_INVALID_ARG; + } + binary += 2; + } else if (base64Len == 2) { + if (!Decode2to1(base64, binary, Base64URLCharToValue)) { + return NS_ERROR_INVALID_ARG; + } + binary += 1; + } 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) { + aBase64.Truncate(); + // Don't encode empty strings. + if (aBinaryLen == 0) { + return NS_OK; + } + + // Allocate a buffer large enough to hold the encoded string with padding. + const auto base64LenOrErr = CalculateBase64EncodedLength(aBinaryLen); + if (base64LenOrErr.isErr()) { + return base64LenOrErr.inspectErr(); + } + const uint32_t base64Len = base64LenOrErr.inspect(); + + auto handleOrErr = aBase64.BulkWrite(base64Len, 0, false); + if (handleOrErr.isErr()) { + return handleOrErr.unwrapErr(); + } + + auto handle = handleOrErr.unwrap(); + + char* base64 = handle.Elements(); + + 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 - handle.Elements(); + 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"); + } + + handle.Finish(length, false); + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/io/Base64.h b/xpcom/io/Base64.h new file mode 100644 index 0000000000..0a9b3a0305 --- /dev/null +++ b/xpcom/io/Base64.h @@ -0,0 +1,93 @@ +/* -*- 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 { + +[[nodiscard]] nsresult Base64EncodeInputStream(nsIInputStream* aInputStream, + nsACString& aDest, + uint32_t aCount, + uint32_t aOffset = 0); +[[nodiscard]] nsresult Base64EncodeInputStream(nsIInputStream* aInputStream, + nsAString& aDest, + uint32_t aCount, + uint32_t aOffset = 0); + +// Encode 8-bit data of a given length and append the Base64 encoded data to +// aBase64. +[[nodiscard]] nsresult Base64EncodeAppend(const char* aBinary, + uint32_t aBinaryLen, + nsAString& aBase64); +[[nodiscard]] nsresult Base64EncodeAppend(const char* aBinary, + uint32_t aBinaryLen, + nsACString& aBase64); +[[nodiscard]] nsresult Base64EncodeAppend(const nsACString& aBinary, + nsACString& aBase64); +[[nodiscard]] nsresult Base64EncodeAppend(const nsACString& aBinary, + nsAString& aBase64); + +[[nodiscard]] nsresult Base64Encode(const char* aBinary, uint32_t aBinaryLen, + char** aBase64); +[[nodiscard]] nsresult Base64Encode(const char* aBinary, uint32_t aBinaryLen, + nsACString& aBase64); +[[nodiscard]] nsresult Base64Encode(const char* aBinary, uint32_t aBinaryLen, + nsAString& aBase64); +[[nodiscard]] nsresult Base64Encode(const nsACString& aBinary, + nsACString& aBase64); +[[nodiscard]] nsresult Base64Encode(const nsACString& aBinary, + nsAString& aBase64); + +// The high bits of any characters in aBinary are dropped. +[[nodiscard]] nsresult Base64Encode(const nsAString& aBinary, + nsAString& aBase64); + +[[nodiscard]] nsresult Base64Decode(const char* aBase64, uint32_t aBase64Len, + char** aBinary, uint32_t* aBinaryLen); +[[nodiscard]] nsresult Base64Decode(const nsACString& aBase64, + nsACString& aBinary); + +// The high bits of any characters in aBase64 are dropped. +[[nodiscard]] nsresult Base64Decode(const nsAString& aBase64, + nsAString& aBinary); +[[nodiscard]] nsresult Base64Decode(const nsAString& aBase64, + nsACString& 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|. + */ +[[nodiscard]] 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|. + */ +[[nodiscard]] 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..4055787fb6 --- /dev/null +++ b/xpcom/io/CocoaFileUtils.h @@ -0,0 +1,46 @@ +/* -*- 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 "nsString.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); + +// Can be called off of the main thread. +void AddOriginMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL); +// Can be called off of the main thread. +void AddQuarantineMetadataToFile(const CFStringRef filePath, + const CFURLRef sourceURL, + const CFURLRef referrerURL, + const bool isFromWeb, + const bool createProps = false); +// Can be called off of the main thread. +void CopyQuarantineReferrerUrl(const CFStringRef aFilePath, + nsAString& aReferrer); + +CFURLRef GetTemporaryFolderCFURLRef(); + +CFURLRef GetProductDirectoryCFURLRef(bool aLocal); + +} // namespace CocoaFileUtils + +#endif diff --git a/xpcom/io/CocoaFileUtils.mm b/xpcom/io/CocoaFileUtils.mm new file mode 100644 index 0000000000..b906365a2b --- /dev/null +++ b/xpcom/io/CocoaFileUtils.mm @@ -0,0 +1,289 @@ +/* -*- 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" +#include "nsString.h" +#include "mozilla/MacStringHelpers.h" + +namespace CocoaFileUtils { + +nsresult RevealFileInFinder(CFURLRef url) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + 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_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +nsresult OpenURL(CFURLRef url) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + 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_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +nsresult GetFileCreatorCode(CFURLRef url, OSType* creatorCode) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + 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_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +nsresult SetFileCreatorCode(CFURLRef url, OSType creatorCode) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + 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_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +nsresult GetFileTypeCode(CFURLRef url, OSType* typeCode) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + 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_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +nsresult SetFileTypeCode(CFURLRef url, OSType typeCode) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + 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_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +// Can be called off of the main thread. +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); +} + +// Can be called off of the main thread. +static CFMutableDictionaryRef CreateQuarantineDictionary(const CFURLRef aFileURL, + const bool aCreateProps) { + CFDictionaryRef quarantineProps = NULL; + if (aCreateProps) { + quarantineProps = ::CFDictionaryCreate(NULL, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + } else { + Boolean success = ::CFURLCopyResourcePropertyForKey(aFileURL, kCFURLQuarantinePropertiesKey, + &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) { + return NULL; + } + } + + // We don't know what to do if the props aren't a dictionary. + if (::CFGetTypeID(quarantineProps) != ::CFDictionaryGetTypeID()) { + ::CFRelease(quarantineProps); + return NULL; + } + + // Make a mutable copy of the properties. + CFMutableDictionaryRef mutQuarantineProps = + ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, (CFDictionaryRef)quarantineProps); + ::CFRelease(quarantineProps); + + return mutQuarantineProps; +} + +// Can be called off of the main thread. +void AddQuarantineMetadataToFile(const CFStringRef filePath, const CFURLRef sourceURL, + const CFURLRef referrerURL, const bool isFromWeb, + const bool createProps /* = false */) { + CFURLRef fileURL = + ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault, filePath, kCFURLPOSIXPathStyle, false); + + CFMutableDictionaryRef mutQuarantineProps = CreateQuarantineDictionary(fileURL, createProps); + if (!mutQuarantineProps) { + ::CFRelease(fileURL); + return; + } + + // 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, kCFURLQuarantinePropertiesKey, mutQuarantineProps, + NULL); + + ::CFRelease(fileURL); + ::CFRelease(mutQuarantineProps); +} + +// Can be called off of the main thread. +void CopyQuarantineReferrerUrl(const CFStringRef aFilePath, nsAString& aReferrer) { + CFURLRef fileURL = + ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault, aFilePath, kCFURLPOSIXPathStyle, false); + + CFMutableDictionaryRef mutQuarantineProps = CreateQuarantineDictionary(fileURL, false); + ::CFRelease(fileURL); + if (!mutQuarantineProps) { + return; + } + + CFTypeRef referrerRef = ::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineOriginURLKey); + if (referrerRef && ::CFGetTypeID(referrerRef) == ::CFURLGetTypeID()) { + // URL string must be copied prior to releasing the dictionary. + mozilla::CopyCocoaStringToXPCOMString( + (NSString*)::CFURLGetString(static_cast<CFURLRef>(referrerRef)), aReferrer); + } + + ::CFRelease(mutQuarantineProps); +} + +CFURLRef GetTemporaryFolderCFURLRef() { + NSString* tempDir = ::NSTemporaryDirectory(); + return tempDir == nil ? NULL : (CFURLRef)[NSURL fileURLWithPath:tempDir isDirectory:YES]; +} + +CFURLRef GetProductDirectoryCFURLRef(bool aLocal) { + NSSearchPathDirectory folderType = aLocal ? NSCachesDirectory : NSLibraryDirectory; + NSFileManager* manager = [NSFileManager defaultManager]; + return static_cast<CFURLRef>([[manager URLsForDirectory:folderType + inDomains:NSUserDomainMask] firstObject]); +} + +} // namespace CocoaFileUtils diff --git a/xpcom/io/FileDescriptorFile.cpp b/xpcom/io/FileDescriptorFile.cpp new file mode 100644 index 0000000000..6398a89760 --- /dev/null +++ b/xpcom/io/FileDescriptorFile.cpp @@ -0,0 +1,442 @@ +/* -*- 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 "FileDescriptorFile.h" + +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/net/NeckoChild.h" +#include "nsNetUtil.h" +#include "nsProxyRelease.h" +#include "nsThreadUtils.h" +#include "private/pprio.h" +#include "SerializedLoadContext.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(FileDescriptorFile, nsIFile) + +LazyLogModule gFDFileLog("FDFile"); +#undef DBG +#define DBG(...) MOZ_LOG(gFDFileLog, LogLevel::Debug, (__VA_ARGS__)) + +FileDescriptorFile::FileDescriptorFile(const FileDescriptor& aFD, + nsIFile* aFile) { + MOZ_ASSERT(aFD.IsValid()); + auto platformHandle = aFD.ClonePlatformHandle(); + mFD = FileDescriptor(platformHandle.get()); + mFile = aFile; +} + +FileDescriptorFile::FileDescriptorFile(const FileDescriptorFile& aOther) { + auto platformHandle = aOther.mFD.ClonePlatformHandle(); + mFD = FileDescriptor(platformHandle.get()); + aOther.mFile->Clone(getter_AddRefs(mFile)); +} + +//----------------------------------------------------------------------------- +// FileDescriptorFile::nsIFile functions that we override logic for +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +FileDescriptorFile::Clone(nsIFile** aFileOut) { + RefPtr<FileDescriptorFile> fdFile = new FileDescriptorFile(*this); + fdFile.forget(aFileOut); + return NS_OK; +} + +NS_IMETHODIMP +FileDescriptorFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode, + PRFileDesc** aRetval) { + // Remove optional OS_READAHEAD flag so we test against PR_RDONLY + aFlags &= ~nsIFile::OS_READAHEAD; + + // Remove optional/deprecated DELETE_ON_CLOSE flag + aFlags &= ~nsIFile::DELETE_ON_CLOSE; + + // All other flags require write access to the file and + // this implementation only provides read access. + if (aFlags != PR_RDONLY) { + DBG("OpenNSPRFileDesc flags error (%" PRIu32 ")\n", aFlags); + return NS_ERROR_NOT_AVAILABLE; + } + + if (!mFD.IsValid()) { + DBG("OpenNSPRFileDesc error: no file descriptor\n"); + return NS_ERROR_NOT_AVAILABLE; + } + + auto platformHandle = mFD.ClonePlatformHandle(); + *aRetval = PR_ImportFile(PROsfd(platformHandle.release())); + + if (!*aRetval) { + DBG("OpenNSPRFileDesc Clone failure\n"); + return NS_ERROR_NOT_AVAILABLE; + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// FileDescriptorFile::nsIFile functions that we delegate to underlying nsIFile +//----------------------------------------------------------------------------- + +nsresult FileDescriptorFile::GetLeafName(nsAString& aLeafName) { + return mFile->GetLeafName(aLeafName); +} + +NS_IMETHODIMP +FileDescriptorFile::GetNativeLeafName(nsACString& aLeafName) { + return mFile->GetNativeLeafName(aLeafName); +} + +NS_IMETHODIMP FileDescriptorFile::GetDisplayName(nsAString& aLeafName) { + return mFile->GetDisplayName(aLeafName); +} + +nsresult FileDescriptorFile::GetTarget(nsAString& aRetVal) { + return mFile->GetTarget(aRetVal); +} + +NS_IMETHODIMP +FileDescriptorFile::GetNativeTarget(nsACString& aRetVal) { + return mFile->GetNativeTarget(aRetVal); +} + +nsresult FileDescriptorFile::GetPath(nsAString& aRetVal) { + return mFile->GetPath(aRetVal); +} + +PathString FileDescriptorFile::NativePath() { return mFile->NativePath(); } + +NS_IMETHODIMP +FileDescriptorFile::Equals(nsIFile* aOther, bool* aRetVal) { + return mFile->Equals(aOther, aRetVal); +} + +NS_IMETHODIMP +FileDescriptorFile::Contains(nsIFile* aOther, bool* aRetVal) { + return mFile->Contains(aOther, aRetVal); +} + +NS_IMETHODIMP +FileDescriptorFile::GetParent(nsIFile** aParent) { + return mFile->GetParent(aParent); +} + +NS_IMETHODIMP +FileDescriptorFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor) { + return mFile->GetPersistentDescriptor(aPersistentDescriptor); +} + +//----------------------------------------------------------------------------- +// FileDescriptorFile::nsIFile functions that are not currently supported +//----------------------------------------------------------------------------- + +nsresult FileDescriptorFile::Append(const nsAString& aNode) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::AppendNative(const nsACString& aFragment) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::Normalize() { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +FileDescriptorFile::Create(uint32_t aType, uint32_t aPermissions, + bool aSkipAncestors) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult FileDescriptorFile::SetLeafName(const nsAString& aLeafName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetNativeLeafName(const nsACString& aLeafName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult FileDescriptorFile::InitWithPath(const nsAString& aPath) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::InitWithNativePath(const nsACString& aPath) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::InitWithFile(nsIFile* aFile) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult FileDescriptorFile::AppendRelativePath(const nsAString& aNode) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::AppendRelativeNativePath(const nsACString& aFragment) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetPersistentDescriptor( + const nsACString& aPersistentDescriptor) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetRelativeDescriptor(nsIFile* aFromFile, + nsACString& aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetRelativeDescriptor(nsIFile* aFromFile, + const nsACString& aRelativeDesc) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetRelativePath(nsIFile* aFromFile, nsACString& aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetRelativePath(nsIFile* aFromFile, + const nsACString& aRelativePath) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult FileDescriptorFile::CopyTo(nsIFile* aNewParentDir, + const nsAString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::CopyToNative(nsIFile* aNewParent, + const nsACString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult FileDescriptorFile::CopyToFollowingLinks(nsIFile* aNewParentDir, + const nsAString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::CopyToFollowingLinksNative(nsIFile* aNewParent, + const nsACString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult FileDescriptorFile::MoveTo(nsIFile* aNewParentDir, + const nsAString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::MoveToNative(nsIFile* aNewParent, + const nsACString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::MoveToFollowingLinks(nsIFile* aNewParent, + const nsAString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::MoveToFollowingLinksNative(nsIFile* aNewParent, + const nsACString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::RenameTo(nsIFile* aNewParentDir, + const nsAString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::RenameToNative(nsIFile* aNewParentDir, + const nsACString& aNewName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::Remove(bool aRecursive, uint32_t* aRemoveCount) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetPermissions(uint32_t* aPermissions) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetPermissions(uint32_t aPermissions) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetPermissionsOfLink(uint32_t* aPermissionsOfLink) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetPermissionsOfLink(uint32_t aPermissions) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetLastAccessedTime(PRTime* aLastAccessedTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetLastAccessedTime(PRTime aLastAccessedTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetLastAccessedTimeOfLink(PRTime* aLastAccessedTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetLastAccessedTimeOfLink(PRTime aLastAccessedTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetLastModifiedTime(PRTime* aLastModTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetLastModifiedTime(PRTime aLastModTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetLastModifiedTimeOfLink(PRTime* aLastModTimeOfLink) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetCreationTime(PRTime* aCreationTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetCreationTimeOfLink(PRTime* aCreationTime) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetFileSize(int64_t* aFileSize) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::SetFileSize(int64_t aFileSize) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetFileSizeOfLink(int64_t* aFileSize) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::Exists(bool* aRetVal) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +FileDescriptorFile::IsWritable(bool* aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::IsReadable(bool* aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::IsExecutable(bool* aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::IsHidden(bool* aRetVal) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +FileDescriptorFile::IsDirectory(bool* aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::IsFile(bool* aRetVal) { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +FileDescriptorFile::IsSymlink(bool* aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::IsSpecial(bool* aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::CreateUnique(uint32_t aType, uint32_t aAttributes) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::OpenANSIFileDesc(const char* aMode, FILE** aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::Load(PRLibrary** aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::GetDiskCapacity(int64_t* aDiskCapacity) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FileDescriptorFile::Reveal() { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +FileDescriptorFile::Launch() { return NS_ERROR_NOT_IMPLEMENTED; } + +} // namespace net +} // namespace mozilla diff --git a/xpcom/io/FileDescriptorFile.h b/xpcom/io/FileDescriptorFile.h new file mode 100644 index 0000000000..1d0fbad2d8 --- /dev/null +++ b/xpcom/io/FileDescriptorFile.h @@ -0,0 +1,48 @@ +/* -*- 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 _FileDescriptorFile_h +#define _FileDescriptorFile_h + +#include "mozilla/Attributes.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "nsIFile.h" +#include "private/pprio.h" + +namespace mozilla { +namespace net { + +/** + * A limited implementation of nsIFile that wraps a FileDescriptor object + * allowing the file to be read from. Added to allow a child process to use + * an nsIFile object for a file it does not have access to on the filesystem + * but has been provided a FileDescriptor for from the parent. Many nsIFile + * methods are not implemented and this is not intended to be a general + * purpose file implementation. + */ +class FileDescriptorFile final : public nsIFile { + typedef mozilla::ipc::FileDescriptor FileDescriptor; + + public: + FileDescriptorFile(const FileDescriptor& aFD, nsIFile* aFile); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIFILE + + private: + ~FileDescriptorFile() = default; + + FileDescriptorFile(const FileDescriptorFile& other); + + // regular nsIFile object, that we forward most calls to. + nsCOMPtr<nsIFile> mFile; + FileDescriptor mFD; +}; + +} // namespace net +} // namespace mozilla + +#endif // _FileDescriptorFile_h diff --git a/xpcom/io/FilePreferences.cpp b/xpcom/io/FilePreferences.cpp new file mode 100644 index 0000000000..1d96c72810 --- /dev/null +++ b/xpcom/io/FilePreferences.cpp @@ -0,0 +1,373 @@ +/* -*- 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 "FilePreferences.h" + +#include "mozilla/Atomics.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Tokenizer.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsString.h" + +namespace mozilla { +namespace FilePreferences { + +static StaticMutex sMutex; + +static bool sBlockUNCPaths = false; +typedef nsTArray<nsString> WinPaths; + +static WinPaths& PathAllowlist() MOZ_REQUIRES(sMutex) { + sMutex.AssertCurrentThreadOwns(); + + static WinPaths sPaths MOZ_GUARDED_BY(sMutex); + return sPaths; +} + +#ifdef XP_WIN +const auto kDevicePathSpecifier = u"\\\\?\\"_ns; + +typedef char16_t char_path_t; +#else +typedef char char_path_t; +#endif + +// Initially false to make concurrent consumers acquire the lock and sync. +// The plain bool is synchronized with sMutex, the atomic one is for a quick +// check w/o the need to acquire the lock on the hot path. +static bool sForbiddenPathsEmpty = false; +static Atomic<bool, Relaxed> sForbiddenPathsEmptyQuickCheck{false}; + +typedef nsTArray<nsTString<char_path_t>> Paths; +static StaticAutoPtr<Paths> sForbiddenPaths; + +static Paths& ForbiddenPaths() { + sMutex.AssertCurrentThreadOwns(); + if (!sForbiddenPaths) { + sForbiddenPaths = new nsTArray<nsTString<char_path_t>>(); + ClearOnShutdown(&sForbiddenPaths); + } + return *sForbiddenPaths; +} + +static void AllowUNCDirectory(char const* directory) { + nsCOMPtr<nsIFile> file; + NS_GetSpecialDirectory(directory, getter_AddRefs(file)); + if (!file) { + return; + } + + nsString path; + if (NS_FAILED(file->GetTarget(path))) { + return; + } + + // The allowlist makes sense only for UNC paths, because this code is used + // to block only UNC paths, hence, no need to add non-UNC directories here + // as those would never pass the check. + if (!StringBeginsWith(path, u"\\\\"_ns)) { + return; + } + + StaticMutexAutoLock lock(sMutex); + + if (!PathAllowlist().Contains(path)) { + PathAllowlist().AppendElement(path); + } +} + +void InitPrefs() { + sBlockUNCPaths = + Preferences::GetBool("network.file.disable_unc_paths", false); + + nsTAutoString<char_path_t> forbidden; +#ifdef XP_WIN + Preferences::GetString("network.file.path_blacklist", forbidden); +#else + Preferences::GetCString("network.file.path_blacklist", forbidden); +#endif + + StaticMutexAutoLock lock(sMutex); + + if (forbidden.IsEmpty()) { + sForbiddenPathsEmptyQuickCheck = (sForbiddenPathsEmpty = true); + return; + } + + ForbiddenPaths().Clear(); + TTokenizer<char_path_t> p(forbidden); + while (!p.CheckEOF()) { + nsTString<char_path_t> path; + Unused << p.ReadUntil(TTokenizer<char_path_t>::Token::Char(','), path); + path.Trim(" "); + if (!path.IsEmpty()) { + ForbiddenPaths().AppendElement(path); + } + Unused << p.CheckChar(','); + } + + sForbiddenPathsEmptyQuickCheck = + (sForbiddenPathsEmpty = ForbiddenPaths().Length() == 0); +} + +void InitDirectoriesAllowlist() { + // NS_GRE_DIR is the installation path where the binary resides. + AllowUNCDirectory(NS_GRE_DIR); + // NS_APP_USER_PROFILE_50_DIR and NS_APP_USER_PROFILE_LOCAL_50_DIR are the two + // parts of the profile we store permanent and local-specific data. + AllowUNCDirectory(NS_APP_USER_PROFILE_50_DIR); + AllowUNCDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR); +} + +namespace { // anon + +template <typename TChar> +class TNormalizer : public TTokenizer<TChar> { + typedef TTokenizer<TChar> base; + + public: + typedef typename base::Token Token; + + TNormalizer(const nsTSubstring<TChar>& aFilePath, const Token& aSeparator) + : TTokenizer<TChar>(aFilePath), mSeparator(aSeparator) {} + + bool Get(nsTSubstring<TChar>& aNormalizedFilePath) { + aNormalizedFilePath.Truncate(); + + // Windows UNC paths begin with double separator (\\) + // Linux paths begin with just one separator (/) + // If we want to use the normalizer for regular windows paths this code + // will need to be updated. +#ifdef XP_WIN + if (base::Check(mSeparator)) { + aNormalizedFilePath.Append(mSeparator.AsChar()); + } +#endif + + if (base::Check(mSeparator)) { + aNormalizedFilePath.Append(mSeparator.AsChar()); + } + + while (base::HasInput()) { + if (!ConsumeName()) { + return false; + } + } + + for (auto const& name : mStack) { + aNormalizedFilePath.Append(name); + } + + return true; + } + + private: + bool ConsumeName() { + if (base::CheckEOF()) { + return true; + } + + if (CheckCurrentDir()) { + return true; + } + + if (CheckParentDir()) { + if (!mStack.Length()) { + // This means there are more \.. than valid names + return false; + } + + mStack.RemoveLastElement(); + return true; + } + + nsTDependentSubstring<TChar> name; + if (base::ReadUntil(mSeparator, name, base::INCLUDE_LAST) && + name.Length() == 1) { + // this means an empty name (a lone slash), which is illegal + return false; + } + mStack.AppendElement(name); + + return true; + } + + bool CheckParentDir() { + typename nsTString<TChar>::const_char_iterator cursor = base::mCursor; + if (base::CheckChar('.') && base::CheckChar('.') && CheckSeparator()) { + return true; + } + + base::mCursor = cursor; + return false; + } + + bool CheckCurrentDir() { + typename nsTString<TChar>::const_char_iterator cursor = base::mCursor; + if (base::CheckChar('.') && CheckSeparator()) { + return true; + } + + base::mCursor = cursor; + return false; + } + + bool CheckSeparator() { return base::Check(mSeparator) || base::CheckEOF(); } + + Token const mSeparator; + nsTArray<nsTDependentSubstring<TChar>> mStack; +}; + +#ifdef XP_WIN +bool IsDOSDevicePathWithDrive(const nsAString& aFilePath) { + if (!StringBeginsWith(aFilePath, kDevicePathSpecifier)) { + return false; + } + + const auto pathNoPrefix = + nsDependentSubstring(aFilePath, kDevicePathSpecifier.Length()); + + // After the device path specifier, the rest of file path can be: + // - starts with the volume or drive. e.g. \\?\C:\... + // - UNCs. e.g. \\?\UNC\Server\Share\Test\Foo.txt + // - device UNCs. e.g. \\?\server1\e:\utilities\\filecomparer\... + // The first case should not be blocked by IsBlockedUNCPath. + if (!StartsWithDiskDesignatorAndBackslash(pathNoPrefix)) { + return false; + } + + return true; +} +#endif + +} // namespace + +bool IsBlockedUNCPath(const nsAString& aFilePath) { + typedef TNormalizer<char16_t> Normalizer; + if (!sBlockUNCPaths) { + return false; + } + + if (!StringBeginsWith(aFilePath, u"\\\\"_ns)) { + return false; + } + +#ifdef XP_WIN + // ToDo: We don't need to check this once we can check if there is a valid + // server or host name that is prefaced by "\\". + // https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats + if (IsDOSDevicePathWithDrive(aFilePath)) { + return false; + } +#endif + + nsAutoString normalized; + if (!Normalizer(aFilePath, Normalizer::Token::Char('\\')).Get(normalized)) { + // Broken paths are considered invalid and thus inaccessible + return true; + } + + StaticMutexAutoLock lock(sMutex); + + for (const auto& allowedPrefix : PathAllowlist()) { + if (StringBeginsWith(normalized, allowedPrefix)) { + if (normalized.Length() == allowedPrefix.Length()) { + return false; + } + if (normalized[allowedPrefix.Length()] == L'\\') { + return false; + } + + // When we are here, the path has a form "\\path\prefixevil" + // while we have an allowed prefix of "\\path\prefix". + // Note that we don't want to add a slash to the end of a prefix + // so that opening the directory (no slash at the end) still works. + break; + } + } + + return true; +} + +#ifdef XP_WIN +const char kPathSeparator = '\\'; +#else +const char kPathSeparator = '/'; +#endif + +bool IsAllowedPath(const nsTSubstring<char_path_t>& aFilePath) { + typedef TNormalizer<char_path_t> Normalizer; + + // An atomic quick check out of the lock, because this is mostly `true`. + if (sForbiddenPathsEmptyQuickCheck) { + return true; + } + + StaticMutexAutoLock lock(sMutex); + + if (sForbiddenPathsEmpty) { + return true; + } + + // If sForbidden has been cleared at shutdown, we must avoid calling + // ForbiddenPaths() again, as that will recreate the array and we will leak. + if (!sForbiddenPaths) { + return true; + } + + nsTAutoString<char_path_t> normalized; + if (!Normalizer(aFilePath, Normalizer::Token::Char(kPathSeparator)) + .Get(normalized)) { + // Broken paths are considered invalid and thus inaccessible + return false; + } + + for (const auto& prefix : ForbiddenPaths()) { + if (StringBeginsWith(normalized, prefix)) { + if (normalized.Length() > prefix.Length() && + normalized[prefix.Length()] != kPathSeparator) { + continue; + } + return false; + } + } + + return true; +} + +#ifdef XP_WIN +bool StartsWithDiskDesignatorAndBackslash(const nsAString& aAbsolutePath) { + // aAbsolutePath can only be (in regular expression): + // UNC path: ^\\\\.* + // A single backslash: ^\\.* + // A disk designator with a backslash: ^[A-Za-z]:\\.* + return aAbsolutePath.Length() >= 3 && IsAsciiAlpha(aAbsolutePath.CharAt(0)) && + aAbsolutePath.CharAt(1) == L':' && + aAbsolutePath.CharAt(2) == kPathSeparator; +} +#endif + +void testing::SetBlockUNCPaths(bool aBlock) { sBlockUNCPaths = aBlock; } + +void testing::AddDirectoryToAllowlist(nsAString const& aPath) { + StaticMutexAutoLock lock(sMutex); + PathAllowlist().AppendElement(aPath); +} + +bool testing::NormalizePath(nsAString const& aPath, nsAString& aNormalized) { + typedef TNormalizer<char16_t> Normalizer; + Normalizer normalizer(aPath, Normalizer::Token::Char('\\')); + return normalizer.Get(aNormalized); +} + +} // namespace FilePreferences +} // namespace mozilla diff --git a/xpcom/io/FilePreferences.h b/xpcom/io/FilePreferences.h new file mode 100644 index 0000000000..ee80429047 --- /dev/null +++ b/xpcom/io/FilePreferences.h @@ -0,0 +1,40 @@ +/* -*- 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 "nsAString.h" + +namespace mozilla { +namespace FilePreferences { + +void InitPrefs(); +void InitDirectoriesAllowlist(); +bool IsBlockedUNCPath(const nsAString& aFilePath); + +#ifdef XP_WIN +bool IsAllowedPath(const nsAString& aFilePath); +#else +bool IsAllowedPath(const nsACString& aFilePath); +#endif + +#ifdef XP_WIN +bool StartsWithDiskDesignatorAndBackslash(const nsAString& aAbsolutePath); +#endif + +extern const char kPathSeparator; +#ifdef XP_WIN +extern const nsLiteralString kDevicePathSpecifier; +#endif + +namespace testing { + +void SetBlockUNCPaths(bool aBlock); +void AddDirectoryToAllowlist(nsAString const& aPath); +bool NormalizePath(nsAString const& aPath, nsAString& aNormalized); + +} // namespace testing + +} // namespace FilePreferences +} // namespace mozilla diff --git a/xpcom/io/FileUtilsWin.cpp b/xpcom/io/FileUtilsWin.cpp new file mode 100644 index 0000000000..9c0bc6faa2 --- /dev/null +++ b/xpcom/io/FileUtilsWin.cpp @@ -0,0 +1,139 @@ +/* -*- 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 "base/process_util.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/Unused.h" +#include "nsWindowsHelpers.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) { + AUTO_PROFILER_LABEL("HandletoFilename", OTHER); + + 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, mappedFilename.get(), + mappedFilename.Length()); + } while (!len && GetLastError() == ERROR_INSUFFICIENT_BUFFER); + if (!len) { + return false; + } + mappedFilename.Truncate(len); + return NtPathToDosPath(mappedFilename, aFilename); +} + +template <class T> +struct RVAMap { + RVAMap(HANDLE map, DWORD offset) { + SYSTEM_INFO info; + GetSystemInfo(&info); + + DWORD alignedOffset = + (offset / info.dwAllocationGranularity) * info.dwAllocationGranularity; + + MOZ_ASSERT(offset - alignedOffset < info.dwAllocationGranularity, "Wtf"); + + mRealView = ::MapViewOfFile(map, FILE_MAP_READ, 0, alignedOffset, + sizeof(T) + (offset - alignedOffset)); + + mMappedView = + mRealView + ? reinterpret_cast<T*>((char*)mRealView + (offset - alignedOffset)) + : nullptr; + } + ~RVAMap() { + if (mRealView) { + ::UnmapViewOfFile(mRealView); + } + } + operator const T*() const { return mMappedView; } + const T* operator->() const { return mMappedView; } + + private: + const T* mMappedView; + void* mRealView; +}; + +uint32_t GetExecutableArchitecture(const wchar_t* aPath) { + nsAutoHandle file(::CreateFileW(aPath, GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, + nullptr)); + if (!file) { + return base::PROCESS_ARCH_INVALID; + } + + nsAutoHandle map( + ::CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, 0, nullptr)); + if (!map) { + return base::PROCESS_ARCH_INVALID; + } + + RVAMap<IMAGE_DOS_HEADER> peHeader(map, 0); + if (!peHeader) { + return base::PROCESS_ARCH_INVALID; + } + + RVAMap<IMAGE_NT_HEADERS> ntHeader(map, peHeader->e_lfanew); + if (!ntHeader) { + return base::PROCESS_ARCH_INVALID; + } + + switch (ntHeader->FileHeader.Machine) { + case IMAGE_FILE_MACHINE_I386: + return base::PROCESS_ARCH_I386; + case IMAGE_FILE_MACHINE_AMD64: + return base::PROCESS_ARCH_X86_64; + case IMAGE_FILE_MACHINE_ARM64: + return base::PROCESS_ARCH_ARM_64; + case IMAGE_FILE_MACHINE_ARM: + case IMAGE_FILE_MACHINE_ARMNT: + case IMAGE_FILE_MACHINE_THUMB: + return base::PROCESS_ARCH_ARM; + default: + return base::PROCESS_ARCH_INVALID; + } +} + +} // namespace mozilla diff --git a/xpcom/io/FileUtilsWin.h b/xpcom/io/FileUtilsWin.h new file mode 100644 index 0000000000..4a59820d08 --- /dev/null +++ b/xpcom/io/FileUtilsWin.h @@ -0,0 +1,147 @@ +/* -*- 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 "nsString.h" + +namespace mozilla { + +inline bool EnsureLongPath(nsAString& aDosPath) { + nsAutoString inputPath(aDosPath); + while (true) { + DWORD requiredLength = GetLongPathNameW( + inputPath.get(), reinterpret_cast<wchar_t*>(aDosPath.BeginWriting()), + aDosPath.Length()); + if (!requiredLength) { + return false; + } + if (requiredLength < aDosPath.Length()) { + // When GetLongPathNameW deems the last argument too small, + // it returns a value, but when you pass that value back, it's + // satisfied and returns a number that's one smaller. If the above + // check was == instead of <, the loop would go on forever with + // GetLongPathNameW returning oscillating values! + aDosPath.Truncate(requiredLength); + return true; + } + aDosPath.SetLength(requiredLength); + } +} + +inline bool NtPathToDosPath(const nsAString& aNtPath, nsAString& aDosPath) { + aDosPath.Truncate(); + if (aNtPath.IsEmpty()) { + return true; + } + constexpr auto symLinkPrefix = u"\\??\\"_ns; + 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; + while (true) { + DWORD requiredLength = GetLogicalDriveStringsW( + logicalDrives.Length(), + reinterpret_cast<wchar_t*>(logicalDrives.BeginWriting())); + if (!requiredLength) { + return false; + } + if (requiredLength < logicalDrives.Length()) { + // When GetLogicalDriveStringsW deems the first argument too small, + // it returns a value, but when you pass that value back, it's + // satisfied and returns a number that's one smaller. If the above + // check was == instead of <, the loop would go on forever with + // GetLogicalDriveStringsW returning oscillating values! + logicalDrives.Truncate(requiredLength); + // logicalDrives now has the format "C:\\\0D:\\\0Z:\\\0". That is, + // the sequence drive letter, colon, backslash, U+0000 repeats. + break; + } + logicalDrives.SetLength(requiredLength); + } + + const char16_t* cur = logicalDrives.BeginReading(); + const char16_t* end = logicalDrives.EndReading(); + nsString targetPath; + targetPath.SetLength(MAX_PATH); + wchar_t driveTemplate[] = L" :"; + while (cur < end) { + // 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); + } + } + // Find the next U+0000 within the logical string + while (*cur) { + // This loop skips the drive letter, the colon + // and the backslash. + cur++; + } + // Skip over the U+0000 that ends a drive entry + // within the logical string + cur++; + } + // 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! + constexpr auto uncPrefix = u"\\\\"_ns; + constexpr auto deviceMupPrefix = u"\\Device\\Mup\\"_ns; + if (StringBeginsWith(aNtPath, deviceMupPrefix)) { + aDosPath = uncPrefix; + aDosPath += Substring(aNtPath, deviceMupPrefix.Length()); + return true; + } + constexpr auto deviceLanmanRedirectorPrefix = + u"\\Device\\LanmanRedirector\\"_ns; + 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); + +uint32_t GetExecutableArchitecture(const wchar_t* aPath); + +} // namespace mozilla + +#endif // mozilla_FileUtilsWin_h diff --git a/xpcom/io/FixedBufferOutputStream.cpp b/xpcom/io/FixedBufferOutputStream.cpp new file mode 100644 index 0000000000..1c841bb91f --- /dev/null +++ b/xpcom/io/FixedBufferOutputStream.cpp @@ -0,0 +1,161 @@ +/* -*- 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 "FixedBufferOutputStream.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/StreamBufferSinkImpl.h" +#include "nsStreamUtils.h" +#include "nsString.h" + +namespace mozilla { + +FixedBufferOutputStream::FixedBufferOutputStream( + UniquePtr<StreamBufferSink>&& aSink) + : mSink(std::move(aSink)), + mMutex("FixedBufferOutputStream::mMutex"), + mOffset(0), + mWriting(false), + mClosed(false) {} + +// static +RefPtr<FixedBufferOutputStream> FixedBufferOutputStream::Create( + size_t aLength) { + MOZ_ASSERT(aLength); + + return MakeRefPtr<FixedBufferOutputStream>(MakeUnique<BufferSink>(aLength)); +} + +// static +RefPtr<FixedBufferOutputStream> FixedBufferOutputStream::Create( + size_t aLength, const mozilla::fallible_t&) { + MOZ_ASSERT(aLength); + + auto sink = BufferSink::Alloc(aLength); + + if (NS_WARN_IF(!sink)) { + return nullptr; + } + + return MakeRefPtr<FixedBufferOutputStream>(std::move(sink)); +} + +// static +RefPtr<FixedBufferOutputStream> FixedBufferOutputStream::Create( + mozilla::Span<char> aBuffer) { + return MakeRefPtr<FixedBufferOutputStream>( + MakeUnique<nsBorrowedSink>(aBuffer)); +} + +// static +RefPtr<FixedBufferOutputStream> FixedBufferOutputStream::Create( + UniquePtr<StreamBufferSink>&& aSink) { + return MakeRefPtr<FixedBufferOutputStream>(std::move(aSink)); +} + +nsDependentCSubstring FixedBufferOutputStream::WrittenData() { + MutexAutoLock autoLock(mMutex); + + return mSink->Slice(mOffset); +} + +NS_IMPL_ISUPPORTS(FixedBufferOutputStream, nsIOutputStream) + +NS_IMETHODIMP +FixedBufferOutputStream::Close() { + MutexAutoLock autoLock(mMutex); + + if (mWriting) { + return NS_ERROR_NOT_AVAILABLE; + } + + mClosed = true; + + return NS_OK; +} + +NS_IMETHODIMP +FixedBufferOutputStream::Flush() { return NS_OK; } + +NS_IMETHODIMP +FixedBufferOutputStream::StreamStatus() { + MutexAutoLock autoLock(mMutex); + return mClosed ? NS_BASE_STREAM_CLOSED : NS_OK; +} + +NS_IMETHODIMP +FixedBufferOutputStream::Write(const char* aBuf, uint32_t aCount, + uint32_t* _retval) { + return WriteSegments(NS_CopyBufferToSegment, const_cast<char*>(aBuf), aCount, + _retval); +} + +NS_IMETHODIMP +FixedBufferOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* _retval) { + return WriteSegments(NS_CopyStreamToSegment, aFromStream, aCount, _retval); +} + +NS_IMETHODIMP +FixedBufferOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* _retval) { + MOZ_ASSERT(_retval); + + MutexAutoLock autoLock(mMutex); + + if (mWriting) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + size_t length = mSink->Data().Length(); + size_t offset = mOffset; + + MOZ_ASSERT(length >= offset, "Bad stream state!"); + + size_t maxCount = length - offset; + if (maxCount == 0) { + *_retval = 0; + return NS_OK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + + mWriting = true; + + nsresult rv; + + { + MutexAutoUnlock autoUnlock(mMutex); + + rv = aReader(this, aClosure, mSink->Data().Elements() + offset, 0, aCount, + _retval); + } + + mWriting = false; + + if (NS_SUCCEEDED(rv)) { + MOZ_ASSERT(*_retval <= aCount, + "Reader should not read more than we asked it to read!"); + mOffset += *_retval; + } + + // As defined in nsIOutputStream.idl, do not pass reader func errors. + return NS_OK; +} + +NS_IMETHODIMP +FixedBufferOutputStream::IsNonBlocking(bool* _retval) { + *_retval = true; + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/io/FixedBufferOutputStream.h b/xpcom/io/FixedBufferOutputStream.h new file mode 100644 index 0000000000..db7fba3c3a --- /dev/null +++ b/xpcom/io/FixedBufferOutputStream.h @@ -0,0 +1,78 @@ +/* -*- 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_FixedBufferOutputStream_h +#define mozilla_FixedBufferOutputStream_h + +#include <cstddef> +#include "mozilla/fallible.h" +#include "mozilla/Mutex.h" +#include "mozilla/Span.h" +#include "mozilla/UniquePtr.h" +#include "nsIOutputStream.h" +#include "nsStringFwd.h" + +template <class T> +class RefPtr; + +namespace mozilla { + +class StreamBufferSink; + +// An output stream so you can read your potentially-async input stream into +// a contiguous buffer using NS_AsyncCopy. Back when streams were more +// synchronous and people didn't know blocking I/O was bad, if you wanted to +// read a stream into a flat buffer, you could use NS_ReadInputStreamToString +// or NS_ReadInputStreamToBuffer. But those don't work with async streams. +// This can be used to replace hand-rolled Read/AsyncWait() loops. Because you +// specify the expected size up front, the buffer is pre-allocated so wasteful +// reallocations can be avoided. +class FixedBufferOutputStream final : public nsIOutputStream { + template <typename T, typename... Args> + friend RefPtr<T> MakeRefPtr(Args&&... aArgs); + + public: + // Factory method to get a FixedBufferOutputStream by allocating a char buffer + // with the given length. + static RefPtr<FixedBufferOutputStream> Create(size_t aLength); + + // Factory method to get a FixedBufferOutputStream by allocating a char buffer + // with the given length, fallible version. + static RefPtr<FixedBufferOutputStream> Create(size_t aLength, + const mozilla::fallible_t&); + + // Factory method to get a FixedBufferOutputStream from a preallocated char + // buffer. The output stream doesn't take ownership of the char buffer, so the + // char buffer must outlive the output stream (to avoid a use-after-free). + static RefPtr<FixedBufferOutputStream> Create(mozilla::Span<char> aBuffer); + + // Factory method to get a FixedBufferOutputStream from an arbitrary + // StreamBufferSink. + static RefPtr<FixedBufferOutputStream> Create( + UniquePtr<StreamBufferSink>&& aSink); + + nsDependentCSubstring WrittenData(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + + private: + explicit FixedBufferOutputStream(UniquePtr<StreamBufferSink>&& aSink); + + ~FixedBufferOutputStream() = default; + + const UniquePtr<StreamBufferSink> mSink; + + Mutex mMutex; + + size_t mOffset MOZ_GUARDED_BY(mMutex); + bool mWriting MOZ_GUARDED_BY(mMutex); + bool mClosed MOZ_GUARDED_BY(mMutex); +}; + +} // namespace mozilla + +#endif // mozilla_FixedBufferOutputStream_h diff --git a/xpcom/io/InputStreamLengthHelper.cpp b/xpcom/io/InputStreamLengthHelper.cpp new file mode 100644 index 0000000000..9a767f9229 --- /dev/null +++ b/xpcom/io/InputStreamLengthHelper.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 "InputStreamLengthHelper.h" +#include "mozilla/dom/WorkerCommon.h" +#include "nsIAsyncInputStream.h" +#include "nsIInputStream.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" + +namespace mozilla { + +namespace { + +class AvailableEvent final : public Runnable { + public: + AvailableEvent(nsIInputStream* stream, + const std::function<void(int64_t aLength)>& aCallback) + : Runnable("mozilla::AvailableEvent"), + mStream(stream), + mCallback(aCallback), + mSize(-1) { + mCallbackTarget = GetCurrentSerialEventTarget(); + MOZ_ASSERT(NS_IsMainThread()); + } + + NS_IMETHOD + Run() override { + // ping + if (!NS_IsMainThread()) { + uint64_t size = 0; + if (NS_WARN_IF(NS_FAILED(mStream->Available(&size)))) { + mSize = -1; + } else { + mSize = (int64_t)size; + } + + mStream = nullptr; + + nsCOMPtr<nsIRunnable> self(this); // overly cute + mCallbackTarget->Dispatch(self.forget(), NS_DISPATCH_NORMAL); + mCallbackTarget = nullptr; + return NS_OK; + } + + // pong + std::function<void(int64_t aLength)> callback; + callback.swap(mCallback); + callback(mSize); + return NS_OK; + } + + private: + nsCOMPtr<nsIInputStream> mStream; + std::function<void(int64_t aLength)> mCallback; + nsCOMPtr<nsIEventTarget> mCallbackTarget; + + int64_t mSize; +}; + +} // namespace + +/* static */ +bool InputStreamLengthHelper::GetSyncLength(nsIInputStream* aStream, + int64_t* aLength) { + MOZ_ASSERT(aStream); + MOZ_ASSERT(aLength); + + *aLength = -1; + + // Sync length access. + nsCOMPtr<nsIInputStreamLength> streamLength = do_QueryInterface(aStream); + if (streamLength) { + int64_t length = -1; + nsresult rv = streamLength->Length(&length); + + // All good! + if (NS_SUCCEEDED(rv)) { + *aLength = length; + return true; + } + + // Already closed stream or an error occurred. + if (rv == NS_BASE_STREAM_CLOSED || + NS_WARN_IF(rv == NS_ERROR_NOT_AVAILABLE) || + NS_WARN_IF(rv != NS_BASE_STREAM_WOULD_BLOCK)) { + return true; + } + } + + nsCOMPtr<nsIAsyncInputStreamLength> asyncStreamLength = + do_QueryInterface(aStream); + if (asyncStreamLength) { + // GetAsyncLength should be used. + return false; + } + + // We cannot calculate the length of an async stream. + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aStream); + if (asyncStream) { + return false; + } + + // For main-thread only, we want to avoid calling ::Available() for blocking + // streams. + if (NS_IsMainThread()) { + bool nonBlocking = false; + if (NS_WARN_IF(NS_FAILED(aStream->IsNonBlocking(&nonBlocking)))) { + // Let's return -1. There is nothing else we can do here. + return true; + } + + if (!nonBlocking) { + return false; + } + } + + // Fallback using available(). + uint64_t available = 0; + nsresult rv = aStream->Available(&available); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Let's return -1. There is nothing else we can do here. + return true; + } + + *aLength = (int64_t)available; + return true; +} + +/* static */ +void InputStreamLengthHelper::GetAsyncLength( + nsIInputStream* aStream, + const std::function<void(int64_t aLength)>& aCallback) { + MOZ_ASSERT(aStream); + MOZ_ASSERT(aCallback); + + // We don't want to allow this class to be used on workers because we are not + // using the correct Runnable types. + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread() || + !dom::IsCurrentThreadRunningWorker()); + + RefPtr<InputStreamLengthHelper> helper = + new InputStreamLengthHelper(aStream, aCallback); + + // Let's be sure that we don't call ::Available() on main-thread. + if (NS_IsMainThread()) { + nsCOMPtr<nsIInputStreamLength> streamLength = do_QueryInterface(aStream); + nsCOMPtr<nsIAsyncInputStreamLength> asyncStreamLength = + do_QueryInterface(aStream); + if (!streamLength && !asyncStreamLength) { + // We cannot calculate the length of an async stream. We must fix the + // caller if this happens. +#ifdef DEBUG + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aStream); + MOZ_DIAGNOSTIC_ASSERT(!asyncStream); +#endif + + bool nonBlocking = false; + if (NS_SUCCEEDED(aStream->IsNonBlocking(&nonBlocking)) && !nonBlocking) { + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target); + + RefPtr<AvailableEvent> event = new AvailableEvent(aStream, aCallback); + target->Dispatch(event.forget(), NS_DISPATCH_NORMAL); + return; + } + } + } + + // Let's go async in order to have similar behaviors for sync and async + // nsIInputStreamLength implementations. + GetCurrentSerialEventTarget()->Dispatch(helper, NS_DISPATCH_NORMAL); +} + +InputStreamLengthHelper::InputStreamLengthHelper( + nsIInputStream* aStream, + const std::function<void(int64_t aLength)>& aCallback) + : Runnable("InputStreamLengthHelper"), + mStream(aStream), + mCallback(aCallback) { + MOZ_ASSERT(aStream); + MOZ_ASSERT(aCallback); +} + +InputStreamLengthHelper::~InputStreamLengthHelper() = default; + +NS_IMETHODIMP +InputStreamLengthHelper::Run() { + // Sync length access. + nsCOMPtr<nsIInputStreamLength> streamLength = do_QueryInterface(mStream); + if (streamLength) { + int64_t length = -1; + nsresult rv = streamLength->Length(&length); + + // All good! + if (NS_SUCCEEDED(rv)) { + ExecCallback(length); + return NS_OK; + } + + // Already closed stream or an error occurred. + if (rv == NS_BASE_STREAM_CLOSED || + NS_WARN_IF(rv == NS_ERROR_NOT_AVAILABLE) || + NS_WARN_IF(rv != NS_BASE_STREAM_WOULD_BLOCK)) { + ExecCallback(-1); + return NS_OK; + } + } + + // Async length access. + nsCOMPtr<nsIAsyncInputStreamLength> asyncStreamLength = + do_QueryInterface(mStream); + if (asyncStreamLength) { + nsresult rv = + asyncStreamLength->AsyncLengthWait(this, GetCurrentSerialEventTarget()); + if (NS_WARN_IF(NS_FAILED(rv))) { + ExecCallback(-1); + } + + return NS_OK; + } + + // Fallback using available(). + uint64_t available = 0; + nsresult rv = mStream->Available(&available); + if (NS_WARN_IF(NS_FAILED(rv))) { + ExecCallback(-1); + return NS_OK; + } + + ExecCallback((int64_t)available); + return NS_OK; +} + +NS_IMETHODIMP +InputStreamLengthHelper::OnInputStreamLengthReady( + nsIAsyncInputStreamLength* aStream, int64_t aLength) { + ExecCallback(aLength); + return NS_OK; +} + +void InputStreamLengthHelper::ExecCallback(int64_t aLength) { + MOZ_ASSERT(mCallback); + + std::function<void(int64_t aLength)> callback; + callback.swap(mCallback); + + callback(aLength); +} + +NS_IMPL_ISUPPORTS_INHERITED(InputStreamLengthHelper, Runnable, + nsIInputStreamLengthCallback) + +} // namespace mozilla diff --git a/xpcom/io/InputStreamLengthHelper.h b/xpcom/io/InputStreamLengthHelper.h new file mode 100644 index 0000000000..1f44c70fe3 --- /dev/null +++ b/xpcom/io/InputStreamLengthHelper.h @@ -0,0 +1,57 @@ +/* -*- 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_InputStreamLengthHelper_h +#define mozilla_InputStreamLengthHelper_h + +#include <functional> + +#include "nsISupportsImpl.h" +#include "nsIInputStreamLength.h" +#include "nsThreadUtils.h" + +class nsIInputStream; + +namespace mozilla { + +// This class helps to retrieve the stream's length. + +class InputStreamLengthHelper final : public Runnable, + public nsIInputStreamLengthCallback { + public: + NS_DECL_ISUPPORTS_INHERITED + + // This is one of the 2 entry points of this class. It returns false if the + // length cannot be taken synchronously. + static bool GetSyncLength(nsIInputStream* aStream, int64_t* aLength); + + // This is one of the 2 entry points of this class. The callback is executed + // asynchronously when the length is known. + static void GetAsyncLength( + nsIInputStream* aStream, + const std::function<void(int64_t aLength)>& aCallback); + + private: + NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK + + InputStreamLengthHelper( + nsIInputStream* aStream, + const std::function<void(int64_t aLength)>& aCallback); + + ~InputStreamLengthHelper(); + + NS_IMETHOD + Run() override; + + void ExecCallback(int64_t aLength); + + nsCOMPtr<nsIInputStream> mStream; + std::function<void(int64_t aLength)> mCallback; +}; + +} // namespace mozilla + +#endif // mozilla_InputStreamLengthHelper_h diff --git a/xpcom/io/InputStreamLengthWrapper.cpp b/xpcom/io/InputStreamLengthWrapper.cpp new file mode 100644 index 0000000000..9ba4968ad2 --- /dev/null +++ b/xpcom/io/InputStreamLengthWrapper.cpp @@ -0,0 +1,345 @@ +/* -*- 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 "InputStreamLengthWrapper.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsISeekableStream.h" +#include "nsStreamUtils.h" + +namespace mozilla { + +using namespace ipc; + +NS_IMPL_ADDREF(InputStreamLengthWrapper); +NS_IMPL_RELEASE(InputStreamLengthWrapper); + +NS_INTERFACE_MAP_BEGIN(InputStreamLengthWrapper) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, + mWeakCloneableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL( + nsIIPCSerializableInputStream, + mWeakIPCSerializableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, + mWeakSeekableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream, + mWeakTellableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, + mWeakAsyncInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, + mWeakAsyncInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamLength) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +/* static */ +already_AddRefed<nsIInputStream> InputStreamLengthWrapper::MaybeWrap( + already_AddRefed<nsIInputStream> aInputStream, int64_t aLength) { + nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream); + MOZ_ASSERT(inputStream); + + nsCOMPtr<nsIInputStreamLength> length = do_QueryInterface(inputStream); + if (length) { + return inputStream.forget(); + } + + nsCOMPtr<nsIAsyncInputStreamLength> asyncLength = + do_QueryInterface(inputStream); + if (asyncLength) { + return inputStream.forget(); + } + + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(inputStream); + if (!asyncStream) { + return inputStream.forget(); + } + + inputStream = new InputStreamLengthWrapper(inputStream.forget(), aLength); + return inputStream.forget(); +} + +InputStreamLengthWrapper::InputStreamLengthWrapper( + already_AddRefed<nsIInputStream> aInputStream, int64_t aLength) + : mWeakCloneableInputStream(nullptr), + mWeakIPCSerializableInputStream(nullptr), + mWeakSeekableInputStream(nullptr), + mWeakTellableInputStream(nullptr), + mWeakAsyncInputStream(nullptr), + mLength(aLength), + mConsumed(false), + mMutex("InputStreamLengthWrapper::mMutex") { + MOZ_ASSERT(mLength >= 0); + + nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream); + SetSourceStream(inputStream.forget()); +} + +InputStreamLengthWrapper::InputStreamLengthWrapper() + : mWeakCloneableInputStream(nullptr), + mWeakIPCSerializableInputStream(nullptr), + mWeakSeekableInputStream(nullptr), + mWeakTellableInputStream(nullptr), + mWeakAsyncInputStream(nullptr), + mLength(-1), + mConsumed(false), + mMutex("InputStreamLengthWrapper::mMutex") {} + +InputStreamLengthWrapper::~InputStreamLengthWrapper() = default; + +void InputStreamLengthWrapper::SetSourceStream( + already_AddRefed<nsIInputStream> aInputStream) { + MOZ_ASSERT(!mInputStream); + + mInputStream = std::move(aInputStream); + + nsCOMPtr<nsICloneableInputStream> cloneableStream = + do_QueryInterface(mInputStream); + if (cloneableStream && SameCOMIdentity(mInputStream, cloneableStream)) { + mWeakCloneableInputStream = cloneableStream; + } + + nsCOMPtr<nsIIPCSerializableInputStream> serializableStream = + do_QueryInterface(mInputStream); + if (serializableStream && SameCOMIdentity(mInputStream, serializableStream)) { + mWeakIPCSerializableInputStream = serializableStream; + } + + nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(mInputStream); + if (seekableStream && SameCOMIdentity(mInputStream, seekableStream)) { + mWeakSeekableInputStream = seekableStream; + } + + nsCOMPtr<nsITellableStream> tellableStream = do_QueryInterface(mInputStream); + if (tellableStream && SameCOMIdentity(mInputStream, tellableStream)) { + mWeakTellableInputStream = tellableStream; + } + + nsCOMPtr<nsIAsyncInputStream> asyncInputStream = + do_QueryInterface(mInputStream); + if (asyncInputStream && SameCOMIdentity(mInputStream, asyncInputStream)) { + mWeakAsyncInputStream = asyncInputStream; + } +} + +// nsIInputStream interface + +NS_IMETHODIMP +InputStreamLengthWrapper::Close() { + NS_ENSURE_STATE(mInputStream); + return mInputStream->Close(); +} + +NS_IMETHODIMP +InputStreamLengthWrapper::Available(uint64_t* aLength) { + NS_ENSURE_STATE(mInputStream); + return mInputStream->Available(aLength); +} + +NS_IMETHODIMP +InputStreamLengthWrapper::StreamStatus() { + NS_ENSURE_STATE(mInputStream); + return mInputStream->StreamStatus(); +} + +NS_IMETHODIMP +InputStreamLengthWrapper::Read(char* aBuffer, uint32_t aCount, + uint32_t* aReadCount) { + NS_ENSURE_STATE(mInputStream); + mConsumed = true; + return mInputStream->Read(aBuffer, aCount, aReadCount); +} + +NS_IMETHODIMP +InputStreamLengthWrapper::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +InputStreamLengthWrapper::IsNonBlocking(bool* aNonBlocking) { + NS_ENSURE_STATE(mInputStream); + return mInputStream->IsNonBlocking(aNonBlocking); +} + +// nsICloneableInputStream interface + +NS_IMETHODIMP +InputStreamLengthWrapper::GetCloneable(bool* aCloneable) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakCloneableInputStream); + mWeakCloneableInputStream->GetCloneable(aCloneable); + return NS_OK; +} + +NS_IMETHODIMP +InputStreamLengthWrapper::Clone(nsIInputStream** aResult) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakCloneableInputStream); + + nsCOMPtr<nsIInputStream> clonedStream; + nsresult rv = mWeakCloneableInputStream->Clone(getter_AddRefs(clonedStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIInputStream> stream = + new InputStreamLengthWrapper(clonedStream.forget(), mLength); + + stream.forget(aResult); + return NS_OK; +} + +// nsIAsyncInputStream interface + +NS_IMETHODIMP +InputStreamLengthWrapper::CloseWithStatus(nsresult aStatus) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakAsyncInputStream); + + mConsumed = true; + return mWeakAsyncInputStream->CloseWithStatus(aStatus); +} + +NS_IMETHODIMP +InputStreamLengthWrapper::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakAsyncInputStream); + + nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr; + { + MutexAutoLock lock(mMutex); + + if (NS_WARN_IF(mAsyncWaitCallback && aCallback && + mAsyncWaitCallback != aCallback)) { + return NS_ERROR_FAILURE; + } + + mAsyncWaitCallback = aCallback; + } + + return mWeakAsyncInputStream->AsyncWait(callback, aFlags, aRequestedCount, + aEventTarget); +} + +// nsIInputStreamCallback + +NS_IMETHODIMP +InputStreamLengthWrapper::OnInputStreamReady(nsIAsyncInputStream* aStream) { + MOZ_ASSERT(mInputStream); + MOZ_ASSERT(mWeakAsyncInputStream); + MOZ_ASSERT(mWeakAsyncInputStream == aStream); + + nsCOMPtr<nsIInputStreamCallback> callback; + { + MutexAutoLock lock(mMutex); + // We have been canceled in the meanwhile. + if (!mAsyncWaitCallback) { + return NS_OK; + } + + callback.swap(mAsyncWaitCallback); + } + + MOZ_ASSERT(callback); + return callback->OnInputStreamReady(this); +} + +// nsIIPCSerializableInputStream + +void InputStreamLengthWrapper::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + InputStreamHelper::SerializedComplexity(mInputStream, aMaxSize, aSizeUsed, + aPipes, aTransferables); +} + +void InputStreamLengthWrapper::Serialize( + mozilla::ipc::InputStreamParams& aParams, uint32_t aMaxSize, + uint32_t* aSizeUsed) { + MOZ_ASSERT(mInputStream); + MOZ_ASSERT(mWeakIPCSerializableInputStream); + + InputStreamLengthWrapperParams params; + InputStreamHelper::SerializeInputStream(mInputStream, params.stream(), + aMaxSize, aSizeUsed); + params.length() = mLength; + params.consumed() = mConsumed; + + aParams = params; +} + +bool InputStreamLengthWrapper::Deserialize( + const mozilla::ipc::InputStreamParams& aParams) { + MOZ_ASSERT(!mInputStream); + MOZ_ASSERT(!mWeakIPCSerializableInputStream); + + if (aParams.type() != InputStreamParams::TInputStreamLengthWrapperParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const InputStreamLengthWrapperParams& params = + aParams.get_InputStreamLengthWrapperParams(); + + nsCOMPtr<nsIInputStream> stream = + InputStreamHelper::DeserializeInputStream(params.stream()); + if (!stream) { + NS_WARNING("Deserialize failed!"); + return false; + } + + SetSourceStream(stream.forget()); + + mLength = params.length(); + mConsumed = params.consumed(); + + return true; +} + +// nsISeekableStream + +NS_IMETHODIMP +InputStreamLengthWrapper::Seek(int32_t aWhence, int64_t aOffset) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakSeekableInputStream); + + mConsumed = true; + return mWeakSeekableInputStream->Seek(aWhence, aOffset); +} + +NS_IMETHODIMP +InputStreamLengthWrapper::SetEOF() { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakSeekableInputStream); + + mConsumed = true; + return mWeakSeekableInputStream->SetEOF(); +} + +// nsITellableStream + +NS_IMETHODIMP +InputStreamLengthWrapper::Tell(int64_t* aResult) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakTellableInputStream); + + return mWeakTellableInputStream->Tell(aResult); +} + +// nsIInputStreamLength + +NS_IMETHODIMP +InputStreamLengthWrapper::Length(int64_t* aLength) { + NS_ENSURE_STATE(mInputStream); + *aLength = mLength; + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/io/InputStreamLengthWrapper.h b/xpcom/io/InputStreamLengthWrapper.h new file mode 100644 index 0000000000..a11a808fa7 --- /dev/null +++ b/xpcom/io/InputStreamLengthWrapper.h @@ -0,0 +1,84 @@ +/* -*- 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 InputStreamLengthWrapper_h +#define InputStreamLengthWrapper_h + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsICloneableInputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsISeekableStream.h" +#include "nsIInputStreamLength.h" + +namespace mozilla { + +// A wrapper keeps an inputStream together with its length. +// This class can be used for nsIInputStreams that do not implement +// nsIInputStreamLength. + +class InputStreamLengthWrapper final : public nsIAsyncInputStream, + public nsICloneableInputStream, + public nsIIPCSerializableInputStream, + public nsISeekableStream, + public nsIInputStreamCallback, + public nsIInputStreamLength { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSIINPUTSTREAMLENGTH + + // This method creates a InputStreamLengthWrapper around aInputStream if + // this doesn't implement nsIInputStreamLength or + // nsIInputStreamAsyncLength interface, but it implements + // nsIAsyncInputStream. For this kind of streams, + // InputStreamLengthHelper is not able to retrieve the length. This + // method will make such streams ready to be used with + // InputStreamLengthHelper. + static already_AddRefed<nsIInputStream> MaybeWrap( + already_AddRefed<nsIInputStream> aInputStream, int64_t aLength); + + // The length here will be used when nsIInputStreamLength::Length() is called. + InputStreamLengthWrapper(already_AddRefed<nsIInputStream> aInputStream, + int64_t aLength); + + // This CTOR is meant to be used just for IPC. + InputStreamLengthWrapper(); + + private: + ~InputStreamLengthWrapper(); + + void SetSourceStream(already_AddRefed<nsIInputStream> aInputStream); + + nsCOMPtr<nsIInputStream> mInputStream; + + // Raw pointers because these are just QI of mInputStream. + nsICloneableInputStream* mWeakCloneableInputStream; + nsIIPCSerializableInputStream* mWeakIPCSerializableInputStream; + nsISeekableStream* mWeakSeekableInputStream; + nsITellableStream* mWeakTellableInputStream; + nsIAsyncInputStream* mWeakAsyncInputStream; + + int64_t mLength; + bool mConsumed; + + mozilla::Mutex mMutex; + + // This is used for AsyncWait and it's protected by mutex. + nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback MOZ_GUARDED_BY(mMutex); +}; + +} // namespace mozilla + +#endif // InputStreamLengthWrapper_h diff --git a/xpcom/io/NonBlockingAsyncInputStream.cpp b/xpcom/io/NonBlockingAsyncInputStream.cpp new file mode 100644 index 0000000000..00e8598d86 --- /dev/null +++ b/xpcom/io/NonBlockingAsyncInputStream.cpp @@ -0,0 +1,388 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "NonBlockingAsyncInputStream.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsIAsyncInputStream.h" +#include "nsICloneableInputStream.h" +#include "nsIInputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsISeekableStream.h" +#include "nsStreamUtils.h" + +namespace mozilla { + +using namespace ipc; + +class NonBlockingAsyncInputStream::AsyncWaitRunnable final + : public CancelableRunnable { + RefPtr<NonBlockingAsyncInputStream> mStream; + nsCOMPtr<nsIInputStreamCallback> mCallback; + + public: + AsyncWaitRunnable(NonBlockingAsyncInputStream* aStream, + nsIInputStreamCallback* aCallback) + : CancelableRunnable("AsyncWaitRunnable"), + mStream(aStream), + mCallback(aCallback) {} + + NS_IMETHOD + Run() override { + mStream->RunAsyncWaitCallback(this, mCallback.forget()); + return NS_OK; + } + + nsresult Cancel() override { + mStream = nullptr; + return NS_OK; + } +}; + +NS_IMPL_ADDREF(NonBlockingAsyncInputStream); +NS_IMPL_RELEASE(NonBlockingAsyncInputStream); + +NonBlockingAsyncInputStream::WaitClosureOnly::WaitClosureOnly( + AsyncWaitRunnable* aRunnable, nsIEventTarget* aEventTarget) + : mRunnable(aRunnable), mEventTarget(aEventTarget) {} + +NS_INTERFACE_MAP_BEGIN(NonBlockingAsyncInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, + mWeakCloneableInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream, + mWeakIPCSerializableInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, + mWeakSeekableInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream, + mWeakTellableInputStream) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +/* static */ +nsresult NonBlockingAsyncInputStream::Create( + already_AddRefed<nsIInputStream> aInputStream, + nsIAsyncInputStream** aResult) { + MOZ_DIAGNOSTIC_ASSERT(aResult); + + nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream); + + bool nonBlocking = false; + nsresult rv = inputStream->IsNonBlocking(&nonBlocking); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_DIAGNOSTIC_ASSERT(nonBlocking); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + nsCOMPtr<nsIAsyncInputStream> asyncInputStream = + do_QueryInterface(inputStream); + MOZ_DIAGNOSTIC_ASSERT(!asyncInputStream); +#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED + + RefPtr<NonBlockingAsyncInputStream> stream = + new NonBlockingAsyncInputStream(inputStream.forget()); + + stream.forget(aResult); + return NS_OK; +} + +NonBlockingAsyncInputStream::NonBlockingAsyncInputStream( + already_AddRefed<nsIInputStream> aInputStream) + : mInputStream(std::move(aInputStream)), + mWeakCloneableInputStream(nullptr), + mWeakIPCSerializableInputStream(nullptr), + mWeakSeekableInputStream(nullptr), + mWeakTellableInputStream(nullptr), + mLock("NonBlockingAsyncInputStream::mLock"), + mClosed(false) { + MOZ_ASSERT(mInputStream); + + nsCOMPtr<nsICloneableInputStream> cloneableStream = + do_QueryInterface(mInputStream); + if (cloneableStream && SameCOMIdentity(mInputStream, cloneableStream)) { + mWeakCloneableInputStream = cloneableStream; + } + + nsCOMPtr<nsIIPCSerializableInputStream> serializableStream = + do_QueryInterface(mInputStream); + if (serializableStream && SameCOMIdentity(mInputStream, serializableStream)) { + mWeakIPCSerializableInputStream = serializableStream; + } + + nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(mInputStream); + if (seekableStream && SameCOMIdentity(mInputStream, seekableStream)) { + mWeakSeekableInputStream = seekableStream; + } + + nsCOMPtr<nsITellableStream> tellableStream = do_QueryInterface(mInputStream); + if (tellableStream && SameCOMIdentity(mInputStream, tellableStream)) { + mWeakTellableInputStream = tellableStream; + } +} + +NonBlockingAsyncInputStream::~NonBlockingAsyncInputStream() = default; + +NS_IMETHODIMP +NonBlockingAsyncInputStream::Close() { + RefPtr<AsyncWaitRunnable> waitClosureOnlyRunnable; + nsCOMPtr<nsIEventTarget> waitClosureOnlyEventTarget; + + { + MutexAutoLock lock(mLock); + + if (mClosed) { + // Here we could return NS_BASE_STREAM_CLOSED as well, but just to avoid + // warning messages, let's make everybody happy with a NS_OK. + return NS_OK; + } + + mClosed = true; + + NS_ENSURE_STATE(mInputStream); + nsresult rv = mInputStream->Close(); + if (NS_WARN_IF(NS_FAILED(rv))) { + mWaitClosureOnly.reset(); + return rv; + } + + // If we have a WaitClosureOnly runnable, it's time to use it. + if (mWaitClosureOnly.isSome()) { + waitClosureOnlyRunnable = std::move(mWaitClosureOnly->mRunnable); + waitClosureOnlyEventTarget = std::move(mWaitClosureOnly->mEventTarget); + + mWaitClosureOnly.reset(); + + // Now we want to dispatch the asyncWaitCallback. + mAsyncWaitCallback = waitClosureOnlyRunnable; + } + } + + if (waitClosureOnlyRunnable) { + if (waitClosureOnlyEventTarget) { + waitClosureOnlyEventTarget->Dispatch(waitClosureOnlyRunnable, + NS_DISPATCH_NORMAL); + } else { + waitClosureOnlyRunnable->Run(); + } + } + + return NS_OK; +} + +// nsIInputStream interface + +NS_IMETHODIMP +NonBlockingAsyncInputStream::Available(uint64_t* aLength) { + nsresult rv = mInputStream->Available(aLength); + // Don't issue warnings for legal condition NS_BASE_STREAM_CLOSED. + if (rv == NS_BASE_STREAM_CLOSED || NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Nothing more to read. Let's close the stream now. + if (*aLength == 0) { + MutexAutoLock lock(mLock); + mInputStream->Close(); + mClosed = true; + return NS_BASE_STREAM_CLOSED; + } + + return NS_OK; +} + +NS_IMETHODIMP +NonBlockingAsyncInputStream::StreamStatus() { + return mInputStream->StreamStatus(); +} + +NS_IMETHODIMP +NonBlockingAsyncInputStream::Read(char* aBuffer, uint32_t aCount, + uint32_t* aReadCount) { + return mInputStream->Read(aBuffer, aCount, aReadCount); +} + +namespace { + +class MOZ_RAII ReadSegmentsData { + public: + ReadSegmentsData(NonBlockingAsyncInputStream* aStream, + nsWriteSegmentFun aFunc, void* aClosure) + : mStream(aStream), mFunc(aFunc), mClosure(aClosure) {} + + NonBlockingAsyncInputStream* mStream; + nsWriteSegmentFun mFunc; + void* mClosure; +}; + +nsresult ReadSegmentsWriter(nsIInputStream* aInStream, void* aClosure, + const char* aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount) { + ReadSegmentsData* data = static_cast<ReadSegmentsData*>(aClosure); + return data->mFunc(data->mStream, data->mClosure, aFromSegment, aToOffset, + aCount, aWriteCount); +} + +} // namespace + +NS_IMETHODIMP +NonBlockingAsyncInputStream::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* aResult) { + ReadSegmentsData data(this, aWriter, aClosure); + return mInputStream->ReadSegments(ReadSegmentsWriter, &data, aCount, aResult); +} + +NS_IMETHODIMP +NonBlockingAsyncInputStream::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = true; + return NS_OK; +} + +// nsICloneableInputStream interface + +NS_IMETHODIMP +NonBlockingAsyncInputStream::GetCloneable(bool* aCloneable) { + NS_ENSURE_STATE(mWeakCloneableInputStream); + return mWeakCloneableInputStream->GetCloneable(aCloneable); +} + +NS_IMETHODIMP +NonBlockingAsyncInputStream::Clone(nsIInputStream** aResult) { + NS_ENSURE_STATE(mWeakCloneableInputStream); + + nsCOMPtr<nsIInputStream> clonedStream; + nsresult rv = mWeakCloneableInputStream->Clone(getter_AddRefs(clonedStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIAsyncInputStream> asyncStream; + rv = Create(clonedStream.forget(), getter_AddRefs(asyncStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + asyncStream.forget(aResult); + return NS_OK; +} + +// nsIAsyncInputStream interface + +NS_IMETHODIMP +NonBlockingAsyncInputStream::CloseWithStatus(nsresult aStatus) { + return Close(); +} + +NS_IMETHODIMP +NonBlockingAsyncInputStream::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + RefPtr<AsyncWaitRunnable> runnable; + { + MutexAutoLock lock(mLock); + + mWaitClosureOnly.reset(); + mAsyncWaitCallback = nullptr; + + if (!aCallback) { + // Canceling previous callbacks, which is done above. + return NS_OK; + } + + // Maybe the stream is already closed. + if (!mClosed) { + uint64_t length; + nsresult rv = mInputStream->Available(&length); + if (NS_SUCCEEDED(rv) && length == 0) { + mInputStream->Close(); + mClosed = true; + } + } + + runnable = new AsyncWaitRunnable(this, aCallback); + if ((aFlags & nsIAsyncInputStream::WAIT_CLOSURE_ONLY) && !mClosed) { + mWaitClosureOnly.emplace(runnable, aEventTarget); + return NS_OK; + } + + mAsyncWaitCallback = runnable; + } + + MOZ_ASSERT(runnable); + + if (aEventTarget) { + return aEventTarget->Dispatch(runnable.forget()); + } + + return runnable->Run(); +} + +// nsIIPCSerializableInputStream + +void NonBlockingAsyncInputStream::SerializedComplexity( + uint32_t aMaxSize, uint32_t* aSizeUsed, uint32_t* aPipes, + uint32_t* aTransferables) { + InputStreamHelper::SerializedComplexity(mInputStream, aMaxSize, aSizeUsed, + aPipes, aTransferables); +} + +void NonBlockingAsyncInputStream::Serialize( + mozilla::ipc::InputStreamParams& aParams, uint32_t aMaxSize, + uint32_t* aSizeUsed) { + MOZ_ASSERT(mWeakIPCSerializableInputStream); + InputStreamHelper::SerializeInputStream(mInputStream, aParams, aMaxSize, + aSizeUsed); +} + +bool NonBlockingAsyncInputStream::Deserialize( + const mozilla::ipc::InputStreamParams& aParams) { + MOZ_CRASH("NonBlockingAsyncInputStream cannot be deserialized!"); + return true; +} + +// nsISeekableStream + +NS_IMETHODIMP +NonBlockingAsyncInputStream::Seek(int32_t aWhence, int64_t aOffset) { + NS_ENSURE_STATE(mWeakSeekableInputStream); + return mWeakSeekableInputStream->Seek(aWhence, aOffset); +} + +NS_IMETHODIMP +NonBlockingAsyncInputStream::SetEOF() { + NS_ENSURE_STATE(mWeakSeekableInputStream); + return NS_ERROR_NOT_IMPLEMENTED; +} + +// nsITellableStream + +NS_IMETHODIMP +NonBlockingAsyncInputStream::Tell(int64_t* aResult) { + NS_ENSURE_STATE(mWeakTellableInputStream); + return mWeakTellableInputStream->Tell(aResult); +} + +void NonBlockingAsyncInputStream::RunAsyncWaitCallback( + NonBlockingAsyncInputStream::AsyncWaitRunnable* aRunnable, + already_AddRefed<nsIInputStreamCallback> aCallback) { + nsCOMPtr<nsIInputStreamCallback> callback = std::move(aCallback); + + { + MutexAutoLock lock(mLock); + if (mAsyncWaitCallback != aRunnable) { + // The callback has been canceled in the meantime. + return; + } + + mAsyncWaitCallback = nullptr; + } + + callback->OnInputStreamReady(this); +} + +} // namespace mozilla diff --git a/xpcom/io/NonBlockingAsyncInputStream.h b/xpcom/io/NonBlockingAsyncInputStream.h new file mode 100644 index 0000000000..453ac2eafc --- /dev/null +++ b/xpcom/io/NonBlockingAsyncInputStream.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NonBlockingAsyncInputStream_h +#define NonBlockingAsyncInputStream_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsICloneableInputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsISeekableStream.h" + +// This class aims to wrap a non-blocking and non-async inputStream and expose +// it as nsIAsyncInputStream. +// Probably you don't want to use this class directly. Instead use +// NS_MakeAsyncNonBlockingInputStream() as it will handle different stream +// variants without requiring you to special-case them yourself. + +namespace mozilla { + +class NonBlockingAsyncInputStream final : public nsIAsyncInputStream, + public nsICloneableInputStream, + public nsIIPCSerializableInputStream, + public nsISeekableStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + + // |aInputStream| must be a non-blocking, non-async inputSteam. + static nsresult Create(already_AddRefed<nsIInputStream> aInputStream, + nsIAsyncInputStream** aAsyncInputStream); + + private: + explicit NonBlockingAsyncInputStream( + already_AddRefed<nsIInputStream> aInputStream); + ~NonBlockingAsyncInputStream(); + + class AsyncWaitRunnable; + + void RunAsyncWaitCallback(AsyncWaitRunnable* aRunnable, + already_AddRefed<nsIInputStreamCallback> aCallback); + + nsCOMPtr<nsIInputStream> mInputStream; + + // Raw pointers because these are just QI of mInputStream. + nsICloneableInputStream* MOZ_NON_OWNING_REF mWeakCloneableInputStream; + nsIIPCSerializableInputStream* MOZ_NON_OWNING_REF + mWeakIPCSerializableInputStream; + nsISeekableStream* MOZ_NON_OWNING_REF mWeakSeekableInputStream; + nsITellableStream* MOZ_NON_OWNING_REF mWeakTellableInputStream; + + Mutex mLock; + + struct WaitClosureOnly { + WaitClosureOnly(AsyncWaitRunnable* aRunnable, nsIEventTarget* aEventTarget); + + RefPtr<AsyncWaitRunnable> mRunnable; + nsCOMPtr<nsIEventTarget> mEventTarget; + }; + + // This is set when AsyncWait is called with a callback and with + // WAIT_CLOSURE_ONLY as flag. + // This is protected by mLock. + Maybe<WaitClosureOnly> mWaitClosureOnly MOZ_GUARDED_BY(mLock); + + // This is protected by mLock. + RefPtr<AsyncWaitRunnable> mAsyncWaitCallback MOZ_GUARDED_BY(mLock); + + // This is protected by mLock. + bool mClosed MOZ_GUARDED_BY(mLock); +}; + +} // namespace mozilla + +#endif // NonBlockingAsyncInputStream_h diff --git a/xpcom/io/SlicedInputStream.cpp b/xpcom/io/SlicedInputStream.cpp new file mode 100644 index 0000000000..c64af8e9dd --- /dev/null +++ b/xpcom/io/SlicedInputStream.cpp @@ -0,0 +1,668 @@ +/* -*- 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 "SlicedInputStream.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/ScopeExit.h" +#include "nsISeekableStream.h" +#include "nsStreamUtils.h" + +namespace mozilla { + +using namespace ipc; + +NS_IMPL_ADDREF(SlicedInputStream); +NS_IMPL_RELEASE(SlicedInputStream); + +NS_INTERFACE_MAP_BEGIN(SlicedInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, + mWeakCloneableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL( + nsIIPCSerializableInputStream, + mWeakIPCSerializableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, + mWeakSeekableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream, + mWeakTellableInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, + mWeakAsyncInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, + mWeakAsyncInputStream || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength, + mWeakInputStreamLength || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL( + nsIAsyncInputStreamLength, mWeakAsyncInputStreamLength || !mInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL( + nsIInputStreamLengthCallback, + mWeakAsyncInputStreamLength || !mInputStream) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +SlicedInputStream::SlicedInputStream( + already_AddRefed<nsIInputStream> aInputStream, uint64_t aStart, + uint64_t aLength) + : mWeakCloneableInputStream(nullptr), + mWeakIPCSerializableInputStream(nullptr), + mWeakSeekableInputStream(nullptr), + mWeakTellableInputStream(nullptr), + mWeakAsyncInputStream(nullptr), + mWeakInputStreamLength(nullptr), + mWeakAsyncInputStreamLength(nullptr), + mStart(aStart), + mLength(aLength), + mCurPos(0), + mClosed(false), + mAsyncWaitFlags(0), + mAsyncWaitRequestedCount(0), + mMutex("SlicedInputStream::mMutex") { + nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream); + SetSourceStream(inputStream.forget()); +} + +SlicedInputStream::SlicedInputStream() + : mWeakCloneableInputStream(nullptr), + mWeakIPCSerializableInputStream(nullptr), + mWeakSeekableInputStream(nullptr), + mWeakTellableInputStream(nullptr), + mWeakAsyncInputStream(nullptr), + mWeakInputStreamLength(nullptr), + mWeakAsyncInputStreamLength(nullptr), + mStart(0), + mLength(0), + mCurPos(0), + mClosed(false), + mAsyncWaitFlags(0), + mAsyncWaitRequestedCount(0), + mMutex("SlicedInputStream::mMutex") {} + +SlicedInputStream::~SlicedInputStream() = default; + +void SlicedInputStream::SetSourceStream( + already_AddRefed<nsIInputStream> aInputStream) { + MOZ_ASSERT(!mInputStream); + + mInputStream = std::move(aInputStream); + + nsCOMPtr<nsICloneableInputStream> cloneableStream = + do_QueryInterface(mInputStream); + if (cloneableStream && SameCOMIdentity(mInputStream, cloneableStream)) { + mWeakCloneableInputStream = cloneableStream; + } + + nsCOMPtr<nsIIPCSerializableInputStream> serializableStream = + do_QueryInterface(mInputStream); + if (serializableStream && SameCOMIdentity(mInputStream, serializableStream)) { + mWeakIPCSerializableInputStream = serializableStream; + } + + nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(mInputStream); + if (seekableStream && SameCOMIdentity(mInputStream, seekableStream)) { + mWeakSeekableInputStream = seekableStream; + } + + nsCOMPtr<nsITellableStream> tellableStream = do_QueryInterface(mInputStream); + if (tellableStream && SameCOMIdentity(mInputStream, tellableStream)) { + mWeakTellableInputStream = tellableStream; + } + + nsCOMPtr<nsIAsyncInputStream> asyncInputStream = + do_QueryInterface(mInputStream); + if (asyncInputStream && SameCOMIdentity(mInputStream, asyncInputStream)) { + mWeakAsyncInputStream = asyncInputStream; + } + + nsCOMPtr<nsIInputStreamLength> streamLength = do_QueryInterface(mInputStream); + if (streamLength && SameCOMIdentity(mInputStream, streamLength)) { + mWeakInputStreamLength = streamLength; + } + + nsCOMPtr<nsIAsyncInputStreamLength> asyncStreamLength = + do_QueryInterface(mInputStream); + if (asyncStreamLength && SameCOMIdentity(mInputStream, asyncStreamLength)) { + mWeakAsyncInputStreamLength = asyncStreamLength; + } +} + +uint64_t SlicedInputStream::AdjustRange(uint64_t aRange) { + CheckedUint64 range(aRange); + range += mCurPos; + + // Let's remove extra length from the end. + if (range.isValid() && range.value() > mStart + mLength) { + aRange -= XPCOM_MIN((uint64_t)aRange, range.value() - (mStart + mLength)); + } + + // Let's remove extra length from the begin. + if (mCurPos < mStart) { + aRange -= XPCOM_MIN((uint64_t)aRange, mStart - mCurPos); + } + + return aRange; +} + +// nsIInputStream interface + +NS_IMETHODIMP +SlicedInputStream::Close() { + NS_ENSURE_STATE(mInputStream); + + mClosed = true; + return mInputStream->Close(); +} + +NS_IMETHODIMP +SlicedInputStream::Available(uint64_t* aLength) { + NS_ENSURE_STATE(mInputStream); + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv = mInputStream->Available(aLength); + if (rv == NS_BASE_STREAM_CLOSED) { + mClosed = true; + return rv; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + *aLength = AdjustRange(*aLength); + return NS_OK; +} + +NS_IMETHODIMP +SlicedInputStream::StreamStatus() { + NS_ENSURE_STATE(mInputStream); + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv = mInputStream->StreamStatus(); + if (rv == NS_BASE_STREAM_CLOSED) { + mClosed = true; + } + return rv; +} + +NS_IMETHODIMP +SlicedInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) { + *aReadCount = 0; + + if (mClosed) { + return NS_OK; + } + + 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_SUCCEEDED(rv) && bytesRead == 0) { + mClosed = true; + return rv; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurPos += bytesRead; + } + } + } + + // Let's reduce aCount in case it's too big. + if (mCurPos + aCount > mStart + mLength) { + aCount = mStart + mLength - mCurPos; + } + + // Nothing else to read. + if (!aCount) { + return NS_OK; + } + + nsresult rv = mInputStream->Read(aBuffer, aCount, aReadCount); + if (NS_SUCCEEDED(rv) && *aReadCount == 0) { + mClosed = true; + return rv; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurPos += *aReadCount; + return NS_OK; +} + +NS_IMETHODIMP +SlicedInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +SlicedInputStream::IsNonBlocking(bool* aNonBlocking) { + NS_ENSURE_STATE(mInputStream); + return mInputStream->IsNonBlocking(aNonBlocking); +} + +// nsICloneableInputStream interface + +NS_IMETHODIMP +SlicedInputStream::GetCloneable(bool* aCloneable) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakCloneableInputStream); + + *aCloneable = true; + return NS_OK; +} + +NS_IMETHODIMP +SlicedInputStream::Clone(nsIInputStream** aResult) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakCloneableInputStream); + + nsCOMPtr<nsIInputStream> clonedStream; + nsresult rv = mWeakCloneableInputStream->Clone(getter_AddRefs(clonedStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIInputStream> sis = + new SlicedInputStream(clonedStream.forget(), mStart, mLength); + + sis.forget(aResult); + return NS_OK; +} + +// nsIAsyncInputStream interface + +NS_IMETHODIMP +SlicedInputStream::CloseWithStatus(nsresult aStatus) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakAsyncInputStream); + + mClosed = true; + return mWeakAsyncInputStream->CloseWithStatus(aStatus); +} + +NS_IMETHODIMP +SlicedInputStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakAsyncInputStream); + + nsCOMPtr<nsIInputStreamCallback> callback = aCallback ? this : nullptr; + + uint32_t flags = aFlags; + uint32_t requestedCount = aRequestedCount; + + { + MutexAutoLock lock(mMutex); + + if (NS_WARN_IF(mAsyncWaitCallback && aCallback && + mAsyncWaitCallback != aCallback)) { + return NS_ERROR_FAILURE; + } + + mAsyncWaitCallback = aCallback; + + // If we haven't started retrieving data, let's see if we can seek. + // If we cannot seek, we will do consecutive reads. + if (mCurPos < mStart && mWeakSeekableInputStream) { + nsresult rv = mWeakSeekableInputStream->Seek( + nsISeekableStream::NS_SEEK_SET, mStart); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurPos = mStart; + } + + mAsyncWaitFlags = aFlags; + mAsyncWaitRequestedCount = aRequestedCount; + mAsyncWaitEventTarget = aEventTarget; + + // If we are not at the right position, let's do an asyncWait just internal. + if (mCurPos < mStart) { + flags = 0; + requestedCount = mStart - mCurPos; + } + } + + return mWeakAsyncInputStream->AsyncWait(callback, flags, requestedCount, + aEventTarget); +} + +// nsIInputStreamCallback + +NS_IMETHODIMP +SlicedInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) { + MOZ_ASSERT(mInputStream); + MOZ_ASSERT(mWeakAsyncInputStream); + MOZ_ASSERT(mWeakAsyncInputStream == aStream); + + nsCOMPtr<nsIInputStreamCallback> callback; + uint32_t asyncWaitFlags = 0; + uint32_t asyncWaitRequestedCount = 0; + nsCOMPtr<nsIEventTarget> asyncWaitEventTarget; + + { + MutexAutoLock lock(mMutex); + + // We have been canceled in the meanwhile. + if (!mAsyncWaitCallback) { + return NS_OK; + } + + auto raii = MakeScopeExit([&] { + mMutex.AssertCurrentThreadOwns(); + mAsyncWaitCallback = nullptr; + mAsyncWaitEventTarget = nullptr; + }); + + asyncWaitFlags = mAsyncWaitFlags; + asyncWaitRequestedCount = mAsyncWaitRequestedCount; + asyncWaitEventTarget = mAsyncWaitEventTarget; + + // If at the end of this locked block, the callback is not null, it will be + // executed, otherwise, we are going to exec another AsyncWait(). + callback = mAsyncWaitCallback; + + if (mCurPos < mStart) { + char buf[4096]; + nsresult rv = NS_OK; + while (mCurPos < mStart) { + uint32_t bytesRead; + uint64_t bufCount = XPCOM_MIN(mStart - mCurPos, (uint64_t)sizeof(buf)); + rv = mInputStream->Read(buf, bufCount, &bytesRead); + if (NS_SUCCEEDED(rv) && bytesRead == 0) { + mClosed = true; + break; + } + + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + asyncWaitFlags = 0; + asyncWaitRequestedCount = mStart - mCurPos; + // Here we want to exec another AsyncWait(). + callback = nullptr; + break; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + mCurPos += bytesRead; + } + + // Now we are ready to do the 'real' asyncWait. + if (mCurPos >= mStart) { + // We don't want to nullify the callback now, because it will be needed + // at the next ::OnInputStreamReady. + raii.release(); + callback = nullptr; + } + } + } + + if (callback) { + return callback->OnInputStreamReady(this); + } + + return mWeakAsyncInputStream->AsyncWait( + this, asyncWaitFlags, asyncWaitRequestedCount, asyncWaitEventTarget); +} + +// nsIIPCSerializableInputStream + +void SlicedInputStream::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + InputStreamHelper::SerializedComplexity(mInputStream, aMaxSize, aSizeUsed, + aPipes, aTransferables); + + // If we're going to be serializing a pipe to transfer the sliced data, and we + // are getting no efficiency improvements from transferables, stream this + // sliced input stream directly as a pipe to avoid streaming data which will + // be sliced off anyway. + if (*aPipes > 0 && *aTransferables == 0) { + *aSizeUsed = 0; + *aPipes = 1; + *aTransferables = 0; + } +} + +void SlicedInputStream::Serialize(mozilla::ipc::InputStreamParams& aParams, + uint32_t aMaxSize, uint32_t* aSizeUsed) { + MOZ_ASSERT(mInputStream); + MOZ_ASSERT(mWeakIPCSerializableInputStream); + + uint32_t sizeUsed = 0, pipes = 0, transferables = 0; + SerializedComplexity(aMaxSize, &sizeUsed, &pipes, &transferables); + if (pipes > 0 && transferables == 0) { + InputStreamHelper::SerializeInputStreamAsPipe(this, aParams); + return; + } + + SlicedInputStreamParams params; + InputStreamHelper::SerializeInputStream(mInputStream, params.stream(), + aMaxSize, aSizeUsed); + params.start() = mStart; + params.length() = mLength; + params.curPos() = mCurPos; + params.closed() = mClosed; + + aParams = params; +} + +bool SlicedInputStream::Deserialize( + const mozilla::ipc::InputStreamParams& aParams) { + MOZ_ASSERT(!mInputStream); + MOZ_ASSERT(!mWeakIPCSerializableInputStream); + + if (aParams.type() != InputStreamParams::TSlicedInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const SlicedInputStreamParams& params = aParams.get_SlicedInputStreamParams(); + + nsCOMPtr<nsIInputStream> stream = + InputStreamHelper::DeserializeInputStream(params.stream()); + if (!stream) { + NS_WARNING("Deserialize failed!"); + return false; + } + + SetSourceStream(stream.forget()); + + mStart = params.start(); + mLength = params.length(); + mCurPos = params.curPos(); + mClosed = params.closed(); + + return true; +} + +// nsISeekableStream + +NS_IMETHODIMP +SlicedInputStream::Seek(int32_t aWhence, int64_t aOffset) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakSeekableInputStream); + + int64_t offset; + nsresult rv; + + switch (aWhence) { + case NS_SEEK_SET: + offset = mStart + aOffset; + break; + case NS_SEEK_CUR: + // mCurPos could be lower than mStart if the reading has not started yet. + offset = XPCOM_MAX(mStart, mCurPos) + aOffset; + break; + case NS_SEEK_END: { + uint64_t available; + rv = mInputStream->Available(&available); + if (rv == NS_BASE_STREAM_CLOSED) { + mClosed = true; + return rv; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + offset = XPCOM_MIN(mStart + mLength, available) + aOffset; + break; + } + default: + return NS_ERROR_ILLEGAL_VALUE; + } + + if (offset < (int64_t)mStart || offset > (int64_t)(mStart + mLength)) { + return NS_ERROR_INVALID_ARG; + } + + rv = mWeakSeekableInputStream->Seek(NS_SEEK_SET, offset); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mCurPos = offset; + return NS_OK; +} + +NS_IMETHODIMP +SlicedInputStream::SetEOF() { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakSeekableInputStream); + + mClosed = true; + return mWeakSeekableInputStream->SetEOF(); +} + +// nsITellableStream + +NS_IMETHODIMP +SlicedInputStream::Tell(int64_t* aResult) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakTellableInputStream); + + int64_t tell = 0; + + nsresult rv = mWeakTellableInputStream->Tell(&tell); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (tell < (int64_t)mStart) { + *aResult = 0; + return NS_OK; + } + + *aResult = tell - mStart; + if (*aResult > (int64_t)mLength) { + *aResult = mLength; + } + + return NS_OK; +} + +// nsIInputStreamLength + +NS_IMETHODIMP +SlicedInputStream::Length(int64_t* aLength) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakInputStreamLength); + + nsresult rv = mWeakInputStreamLength->Length(aLength); + if (rv == NS_BASE_STREAM_CLOSED) { + mClosed = true; + return rv; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (*aLength == -1) { + return NS_OK; + } + + *aLength = (int64_t)AdjustRange((uint64_t)*aLength); + return NS_OK; +} + +// nsIAsyncInputStreamLength + +NS_IMETHODIMP +SlicedInputStream::AsyncLengthWait(nsIInputStreamLengthCallback* aCallback, + nsIEventTarget* aEventTarget) { + NS_ENSURE_STATE(mInputStream); + NS_ENSURE_STATE(mWeakAsyncInputStreamLength); + + nsCOMPtr<nsIInputStreamLengthCallback> callback = aCallback ? this : nullptr; + { + MutexAutoLock lock(mMutex); + mAsyncWaitLengthCallback = aCallback; + } + + return mWeakAsyncInputStreamLength->AsyncLengthWait(callback, aEventTarget); +} + +// nsIInputStreamLengthCallback + +NS_IMETHODIMP +SlicedInputStream::OnInputStreamLengthReady(nsIAsyncInputStreamLength* aStream, + int64_t aLength) { + MOZ_ASSERT(mInputStream); + MOZ_ASSERT(mWeakAsyncInputStreamLength); + MOZ_ASSERT(mWeakAsyncInputStreamLength == aStream); + + nsCOMPtr<nsIInputStreamLengthCallback> callback; + { + MutexAutoLock lock(mMutex); + + // We have been canceled in the meanwhile. + if (!mAsyncWaitLengthCallback) { + return NS_OK; + } + + callback.swap(mAsyncWaitLengthCallback); + } + + if (aLength != -1) { + aLength = (int64_t)AdjustRange((uint64_t)aLength); + } + + MOZ_ASSERT(callback); + return callback->OnInputStreamLengthReady(this, aLength); +} + +} // namespace mozilla diff --git a/xpcom/io/SlicedInputStream.h b/xpcom/io/SlicedInputStream.h new file mode 100644 index 0000000000..30b6c118ab --- /dev/null +++ b/xpcom/io/SlicedInputStream.h @@ -0,0 +1,102 @@ +/* -*- 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 SlicedInputStream_h +#define SlicedInputStream_h + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsICloneableInputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsISeekableStream.h" +#include "nsIInputStreamLength.h" + +namespace mozilla { + +// A wrapper for a slice of an underlying input stream. + +class SlicedInputStream final : public nsIAsyncInputStream, + public nsICloneableInputStream, + public nsIIPCSerializableInputStream, + public nsISeekableStream, + public nsIInputStreamCallback, + public nsIInputStreamLength, + public nsIAsyncInputStreamLength, + public nsIInputStreamLengthCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSIINPUTSTREAMLENGTH + NS_DECL_NSIASYNCINPUTSTREAMLENGTH + NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK + + // 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(already_AddRefed<nsIInputStream> aInputStream, + uint64_t aStart, uint64_t aLength); + + // This CTOR is meant to be used just for IPC. + SlicedInputStream(); + + private: + ~SlicedInputStream(); + + void SetSourceStream(already_AddRefed<nsIInputStream> aInputStream); + + uint64_t AdjustRange(uint64_t aRange); + + nsCOMPtr<nsIInputStream> mInputStream; + + // Raw pointers because these are just QI of mInputStream. + nsICloneableInputStream* mWeakCloneableInputStream; + nsIIPCSerializableInputStream* mWeakIPCSerializableInputStream; + nsISeekableStream* mWeakSeekableInputStream; + nsITellableStream* mWeakTellableInputStream; + nsIAsyncInputStream* mWeakAsyncInputStream; + nsIInputStreamLength* mWeakInputStreamLength; + nsIAsyncInputStreamLength* mWeakAsyncInputStreamLength; + + uint64_t mStart; + uint64_t mLength; + uint64_t mCurPos; + + bool mClosed; + + // These four are used for AsyncWait. They are protected by mutex because + // touched on multiple threads. + nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback MOZ_GUARDED_BY(mMutex); + nsCOMPtr<nsIEventTarget> mAsyncWaitEventTarget MOZ_GUARDED_BY(mMutex); + uint32_t mAsyncWaitFlags MOZ_GUARDED_BY(mMutex); + uint32_t mAsyncWaitRequestedCount MOZ_GUARDED_BY(mMutex); + + // This is use for nsIAsyncInputStreamLength::AsyncWait. + // This is protected by mutex. + nsCOMPtr<nsIInputStreamLengthCallback> mAsyncWaitLengthCallback + MOZ_GUARDED_BY(mMutex); + + Mutex mMutex; +}; + +} // namespace mozilla + +#endif // SlicedInputStream_h diff --git a/xpcom/io/SnappyCompressOutputStream.cpp b/xpcom/io/SnappyCompressOutputStream.cpp new file mode 100644 index 0000000000..ebe9e1073c --- /dev/null +++ b/xpcom/io/SnappyCompressOutputStream.cpp @@ -0,0 +1,259 @@ +/* -*- 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::StreamStatus() { + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + return mBaseStream->StreamStatus(); +} + +NS_IMETHODIMP +SnappyCompressOutputStream::Write(const char* aBuf, uint32_t aCount, + uint32_t* aResultOut) { + return WriteSegments(NS_CopyBufferToSegment, 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..895691034b --- /dev/null +++ b/xpcom/io/SnappyCompressOutputStream.h @@ -0,0 +1,68 @@ +/* -*- 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..c606794fd9 --- /dev/null +++ b/xpcom/io/SnappyFrameUtils.cpp @@ -0,0 +1,241 @@ +/* -*- 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::NativeEndian; +using mozilla::detail::SnappyFrameUtils; + +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..923994c661 --- /dev/null +++ b/xpcom/io/SnappyFrameUtils.h @@ -0,0 +1,80 @@ +/* -*- 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 <cstddef> + +#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() = default; + virtual ~SnappyFrameUtils() = default; +}; + +} // 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..2872c8c7a2 --- /dev/null +++ b/xpcom/io/SnappyUncompressInputStream.cpp @@ -0,0 +1,386 @@ +/* -*- 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::StreamStatus() { + if (!mBaseStream) { + return NS_BASE_STREAM_CLOSED; + } + + // If we have uncompressed bytes, then we're still open. + if (UncompressedLength() > 0) { + return NS_OK; + } + + // Otherwise we'll need to read from the underlying stream, so check it + return mBaseStream->StreamStatus(); +} + +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..c61ec321a9 --- /dev/null +++ b/xpcom/io/SnappyUncompressInputStream.h @@ -0,0 +1,89 @@ +/* -*- 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..fcb366aede --- /dev/null +++ b/xpcom/io/SpecialSystemDirectory.cpp @@ -0,0 +1,748 @@ +/* -*- 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 "nsIXULAppInfo.h" + +#if defined(XP_WIN) + +# include <windows.h> +# include <stdlib.h> +# include <stdio.h> +# include <string.h> +# include <direct.h> +# include <shlobj.h> +# include <knownfolders.h> +# include <guiddef.h> + +#elif defined(XP_UNIX) + +# include <limits.h> +# include <unistd.h> +# include <stdlib.h> +# include <sys/param.h> +# include "prenv.h" +# if defined(MOZ_WIDGET_COCOA) +# include "CFTypeRefPtr.h" +# include "CocoaFileUtils.h" +# endif +# if defined(MOZ_WIDGET_GTK) +# include "mozilla/WidgetUtilsGtk.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 + +#if defined(XP_WIN) + +static nsresult GetKnownFolder(GUID* aGuid, nsIFile** aFile) { + if (!aGuid) { + return NS_ERROR_FAILURE; + } + + PWSTR path = nullptr; + SHGetKnownFolderPath(*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; + BOOL result = SHGetSpecialFolderPathW(nullptr, path, aFolder, true); + + if (!result) { + return NS_ERROR_FAILURE; + } + + // Append the trailing slash + int len = wcslen(path); + if (len == 0) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + if (len > 1 && path[len - 1] != L'\\') { + path[len] = L'\\'; + path[++len] = L'\0'; + } + + return NS_NewLocalFile(nsDependentString(path, len), true, aFile); +} + +# if WINVER < 0x0601 +__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; +} +# endif + +# if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) +/* + * Return the default save-to location for the Windows Library passed in + * through aFolderId. + */ +static nsresult GetLibrarySaveToPath(int aFallbackFolderId, + REFKNOWNFOLDERID aFolderId, + nsIFile** aFile) { + RefPtr<IShellLibrary> shellLib; + RefPtr<IShellItem> savePath; + 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); +} +# endif + +/** + * 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; + LPCWSTR keyName = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"; + DWORD res = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName, 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 +} + +static nsresult GetUnixSystemConfigDir(nsIFile** aFile) { +# if defined(ANDROID) + return NS_ERROR_FAILURE; +# else + nsAutoCString appName; + if (nsCOMPtr<nsIXULAppInfo> appInfo = + do_GetService("@mozilla.org/xre/app-info;1")) { + MOZ_TRY(appInfo->GetName(appName)); + } else { + appName.AssignLiteral(MOZ_APP_BASENAME); + } + + ToLowerCase(appName); + + nsDependentCString sysConfigDir; + if (PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) { + const char* mozSystemConfigDir = PR_GetEnv("MOZ_SYSTEM_CONFIG_DIR"); + if (mozSystemConfigDir) { + sysConfigDir.Assign(nsDependentCString(mozSystemConfigDir)); + } + } +# if defined(MOZ_WIDGET_GTK) + if (sysConfigDir.IsEmpty() && mozilla::widget::IsRunningUnderFlatpak()) { + sysConfigDir.Assign(nsLiteralCString("/app/etc")); + } +# endif + if (sysConfigDir.IsEmpty()) { + sysConfigDir.Assign(nsLiteralCString("/etc")); + } + MOZ_TRY(NS_NewNativeLocalFile(sysConfigDir, true, aFile)); + MOZ_TRY((*aFile)->AppendNative(appName)); + return NS_OK; +# 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; + bool exists; + if (dir) { + rv = NS_NewNativeLocalFile(nsDependentCString(dir), true, + getter_AddRefs(file)); + free(dir); + + if (NS_FAILED(rv)) { + return rv; + } + + 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; + } + } + } else if (Unix_XDG_Desktop == aSystemDirectory) { + // for the XDG desktop dir, fall back to HOME/Desktop + // (for historical compatibility) + nsCOMPtr<nsIFile> home; + rv = GetUnixHomeDir(getter_AddRefs(home)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = home->Clone(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = file->AppendNative("Desktop"_ns); + if (NS_FAILED(rv)) { + return rv; + } + + rv = file->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + + // fallback to HOME only if HOME/Desktop doesn't exist + if (!exists) { + file = home; + } + } else { + // no fallback for the other XDG dirs + return NS_ERROR_FAILURE; + } + + *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_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(MOZ_WIDGET_COCOA) + case Mac_SystemDirectory: { + return GetOSXFolderType(kClassicDomain, kSystemFolderType, aFile); + } + case Mac_UserLibDirectory: { + return GetOSXFolderType(kUserDomain, kDomainLibraryFolderType, aFile); + } + case Mac_HomeDirectory: { + return GetOSXFolderType(kUserDomain, kDomainTopLevelFolderType, aFile); + } + case Mac_DefaultDownloadDirectory: { + nsresult rv = GetOSXFolderType(kUserDomain, kDownloadsFolderType, aFile); + if (NS_FAILED(rv)) { + return GetOSXFolderType(kUserDomain, kDesktopFolderType, aFile); + } + return NS_OK; + } + case Mac_UserDesktopDirectory: { + return GetOSXFolderType(kUserDomain, kDesktopFolderType, aFile); + } + case Mac_LocalApplicationsDirectory: { + return GetOSXFolderType(kLocalDomain, kApplicationsFolderType, aFile); + } + case Mac_UserPreferencesDirectory: { + return GetOSXFolderType(kUserDomain, kPreferencesFolderType, aFile); + } + case Mac_PictureDocumentsDirectory: { + return GetOSXFolderType(kUserDomain, kPictureDocumentsFolderType, aFile); + } + case Mac_DefaultScreenshotDirectory: { + auto prefValue = CFTypeRefPtr<CFPropertyListRef>::WrapUnderCreateRule( + CFPreferencesCopyAppValue(CFSTR("location"), + CFSTR("com.apple.screencapture"))); + + if (!prefValue || CFGetTypeID(prefValue.get()) != CFStringGetTypeID()) { + return GetOSXFolderType(kUserDomain, kPictureDocumentsFolderType, + aFile); + } + + nsAutoString path; + mozilla::Span<char16_t> data = + path.GetMutableData(CFStringGetLength((CFStringRef)prefValue.get())); + CFStringGetCharacters((CFStringRef)prefValue.get(), + CFRangeMake(0, data.Length()), + reinterpret_cast<UniChar*>(data.Elements())); + + return NS_NewLocalFile(path, true, aFile); + } +#elif 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); + } + break; + } + 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_Favorites: { + return GetWindowsFolder(CSIDL_FAVORITES, aFile); + } + case Win_Desktopdirectory: { + return GetWindowsFolder(CSIDL_DESKTOPDIRECTORY, 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_THUNDERBIRD) || defined(MOZ_SUITE) + case Win_Documents: { + return GetLibrarySaveToPath(CSIDL_MYDOCUMENTS, FOLDERID_DocumentsLibrary, + aFile); + } +# endif +#endif // XP_WIN + +#if defined(XP_UNIX) + case Unix_HomeDirectory: + return GetUnixHomeDir(aFile); + + case Unix_XDG_Desktop: + case Unix_XDG_Download: + return GetUnixXDGUserDirectory(aSystemSystemDirectory, aFile); + + case Unix_SystemConfigDirectory: + return GetUnixSystemConfigDir(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(u""_ns, 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(u""_ns, 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..e760b0ae26 --- /dev/null +++ b/xpcom/io/SpecialSystemDirectory.h @@ -0,0 +1,62 @@ +/* -*- 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 "nsILocalFileMac.h" +# include "prenv.h" +#endif + +enum SystemDirectories { + OS_TemporaryDirectory = 2, + // 3 Used to be OS_CurrentProcessDirectory, which we never actually + // supported getting... + OS_CurrentWorkingDirectory = 4, + + Mac_SystemDirectory = 101, + Mac_UserLibDirectory = 102, + Mac_HomeDirectory = 103, + Mac_DefaultDownloadDirectory = 104, + Mac_UserDesktopDirectory = 105, + Mac_LocalApplicationsDirectory = 106, + Mac_UserPreferencesDirectory = 107, + Mac_PictureDocumentsDirectory = 108, + Mac_DefaultScreenshotDirectory = 109, + + Win_SystemDirectory = 201, + Win_WindowsDirectory = 202, + Win_HomeDirectory = 203, + Win_Programs = 205, + Win_Favorites = 209, + Win_Desktopdirectory = 213, + Win_Appdata = 221, + Win_Cookies = 223, + Win_LocalAppdata = 224, + Win_ProgramFiles = 225, + Win_Downloads = 226, +#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) + Win_Documents = 228, +#endif + + Unix_HomeDirectory = 303, + Unix_XDG_Desktop = 304, + Unix_XDG_Download = 306, + Unix_SystemConfigDirectory = 307 +}; + +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/StreamBufferSink.h b/xpcom/io/StreamBufferSink.h new file mode 100644 index 0000000000..bf00527aea --- /dev/null +++ b/xpcom/io/StreamBufferSink.h @@ -0,0 +1,29 @@ +/* -*- 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_StreamBufferSink_h +#define mozilla_StreamBufferSink_h + +#include <cstddef> +#include "mozilla/Span.h" +#include "nsString.h" + +namespace mozilla { + +class StreamBufferSink { + public: + virtual mozilla::Span<char> Data() = 0; + + nsDependentCSubstring Slice(size_t aOffset) { + return nsDependentCSubstring(Data().First(aOffset)); + } + + virtual ~StreamBufferSink() = default; +}; + +} // namespace mozilla + +#endif // mozilla_StreamBufferSink_h diff --git a/xpcom/io/StreamBufferSinkImpl.h b/xpcom/io/StreamBufferSinkImpl.h new file mode 100644 index 0000000000..9fba413ef2 --- /dev/null +++ b/xpcom/io/StreamBufferSinkImpl.h @@ -0,0 +1,49 @@ +/* -*- 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_StreamBufferSinkImpl_h +#define mozilla_StreamBufferSinkImpl_h + +#include "mozilla/Buffer.h" +#include "mozilla/StreamBufferSink.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +class BufferSink final : public StreamBufferSink { + public: + explicit BufferSink(Buffer<char>&& aBuffer) : mBuffer(std::move(aBuffer)) {} + + explicit BufferSink(size_t aLength) : mBuffer(aLength) {} + + static UniquePtr<BufferSink> Alloc(size_t aLength) { + auto maybeBuffer = Buffer<char>::Alloc(aLength); + if (!maybeBuffer) { + return nullptr; + } + + return MakeUnique<BufferSink>(maybeBuffer.extract()); + } + + mozilla::Span<char> Data() override { return mBuffer.AsWritableSpan(); } + + private: + Buffer<char> mBuffer; +}; + +class nsBorrowedSink final : public StreamBufferSink { + public: + explicit nsBorrowedSink(mozilla::Span<char> aBuffer) : mBuffer(aBuffer) {} + + mozilla::Span<char> Data() override { return mBuffer; } + + private: + mozilla::Span<char> mBuffer; +}; + +} // namespace mozilla + +#endif // mozilla_StreamBufferSinkImpl_h diff --git a/xpcom/io/StreamBufferSource.h b/xpcom/io/StreamBufferSource.h new file mode 100644 index 0000000000..f0a3d30eea --- /dev/null +++ b/xpcom/io/StreamBufferSource.h @@ -0,0 +1,61 @@ +/* -*- 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_StreamBufferSource_h +#define mozilla_StreamBufferSource_h + +#include <cstddef> +#include "ErrorList.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Span.h" +#include "nsISupportsImpl.h" +#include "nsString.h" + +namespace mozilla { + +class StreamBufferSource { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StreamBufferSource) + + virtual Span<const char> Data() = 0; + + virtual nsresult GetData(nsACString& aString) { + Span<const char> data = Data(); + if (!aString.Assign(data.Elements(), data.Length(), fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; + } + + virtual bool Owning() = 0; + + virtual size_t SizeOfExcludingThisIfUnshared(MallocSizeOf aMallocSizeOf) { + return SizeOfExcludingThisEvenIfShared(aMallocSizeOf); + } + size_t SizeOfIncludingThisIfUnshared(MallocSizeOf aMallocSizeOf) { + if (mRefCnt > 1) { + return 0; + } + size_t n = aMallocSizeOf(this); + n += SizeOfExcludingThisIfUnshared(aMallocSizeOf); + return n; + } + + virtual size_t SizeOfExcludingThisEvenIfShared( + MallocSizeOf aMallocSizeOf) = 0; + size_t SizeOfIncludingThisEvenIfShared(MallocSizeOf aMallocSizeOf) { + size_t n = aMallocSizeOf(this); + n += SizeOfExcludingThisEvenIfShared(aMallocSizeOf); + return n; + } + + protected: + virtual ~StreamBufferSource() = default; +}; + +} // namespace mozilla + +#endif // mozilla_StreamBufferSource_h diff --git a/xpcom/io/StreamBufferSourceImpl.h b/xpcom/io/StreamBufferSourceImpl.h new file mode 100644 index 0000000000..0d04adcc24 --- /dev/null +++ b/xpcom/io/StreamBufferSourceImpl.h @@ -0,0 +1,82 @@ +/* -*- 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_StreamBufferSourceImpl_h +#define mozilla_StreamBufferSourceImpl_h + +#include "mozilla/StreamBufferSource.h" + +#include "nsTArray.h" + +namespace mozilla { + +class nsTArraySource final : public StreamBufferSource { + public: + explicit nsTArraySource(nsTArray<uint8_t>&& aArray) + : mArray(std::move(aArray)) {} + + Span<const char> Data() override { + return Span{reinterpret_cast<const char*>(mArray.Elements()), + mArray.Length()}; + } + + bool Owning() override { return true; } + + size_t SizeOfExcludingThisEvenIfShared(MallocSizeOf aMallocSizeOf) override { + return mArray.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + private: + const nsTArray<uint8_t> mArray; +}; + +class nsCStringSource final : public StreamBufferSource { + public: + explicit nsCStringSource(nsACString&& aString) + : mString(std::move(aString)) {} + + Span<const char> Data() override { return mString; } + + nsresult GetData(nsACString& aString) override { + if (!aString.Assign(mString, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; + } + + bool Owning() override { return true; } + + size_t SizeOfExcludingThisIfUnshared(MallocSizeOf aMallocSizeOf) override { + return mString.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + size_t SizeOfExcludingThisEvenIfShared(MallocSizeOf aMallocSizeOf) override { + return mString.SizeOfExcludingThisEvenIfShared(aMallocSizeOf); + } + + private: + const nsCString mString; +}; + +class nsBorrowedSource final : public StreamBufferSource { + public: + explicit nsBorrowedSource(Span<const char> aBuffer) : mBuffer(aBuffer) {} + + Span<const char> Data() override { return mBuffer; } + + bool Owning() override { return false; } + + size_t SizeOfExcludingThisEvenIfShared(MallocSizeOf aMallocSizeOf) override { + return 0; + } + + private: + const Span<const char> mBuffer; +}; + +} // namespace mozilla + +#endif // mozilla_StreamBufferSourceImpl_h diff --git a/xpcom/io/components.conf b/xpcom/io/components.conf new file mode 100644 index 0000000000..0c4f44b712 --- /dev/null +++ b/xpcom/io/components.conf @@ -0,0 +1,42 @@ +# -*- 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/. + +Classes = [ + { + 'name': 'Directory', + 'js_name': 'dirsvc', + 'cid': '{f00152d0-b40b-11d3-8c9c-000064657374}', + 'contract_ids': ['@mozilla.org/file/directory_service;1'], + 'interfaces': ['nsIDirectoryService', 'nsIProperties'], + 'legacy_constructor': 'nsDirectoryService::Create', + 'headers': ['nsDirectoryService.h'], + }, + { + 'cid': '{565e3a2c-1dd2-11b2-8da1-b4cef17e568d}', + 'contract_ids': ['@mozilla.org/io/multiplex-input-stream;1'], + 'legacy_constructor': 'nsMultiplexInputStreamConstructor', + 'headers': ['nsMultiplexInputStream.h'], + }, + { + 'cid': '{e4a0ee4e-0775-457b-9118-b3ae97a7c758}', + 'contract_ids': ['@mozilla.org/pipe;1'], + 'legacy_constructor': 'nsPipeConstructor', + 'headers': ['/xpcom/io/nsPipe.h'], + }, + { + 'cid': '{7225c040-a9bf-11d3-a197-0050041caf44}', + 'contract_ids': ['@mozilla.org/scriptableinputstream;1'], + 'legacy_constructor': 'nsScriptableInputStream::Create', + 'headers': ['nsScriptableInputStream.h'], + }, + { + 'cid': '{0abb0835-5000-4790-af28-61b3ba17c295}', + 'contract_ids': ['@mozilla.org/io/string-input-stream;1'], + 'legacy_constructor': 'nsStringInputStreamConstructor', + 'headers': ['/xpcom/build/XPCOMModule.h'], + 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS, + }, +] 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..2830c28659 --- /dev/null +++ b/xpcom/io/crc32c.h @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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..97fe1663ab --- /dev/null +++ b/xpcom/io/moz.build @@ -0,0 +1,162 @@ +# -*- 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", + "nsIInputStreamLength.idl", + "nsIInputStreamPriority.idl", + "nsIInputStreamTee.idl", + "nsIIOUtil.idl", + "nsILineInputStream.idl", + "nsILocalFileWin.idl", + "nsIMultiplexInputStream.idl", + "nsIObjectInputStream.idl", + "nsIObjectOutputStream.idl", + "nsIOutputStream.idl", + "nsIPipe.idl", + "nsIRandomAccessStream.idl", + "nsISafeOutputStream.idl", + "nsIScriptableBase64Encoder.idl", + "nsIScriptableInputStream.idl", + "nsISeekableStream.idl", + "nsIStorageStream.idl", + "nsIStreamBufferAccess.idl", + "nsIStringStream.idl", + "nsITellableStream.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" + +XPCOM_MANIFESTS += [ + "components.conf", +] + +EXPORTS += [ + "FileDescriptorFile.h", + "nsAnonymousTemporaryFile.h", + "nsAppDirectoryServiceDefs.h", + "nsDirectoryService.h", + "nsDirectoryServiceDefs.h", + "nsDirectoryServiceUtils.h", + "nsEscape.h", + "nsLinebreakConverter.h", + "nsLocalFile.h", + "nsLocalFileCommon.h", + "nsMultiplexInputStream.h", + "nsNativeCharsetUtils.h", + "nsScriptableInputStream.h", + "nsStorageStream.h", + "nsStreamUtils.h", + "nsStringStream.h", + "nsUnicharInputStream.h", + "nsWildCard.h", + "SpecialSystemDirectory.h", +] + +EXPORTS.mozilla += [ + "Base64.h", + "FilePreferences.h", + "FixedBufferOutputStream.h", + "InputStreamLengthHelper.h", + "InputStreamLengthWrapper.h", + "NonBlockingAsyncInputStream.h", + "SlicedInputStream.h", + "SnappyCompressOutputStream.h", + "SnappyFrameUtils.h", + "SnappyUncompressInputStream.h", + "StreamBufferSink.h", + "StreamBufferSinkImpl.h", + "StreamBufferSource.h", + "StreamBufferSourceImpl.h", +] + +UNIFIED_SOURCES += [ + "Base64.cpp", + "crc32c.c", + "FileDescriptorFile.cpp", + "FilePreferences.cpp", + "FixedBufferOutputStream.cpp", + "InputStreamLengthHelper.cpp", + "InputStreamLengthWrapper.cpp", + "NonBlockingAsyncInputStream.cpp", + "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", + ] + +DEFINES["MOZ_APP_BASENAME"] = '"%s"' % CONFIG["MOZ_APP_BASENAME"] + +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 += [ + "!..", + "../build", +] diff --git a/xpcom/io/nsAnonymousTemporaryFile.cpp b/xpcom/io/nsAnonymousTemporaryFile.cpp new file mode 100644 index 0000000000..1c9dce57d5 --- /dev/null +++ b/xpcom/io/nsAnonymousTemporaryFile.cpp @@ -0,0 +1,264 @@ +/* -*- 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 "nsAnonymousTemporaryFile.h" +#include "nsXULAppAPI.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "prio.h" +#include "SpecialSystemDirectory.h" + +#ifdef XP_WIN +# include "nsIObserver.h" +# include "nsIObserverService.h" +# include "mozilla/ResultExtensions.h" +# include "mozilla/Services.h" +# include "nsIUserIdleService.h" +# include "nsISimpleEnumerator.h" +# include "nsIFile.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 = + GetSpecialSystemDirectory(OS_TemporaryDirectory, 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; +} + +nsresult NS_OpenAnonymousTemporaryNsIFile(nsIFile** aFile) { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + + 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, 0600); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + tmpFile.forget(aFile); + return NS_OK; +} + +nsresult NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc) { + nsCOMPtr<nsIFile> tmpFile; + nsresult rv = NS_OpenAnonymousTemporaryNsIFile(getter_AddRefs(tmpFile)); + 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 nsINamed { + 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. + MOZ_TRY_VAR(mTimer, NS_NewTimerWithObserver(this, SCHEDULE_TIMEOUT_MS, + nsITimer::TYPE_ONE_SHOT)); + + // 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<nsIUserIdleService> idleSvc = + do_GetService("@mozilla.org/widget/useridleservice;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; + } + + NS_IMETHODIMP GetName(nsACString& aName) { + aName.AssignLiteral("nsAnonTempFileRemover"); + 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<nsIUserIdleService> idleSvc = + do_GetService("@mozilla.org/widget/useridleservice;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, nsINamed) + +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..8e900dda4c --- /dev/null +++ b/xpcom/io/nsAnonymousTemporaryFile.h @@ -0,0 +1,39 @@ +/* -*- 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" +#include "nsIFile.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); + +/** + * OpenAnonymousTemporaryNsIFile + * + * Similar to the previous function, it returns a nsIFile. Note that the nsIFile + * will not be deleted automagically. The callee has to call aFile->Remove() in + * order to remove the temporary file. + */ +nsresult NS_OpenAnonymousTemporaryNsIFile(nsIFile** aFile); diff --git a/xpcom/io/nsAppDirectoryServiceDefs.h b/xpcom/io/nsAppDirectoryServiceDefs.h new file mode 100644 index 0000000000..521dbe0d50 --- /dev/null +++ b/xpcom/io/nsAppDirectoryServiceDefs.h @@ -0,0 +1,102 @@ +/* -*- 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_CHROME_DIR_LIST "AChromDL" + +// -------------------------------------------------------------------------------------- +// 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_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_PANELS_50_FILE "UPnls" +#define NS_APP_CACHE_PARENT_DIR "cachePDir" + +#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(MOZ_CONTENT_TEMP_DIR) +// +// 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. +// +// When MOZ_CONTENT_TEMP_DIR 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_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" +#endif // defined(MOZ_SANDBOX) + +#endif // nsAppDirectoryServiceDefs_h___ diff --git a/xpcom/io/nsAppFileLocationProvider.cpp b/xpcom/io/nsAppFileLocationProvider.cpp new file mode 100644 index 0000000000..aedaad1b4f --- /dev/null +++ b/xpcom/io/nsAppFileLocationProvider.cpp @@ -0,0 +1,333 @@ +/* -*- 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 "nsAtom.h" +#include "nsIDirectoryService.h" +#include "nsIFile.h" +#include "nsString.h" +#include "nsSimpleEnumerator.h" +#include "prenv.h" +#include "nsCRT.h" +#if defined(MOZ_WIDGET_COCOA) +# include <Carbon/Carbon.h> +# include "CocoaFileUtils.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 "Application Registry"_ns +# define ESSENTIAL_FILES "Essential Files"_ns +#elif defined(XP_WIN) +# define APP_REGISTRY_NAME "registry.dat"_ns +#else +# define APP_REGISTRY_NAME "appreg"_ns +#endif + +// define default product directory +#define DEFAULT_PRODUCT_DIR nsLiteralCString(MOZ_USER_DIR) + +#define DEFAULTS_DIR_NAME "defaults"_ns +#define DEFAULTS_PREF_DIR_NAME "pref"_ns +#define RES_DIR_NAME "res"_ns +#define CHROME_DIR_NAME "chrome"_ns + +//***************************************************************************** +// nsAppFileLocationProvider::Constructor/Destructor +//***************************************************************************** + +nsAppFileLocationProvider::nsAppFileLocationProvider() = default; + +//***************************************************************************** +// nsAppFileLocationProvider::nsISupports +//***************************************************************************** + +NS_IMPL_ISUPPORTS(nsAppFileLocationProvider, nsIDirectoryServiceProvider) + +//***************************************************************************** +// 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; + + 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_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) + NS_NewLocalFile(u""_ns, true, getter_AddRefs(localDir)); + if (!localDir) { + return NS_ERROR_FAILURE; + } + nsCOMPtr<nsILocalFileMac> localDirMac(do_QueryInterface(localDir)); + + rv = localDirMac->InitWithCFURL( + CocoaFileUtils::GetProductDirectoryCFURLRef(aLocal)); + 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("Profiles"_ns); + 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::nsIDirectoryServiceProvider +//***************************************************************************** + +class nsAppDirectoryEnumerator : public nsSimpleEnumerator { + public: + /** + * 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) {} + + const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); } + + NS_IMETHOD HasMoreElements(bool* aResult) override { + while (!mNext && *mCurrentKey) { + bool dontCare; + nsCOMPtr<nsIFile> testFile; + (void)mProvider->GetFile(*mCurrentKey++, &dontCare, + getter_AddRefs(testFile)); + 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; +}; diff --git a/xpcom/io/nsAppFileLocationProvider.h b/xpcom/io/nsAppFileLocationProvider.h new file mode 100644 index 0000000000..d2fd9cf651 --- /dev/null +++ b/xpcom/io/nsAppFileLocationProvider.h @@ -0,0 +1,45 @@ +/* -*- 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 "nsCOMPtr.h" +#include "mozilla/Attributes.h" + +class nsIFile; + +//***************************************************************************** +// class nsAppFileLocationProvider +//***************************************************************************** + +class nsAppFileLocationProvider final : public nsIDirectoryServiceProvider { + public: + nsAppFileLocationProvider(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + + private: + ~nsAppFileLocationProvider() = default; + + 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); + + nsCOMPtr<nsIFile> mMozBinDirectory; +}; + +#endif diff --git a/xpcom/io/nsBinaryStream.cpp b/xpcom/io/nsBinaryStream.cpp new file mode 100644 index 0000000000..555edf5345 --- /dev/null +++ b/xpcom/io/nsBinaryStream.cpp @@ -0,0 +1,1007 @@ +/* -*- 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/RefPtr.h" +#include "mozilla/Span.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 "js/ArrayBuffer.h" // JS::{GetArrayBuffer{,ByteLength},IsArrayBufferObject} +#include "js/GCAPI.h" // JS::AutoCheckCannotGC +#include "js/RootingAPI.h" // JS::{Handle,Rooted} +#include "js/Value.h" // JS::Value + +using mozilla::AsBytes; +using mozilla::MakeUnique; +using mozilla::PodCopy; +using mozilla::Span; +using mozilla::UniquePtr; + +already_AddRefed<nsIObjectOutputStream> NS_NewObjectOutputStream( + nsIOutputStream* aOutputStream) { + MOZ_ASSERT(aOutputStream); + auto stream = mozilla::MakeRefPtr<nsBinaryOutputStream>(); + + MOZ_ALWAYS_SUCCEEDS(stream->SetOutputStream(aOutputStream)); + return stream.forget(); +} + +already_AddRefed<nsIObjectInputStream> NS_NewObjectInputStream( + nsIInputStream* aInputStream) { + MOZ_ASSERT(aInputStream); + auto stream = mozilla::MakeRefPtr<nsBinaryInputStream>(); + + MOZ_ALWAYS_SUCCEEDS(stream->SetInputStream(aInputStream)); + return stream.forget(); +} + +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::StreamStatus() { + if (NS_WARN_IF(!mOutputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mOutputStream->StreamStatus(); +} + +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) { + MOZ_ASSERT_UNREACHABLE("WriteFrom"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* aResult) { + MOZ_ASSERT_UNREACHABLE("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) { + static_assert(sizeof(float) == sizeof(uint32_t), + "False assumption about sizeof(float)"); + return Write32(*reinterpret_cast<uint32_t*>(&aFloat)); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteDouble(double aDouble) { + static_assert(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 = NS_strlen(aString); + nsresult rv = Write32(length); + if (NS_FAILED(rv)) { + return rv; + } + + if (length == 0) { + return NS_OK; + } + +#ifdef IS_BIG_ENDIAN + rv = WriteBytes(AsBytes(Span(aString, length))); +#else + // XXX use WriteSegments here to avoid copy! + char16_t* copy; + char16_t temp[64]; + if (length <= 64) { + copy = temp; + } else { + copy = static_cast<char16_t*>(malloc(length * sizeof(char16_t))); + 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(AsBytes(Span(copy, length))); + if (copy != temp) { + free(copy); + } +#endif + + return rv; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteUtf8Z(const char16_t* aString) { + return WriteStringZ(NS_ConvertUTF16toUTF8(aString).get()); +} + +nsresult nsBinaryOutputStream::WriteBytes(Span<const uint8_t> aBytes) { + nsresult rv; + uint32_t bytesWritten; + + rv = Write(reinterpret_cast<const char*>(aBytes.Elements()), aBytes.Length(), + &bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesWritten != aBytes.Length()) { + return NS_ERROR_FAILURE; + } + return rv; +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteBytesFromJS(const char* aString, uint32_t aLength) { + return WriteBytes(AsBytes(Span(aString, aLength))); +} + +NS_IMETHODIMP +nsBinaryOutputStream::WriteByteArray(const nsTArray<uint8_t>& aByteArray) { + return WriteBytes(aByteArray); +} + +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; + } + + rv = WriteBytes(aIID.m3); + 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::StreamStatus() { + if (NS_WARN_IF(!mInputStream)) { + return NS_ERROR_UNEXPECTED; + } + return mInputStream->StreamStatus(); +} + +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) { + static_assert(sizeof(float) == sizeof(uint32_t), + "False assumption about sizeof(float)"); + return Read32(reinterpret_cast<uint32_t*>(aFloat)); +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadDouble(double* aDouble) { + static_assert(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) { + MOZ_ASSERT(aCount > 0, "Why are we being told to write 0 bytes?"); + static_assert(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; + } + + WriteStringClosure closure; + closure.mWriteCursor = aString.BeginWriting(); + 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; +} + +nsresult nsBinaryInputStream::ReadBytesToBuffer(uint32_t aLength, + uint8_t* aBuffer) { + uint32_t bytesRead; + nsresult rv = Read(reinterpret_cast<char*>(aBuffer), aLength, &bytesRead); + if (NS_FAILED(rv)) { + return rv; + } + if (bytesRead != aLength) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadBytes(uint32_t aLength, char** aResult) { + char* s = static_cast<char*>(malloc(aLength)); + if (!s) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = ReadBytesToBuffer(aLength, reinterpret_cast<uint8_t*>(s)); + if (NS_FAILED(rv)) { + free(s); + return rv; + } + + *aResult = s; + return NS_OK; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadByteArray(uint32_t aLength, + nsTArray<uint8_t>& aResult) { + if (!aResult.SetLength(aLength, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsresult rv = ReadBytesToBuffer(aLength, aResult.Elements()); + if (NS_FAILED(rv)) { + aResult.Clear(); + } + return rv; +} + +NS_IMETHODIMP +nsBinaryInputStream::ReadArrayBuffer(uint64_t aLength, + JS::Handle<JS::Value> aBuffer, + JSContext* aCx, uint64_t* aReadLength) { + if (!aBuffer.isObject()) { + return NS_ERROR_FAILURE; + } + JS::Rooted<JSObject*> buffer(aCx, &aBuffer.toObject()); + if (!JS::IsArrayBufferObject(buffer)) { + return NS_ERROR_FAILURE; + } + + size_t bufferLength = JS::GetArrayBufferByteLength(buffer); + if (bufferLength < aLength) { + return NS_ERROR_FAILURE; + } + + uint32_t bufSize = std::min<uint64_t>(aLength, 4096); + UniquePtr<char[]> buf = MakeUnique<char[]>(bufSize); + + uint64_t pos = 0; + *aReadLength = 0; + do { + // Read data into temporary buffer. + uint32_t bytesRead; + uint32_t amount = std::min<uint64_t>(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; + } + + // Hack around bug 1508939 + // The old CSP serialization can't be handled cleanly when + // it's embedded in an old style principal + static const nsIID oldCSPiid = { + 0xb3c4c0ae, + 0xbd5e, + 0x4cad, + {0x87, 0xe0, 0x8d, 0x21, 0x0d, 0xbb, 0x3f, 0x9f}}; + if (iid.Equals(oldCSPiid)) { + return NS_ERROR_FAILURE; + } + // 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; + } + + const uint32_t toRead = sizeof(aResult->m3); + uint32_t bytesRead = 0; + rv = Read(reinterpret_cast<char*>(&aResult->m3[0]), toRead, &bytesRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + if (bytesRead != toRead) { + return NS_ERROR_FAILURE; + } + + 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..94b92bb746 --- /dev/null +++ b/xpcom/io/nsBinaryStream.h @@ -0,0 +1,99 @@ +/* -*- 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() = default; + + protected: + friend already_AddRefed<nsIObjectOutputStream> NS_NewObjectOutputStream( + nsIOutputStream*); + + // 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() = default; +}; + +#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() = default; + + protected: + friend already_AddRefed<nsIObjectInputStream> NS_NewObjectInputStream( + nsIInputStream*); + + // 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: + // Shared infrastructure for ReadBytes and ReadByteArray. Callers + // are expected to provide a buffer that can contain aLength bytes. + nsresult ReadBytesToBuffer(uint32_t aLength, uint8_t* aBuffer); + + // virtual dtor since subclasses call our Release() + virtual ~nsBinaryInputStream() = default; +}; + +#endif // nsBinaryStream_h___ diff --git a/xpcom/io/nsDirectoryService.cpp b/xpcom/io/nsDirectoryService.cpp new file mode 100644 index 0000000000..f5e841c6ea --- /dev/null +++ b/xpcom/io/nsDirectoryService.cpp @@ -0,0 +1,448 @@ +/* -*- 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 "nsDirectoryService.h" +#include "nsLocalFile.h" +#include "nsDebug.h" +#include "nsGkAtoms.h" +#include "nsEnumeratorUtils.h" +#include "nsThreadUtils.h" + +#include "mozilla/SimpleEnumerator.h" +#include "nsICategoryManager.h" +#include "nsISimpleEnumerator.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" +#include "BinaryPath.h" + +using namespace mozilla; + +//---------------------------------------------------------------------------------------- +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; + } + + if (!mXCurProcD) { +#if defined(ANDROID) + // Some callers relying on this fallback make assumptions that don't + // hold on Android for BinaryPath::GetFile, so use GRE_HOME instead. + const char* greHome = getenv("GRE_HOME"); + if (!greHome) { + return NS_ERROR_FAILURE; + } + nsresult rv = NS_NewNativeLocalFile(nsDependentCString(greHome), true, + getter_AddRefs(mXCurProcD)); + if (NS_FAILED(rv)) { + return rv; + } +#else + nsCOMPtr<nsIFile> file; + if (NS_SUCCEEDED(BinaryPath::GetFile(getter_AddRefs(file)))) { + nsresult rv = file->GetParent(getter_AddRefs(mXCurProcD)); + if (NS_FAILED(rv)) { + return rv; + } + } +#endif + } + return mXCurProcD->Clone(aFile); +} // GetCurrentProcessDirectory() + +StaticRefPtr<nsDirectoryService> nsDirectoryService::gService; + +nsDirectoryService::nsDirectoryService() : mHashtable(128) {} + +nsresult nsDirectoryService::Create(REFNSIID aIID, void** aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + if (!gService) { + return NS_ERROR_NOT_INITIALIZED; + } + + return gService->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsDirectoryService::Init() { + MOZ_ASSERT_UNREACHABLE("nsDirectoryService::Init() for internal use only!"); + return NS_OK; +} + +void nsDirectoryService::RealInit() { + NS_ASSERTION(!gService, + "nsDirectoryService::RealInit Mustn't initialize twice!"); + + gService = new nsDirectoryService(); + + // Let the list hold the only reference to the provider. + nsAppFileLocationProvider* defaultProvider = new nsAppFileLocationProvider; + gService->mProviders.AppendElement(defaultProvider); +} + +nsDirectoryService::~nsDirectoryService() = default; + +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); + return mHashtable.Remove(key) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDirectoryService::GetKeys(nsTArray<nsCString>& 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; + } + + MOZ_ASSERT(NS_IsMainThread(), "Do not call dirsvc::get on non-main threads!"); + + 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; + } + if (!aValue) { + return NS_ERROR_FAILURE; + } + + const nsDependentCString key(aProp); + return mHashtable.WithEntryHandle(key, [&](auto&& entry) { + if (!entry) { + nsCOMPtr<nsIFile> ourFile = do_QueryInterface(aValue); + if (ourFile) { + nsCOMPtr<nsIFile> cloneFile; + ourFile->Clone(getter_AddRefs(cloneFile)); + entry.Insert(std::move(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)); + + for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(entries)) { + nsAutoCString contractID; + categoryEntry->GetValue(contractID); + + if (nsCOMPtr<nsIDirectoryServiceProvider> provider = + do_GetService(contractID.get())) { + RegisterProvider(provider); + } + } +} + +NS_IMETHODIMP +nsDirectoryService::UnregisterProvider(nsIDirectoryServiceProvider* aProv) { + if (!aProv) { + return NS_ERROR_FAILURE; + } + + mProviders.RemoveElement(aProv); + return NS_OK; +} + +// DO NOT ADD ANY LOCATIONS TO THIS FUNCTION UNTIL YOU TALK TO: +// dougt@netscape.com. This is meant to be a place of xpcom or system specific +// file locations, not application specific locations. If you need the later, +// register a callback for your application. + +NS_IMETHODIMP +nsDirectoryService::GetFile(const char* aProp, bool* aPersistent, + nsIFile** aResult) { + nsCOMPtr<nsIFile> localFile; + nsresult rv = NS_ERROR_FAILURE; + + *aResult = nullptr; + *aPersistent = true; + + RefPtr<nsAtom> inAtom = NS_Atomize(aProp); + + // check to see if it is one of our defaults + + if (inAtom == nsGkAtoms::DirectoryService_CurrentProcess || + inAtom == nsGkAtoms::DirectoryService_OS_CurrentProcessDirectory) { + rv = GetCurrentProcessDirectory(getter_AddRefs(localFile)); + } + + // Unless otherwise set, the core pieces of the GRE exist + // in the current process directory. + else if (inAtom == nsGkAtoms::DirectoryService_GRE_Directory || + inAtom == nsGkAtoms::DirectoryService_GRE_BinDirectory) { + rv = GetCurrentProcessDirectory(getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_OS_TemporaryDirectory) { + rv = GetSpecialSystemDirectory(OS_TemporaryDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_OS_CurrentWorkingDirectory) { + rv = GetSpecialSystemDirectory(OS_CurrentWorkingDirectory, + getter_AddRefs(localFile)); + } +#if defined(MOZ_WIDGET_COCOA) + else if (inAtom == nsGkAtoms::DirectoryService_SystemDirectory) { + rv = GetSpecialSystemDirectory(Mac_SystemDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_UserLibDirectory) { + rv = GetSpecialSystemDirectory(Mac_UserLibDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::Home) { + rv = + GetSpecialSystemDirectory(Mac_HomeDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_DefaultDownloadDirectory) { + rv = GetSpecialSystemDirectory(Mac_DefaultDownloadDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_OS_DesktopDirectory) { + rv = GetSpecialSystemDirectory(Mac_UserDesktopDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_LocalApplicationsDirectory) { + rv = GetSpecialSystemDirectory(Mac_LocalApplicationsDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_UserPreferencesDirectory) { + rv = GetSpecialSystemDirectory(Mac_UserPreferencesDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_PictureDocumentsDirectory) { + rv = GetSpecialSystemDirectory(Mac_PictureDocumentsDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_DefaultScreenshotDirectory) { + rv = GetSpecialSystemDirectory(Mac_DefaultScreenshotDirectory, + getter_AddRefs(localFile)); + } +#elif defined(XP_WIN) + else if (inAtom == nsGkAtoms::DirectoryService_SystemDirectory) { + rv = GetSpecialSystemDirectory(Win_SystemDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_WindowsDirectory) { + rv = GetSpecialSystemDirectory(Win_WindowsDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_WindowsProgramFiles) { + rv = GetSpecialSystemDirectory(Win_ProgramFiles, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::Home) { + rv = + GetSpecialSystemDirectory(Win_HomeDirectory, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_Programs) { + rv = GetSpecialSystemDirectory(Win_Programs, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_Favorites) { + rv = GetSpecialSystemDirectory(Win_Favorites, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_OS_DesktopDirectory) { + rv = GetSpecialSystemDirectory(Win_Desktopdirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_Appdata) { + rv = GetSpecialSystemDirectory(Win_Appdata, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_LocalAppdata) { + rv = GetSpecialSystemDirectory(Win_LocalAppdata, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_WinCookiesDirectory) { + rv = GetSpecialSystemDirectory(Win_Cookies, getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_DefaultDownloadDirectory) { + rv = GetSpecialSystemDirectory(Win_Downloads, getter_AddRefs(localFile)); + } +#elif defined(XP_UNIX) + else if (inAtom == nsGkAtoms::Home) { + rv = GetSpecialSystemDirectory(Unix_HomeDirectory, + getter_AddRefs(localFile)); + } else if (inAtom == nsGkAtoms::DirectoryService_OS_DesktopDirectory) { + rv = GetSpecialSystemDirectory(Unix_XDG_Desktop, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsGkAtoms::DirectoryService_DefaultDownloadDirectory) { + rv = + GetSpecialSystemDirectory(Unix_XDG_Download, getter_AddRefs(localFile)); + *aPersistent = false; + } else if (inAtom == nsGkAtoms::DirectoryService_OS_SystemConfigDir) { + rv = GetSpecialSystemDirectory(Unix_SystemConfigDirectory, + getter_AddRefs(localFile)); + } +#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..49f3c85f51 --- /dev/null +++ b/xpcom/io/nsDirectoryService.h @@ -0,0 +1,59 @@ +/* -*- 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 "nsDirectoryServiceDefs.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" +#include "mozilla/StaticPtr.h" + +#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(REFNSIID aIID, void** aResult); + + static mozilla::StaticRefPtr<nsDirectoryService> gService; + + void SetCurrentProcessDirectory(nsIFile* aFile) { mXCurProcD = aFile; } + nsresult GetCurrentProcessDirectory(nsIFile**); + + private: + ~nsDirectoryService(); + nsCOMPtr<nsIFile> mXCurProcD; + + nsInterfaceHashtable<nsCStringHashKey, nsIFile> mHashtable; + nsTArray<nsCOMPtr<nsIDirectoryServiceProvider>> mProviders; +}; + +#endif diff --git a/xpcom/io/nsDirectoryServiceDefs.h b/xpcom/io/nsDirectoryServiceDefs.h new file mode 100644 index 0000000000..9f0368ff06 --- /dev/null +++ b/xpcom/io/nsDirectoryServiceDefs.h @@ -0,0 +1,96 @@ +/* -*- 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. Each unique + * string in this file should have a corresponding atom defined in + * StaticAtoms.py (search for "DirectoryService"), regardless of whether it + * is defined here due to conditional compilation. + */ + +#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" + +#define NS_OS_DEFAULT_DOWNLOAD_DIR "DfltDwnld" + +/* Property returns the directory in which the procces was started from. + */ +#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_InitXPCOM(). + */ +#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_USER_LIB_DIR "ULibDir" +# define NS_OSX_LOCAL_APPLICATIONS_DIR "LocApp" +# define NS_OSX_USER_PREFERENCES_DIR "UsrPrfs" +# define NS_OSX_PICTURE_DOCUMENTS_DIR "Pct" +#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_PROGRAMS_DIR "Progs" // User start menu programs directory! +# define NS_WIN_FAVORITES_DIR "Favs" +# define NS_WIN_APPDATA_DIR "AppData" +# define NS_WIN_LOCAL_APPDATA_DIR "LocalAppData" +# define NS_WIN_COOKIES_DIR "CookD" +#elif defined(XP_UNIX) +# define NS_UNIX_HOME_DIR NS_OS_HOME_DIR +#endif + +#if defined(MOZ_WIDGET_GTK) +# define NS_OS_SYSTEM_CONFIG_DIR "SysConfD" +#endif // defined(MOZ_WIDGET_GTK) + +#endif diff --git a/xpcom/io/nsDirectoryServiceUtils.h b/xpcom/io/nsDirectoryServiceUtils.h new file mode 100644 index 0000000000..c48bf4e2fe --- /dev/null +++ b/xpcom/io/nsDirectoryServiceUtils.h @@ -0,0 +1,29 @@ +/* -*- 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 "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..c1714c0b9a --- /dev/null +++ b/xpcom/io/nsEscape.cpp @@ -0,0 +1,664 @@ +/* -*- 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 "mozilla/CheckedInt.h" +#include "mozilla/TextUtils.h" +#include "nsTArray.h" +#include "nsCRT.h" +#include "nsASCIIMask.h" + +static const char hexCharsUpper[] = "0123456789ABCDEF"; +static const char hexCharsUpperLower[] = "0123456789ABCDEFabcdef"; + +static const unsigned char netCharType[256] = + // clang-format off +/* 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 '/' +** Bit 4 ... NSURL-ref -- extra encoding for Apple NSURL compatibility. +** This encoding set is used on encoded URL ref +** components before converting a URL to an NSURL +** so we don't include '%' to avoid double encoding. +*/ + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + { 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, /* 0x */ + 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, /* 1x */ + /* ! " # $ % & ' ( ) * + , - . / */ + 0x0,0x8,0x0,0x0,0x8,0x8,0x8,0x8,0x8,0x8,0xf,0xc,0x8,0xf,0xf,0xc, /* 2x */ + /* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ + 0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0x8,0x8,0x0,0x8,0x0,0x8, /* 3x */ + /* @ A B C D E F G H I J K L M N O */ + 0x8,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf, /* 4x */ + /* bits for '@' changed from 7 to 0 so '@' can be escaped */ + /* in usernames and passwords in publishing. */ + /* P Q R S T U V W X Y Z [ \ ] ^ _ */ + 0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0x0,0x0,0x0,0x0,0xf, /* 5x */ + /* ` a b c d e f g h i j k l m n o */ + 0x0,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf, /* 6x */ + /* p q r s t u v w x y z { | } ~ DEL */ + 0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0xf,0x0,0x0,0x0,0x8,0x0, /* 7x */ + 0x0, + }; + +/* 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))) +// clang-format on + +#define IS_OK(C) (netCharType[((unsigned char)(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); + + unsigned char* dst = (unsigned char*)result; + 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 || strpbrk(pc1, hexCharsUpperLower) == nullptr || + strpbrk(pc2, hexCharsUpperLower) == nullptr) { + *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 */ + +void nsAppendEscapedHTML(const nsACString& aSrc, nsACString& aDst) { + // Preparation: aDst's length will increase by at least aSrc's length. If the + // addition overflows, we skip this, which is fine, and we'll likely abort + // while (infallibly) appending due to aDst becoming too large. + mozilla::CheckedInt<nsACString::size_type> newCapacity = aDst.Length(); + newCapacity += aSrc.Length(); + if (newCapacity.isValid()) { + aDst.SetCapacity(newCapacity.value()); + } + + for (auto cur = aSrc.BeginReading(); cur != aSrc.EndReading(); cur++) { + if (*cur == '<') { + aDst.AppendLiteral("<"); + } else if (*cur == '>') { + aDst.AppendLiteral(">"); + } else if (*cur == '&') { + aDst.AppendLiteral("&"); + } else if (*cur == '"') { + aDst.AppendLiteral("""); + } else if (*cur == '\'') { + aDst.AppendLiteral("'"); + } else { + aDst.Append(*cur); + } + } +} + +//---------------------------------------------------------------------------------------- +// +// 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. + +template <size_t N> +static constexpr void AddUnescapedChars(const char (&aChars)[N], + uint32_t aFlags, + std::array<uint32_t, 256>& aTable) { + for (size_t i = 0; i < N - 1; ++i) { + aTable[static_cast<unsigned char>(aChars[i])] |= aFlags; + } +} + +static constexpr std::array<uint32_t, 256> BuildEscapeChars() { + constexpr uint32_t kAllModes = esc_Scheme | esc_Username | esc_Password | + esc_Host | esc_Directory | esc_FileBaseName | + esc_FileExtension | esc_Param | esc_Query | + esc_Ref | esc_ExtHandler; + + std::array<uint32_t, 256> table{0}; + + // Alphanumerics shouldn't be escaped in all escape modes. + AddUnescapedChars("0123456789", kAllModes, table); + AddUnescapedChars("ABCDEFGHIJKLMNOPQRSTUVWXYZ", kAllModes, table); + AddUnescapedChars("abcdefghijklmnopqrstuvwxyz", kAllModes, table); + AddUnescapedChars("!$&()*+,-_~", kAllModes, table); + + // Extra characters which aren't escaped in particular escape modes. + AddUnescapedChars(".", esc_Scheme, table); + // esc_Username has no additional unescaped characters. + AddUnescapedChars("|", esc_Password, table); + AddUnescapedChars(".", esc_Host, table); + AddUnescapedChars("'./:;=@[]|", esc_Directory, table); + AddUnescapedChars("'.:;=@[]|", esc_FileBaseName, table); + AddUnescapedChars("':;=@[]|", esc_FileExtension, table); + AddUnescapedChars(".:;=@[\\]^`{|}", esc_Param, table); + AddUnescapedChars("./:;=?@[\\]^`{|}", esc_Query, table); + AddUnescapedChars("#'./:;=?@[\\]^{|}", esc_Ref, table); + AddUnescapedChars("#'./:;=?@[]", esc_ExtHandler, table); + + return table; +} + +static constexpr std::array<uint32_t, 256> EscapeChars = BuildEscapeChars(); + +static bool dontNeedEscape(unsigned char aChar, uint32_t aFlags) { + return EscapeChars[(size_t)aChar] & aFlags; +} +static bool dontNeedEscape(uint16_t aChar, uint32_t aFlags) { + return aChar < EscapeChars.size() ? (EscapeChars[(size_t)aChar] & aFlags) + : false; +} + +// Temporary static assert to make sure that the rewrite to using +// `BuildEscapeChars` didn't change the final array in memory. +// It will be removed in Bug 1750945. + +static_assert([]() constexpr { + constexpr uint32_t OldEscapeChars[256] = + // clang-format off +// 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,132095, 0,131584,132095, 0,132095,131696,132095,132095,132095,132095,132095,132095,132025,131856, // 2x !"#$%&'()*+,-./ + 132095,132095,132095,132095,132095,132095,132095,132095,132095,132095,132080,132080, 0,132080, 0,131840, // 3x 0123456789:;<=>? + 132080,132095,132095,132095,132095,132095,132095,132095,132095,132095,132095,132095,132095,132095,132095,132095, // 4x @ABCDEFGHIJKLMNO + 132095,132095,132095,132095,132095,132095,132095,132095,132095,132095,132095,132080, 896,132080, 896,132095, // 5x PQRSTUVWXYZ[\]^_ + 384,132095,132095,132095,132095,132095,132095,132095,132095,132095,132095,132095,132095,132095,132095,132095, // 6x `abcdefghijklmno + 132095,132095,132095,132095,132095,132095,132095,132095,132095,132095,132095, 896, 1012, 896,132095, 0, // 7x pqrstuvwxyz{|}~ DEL + 0 // 80 to FF are zero +}; + // clang-format on + + for (size_t i = 0; i < EscapeChars.size(); ++i) { + if (OldEscapeChars[i] != EscapeChars[i]) { + return false; + } + } + return true; +}()); + +//---------------------------------------------------------------------------------------- + +/** + * 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, const ASCIIMaskArray* aFilterMask, + 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) { + MOZ_ASSERT_UNREACHABLE("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); + bool spaces = !!(aFlags & esc_Spaces); + + 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 there is a filter, we wish to skip any characters which match it. + // This is needed so we don't perform an extra pass just to extract the + // filtered characters. + if (aFilterMask && mozilla::ASCIIMask::IsMasked(*aFilterMask, c)) { + if (!writing) { + if (!aResult.Append(aPart, i, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + writing = true; + } + continue; + } + + // 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. + if ((dontNeedEscape(c, aFlags) || (c == HEX_ESCAPE && !forced) || + (c > 0x7f && ignoreNonAscii) || + (c >= 0x20 && c < 0x7f && ignoreAscii)) && + !(c == ':' && colon) && !(c == ' ' && spaces) && + !(previousIsNonASCII && c == '|' && !ignoreNonAscii)) { + if (writing) { + tempBuffer[tempBufferPos++] = c; + } + } else { /* do the escape magic */ + if (!writing) { + if (!aResult.Append(aPart, i, mozilla::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, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + tempBufferPos = 0; + } + + previousIsNonASCII = (c > 0x7f); + } + if (writing) { + if (!aResult.Append(tempBuffer, tempBufferPos, mozilla::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) { + size_t partLen; + if (aPartLen < 0) { + partLen = strlen(aPart); + } else { + partLen = aPartLen; + } + + return NS_EscapeURLSpan(mozilla::Span(aPart, partLen), aFlags, aResult); +} + +bool NS_EscapeURLSpan(mozilla::Span<const char> aStr, uint32_t aFlags, + nsACString& aResult) { + bool appended = false; + nsresult rv = T_EscapeURL(aStr.Elements(), aStr.Length(), aFlags, nullptr, + aResult, appended); + if (NS_FAILED(rv)) { + ::NS_ABORT_OOM(aResult.Length() * sizeof(nsACString::char_type)); + } + + return appended; +} + +nsresult NS_EscapeURL(const nsACString& aStr, uint32_t aFlags, + nsACString& aResult, const mozilla::fallible_t&) { + bool appended = false; + nsresult rv = T_EscapeURL(aStr.Data(), aStr.Length(), aFlags, nullptr, + aResult, appended); + if (NS_FAILED(rv)) { + aResult.Truncate(); + return rv; + } + + if (!appended) { + aResult = aStr; + } + + return rv; +} + +nsresult NS_EscapeAndFilterURL(const nsACString& aStr, uint32_t aFlags, + const ASCIIMaskArray* aFilterMask, + nsACString& aResult, + const mozilla::fallible_t&) { + bool appended = false; + nsresult rv = T_EscapeURL(aStr.Data(), aStr.Length(), aFlags, aFilterMask, + aResult, appended); + if (NS_FAILED(rv)) { + aResult.Truncate(); + return rv; + } + + if (!appended) { + if (!aResult.Assign(aStr, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + return rv; +} + +const nsAString& NS_EscapeURL(const nsAString& aStr, uint32_t aFlags, + nsAString& aResult) { + bool result = false; + nsresult rv = T_EscapeURL<nsAString>(aStr.Data(), aStr.Length(), aFlags, + nullptr, aResult, result); + + if (NS_FAILED(rv)) { + ::NS_ABORT_OOM(aResult.Length() * sizeof(nsAString::char_type)); + } + + if (result) { + return aResult; + } + return aStr; +} + +// Starting at aStr[aStart] find the first index in aStr that matches any +// character that is forbidden by aFunction. Return false if not found. +static bool FindFirstMatchFrom(const nsString& aStr, size_t aStart, + const std::function<bool(char16_t)>& aFunction, + size_t* aIndex) { + for (size_t j = aStart, l = aStr.Length(); j < l; ++j) { + if (aFunction(aStr[j])) { + *aIndex = j; + return true; + } + } + return false; +} + +const nsAString& NS_EscapeURL(const nsString& aStr, + const std::function<bool(char16_t)>& aFunction, + nsAString& aResult) { + bool didEscape = false; + for (size_t i = 0, strLen = aStr.Length(); i < strLen;) { + size_t j; + if (MOZ_UNLIKELY(FindFirstMatchFrom(aStr, i, aFunction, &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; +} + +bool NS_UnescapeURL(const char* aStr, int32_t aLen, uint32_t aFlags, + nsACString& aResult) { + bool didAppend = false; + nsresult rv = + NS_UnescapeURL(aStr, aLen, aFlags, aResult, didAppend, mozilla::fallible); + if (rv == NS_ERROR_OUT_OF_MEMORY) { + ::NS_ABORT_OOM(aLen * sizeof(nsACString::char_type)); + } + + return didAppend; +} + +nsresult NS_UnescapeURL(const char* aStr, int32_t aLen, uint32_t aFlags, + nsACString& aResult, bool& aDidAppend, + const mozilla::fallible_t&) { + if (!aStr) { + MOZ_ASSERT_UNREACHABLE("null pointer"); + return NS_ERROR_INVALID_ARG; + } + + MOZ_ASSERT(aResult.IsEmpty(), + "Passing a non-empty string as an out parameter!"); + + uint32_t len; + if (aLen < 0) { + size_t stringLength = strlen(aStr); + if (stringLength >= UINT32_MAX) { + return NS_ERROR_OUT_OF_MEMORY; + } + len = stringLength; + } else { + len = aLen; + } + + 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); + + unsigned char* destPtr; + uint32_t destPos; + + if (writing) { + if (!aResult.SetLength(len, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + destPos = 0; + destPtr = reinterpret_cast<unsigned char*>(aResult.BeginWriting()); + } + + const char* last = aStr; + const char* end = aStr + len; + + for (const char* p = aStr; p < end; ++p) { + if (*p == HEX_ESCAPE && p + 2 < end) { + unsigned char c1 = *((unsigned char*)p + 1); + unsigned char c2 = *((unsigned char*)p + 2); + unsigned char u = (UNHEX(c1) << 4) + UNHEX(c2); + if (mozilla::IsAsciiHexDigit(c1) && mozilla::IsAsciiHexDigit(c2) && + (!skipInvalidHostChar || dontNeedEscape(u, aFlags) || c1 >= '8') && + ((c1 < '8' && !ignoreAscii) || (c1 >= '8' && !ignoreNonAscii)) && + !(skipControl && + (c1 < '2' || (c1 == '7' && (c2 == 'f' || c2 == 'F'))))) { + if (MOZ_UNLIKELY(!writing)) { + writing = true; + if (!aResult.SetLength(len, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + destPos = 0; + destPtr = reinterpret_cast<unsigned char*>(aResult.BeginWriting()); + } + if (p > last) { + auto toCopy = p - last; + memcpy(destPtr + destPos, last, toCopy); + destPos += toCopy; + MOZ_ASSERT(destPos <= len); + last = p; + } + destPtr[destPos] = u; + destPos += 1; + MOZ_ASSERT(destPos <= len); + p += 2; + last += 3; + } + } + } + if (writing && last < end) { + auto toCopy = end - last; + memcpy(destPtr + destPos, last, toCopy); + destPos += toCopy; + MOZ_ASSERT(destPos <= len); + } + + if (writing) { + aResult.Truncate(destPos); + } + + aDidAppend = writing; + return NS_OK; +} diff --git a/xpcom/io/nsEscape.h b/xpcom/io/nsEscape.h new file mode 100644 index 0000000000..0e86fe8487 --- /dev/null +++ b/xpcom/io/nsEscape.h @@ -0,0 +1,243 @@ +/* -*- 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" +#include <functional> + +/** + * 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 ('/') + url_NSURLRef = 1u << 3 // Extra URL ref encoding required for Apple's + // NSURL compatibility +} 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); + +/** + * Decodes '%'-escaped hex codes into character values, modifies the parameter, + * returns the same buffer + */ +char* nsUnescape(char* aStr); + +/** + * Decodes '%'-escaped hex codes into character values, modifies the parameter + * buffer, returns the length of the result (result may contain \0's). + */ +int32_t nsUnescapeCount(char* aStr); + +#ifdef __cplusplus +} +#endif + +/** + * Infallibly append aSrc to aDst, escaping chars that are problematic for HTML + * display. + */ +void nsAppendEscapedHTML(const nsACString& aSrc, nsACString& aDst); + +/** + * 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 */ + esc_Spaces = 1u << 16, /* forces escape of spaces */ + esc_ExtHandler = 1u << 17 /* For escaping external protocol handler urls. + * Escapes everything except: + * a-z, 0-9 and !#$&'()*+,-./:;=?@[]_~ */ +}; + +/** + * 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); + +bool NS_EscapeURLSpan(mozilla::Span<const char> aStr, 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); + +/** + * Fallible version of |NS_UnescapeURL|. See above for details. + * + * @param aAppended Out param: true if aResult was written to (i.e. at least + * one character was unescaped or esc_AlwaysCopy was + * requested), false otherwise. + */ +nsresult NS_UnescapeURL(const char* aStr, int32_t aLen, uint32_t aFlags, + nsACString& aResult, bool& aAppended, + const mozilla::fallible_t&); + +/** returns resultant string length **/ +inline int32_t NS_UnescapeURL(char* aStr) { return nsUnescapeCount(aStr); } + +/** + * String friendly versions... + */ +inline const nsACString& NS_EscapeURL(const nsACString& aStr, uint32_t aFlags, + nsACString& aResult) { + if (NS_EscapeURLSpan(aStr, 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 nsACString& aStr, uint32_t aFlags, + nsACString& aResult, const mozilla::fallible_t&); + +// Forward declaration for nsASCIIMask.h +typedef std::array<bool, 128> ASCIIMaskArray; + +/** + * The same as NS_EscapeURL, except it also filters out characters that match + * aFilterMask. + */ +nsresult NS_EscapeAndFilterURL(const nsACString& aStr, uint32_t aFlags, + const ASCIIMaskArray* aFilterMask, + nsACString& aResult, const mozilla::fallible_t&); + +inline const nsACString& NS_UnescapeURL(const nsACString& aStr, uint32_t aFlags, + nsACString& aResult) { + if (NS_UnescapeURL(aStr.Data(), aStr.Length(), aFlags, aResult)) { + return aResult; + } + return aStr; +} + +const nsAString& NS_EscapeURL(const nsAString& aStr, uint32_t aFlags, + nsAString& aResult); + +/** + * Percent-escapes all characters in aStr that occurs in aForbidden. + * @param aStr the input URL string + * @param aFunction returns true for characters that should be escaped + * @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 nsAString& NS_EscapeURL(const nsString& aStr, + const std::function<bool(char16_t)>& aFunction, + nsAString& 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..8dbe442663 --- /dev/null +++ b/xpcom/io/nsIAsyncInputStream.idl @@ -0,0 +1,105 @@ +/* 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, builtinclass, 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 and will NOT + * result in an error return, e.g., NS_BASE_STREAM_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..88f9d107cf --- /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, builtinclass, 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..698dbae4f8 --- /dev/null +++ b/xpcom/io/nsIBinaryInputStream.idl @@ -0,0 +1,118 @@ +/* -*- 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, builtinclass, 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 + */ + Array<uint8_t> readByteArray(in uint32_t aLength); + + /** + * 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] + uint64_t readArrayBuffer(in uint64_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..caa96eb699 --- /dev/null +++ b/xpcom/io/nsIBinaryOutputStream.idl @@ -0,0 +1,103 @@ +/* -*- 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" + +%{C++ +namespace mozilla { +template<class ElementType, size_t Extent> class Span; +} +%} +native Bytes(mozilla::Span<const uint8_t>); + +/** + * 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, builtinclass, 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. + */ + [binaryname(WriteBytesFromJS)] + void writeBytes([size_is(aLength)] in string aString, + [optional] in uint32_t aLength); + + /** + * Non-scriptable and saner-signature version of the same. + */ + [noscript, nostdcall, binaryname(WriteBytes)] + void writeBytesNative(in Bytes aBytes); + + /** + * Write an opaque byte array to the stream. + */ + void writeByteArray(in Array<uint8_t> aBytes); +}; + +%{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..adefd0f428 --- /dev/null +++ b/xpcom/io/nsICloneableInputStream.idl @@ -0,0 +1,31 @@ +/* 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(); +}; + +// This interface implements cloneWithRange() because for some streams +// (RemoteLazyInputStream only, so far) are more efficient to produce a sub +// stream with range than doing clone + SlicedInputStream(). +[scriptable, builtinclass, uuid(ece853c3-aded-4cef-8f51-0d1493d60bd5)] +interface nsICloneableInputStreamWithRange : nsICloneableInputStream +{ + nsIInputStream cloneWithRange(in uint64_t start, in uint64_t length); +}; diff --git a/xpcom/io/nsIConverterInputStream.idl b/xpcom/io/nsIConverterInputStream.idl new file mode 100644 index 0000000000..ad1f9bfbc4 --- /dev/null +++ b/xpcom/io/nsIConverterInputStream.idl @@ -0,0 +1,45 @@ +/* -*- 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; + + /** + * Special replacement character value that requests errors to + * be treated as fatal. + */ + const char16_t ERRORS_ARE_FATAL = 0; + + /** + * 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..41faa5332d --- /dev/null +++ b/xpcom/io/nsIConverterOutputStream.idl @@ -0,0 +1,30 @@ +/* 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, builtinclass, 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. + */ + void init(in nsIOutputStream aOutStream, in string aCharset); +}; diff --git a/xpcom/io/nsIDirectoryEnumerator.idl b/xpcom/io/nsIDirectoryEnumerator.idl new file mode 100644 index 0000000000..e8abb81acd --- /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" +#include "nsISimpleEnumerator.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 : nsISimpleEnumerator +{ + /** + * 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..99c4324782 --- /dev/null +++ b/xpcom/io/nsIDirectoryService.idl @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +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..535a7a88ee --- /dev/null +++ b/xpcom/io/nsIFile.idl @@ -0,0 +1,597 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIDirectoryEnumerator.idl" + +%{C++ +struct PRFileDesc; +struct PRLibrary; +#include <stdio.h> +#include "mozilla/Path.h" +#include "nsCOMPtr.h" +#include "nsStringFwd.h" +namespace mozilla { +using PathString = nsTString<filesystem::Path::value_type>; +using PathSubstring = nsTSubstring<filesystem::Path::value_type>; +} // namespace mozilla +%} + +[ptr] native PRFileDescStar(PRFileDesc); +[ptr] native PRLibraryStar(PRLibrary); +[ptr] native FILE(FILE); +native PathString(mozilla::PathString); + +/** + * 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 security reasons, this cannot contain .. and cannot start with + * a directory separator. 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. + * + * @param skipAncestors + * Optional; if set to true, we'll skip creating + * ancestor directories (and return an error instead). + */ + [must_use] void create(in unsigned long type, in unsigned long permissions, + [optional,default(false)] in bool skipAncestors); + + /** + * 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; + + /** + * The leaf name as displayed in OS-provided file pickers and similar UI. + * On Windows and macOS, 'real' leaf names of some directories can be + * in English, but the OS will show a different, translated name to users + * using a different locale. So folders like "Downloads", "Desktop" and + * "Documents" might not normally appear to users with that (English) name, + * but with an OS-localized translation. This API will return such a + * translation if it exists, or the leafName if it doesn't. + * On Linux, this will always be the same as `leafName`. + */ + readonly attribute AString displayName; + + /** + * 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_NOT_FOUND). + * + * 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_NOT_FOUND). + * 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); + + /** + * moveToFollowingLinks[Native] + * + * This function is identical to moveTo with the exception that, + * as the name implies, it follows symbolic links. The XP_UNIX + * implementation always follows symbolic links when moving. For + * the |MoveToFollowingLinks| method, the newName ust be in the native + * filesystem charset. + */ + void moveToFollowingLinks(in nsIFile newParentDir, in AString newName); + [noscript] void moveToFollowingLinksNative(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. + * + * If passed, 'removeCount' will be incremented by the total number of files + * and/or directories removed. Will be 1 unless the 'recursive' flag is + * set. The parameter must be initialized beforehand. + * + * This will not resolve any symlinks. + */ + void remove(in boolean recursive, [optional] inout uint32_t removeCount); + + /** + * Attributes of nsIFile. + */ + + attribute unsigned long permissions; + attribute unsigned long permissionsOfLink; + + /** + * The last accesss time of the file in milliseconds from midnight, January + * 1, 1970 GMT, if available. + */ + attribute PRTime lastAccessedTime; + attribute PRTime lastAccessedTimeOfLink; + + /** + * 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; + + /** + * The creation time of file in milliseconds from midnight, January 1, 1970 + * GMT, if available. + * + * This attribute is only implemented on Windows and macOS. Accessing this + * on another platform will this will throw NS_ERROR_NOT_IMPLEMENTED. + */ + readonly attribute PRTime creationTime; + readonly attribute PRTime creationTimeOfLink; + + /** + * 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; + [notxpcom,nostdcall,must_use] PathString nativePath(); +%{C++ +#ifndef XP_WIN + nsresult GetNativePath(nsACString& aPath); +#endif + /* + * Returns a human-readable path string. + */ + nsCString HumanReadablePath(); +%} + + 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. + */ + [binaryname(DirectoryEntriesImpl)] + readonly attribute nsIDirectoryEnumerator directoryEntries; + + %{C++ + nsresult GetDirectoryEntries(nsIDirectoryEnumerator** aOut) + { + return GetDirectoryEntriesImpl(aOut); + }; + %} + + /** + * 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); + + /** + * 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; + + // disk capacity in bytes + [must_use] readonly attribute int64_t diskCapacity; + + /** + * 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 .. and 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" +#include "nsString.h" + +inline std::ostream& operator<<(std::ostream& aOut, const nsIFile& aFile) { + nsIFile* file = const_cast<nsIFile*>(&aFile); + nsAutoString path; + file->GetPath(path); + return aOut << "nsIFile { " << path << " }"; +} +#endif +%} diff --git a/xpcom/io/nsIIOUtil.idl b/xpcom/io/nsIIOUtil.idl new file mode 100644 index 0000000000..589556b367 --- /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, builtinclass, 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..9a72eaeae2 --- /dev/null +++ b/xpcom/io/nsIInputStream.idl @@ -0,0 +1,172 @@ +/* -*- 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, builtinclass, 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 or StreamStatus should throw + * NS_BASE_STREAM_CLOSED. + * + * Succeeds (without side effects) if already 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). + * + * NOTE: Streams implementing nsIAsyncInputStream must automatically close + * when eof is reached, as otherwise it is impossible to distinguish between + * a stream waiting for more data and a stream at EOF using Available(). + * + * @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(); + + /** + * Check 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. + * + * This method will not throw NS_BASE_STREAM_WOULD_BLOCK, even if the stream + * is an non-blocking stream with no data. A non-blocking stream that does + * not yet have any data to read should return NS_OK. + * + * NOTE: Unlike available, his method should not block the calling thread + * (e.g. to query the state of a file descriptor), even when called on a + * blocking stream. + * + * @throws NS_BASE_STREAM_CLOSED if the stream closed normally + * @throws <other-error> if the stream closed with a different status + */ + void streamStatus(); + + /** + * 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/nsIInputStreamLength.idl b/xpcom/io/nsIInputStreamLength.idl new file mode 100644 index 0000000000..f9f685704a --- /dev/null +++ b/xpcom/io/nsIInputStreamLength.idl @@ -0,0 +1,85 @@ +/* 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 nsIEventTarget; +interface nsIInputStreamLengthCallback; + +/** + * Note: Instead of using these interfaces directly, consider to use + * InputStreamLengthHelper class. + */ + +[uuid(452d059f-9a9c-4434-8839-e10d1405647c)] +interface nsIInputStreamLength : nsISupports +{ + /** + * Returns the total length of the stream if known. Otherwise it returns -1. + * This is different than calling available() which returns the number of + * bytes ready to be read from the stream. + * -1 is a valid value for a stream that doesn't know its length. For + * instance, a pipe stream could return such value. + * + * It could throw NS_BASE_STREAM_WOULD_BLOCK if the inputStream is + * non-blocking. If this happens, you should use + * nsIAsyncInputStreamLength::asyncLengthWait(). + * + * If the stream has already been read (read()/readSegments()/close()/seek() + * methods has been called), length() returns NS_ERROR_NOT_AVAILABLE. + * + * This is not an attribute because a stream can change its length. For + * instance, if the stream is a file inputStream and the underlying OS file + * changes, its length will change as well. + */ + long long length(); +}; + +[uuid(b63f9ecf-4668-44a3-93bd-72dbc65a6125)] +interface nsIAsyncInputStreamLength : nsISupports +{ + /** + * If the stream is non-blocking, nsIInputStreamLength::length() can return + * NS_BASE_STREAM_WOULD_BLOCK. The caller must then wait for the stream to + * know its length. + * + * If the stream implements nsIAsyncInputStreamLength, then the caller can + * use this interface to request an asynchronous notification when the + * stream's length becomes known (via the AsyncLengthWait method). + * If the length is already known, the aCallback will be still called + * asynchronously. + * + * If the stream has already been read (read()/readSegments()/close()/seek() + * methods has been called), length() returns NS_ERROR_NOT_AVAILABLE. + * + * @param aCallback + * This object is notified when the length becomes known. This + * parameter may be null to clear an existing callback. + * @param aEventTarget + * Specify that the notification must be delivered to a specific event + * target. + */ + void asyncLengthWait(in nsIInputStreamLengthCallback aCallback, + in nsIEventTarget aEventTarget); +}; + +/** + * This is a companion interface for + * nsIAsyncInputStreamLength::asyncLengthWait. + */ +[function, uuid(9c0c13b9-1b33-445d-8adb-a8a7866a6c06)] +interface nsIInputStreamLengthCallback : nsISupports +{ + /** + * Called to inform what the total length of the stream is. + * + * @param aStream + * The stream whose asyncLengthWait method was called. + * @param aLength + * The stream's length. It can be -1 if the stream doesn't know its + * length. For instance, this can happen for a pipe inputStream. + */ + void onInputStreamLengthReady(in nsIAsyncInputStreamLength aStream, + in long long aLength); +}; diff --git a/xpcom/io/nsIInputStreamPriority.idl b/xpcom/io/nsIInputStreamPriority.idl new file mode 100644 index 0000000000..d5938b7c27 --- /dev/null +++ b/xpcom/io/nsIInputStreamPriority.idl @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIRunnable.idl" + +[scriptable, uuid(daa45b24-98ee-4eb2-9cec-aad0bc023e9d)] +interface nsIInputStreamPriority : nsISupports +{ + /** + * An input stream implementing this interface will dispatch runnable + * events with this priority. See nsIRunnablePriority. + */ + attribute unsigned long priority; +}; diff --git a/xpcom/io/nsIInputStreamTee.idl b/xpcom/io/nsIInputStreamTee.idl new file mode 100644 index 0000000000..60881e0fb8 --- /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, builtinclass, 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..34ef1e7320 --- /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/nsILocalFileMac.idl b/xpcom/io/nsILocalFileMac.idl new file mode 100644 index 0000000000..38559517e7 --- /dev/null +++ b/xpcom/io/nsILocalFileMac.idl @@ -0,0 +1,212 @@ +/* -*- 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 "nsIFile.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 : nsIFile +{ + /** + * 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; + + /** + * Return whether or not the file has an extended attribute. + * + * @param aAttrName The attribute name to check for. + * + * @return Whether or not the extended attribute is present. + */ + bool hasXAttr(in ACString aAttrName); + + /** + * Get the value of the extended attribute. + * + * @param aAttrName The attribute name to read. + * + * @return The extended attribute value. + */ + Array<uint8_t> getXAttr(in ACString aAttrName); + + /** + * Set an extended attribute. + * + * @param aAttrName The attribute name to set a value for. + * @param aAttrValue The value to set for the attribute. + */ + void setXAttr(in ACString aAttrName, in Array<uint8_t> aAttrValue); + + /** + * Delete an extended attribute. + * + * @param aAttrName The extended attribute to delete. + */ + void delXAttr(in ACString aAttrName); +}; + +%{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..a3f80d391b --- /dev/null +++ b/xpcom/io/nsILocalFileWin.idl @@ -0,0 +1,98 @@ +/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "nsIFile.idl" + +%{C++ +struct PRFileDesc; +%} + +[ptr] native PRFileDescStar(PRFileDesc); + +[scriptable, builtinclass, uuid(e7a3a954-384b-4aeb-a5f7-55626b0de9be)] +interface nsILocalFileWin : nsIFile +{ + /** + * 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. + */ + [noscript] readonly attribute AString canonicalPath; + + /** + * Get or set whether this file is marked read-only. + * + * Throws NS_ERROR_FILE_INVALID_PATH for an invalid file. + * Throws NS_ERROR_FAILURE if the set or get fails. + */ + attribute bool readOnly; + + /** + * Setting this to true will prepend the prefix "\\?\" to all parsed file + * paths which match ^[A-Za-z]:\\.* (regex) syntax. + * + * There are two known issues (and potentially more) which can be resolved + * by the prefix: + * - In the Windows API, the maximum length for a path is MAX_PATH in + * general. However, Windows API has many functions that also have Unicode + * versions to permit an extended-length path for a maximum total path + * length of 32,767 characters. + * + * - A path component which ends with a dot is not allowed for Windows + * API. + * + * If either of these issues are expected to be common in your code, you + * should set this flag to true. (You should probably not have to set this + * flag to true.) + */ + attribute boolean useDOSDevicePathSyntax; + + /** + * Identical to nsIFile::openNSPRFileDesc except it also uses the + * FILE_SHARE_DELETE flag. + */ + [noscript] PRFileDescStar openNSPRFileDescShareDelete(in long flags, + in long mode); + + /** + * Return the Windows-specific file attributes of this file. + */ + [noscript] unsigned long getWindowsFileAttributes(); + + /** + * Set or clear the Windows specific file attributes of this file. + * + * @param aSetAttrs Attribute to set on the file. + * @param aClearAttrs Attributes to clear on the file. + */ + [noscript] void setWindowsFileAttributes(in unsigned long aSetAttrs, + in unsigned long aClearAttrs); +}; diff --git a/xpcom/io/nsIMultiplexInputStream.idl b/xpcom/io/nsIMultiplexInputStream.idl new file mode 100644 index 0000000000..3729ccf753 --- /dev/null +++ b/xpcom/io/nsIMultiplexInputStream.idl @@ -0,0 +1,35 @@ +/* -*- 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, builtinclass, uuid(a076fd12-1dd1-11b2-b19a-d53b5dffaade)] +interface nsIMultiplexInputStream : nsISupports +{ + /** + * Number of streams in this multiplex-stream + */ + [infallible] 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); + + /** + * 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..b637e34a88 --- /dev/null +++ b/xpcom/io/nsIOUtil.cpp @@ -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/. */ + +#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..32794433eb --- /dev/null +++ b/xpcom/io/nsIOUtil.h @@ -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/. */ + +#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() = default; + + 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..c00b93a99a --- /dev/null +++ b/xpcom/io/nsIObjectInputStream.idl @@ -0,0 +1,56 @@ +/* -*- 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, builtinclass, 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++ + +already_AddRefed<nsIObjectInputStream> +NS_NewObjectInputStream(nsIInputStream* aOutputStream); + +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..c5cdf27942 --- /dev/null +++ b/xpcom/io/nsIObjectOutputStream.idl @@ -0,0 +1,99 @@ +/* -*- 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, builtinclass, 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++ +already_AddRefed<nsIObjectOutputStream> +NS_NewObjectOutputStream(nsIOutputStream* aOutputStream); + +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..ffe9a5ffed --- /dev/null +++ b/xpcom/io/nsIOutputStream.idl @@ -0,0 +1,164 @@ +/* -*- 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, builtinclass, uuid(0d0acd2a-61b4-11d4-9877-00c04fa0cf4a)] +interface nsIOutputStream : nsISupports +{ + /** + * Close the stream. Forces the output stream to flush any buffered data. + * Any subsequent calls to StreamStatus should throw NS_BASE_STREAM_CLOSED. + * Succeeds without effect if already closed. + * + * @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(); + + /** + * Check 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 write into it. + * + * This method will not throw NS_BASE_STREAM_WOULD_BLOCK, even if the stream + * is a non-blocking stream with no available space. A non-blocking stream + * which has not been closed, but has no available room should return NS_OK. + * + * NOTE: This method should not block the calling thread (e.g. to query the + * state of a file descriptor), even when called on a blocking stream. + * + * @throws NS_BASE_STREAM_CLOSED if the stream closed normally + * @throws <other-error> if the stream closed with a different status + */ + void streamStatus(); + + /** + * 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..cb5a035a32 --- /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 void +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 void +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/nsIRandomAccessStream.idl b/xpcom/io/nsIRandomAccessStream.idl new file mode 100644 index 0000000000..20421def17 --- /dev/null +++ b/xpcom/io/nsIRandomAccessStream.idl @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsISeekableStream.idl" + +interface nsIInputStream; +interface nsIInterfaceRequestor; +interface nsIOutputStream; + +%{C++ +namespace mozilla::ipc { +class RandomAccessStreamParams; +} // namespace mozilla::ipc +%} + +native RandomAccessStreamParams(mozilla::ipc::RandomAccessStreamParams); +[ref] native RandomAccessStreamParamsRef(mozilla::ipc::RandomAccessStreamParams); + +/** + * nsIRandomAccessStream + * + * An interface which supports both reading and writing to a storage starting + * at the current offset. Both the input stream and the output stream share the + * offset in the stream. Read operations invoked on the input stream start at + * the offset and advance it past the bytes read. Write operations invoked on + * the output stream start the offset and advance it past the bytes written. + * The offset can be set to an arbitrary value prior reading or writting. Each + * call to getInputStream or getOutputStream always returns the same object, + * rather than creating a new stream. It's recommended for objects implementing + * this interface to also implement nsIInputStream and nsIOutputStream, so they + * can be easilly used with e.g. NS_AsyncCopy. + */ +[scriptable, builtinclass, uuid(9b5904a8-886a-420f-a1d8-847de8ffc133)] +interface nsIRandomAccessStream : nsISeekableStream +{ + /** + * This method always returns the same object. + */ + nsIInputStream getInputStream(); + + /** + * This method always returns the same object. + */ + nsIOutputStream getOutputStream(); + + /** + * Like getInputStream but infallible. + */ + [notxpcom, nostdcall] nsIInputStream inputStream(); + + /** + * Like getOutputStream but infallible. + */ + [notxpcom, nostdcall] nsIOutputStream outputStream(); + + [notxpcom, nostdcall] RandomAccessStreamParams serialize(in nsIInterfaceRequestor aCallbacks); + + [notxpcom, nostdcall] bool deserialize(inout RandomAccessStreamParamsRef params); +}; diff --git a/xpcom/io/nsISafeOutputStream.idl b/xpcom/io/nsISafeOutputStream.idl new file mode 100644 index 0000000000..dae78b554a --- /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..7c18274055 --- /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..b12a7f9baf --- /dev/null +++ b/xpcom/io/nsISeekableStream.idl @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "nsITellableStream.idl" + +/* + * 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 : nsITellableStream +{ + /* + * 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); + + /** + * 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..c2792f5002 --- /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..c0883a439b --- /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, builtinclass, 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..3e084cbe7f --- /dev/null +++ b/xpcom/io/nsIStringStream.idl @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIInputStream.idl" + +%{C++ +#include "mozilla/MemoryReporting.h" + +namespace mozilla { +class StreamBufferSource; +} // namespace mozilla +%} + +native MallocSizeOf(mozilla::MallocSizeOf); +[ptr] native StreamBufferSource(mozilla::StreamBufferSource); + +/** + * 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); + + /** + * SetUTF8Data - encode input data to UTF-8 and assign it to the input + * stream. + * + * @param data - stream data + * + * NOTE: This method is meant to be used by JS callers, + */ + void setUTF8Data(in AUTF8String data); + + /** + * 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); + + /** + * SetDataSource - assign data to the input stream. the input stream holds + * a strong reference to the given data buffer until it is destroyed. + * + * @param source - stream data source + */ + [noscript] void setDataSource(in StreamBufferSource source); + + [noscript, notxpcom] + size_t SizeOfIncludingThisIfUnshared(in MallocSizeOf aMallocSizeOf); + + [noscript, notxpcom] + size_t SizeOfIncludingThisEvenIfShared(in MallocSizeOf aMallocSizeOf); +}; diff --git a/xpcom/io/nsITellableStream.idl b/xpcom/io/nsITellableStream.idl new file mode 100644 index 0000000000..c2cfaba0de --- /dev/null +++ b/xpcom/io/nsITellableStream.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/. */ + + +/* + * nsITellableStream + * + * This class is separate from nsISeekableStream in order to let streams to + * implement ::Tell() without implementing the whole nsISeekableStream + * interface. Callers can QI the stream to know what is implemented. This is + * mainly done for nsPipeInputStream. + * + * + * Implementing this interface, streams are able to expose the current offset + * via ::tell(). + */ + +#include "nsISupports.idl" + +[scriptable, uuid(ee942946-4538-45d2-bf05-ffdbf5932621)] +interface nsITellableStream : nsISupports +{ + /** + * 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(); +}; diff --git a/xpcom/io/nsIUnicharInputStream.idl b/xpcom/io/nsIUnicharInputStream.idl new file mode 100644 index 0000000000..3ae467cc83 --- /dev/null +++ b/xpcom/io/nsIUnicharInputStream.idl @@ -0,0 +1,97 @@ +/* -*- 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 number of UTF-16 code units already read + * (since ReadSegments was called) + * @param aCount length of fromSegment + * @param aWriteCount number of UTF-16 code units 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 UTF-16 input stream + * @see nsIInputStream + */ +[scriptable, uuid(d5e3bd80-6723-4b92-b0c9-22f6162fd94f)] +interface nsIUnicharInputStream : nsISupports { + /** + * Reads into a caller-provided array. + * + * @return The number of utf-16 code units 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 code units, 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 UTF-16 code units 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 UTF-16 code units to be read + * + * @return number of UTF-16 code units 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 UTF-16 code units that should be read + * @return The number of UTF-16 code units 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..0322c6a1eb --- /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..743f532e03 --- /dev/null +++ b/xpcom/io/nsIUnicharOutputStream.idl @@ -0,0 +1,45 @@ +/* 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. + */ + 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..5b7a651350 --- /dev/null +++ b/xpcom/io/nsInputStreamTee.cpp @@ -0,0 +1,341 @@ +/* -*- 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/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/Attributes.h" +#include "nsIInputStreamTee.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsCOMPtr.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() = default; + + 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 + Maybe<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) + : mozilla::Runnable("nsInputStreamTeeWriteEvent") { + // 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 %" PRIx32 " in writing", + this, static_cast<uint32_t>(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() + : mWriter(nullptr), mClosure(nullptr), mLock(), 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::StreamStatus() { + if (NS_WARN_IF(!mSource)) { + return NS_ERROR_NOT_INITIALIZED; + } + return mSource->StreamStatus(); +} + +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.emplace("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..019fc0e4ae --- /dev/null +++ b/xpcom/io/nsLinebreakConverter.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/. */ + +#include "nsLinebreakConverter.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 + 1 < 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 + 1 < 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 + char16_t* stringBuf = aIoString.BeginWriting(mozilla::fallible); + if (!stringBuf) { + return NS_ERROR_OUT_OF_MEMORY; + } + + int32_t newLen; + + rv = ConvertUnicharLineBreaksInSitu(&stringBuf, aSrcBreaks, aDestBreaks, + aIoString.Length() + 1, &newLen); + if (NS_FAILED(rv)) { + return rv; + } + + const char16_t* currentBuf = aIoString.get(); + if (currentBuf != stringBuf) { + 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..2ecde3beec --- /dev/null +++ b/xpcom/io/nsLinebreakConverter.h @@ -0,0 +1,141 @@ +/* -*- 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..779b2e6c95 --- /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/. */ + +#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 "nsIFile.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 NS_ERROR_FILE_NO_DEVICE_SPACE +#endif + case ENOSPC: + return NS_ERROR_FILE_NO_DEVICE_SPACE; +#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_NOT_FOUND; + 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; + +#ifdef ENOATTR + case ENOATTR: + return NS_ERROR_NOT_AVAILABLE; +#endif // ENOATTR + + default: + return NS_ERROR_FAILURE; + } +} + +#define NSRESULT_FOR_ERRNO() nsresultForErrno(errno) + +#endif diff --git a/xpcom/io/nsLocalFileCommon.cpp b/xpcom/io/nsLocalFileCommon.cpp new file mode 100644 index 0000000000..f6eabf2d0f --- /dev/null +++ b/xpcom/io/nsLocalFileCommon.cpp @@ -0,0 +1,438 @@ +/* -*- 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 "nsLocalFile.h" // includes platform-specific headers + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsReadableUtils.h" +#include "nsPrintfCString.h" +#include "nsCRT.h" +#include "nsNativeCharsetUtils.h" +#include "nsUTF8Utils.h" +#include "nsArray.h" +#include "nsLocalFileCommon.h" + +#ifdef XP_WIN +# include <string.h> +#endif + +// Extensions that should be considered 'executable', ie will not allow users +// to open immediately without first saving to disk, and potentially provoke +// other warnings. PLEASE read the longer comment in +// toolkit/components/reputationservice/ApplicationReputation.cpp +// before modifying this list! +// If you update this list, make sure to update the length of sExecutableExts +// in nsLocalFileCommmon.h. +/* static */ +const char* const sExecutableExts[] = { + // clang-format off + ".accda", // MS Access database + ".accdb", // MS Access database + ".accde", // MS Access database + ".accdr", // MS Access database + ".ad", + ".ade", // access project extension + ".adp", + ".afploc", // Apple Filing Protocol Location + ".air", // Adobe AIR installer + ".app", // executable application + ".application", // from bug 348763 + ".appref-ms", // ClickOnce link + ".appx", + ".appxbundle", + ".asp", + ".atloc", // Appletalk Location + ".bas", + ".bat", + ".cer", // Signed certificate file + ".chm", + ".cmd", + ".com", + ".cpl", + ".crt", + ".der", + ".diagcab", // Windows archive + ".exe", + ".fileloc", // Apple finder internet location data file + ".ftploc", // Apple FTP Location + ".fxp", // FoxPro compiled app + ".hlp", + ".hta", + ".inetloc", // Apple finder internet location data file + ".inf", + ".ins", + ".isp", + ".jar", // java application bundle +#ifndef MOZ_ESR + ".jnlp", +#endif + ".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 + ".msh1", // Microsoft Shell + ".msh1xml", // Microsoft Shell + ".msh2", // Microsoft Shell + ".msh2xml", // Microsoft Shell + ".mshxml", // Microsoft Shell + ".msi", + ".msix", + ".msixbundle", + ".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", + ".settingcontent-ms", + ".shb", + ".shs", + ".url", + ".vb", + ".vbe", + ".vbs", + ".vdx", + ".vsd", + ".vsdm", + ".vsdx", + ".vsmacros", // Visual Studio .NET Binary-based Macro Project + ".vss", + ".vssm", + ".vssx", + ".vst", + ".vstm", + ".vstx", + ".vsw", + ".vsx", + ".vtx", + ".webloc", // MacOS website location file + ".ws", + ".wsc", + ".wsf", + ".wsh", + ".xll" // MS Excel dynamic link library + // clang-format on +}; + +#if !defined(MOZ_WIDGET_COCOA) && !defined(XP_WIN) +NS_IMETHODIMP +nsLocalFile::InitWithFile(nsIFile* aFile) { + if (NS_WARN_IF(!aFile)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString path; + aFile->GetNativePath(path); + if (path.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + return InitWithNativePath(path); +} +#endif + +#define kMaxFilenameLength 255 +#define kMaxExtensionLength 100 +#define kMaxSequenceNumberLength 5 // "-9999" +// requirement: kMaxExtensionLength < +// kMaxFilenameLength - kMaxSequenceNumberLength + +NS_IMETHODIMP +nsLocalFile::CreateUnique(uint32_t aType, uint32_t aAttributes) { + nsresult rv; + bool longName; + +#ifdef XP_WIN + nsAutoString pathName, leafName, rootName, suffix; + rv = GetPath(pathName); +#else + nsAutoCString pathName, leafName, rootName, suffix; + rv = GetNativePath(pathName); +#endif + if (NS_FAILED(rv)) { + return rv; + } + + auto FailedBecauseExists = [&](nsresult aRv) { + if (aRv == NS_ERROR_FILE_ACCESS_DENIED) { + bool exists; + return NS_SUCCEEDED(Exists(&exists)) && exists; + } + return aRv == NS_ERROR_FILE_ALREADY_EXISTS; + }; + + longName = + (pathName.Length() + kMaxSequenceNumberLength > kMaxFilenameLength); + if (!longName) { + rv = Create(aType, aAttributes); + if (!FailedBecauseExists(rv)) { + return rv; + } + } + +#ifdef XP_WIN + rv = GetLeafName(leafName); + if (NS_FAILED(rv)) { + return rv; + } + + const int32_t lastDot = leafName.RFindChar(char16_t('.')); +#else + rv = GetNativeLeafName(leafName); + if (NS_FAILED(rv)) { + return rv; + } + + const int32_t lastDot = leafName.RFindChar('.'); +#endif + + if (lastDot == kNotFound) { + rootName = leafName; + } else { + suffix = Substring(leafName, lastDot); // include '.' + rootName = Substring(leafName, 0, lastDot); // strip suffix and dot + } + + if (longName) { + int32_t maxRootLength = + (kMaxFilenameLength - (pathName.Length() - leafName.Length()) - + suffix.Length() - kMaxSequenceNumberLength); + + // We cannot create an item inside a directory whose name is too long. + // Also, ensure that at least one character remains after we truncate + // the root name, as we don't want to end up with an empty leaf name. + if (maxRootLength < 2) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + +#ifdef XP_WIN + // ensure that we don't cut the name in mid-UTF16-character + rootName.SetLength(NS_IS_LOW_SURROGATE(rootName[maxRootLength]) + ? maxRootLength - 1 + : maxRootLength); + SetLeafName(rootName + suffix); +#else + if (NS_IsNativeUTF8()) { + // ensure that we don't cut the name in mid-UTF8-character + // (assume the name is valid UTF8 to begin with) + while (UTF8traits::isInSeq(rootName[maxRootLength])) { + --maxRootLength; + } + + // Another check to avoid ending up with an empty leaf name. + if (maxRootLength == 0 && suffix.IsEmpty()) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + } + + rootName.SetLength(maxRootLength); + SetNativeLeafName(rootName + suffix); +#endif + nsresult rvCreate = Create(aType, aAttributes); + if (!FailedBecauseExists(rvCreate)) { + return rvCreate; + } + } + + for (int indx = 1; indx < 10000; ++indx) { + // start with "Picture-1.jpg" after "Picture.jpg" exists +#ifdef XP_WIN + SetLeafName(rootName + + NS_ConvertASCIItoUTF16(nsPrintfCString("-%d", indx)) + suffix); +#else + SetNativeLeafName(rootName + nsPrintfCString("-%d", indx) + suffix); +#endif + rv = Create(aType, aAttributes); + if (NS_SUCCEEDED(rv) || !FailedBecauseExists(rv)) { + return rv; + } + } + + // The disk is full, sort of + return NS_ERROR_FILE_TOO_BIG; +} + +#if defined(XP_WIN) +static const char16_t kPathSeparatorChar = '\\'; +#elif defined(XP_UNIX) +static const char16_t kPathSeparatorChar = '/'; +#else +# error Need to define file path separator for your platform +#endif + +static void SplitPath(char16_t* aPath, nsTArray<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(); + char16_t* fromPathPtr = fromPath.BeginWriting(); + + 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("../"); + } + StringJoinAppend(aResult, "/"_ns, mozilla::Span{thisNodes}.From(branchIndex), + [](nsACString& dest, const auto& thisNode) { + // XXX(Bug 1682869) We wouldn't need to reconstruct a + // nsDependentString here if SplitPath already returned + // nsDependentString. In fact, it seems SplitPath might be + // replaced by ParseString? + AppendUTF16toUTF8(nsDependentString{thisNode}, dest); + }); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetRelativeDescriptor(nsIFile* aFromFile, + const nsACString& aRelativeDesc) { + constexpr auto kParentDirStr = "../"_ns; + + 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/nsLocalFileCommon.h b/xpcom/io/nsLocalFileCommon.h new file mode 100644 index 0000000000..3db0ac9e89 --- /dev/null +++ b/xpcom/io/nsLocalFileCommon.h @@ -0,0 +1,16 @@ +/* -*- 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 _NS_LOCAL_FILE_COMMON_H_ +#define _NS_LOCAL_FILE_COMMON_H_ + +#ifdef MOZ_ESR +extern const char* const sExecutableExts[107]; +#else +extern const char* const sExecutableExts[108]; +#endif + +#endif diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp new file mode 100644 index 0000000000..f841a78da5 --- /dev/null +++ b/xpcom/io/nsLocalFileUnix.cpp @@ -0,0 +1,2916 @@ +/* -*- 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 "nsLocalFile.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Sprintf.h" +#include "mozilla/FilePreferences.h" +#include "prtime.h" + +#include <sys/select.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <dirent.h> + +#if defined(XP_MACOSX) +# include <sys/xattr.h> +#endif + +#if defined(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 "nsDirectoryServiceDefs.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsString.h" +#include "nsIDirectoryEnumerator.h" +#include "nsSimpleEnumerator.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 "mozilla/java/GeckoAppShellWrappers.h" +# include "nsIMIMEService.h" +# include <linux/magic.h> +#endif + +#include "nsNativeCharsetUtils.h" +#include "nsTraceRefcnt.h" + +/** + * 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 + +#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 + +using namespace mozilla; + +#define ENSURE_STAT_CACHE() \ + do { \ + if (!FillStatCache()) return NSRESULT_FOR_ERRNO(); \ + } while (0) + +#define CHECK_mPath() \ + do { \ + if (mPath.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; \ + if (!FilePreferences::IsAllowedPath(mPath)) \ + return NS_ERROR_FILE_ACCESS_DENIED; \ + } while (0) + +static PRTime TimespecToMillis(const struct timespec& aTimeSpec) { + return PRTime(aTimeSpec.tv_sec) * PR_MSEC_PER_SEC + + PRTime(aTimeSpec.tv_nsec) / PR_NSEC_PER_MSEC; +} + +/* directory enumerator */ +class nsDirEnumeratorUnix final : public nsSimpleEnumerator, + public nsIDirectoryEnumerator { + public: + nsDirEnumeratorUnix(); + + // nsISupports interface + NS_DECL_ISUPPORTS_INHERITED + + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR + + // nsIDirectoryEnumerator interface + NS_DECL_NSIDIRECTORYENUMERATOR + + NS_IMETHOD Init(nsLocalFile* aParent, bool aIgnored); + + NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::) + + const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); } + + private: + ~nsDirEnumeratorUnix() override; + + protected: + NS_IMETHOD GetNextEntry(); + + DIR* mDir; + struct dirent* mEntry; + nsCString mParentPath; +}; + +nsDirEnumeratorUnix::nsDirEnumeratorUnix() : mDir(nullptr), mEntry(nullptr) {} + +nsDirEnumeratorUnix::~nsDirEnumeratorUnix() { Close(); } + +NS_IMPL_ISUPPORTS_INHERITED(nsDirEnumeratorUnix, nsSimpleEnumerator, + 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; + } + + // When enumerating the directory, the paths must have a slash at the end. + nsAutoCString dirPathWithSlash(dirPath); + dirPathWithSlash.Append('/'); + if (!FilePreferences::IsAllowedPath(dirPathWithSlash)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + 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; + } + if (!file) { + return NS_ERROR_FAILURE; + } + file.forget(aResult); + 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() : mCachedStat() {} + +nsLocalFile::nsLocalFile(const nsACString& aFilePath) : mCachedStat() { + InitWithNativePath(aFilePath); +} + +nsLocalFile::nsLocalFile(const nsLocalFile& aOther) : mPath(aOther.mPath) {} + +#ifdef MOZ_WIDGET_COCOA +NS_IMPL_ISUPPORTS(nsLocalFile, nsILocalFileMac, nsIFile) +#else +NS_IMPL_ISUPPORTS(nsLocalFile, nsIFile) +#endif + +nsresult nsLocalFile::nsLocalFileConstructor(const nsIID& aIID, + void** aInstancePtr) { + if (NS_WARN_IF(!aInstancePtr)) { + return NS_ERROR_INVALID_ARG; + } + + *aInstancePtr = nullptr; + + nsCOMPtr<nsIFile> inst = new nsLocalFile(); + return inst->QueryInterface(aIID, aInstancePtr); +} + +bool nsLocalFile::FillStatCache() { + if (!FilePreferences::IsAllowedPath(mPath)) { + errno = EACCES; + return false; + } + + 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; + } + + if (!FilePreferences::IsAllowedPath(mPath)) { + mPath.Truncate(); + return NS_ERROR_FILE_ACCESS_DENIED; + } + + // 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) { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + // <jband> I promise to play nice + char* buffer = mPath.BeginWriting(); + char* slashp = buffer; + int mkdir_result = 0; + int mkdir_errno; + +#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 + mkdir_result = mkdir(buffer, aPermissions); + if (mkdir_result == -1) { + mkdir_errno = errno; + /* + * 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 (mkdir_errno != EEXIST && access(buffer, F_OK) == 0) { + mkdir_errno = EEXIST; + } +#ifdef DEBUG_NSIFILE + fprintf(stderr, "nsIFile: errno: %d\n", mkdir_errno); +#endif + } + + /* Put the / back */ + *slashp = '/'; + } + + /* + * We could get EEXIST for an existing file -- not directory -- + * but that's OK: we'll get ENOTDIR when we try to make the final + * component of the path back in Create and error out appropriately. + */ + if (mkdir_result == -1 && mkdir_errno != EEXIST) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode, + PRFileDesc** aResult) { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + *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) { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + *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, + bool aSkipAncestors, + PRFileDesc** aResult) { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + 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 && !aSkipAncestors) { + /* + * 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, + bool aSkipAncestors) { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + PRFileDesc* junk = nullptr; + nsresult rv = CreateAndKeepOpen( + aType, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE | PR_EXCL, aPermissions, + aSkipAncestors, &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 and cannot append ".." + nsACString::const_iterator begin, end; + if (aFragment.EqualsASCII("..") || + 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 '/' and cannot be ".." + if (aFragment.First() == '/' || aFragment.EqualsASCII("..")) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + if (aFragment.Contains('/')) { + // 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. + constexpr auto doubleDot = "/.."_ns; + nsACString::const_iterator start, end, offset; + aFragment.BeginReading(start); + aFragment.EndReading(end); + offset = end; + while (FindInReadable(doubleDot, start, offset)) { + if (offset == end || *offset == '/') { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + start = offset; + offset = end; + } + + // catches the remaining cases of prefixes + if (StringBeginsWith(aFragment, "../"_ns)) { + 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; + + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + 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::GetDisplayName(nsAString& aLeafName) { + return GetLeafName(aLeafName); +} + +nsCString nsLocalFile::NativePath() { return mPath; } + +nsresult nsIFile::GetNativePath(nsACString& aResult) { + aResult = NativePath(); + return NS_OK; +} + +nsCString nsIFile::HumanReadablePath() { + nsCString path; + DebugOnly<nsresult> rv = GetNativePath(path); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return path; +} + +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 + 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, ""_ns); + } + + 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<nsIDirectoryEnumerator> dirIterator; + if (NS_FAILED(rv = GetDirectoryEntries(getter_AddRefs(dirIterator)))) { + return rv; + } + + nsCOMPtr<nsIFile> entry; + while (NS_SUCCEEDED(dirIterator->GetNextFile(getter_AddRefs(entry))) && + entry) { + 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, ""_ns))) { +#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, ""_ns))) { +#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. + auto* 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 = 0; + rv = GetPermissions(&myPerms); + if (NS_FAILED(rv)) { + return rv; + } + + // 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, + /* aSkipAncestors = */ false, &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; + } + +#if defined(XP_MACOSX) + bool quarantined = true; + (void)HasXAttr("com.apple.quarantine"_ns, &quarantined); +#endif + + 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 defined(XP_MACOSX) + else if (!quarantined) { + // If the original file was not in quarantine, lift the quarantine that + // file creation added because of LSFileQuarantineEnabled. + (void)newFile->DelXAttr("com.apple.quarantine"_ns); + } +#endif // defined(XP_MACOSX) + + 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; + } + if (saved_read_error != NS_OK) { + return saved_read_error; + } +#if 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; + } + + if (!FilePreferences::IsAllowedPath(newPathName)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + // 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::MoveToFollowingLinksNative(nsIFile* aNewParent, + const nsACString& aNewName) { + return MoveToNative(aNewParent, aNewName); +} + +NS_IMETHODIMP +nsLocalFile::Remove(bool aRecursive, uint32_t* aRemoveCount) { + CHECK_mPath(); + ENSURE_STAT_CACHE(); + + bool isSymLink; + + nsresult rv = IsSymlink(&isSymLink); + if (NS_FAILED(rv)) { + return rv; + } + + if (isSymLink || !S_ISDIR(mCachedStat.st_mode)) { + rv = NSRESULT_FOR_RETURN(unlink(mPath.get())); + if (NS_SUCCEEDED(rv) && aRemoveCount) { + *aRemoveCount += 1; + } + return rv; + } + + if (aRecursive) { + auto* dir = new nsDirEnumeratorUnix(); + + RefPtr<nsSimpleEnumerator> dirRef(dir); // release on exit + + rv = dir->Init(this, false); + if (NS_FAILED(rv)) { + return rv; + } + + bool more; + while (NS_SUCCEEDED(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; + } + // XXX: We care the result of the removal here while + // nsLocalFileWin does not. We should align the behavior. (bug 1779696) + rv = file->Remove(aRecursive, aRemoveCount); + +#ifdef ANDROID + // See bug 580434 - Bionic gives us just deleted files + if (rv == NS_ERROR_FILE_NOT_FOUND) { + continue; + } +#endif + if (NS_FAILED(rv)) { + return rv; + } + } + } + + rv = NSRESULT_FOR_RETURN(rmdir(mPath.get())); + if (NS_SUCCEEDED(rv) && aRemoveCount) { + *aRemoveCount += 1; + } + return rv; +} + +nsresult nsLocalFile::GetTimeImpl(PRTime* aTime, + nsLocalFile::TimeField aTimeField, + bool aFollowLinks) { + CHECK_mPath(); + if (NS_WARN_IF(!aTime)) { + return NS_ERROR_INVALID_ARG; + } + + using StatFn = int (*)(const char*, struct STAT*); + StatFn statFn = aFollowLinks ? &STAT : &LSTAT; + + struct STAT fileStats {}; + if (statFn(mPath.get(), &fileStats) < 0) { + return NSRESULT_FOR_ERRNO(); + } + + struct timespec* timespec; + switch (aTimeField) { + case TimeField::AccessedTime: +#if (defined(__APPLE__) && defined(__MACH__)) + timespec = &fileStats.st_atimespec; +#else + timespec = &fileStats.st_atim; +#endif + break; + + case TimeField::ModifiedTime: +#if (defined(__APPLE__) && defined(__MACH__)) + timespec = &fileStats.st_mtimespec; +#else + timespec = &fileStats.st_mtim; +#endif + break; + + default: + MOZ_CRASH("Unknown TimeField"); + } + + *aTime = TimespecToMillis(*timespec); + + return NS_OK; +} + +nsresult nsLocalFile::SetTimeImpl(PRTime aTime, + nsLocalFile::TimeField aTimeField, + bool aFollowLinks) { + CHECK_mPath(); + + using UtimesFn = int (*)(const char*, const timeval*); + UtimesFn utimesFn = &utimes; + +#if HAVE_LUTIMES + if (!aFollowLinks) { + utimesFn = &lutimes; + } +#endif + + ENSURE_STAT_CACHE(); + + if (aTime == 0) { + aTime = PR_Now(); + } + + // We only want to write to a single field (accessed time or modified time), + // but utimes() doesn't let you omit one. If you do, it will set that field to + // the current time, which is not what we want. + // + // So what we do is write to both fields, but copy one of the fields from our + // cached stat structure. + // + // If we are writing to the accessed time field, then we want to copy the + // modified time and vice versa. + + timeval times[2]; + + const size_t writeIndex = aTimeField == TimeField::AccessedTime ? 0 : 1; + const size_t copyIndex = aTimeField == TimeField::AccessedTime ? 1 : 0; + +#if (defined(__APPLE__) && defined(__MACH__)) + auto* copyFrom = aTimeField == TimeField::AccessedTime + ? &mCachedStat.st_mtimespec + : &mCachedStat.st_atimespec; +#else + auto* copyFrom = aTimeField == TimeField::AccessedTime ? &mCachedStat.st_mtim + : &mCachedStat.st_atim; +#endif + + times[copyIndex].tv_sec = copyFrom->tv_sec; + times[copyIndex].tv_usec = copyFrom->tv_nsec / 1000; + + times[writeIndex].tv_sec = aTime / PR_MSEC_PER_SEC; + times[writeIndex].tv_usec = (aTime % PR_MSEC_PER_SEC) * PR_USEC_PER_MSEC; + + int result = utimesFn(mPath.get(), times); + return NSRESULT_FOR_RETURN(result); +} + +NS_IMETHODIMP +nsLocalFile::GetLastAccessedTime(PRTime* aLastAccessedTime) { + return GetTimeImpl(aLastAccessedTime, TimeField::AccessedTime, + /* follow links? */ true); +} + +NS_IMETHODIMP +nsLocalFile::SetLastAccessedTime(PRTime aLastAccessedTime) { + return SetTimeImpl(aLastAccessedTime, TimeField::AccessedTime, + /* follow links? */ true); +} + +NS_IMETHODIMP +nsLocalFile::GetLastAccessedTimeOfLink(PRTime* aLastAccessedTime) { + return GetTimeImpl(aLastAccessedTime, TimeField::AccessedTime, + /* follow links? */ false); +} + +NS_IMETHODIMP +nsLocalFile::SetLastAccessedTimeOfLink(PRTime aLastAccessedTime) { + return SetTimeImpl(aLastAccessedTime, TimeField::AccessedTime, + /* follow links? */ false); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTime(PRTime* aLastModTime) { + return GetTimeImpl(aLastModTime, TimeField::ModifiedTime, + /* follow links? */ true); +} + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTime(PRTime aLastModTime) { + return SetTimeImpl(aLastModTime, TimeField::ModifiedTime, + /* follow links ? */ true); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModTimeOfLink) { + return GetTimeImpl(aLastModTimeOfLink, TimeField::ModifiedTime, + /* follow link? */ false); +} + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink) { + return SetTimeImpl(aLastModTimeOfLink, TimeField::ModifiedTime, + /* follow links? */ false); +} + +NS_IMETHODIMP +nsLocalFile::GetCreationTime(PRTime* aCreationTime) { + return GetCreationTimeImpl(aCreationTime, false); +} + +NS_IMETHODIMP +nsLocalFile::GetCreationTimeOfLink(PRTime* aCreationTimeOfLink) { + return GetCreationTimeImpl(aCreationTimeOfLink, /* aFollowLinks = */ true); +} + +nsresult nsLocalFile::GetCreationTimeImpl(PRTime* aCreationTime, + bool aFollowLinks) { + CHECK_mPath(); + if (NS_WARN_IF(!aCreationTime)) { + return NS_ERROR_INVALID_ARG; + } + +#if defined(_DARWIN_FEATURE_64_BIT_INODE) + using StatFn = int (*)(const char*, struct STAT*); + StatFn statFn = aFollowLinks ? &STAT : &LSTAT; + + struct STAT fileStats {}; + if (statFn(mPath.get(), &fileStats) < 0) { + return NSRESULT_FOR_ERRNO(); + } + + *aCreationTime = TimespecToMillis(fileStats.st_birthtimespec); + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +/* + * 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(unsigned int aDeviceMajor, unsigned int aDeviceMinor, + nsACString& aDeviceName) { + bool ret = false; + + const int kMountInfoLineLength = 200; + const int kMountInfoDevPosition = 6; + + char mountinfoLine[kMountInfoLineLength]; + char deviceNum[kMountInfoLineLength]; + + SprintfLiteral(deviceNum, "%u:%u", 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 + +#if defined(USE_LINUX_QUOTACTL) +template <typename StatInfoFunc, typename QuotaInfoFunc> +nsresult nsLocalFile::GetDiskInfo(StatInfoFunc&& aStatInfoFunc, + QuotaInfoFunc&& aQuotaInfoFunc, + int64_t* aResult) +#else +template <typename StatInfoFunc> +nsresult nsLocalFile::GetDiskInfo(StatInfoFunc&& aStatInfoFunc, + int64_t* aResult) +#endif +{ + if (NS_WARN_IF(!aResult)) { + 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. + * f_blocks = number of total used or free blocks in file system. + */ + + if (STATFS(mPath.get(), &fs_buf) < 0) { + // The call to STATFS failed. +# ifdef DEBUG + printf("ERROR: GetDiskInfo: STATFS call FAILED. \n"); +# endif + return NS_ERROR_FAILURE; + } + + CheckedInt64 statfsResult = std::forward<StatInfoFunc>(aStatInfoFunc)(fs_buf); + if (!statfsResult.isValid()) { + return NS_ERROR_CANNOT_CONVERT_DATA; + } + + // Assign statfsResult to *aResult in case one of the quota calls fails. + *aResult = statfsResult.value(); + +# if defined(USE_LINUX_QUOTACTL) + + if (!FillStatCache()) { + // Returns info from statfs + return NS_OK; + } + + nsAutoCString deviceName; + if (!GetDeviceName(major(mCachedStat.st_dev), minor(mCachedStat.st_dev), + deviceName)) { + // Returns info from statfs + 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) { + CheckedInt64 quotaResult = std::forward<QuotaInfoFunc>(aQuotaInfoFunc)(dq); + if (!quotaResult.isValid()) { + // Returns info from statfs + return NS_OK; + } + + if (quotaResult.value() < *aResult) { + *aResult = quotaResult.value(); + } + } +# endif // defined(USE_LINUX_QUOTACTL) + +# ifdef DEBUG_DISK_SPACE + printf("DiskInfo: %lu bytes\n", *aResult); +# endif + + return NS_OK; + +#else // STATFS + /* + * This platform doesn't have statfs or statvfs. I'm sure that there's + * a way to check for free disk space and disk capacity 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: GetDiskInfo: Not implemented for plaforms without statfs.\n"); +# endif + return NS_ERROR_NOT_IMPLEMENTED; + +#endif // STATFS +} + +NS_IMETHODIMP +nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) { + return GetDiskInfo( + [](const struct STATFS& aStatInfo) { + return aStatInfo.f_bavail * static_cast<uint64_t>(aStatInfo.F_BSIZE); + }, +#if defined(USE_LINUX_QUOTACTL) + [](const struct dqblk& aQuotaInfo) -> uint64_t { + // dqb_bhardlimit is count of BLOCK_SIZE blocks, dqb_curspace is bytes + const uint64_t hardlimit = aQuotaInfo.dqb_bhardlimit * BLOCK_SIZE; + if (hardlimit > aQuotaInfo.dqb_curspace) { + return hardlimit - aQuotaInfo.dqb_curspace; + } + return 0; + }, +#endif + aDiskSpaceAvailable); +} + +NS_IMETHODIMP +nsLocalFile::GetDiskCapacity(int64_t* aDiskCapacity) { + return GetDiskInfo( + [](const struct STATFS& aStatInfo) { + return aStatInfo.f_blocks * static_cast<uint64_t>(aStatInfo.F_BSIZE); + }, +#if defined(USE_LINUX_QUOTACTL) + [](const struct dqblk& aQuotaInfo) { + // dqb_bhardlimit is count of BLOCK_SIZE blocks + return aQuotaInfo.dqb_bhardlimit * BLOCK_SIZE; + }, +#endif + aDiskCapacity); +} + +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[] = { +#ifdef MOZ_WIDGET_COCOA + "afploc", // Can point to other files. +#endif + "air", // Adobe AIR installer +#ifdef MOZ_WIDGET_COCOA + "atloc", // Can point to other files. + "fileloc", // File location files can be used to point to other + // files. + "ftploc", // Can point to other files. + "inetloc", // Shouldn't be able to do the same, but can, due to + // macOS vulnerabilities. +#endif + "jar" // java application bundle + }; + nsDependentSubstring ext = Substring(path, dotIdx + 1); + for (auto executableExt : executableExts) { + if (ext.EqualsASCII(executableExt)) { + // 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; +} + +static nsresult ReadLinkSafe(const nsCString& aTarget, int32_t aExpectedSize, + nsACString& aOutBuffer) { + // If we call readlink with a buffer size S it returns S, then we cannot tell + // if the buffer was big enough to hold the entire path. We allocate an + // additional byte so we can check if the buffer was large enough. + const auto allocSize = CheckedInt<size_t>(aExpectedSize) + 1; + if (!allocSize.isValid()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + auto result = aOutBuffer.BulkWrite(allocSize.value(), 0, false); + if (result.isErr()) { + return result.unwrapErr(); + } + + auto handle = result.unwrap(); + + while (true) { + ssize_t bytesWritten = + readlink(aTarget.get(), handle.Elements(), handle.Length()); + if (bytesWritten < 0) { + return NSRESULT_FOR_ERRNO(); + } + + // written >= 0 so it is safe to cast to size_t. + if ((size_t)bytesWritten < handle.Length()) { + // Target might have changed since the lstat call, or lstat might lie, see + // bug 1791029. + handle.Finish(bytesWritten, false); + return NS_OK; + } + + // The buffer was not large enough, so double it and try again. + auto restartResult = handle.RestartBulkWrite(handle.Length() * 2, 0, false); + if (restartResult.isErr()) { + return restartResult.unwrapErr(); + } + } +} + +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; + } + + nsAutoCString target; + nsresult rv = ReadLinkSafe(mPath, symStat.st_size, target); + if (NS_FAILED(rv)) { + return rv; + } + + 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(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; + } + + nsAutoCString newTarget; + rv = ReadLinkSafe(flatRetval, symStat.st_size, newTarget); + if (NS_FAILED(rv)) { + break; + } + + target = newTarget; + } + + if (NS_FAILED(rv)) { + aResult.Truncate(); + } + return rv; +} + +NS_IMETHODIMP +nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** 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); // PL_Base64Decode() uses PR_Malloc(). + 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); // PL_Base64Decode() uses PR_Malloc(). + 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() { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + +#ifdef MOZ_WIDGET_GTK + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (!giovfs) { + return NS_ERROR_FAILURE; + } + return giovfs->RevealFile(this); +#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() { + if (!FilePreferences::IsAllowedPath(mPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + +#ifdef MOZ_WIDGET_GTK + nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID); + if (!giovfs) { + return NS_ERROR_FAILURE; + } + + return giovfs->LaunchFile(mPath); +#elif defined(MOZ_WIDGET_ANDROID) + // Not supported on GeckoView + return NS_ERROR_NOT_IMPLEMENTED; +#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(); + + 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::MoveToFollowingLinks(nsIFile* aNewParentDir, + const nsAString& aNewName) { + SET_UCS_2ARGS_2(MoveToFollowingLinksNative, 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; + } + + if (!FilePreferences::IsAllowedPath(newPathName)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + // 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); +} + +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); +} + +// nsILocalFileMac + +#ifdef MOZ_WIDGET_COCOA + +NS_IMETHODIMP +nsLocalFile::HasXAttr(const nsACString& aAttrName, bool* aHasAttr) { + NS_ENSURE_ARG_POINTER(aHasAttr); + + nsAutoCString attrName{aAttrName}; + + ssize_t size = getxattr(mPath.get(), attrName.get(), nullptr, 0, 0, 0); + if (size == -1) { + if (errno == ENOATTR) { + *aHasAttr = false; + } else { + return NSRESULT_FOR_ERRNO(); + } + } else { + *aHasAttr = true; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetXAttr(const nsACString& aAttrName, + nsTArray<uint8_t>& aAttrValue) { + aAttrValue.Clear(); + + nsAutoCString attrName{aAttrName}; + + ssize_t size = getxattr(mPath.get(), attrName.get(), nullptr, 0, 0, 0); + + if (size == -1) { + return NSRESULT_FOR_ERRNO(); + } + + for (;;) { + aAttrValue.SetCapacity(size); + + // The attribute can change between our first call and this call, so we need + // to re-check the size and possibly call with a larger buffer. + ssize_t newSize = getxattr(mPath.get(), attrName.get(), + aAttrValue.Elements(), size, 0, 0); + if (newSize == -1) { + return NSRESULT_FOR_ERRNO(); + } + + if (newSize <= size) { + aAttrValue.SetLength(newSize); + break; + } else { + size = newSize; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetXAttr(const nsACString& aAttrName, + const nsTArray<uint8_t>& aAttrValue) { + nsAutoCString attrName{aAttrName}; + + if (setxattr(mPath.get(), attrName.get(), aAttrValue.Elements(), + aAttrValue.Length(), 0, 0) == -1) { + return NSRESULT_FOR_ERRNO(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::DelXAttr(const nsACString& aAttrName) { + nsAutoCString attrName{aAttrName}; + + // Ignore removing an attribute that does not exist. + if (removexattr(mPath.get(), attrName.get(), 0) == -1) { + return NSRESULT_FOR_ERRNO(); + } + + return NS_OK; +} + +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_NO_DEVICE_SPACE; + 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(); + + 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(); + + 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..eb37e3effd --- /dev/null +++ b/xpcom/io/nsLocalFileUnix.h @@ -0,0 +1,104 @@ +/* -*- 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 "nscore.h" +#include "nsString.h" +#ifdef MOZ_WIDGET_COCOA +# include "nsILocalFileMac.h" +#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 + +#if defined(HAVE_SYS_QUOTA_H) && defined(HAVE_LINUX_QUOTA_H) +# define USE_LINUX_QUOTACTL +#endif + +class nsLocalFile final +#ifdef MOZ_WIDGET_COCOA + : public nsILocalFileMac +#else + : public nsIFile +#endif +{ + public: + NS_DEFINE_STATIC_CID_ACCESSOR(NS_LOCAL_FILE_CID) + + nsLocalFile(); + explicit nsLocalFile(const nsACString& aFilePath); + + static nsresult nsLocalFileConstructor(const nsIID& aIID, + void** aInstancePtr); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIFILE +#ifdef MOZ_WIDGET_COCOA + NS_DECL_NSILOCALFILEMAC +#endif + + private: + nsLocalFile(const nsLocalFile& aOther); + ~nsLocalFile() = default; + + 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, + bool aSkipAncestors, PRFileDesc** aResult); + + enum class TimeField { AccessedTime, ModifiedTime }; + nsresult SetTimeImpl(PRTime aTime, TimeField aTimeField, bool aFollowLinks); + nsresult GetTimeImpl(PRTime* aTime, TimeField aTimeField, bool aFollowLinks); + + nsresult GetCreationTimeImpl(PRTime* aCreationTime, bool aFollowLinks); + +#if defined(USE_LINUX_QUOTACTL) + template <typename StatInfoFunc, typename QuotaInfoFunc> + nsresult GetDiskInfo(StatInfoFunc&& aStatInfoFunc, + QuotaInfoFunc&& aQuotaInfoFunc, int64_t* aResult); +#else + template <typename StatInfoFunc> + nsresult GetDiskInfo(StatInfoFunc&& aStatInfoFunc, int64_t* aResult); +#endif +}; + +#endif /* _nsLocalFileUNIX_H_ */ diff --git a/xpcom/io/nsLocalFileWin.cpp b/xpcom/io/nsLocalFileWin.cpp new file mode 100644 index 0000000000..13fea1d2ca --- /dev/null +++ b/xpcom/io/nsLocalFileWin.cpp @@ -0,0 +1,3697 @@ +/* -*- 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/ProfilerLabels.h" +#include "mozilla/TextUtils.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/Utf8.h" +#include "mozilla/WinHeaderOnlyUtils.h" + +#include "nsCOMPtr.h" + +#include "nsLocalFile.h" +#include "nsLocalFileCommon.h" +#include "nsIDirectoryEnumerator.h" +#include "nsNativeCharsetUtils.h" + +#include "nsSimpleEnumerator.h" +#include "prio.h" +#include "private/pprio.h" // To get PR_ImportFile +#include "nsHashKeys.h" + +#include "nsString.h" +#include "nsReadableUtils.h" + +#include <direct.h> +#include <fileapi.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 "prproces.h" +#include "prlink.h" + +#include "mozilla/FilePreferences.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/ShellHeaderOnlyUtils.h" +#include "mozilla/WidgetUtils.h" +#include "WinUtils.h" + +using namespace mozilla; +using mozilla::FilePreferences::kDevicePathSpecifier; +using mozilla::FilePreferences::kPathSeparator; + +#define CHECK_mWorkingPath() \ + do { \ + if (mWorkingPath.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; \ + } while (0) + +#ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED +# define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000 +#endif + +#ifndef DRIVE_REMOTE +# define DRIVE_REMOTE 4 +#endif + +namespace { + +nsresult NewLocalFile(const nsAString& aPath, bool aUseDOSDevicePathSyntax, + nsIFile** aResult) { + RefPtr<nsLocalFile> file = new nsLocalFile(); + + file->SetUseDOSDevicePathSyntax(aUseDOSDevicePathSyntax); + + if (!aPath.IsEmpty()) { + nsresult rv = file->InitWithPath(aPath); + if (NS_FAILED(rv)) { + return rv; + } + } + + file.forget(aResult); + return NS_OK; +} + +} // anonymous namespace + +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)); +} + +nsresult nsLocalFile::RevealFile(const nsString& aResolvedPath) { + MOZ_ASSERT(!NS_IsMainThread(), "Don't run on the main thread"); + + DWORD attributes = GetFileAttributesW(aResolvedPath.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(aResolvedPath.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 = aResolvedPath.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, aResolvedPath.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(aResolvedPath.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; +} + +// static +bool nsLocalFile::CheckForReservedFileName(const nsString& aFileName) { + static const nsLiteralString forbiddenNames[] = { + u"COM1"_ns, u"COM2"_ns, u"COM3"_ns, u"COM4"_ns, u"COM5"_ns, u"COM6"_ns, + u"COM7"_ns, u"COM8"_ns, u"COM9"_ns, u"LPT1"_ns, u"LPT2"_ns, u"LPT3"_ns, + u"LPT4"_ns, u"LPT5"_ns, u"LPT6"_ns, u"LPT7"_ns, u"LPT8"_ns, u"LPT9"_ns, + u"CON"_ns, u"PRN"_ns, u"AUX"_ns, u"NUL"_ns, u"CLOCK$"_ns}; + + for (const nsLiteralString& forbiddenName : forbiddenNames) { + if (StringBeginsWith(aFileName, forbiddenName, + nsASCIICaseInsensitiveStringComparator)) { + // invalid name is either the entire string, or a prefix with a period + if (aFileName.Length() == forbiddenName.Length() || + aFileName.CharAt(forbiddenName.Length()) == char16_t('.')) { + return true; + } + } + } + + return false; +} + +class nsDriveEnumerator : public nsSimpleEnumerator, + public nsIDirectoryEnumerator { + public: + explicit nsDriveEnumerator(bool aUseDOSDevicePathSyntax); + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSISIMPLEENUMERATOR + NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::) + nsresult Init(); + + const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); } + + NS_IMETHOD GetNextFile(nsIFile** aResult) override { + bool hasMore = false; + nsresult rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv) || !hasMore) { + return rv; + } + nsCOMPtr<nsISupports> next; + rv = GetNext(getter_AddRefs(next)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> result = do_QueryInterface(next); + result.forget(aResult); + return NS_OK; + } + + NS_IMETHOD Close() override { return NS_OK; } + + 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; + const bool mUseDOSDevicePathSyntax; +}; + +//----------------------------------------------------------------------------- +// static helper functions +//----------------------------------------------------------------------------- + +/** + * While not comprehensive, this will map many common Windows error codes to a + * corresponding nsresult. If an unmapped error is encountered, the hex error + * code will be logged to stderr. Error codes, names, and descriptions can be + * found at the following MSDN page: + * https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes + * + * \note When adding more mappings here, it must be checked if there's code that + * depends on the current generic NS_ERROR_MODULE_WIN32 mapping for such error + * codes. + */ +static nsresult ConvertWinError(DWORD aWinErr) { + nsresult rv; + + switch (aWinErr) { + case ERROR_FILE_NOT_FOUND: + [[fallthrough]]; // to NS_ERROR_FILE_NOT_FOUND + case ERROR_PATH_NOT_FOUND: + [[fallthrough]]; // to NS_ERROR_FILE_NOT_FOUND + case ERROR_INVALID_DRIVE: + rv = NS_ERROR_FILE_NOT_FOUND; + break; + case ERROR_ACCESS_DENIED: + [[fallthrough]]; // to NS_ERROR_FILE_ACCESS_DENIED + case ERROR_NOT_SAME_DEVICE: + [[fallthrough]]; // to NS_ERROR_FILE_ACCESS_DENIED + case ERROR_CANNOT_MAKE: + [[fallthrough]]; // to NS_ERROR_FILE_ACCESS_DENIED + case ERROR_CONTENT_BLOCKED: + rv = NS_ERROR_FILE_ACCESS_DENIED; + break; + case ERROR_SHARING_VIOLATION: // CreateFile without sharing flags + [[fallthrough]]; // to NS_ERROR_FILE_IS_LOCKED + case ERROR_LOCK_VIOLATION: // LockFile, LockFileEx + rv = NS_ERROR_FILE_IS_LOCKED; + break; + case ERROR_NOT_ENOUGH_MEMORY: + [[fallthrough]]; // to NS_ERROR_OUT_OF_MEMORY + case ERROR_NO_SYSTEM_RESOURCES: + rv = NS_ERROR_OUT_OF_MEMORY; + break; + case ERROR_DIR_NOT_EMPTY: + [[fallthrough]]; // to NS_ERROR_FILE_DIR_NOT_EMPTY + 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: + [[fallthrough]]; // to NS_ERROR_FILE_NO_DEVICE_SPACE + case ERROR_DISK_FULL: + rv = NS_ERROR_FILE_NO_DEVICE_SPACE; + break; + case ERROR_FILE_EXISTS: + [[fallthrough]]; // to NS_ERROR_FILE_ALREADY_EXISTS + case ERROR_ALREADY_EXISTS: + 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 ERROR_FILE_CORRUPT: + [[fallthrough]]; // to NS_ERROR_FILE_FS_CORRUPTED + case ERROR_DISK_CORRUPT: + rv = NS_ERROR_FILE_FS_CORRUPTED; + break; + case ERROR_DEVICE_HARDWARE_ERROR: + [[fallthrough]]; // to NS_ERROR_FILE_DEVICE_FAILURE + case ERROR_DEVICE_NOT_CONNECTED: + [[fallthrough]]; // to NS_ERROR_FILE_DEVICE_FAILURE + case ERROR_DEV_NOT_EXIST: + [[fallthrough]]; // to NS_ERROR_FILE_DEVICE_FAILURE + case ERROR_IO_DEVICE: + rv = NS_ERROR_FILE_DEVICE_FAILURE; + break; + case ERROR_NOT_READY: + rv = NS_ERROR_FILE_DEVICE_TEMPORARY_FAILURE; + break; + case ERROR_INVALID_NAME: + rv = NS_ERROR_FILE_INVALID_PATH; + break; + case ERROR_INVALID_BLOCK: + [[fallthrough]]; // to NS_ERROR_FILE_INVALID_HANDLE + case ERROR_INVALID_HANDLE: + [[fallthrough]]; // to NS_ERROR_FILE_INVALID_HANDLE + case ERROR_ARENA_TRASHED: + rv = NS_ERROR_FILE_INVALID_HANDLE; + break; + case 0: + rv = NS_OK; + break; + default: + printf_stderr( + "ConvertWinError received an unrecognized WinError: 0x%" PRIx32 "\n", + static_cast<uint32_t>(aWinErr)); + MOZ_ASSERT((aWinErr & 0xFFFF) == aWinErr); + rv = NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_WIN32, aWinErr & 0xFFFF); + break; + } + return rv; +} + +// Check whether a path is a volume root. Expects paths to be \-terminated. +static bool IsRootPath(const nsAString& aPath) { + // Easy cases first: + if (aPath.Last() != L'\\') { + return false; + } + if (StringEndsWith(aPath, u":\\"_ns)) { + return true; + } + + nsAString::const_iterator begin, end; + aPath.BeginReading(begin); + aPath.EndReading(end); + // We know we've got a trailing slash, skip that: + end--; + // Find the next last slash: + if (RFindInReadable(u"\\"_ns, begin, end)) { + // Reset iterator: + aPath.EndReading(end); + end--; + auto lastSegment = Substring(++begin, end); + if (lastSegment.IsEmpty()) { + return false; + } + + // Check if we end with e.g. "c$", a drive letter in UNC or network shares + if (lastSegment.Last() == L'$' && lastSegment.Length() == 2 && + IsAsciiAlpha(lastSegment.First())) { + return true; + } + // Volume GUID paths: + if (StringBeginsWith(lastSegment, u"Volume{"_ns) && + lastSegment.Last() == L'}') { + return true; + } + } + return false; +} + +static auto kSpecialNTFSFilesInRoot = { + u"$MFT"_ns, u"$MFTMirr"_ns, u"$LogFile"_ns, u"$Volume"_ns, + u"$AttrDef"_ns, u"$Bitmap"_ns, u"$Boot"_ns, u"$BadClus"_ns, + u"$Secure"_ns, u"$UpCase"_ns, u"$Extend"_ns}; +static bool IsSpecialNTFSPath(const nsAString& aFilePath) { + nsAString::const_iterator begin, end; + aFilePath.BeginReading(begin); + aFilePath.EndReading(end); + auto iter = begin; + // Early exit if there's no '$' (common case) + if (!FindCharInReadable(L'$', iter, end)) { + return false; + } + + iter = begin; + // Any use of ':$' is illegal in filenames anyway; while we support some + // ADS stuff (ie ":Zone.Identifier"), none of them use the ':$' syntax: + if (FindInReadable(u":$"_ns, iter, end)) { + return true; + } + + auto normalized = mozilla::MakeUniqueFallible<wchar_t[]>(MAX_PATH); + if (!normalized) { + return true; + } + auto flatPath = PromiseFlatString(aFilePath); + auto fullPathRV = + GetFullPathNameW(flatPath.get(), MAX_PATH - 1, normalized.get(), nullptr); + if (fullPathRV == 0 || fullPathRV > MAX_PATH - 1) { + return false; + } + + nsString normalizedPath(normalized.get()); + normalizedPath.BeginReading(begin); + normalizedPath.EndReading(end); + iter = begin; + auto kDelimiters = u"\\:"_ns; + while (iter != end && FindCharInReadable(L'$', iter, end)) { + for (auto str : kSpecialNTFSFilesInRoot) { + if (StringBeginsWith(Substring(iter, end), str, + nsCaseInsensitiveStringComparator)) { + // If we're enclosed by separators or the beginning/end of the string, + // this is one of the special files. Check if we're on a volume root. + auto iterCopy = iter; + iterCopy.advance(str.Length()); + // We check for both \ and : here because the filename could be + // followd by a colon and a stream name/type, which shouldn't affect + // our check: + if (iterCopy == end || kDelimiters.Contains(*iterCopy)) { + iterCopy = iter; + // At the start of this path component, we don't need to care about + // colons: we would have caught those in the check for `:$` above. + if (iterCopy == begin || *(--iterCopy) == L'\\') { + return IsRootPath(Substring(begin, iter)); + } + } + } + } + iter++; + } + + return false; +} + +//----------------------------------------------------------------------------- +// 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 nsString& 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 nsString& aName, + nsLocalFile::FileInfo* aInfo) { + if (aName.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + // Checking u"?*" for the file path excluding the kDevicePathSpecifier. + // ToDo: Check if checking "?" for the file path is still needed. + const int32_t offset = StringBeginsWith(aName, kDevicePathSpecifier) + ? kDevicePathSpecifier.Length() + : 0; + + if (aName.FindCharInSet(u"?*", offset) != kNotFound) { + return NS_ERROR_INVALID_ARG; + } + + WIN32_FILE_ATTRIBUTE_DATA fileData; + if (!::GetFileAttributesExW(aName.get(), GetFileExInfoStandard, &fileData)) { + return ConvertWinError(GetLastError()); + } + + if (fileData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + aInfo->type = PR_FILE_OTHER; + } else 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; + + if (0 == fileData.ftCreationTime.dwLowDateTime && + 0 == fileData.ftCreationTime.dwHighDateTime) { + aInfo->creationTime = aInfo->modifyTime; + } else { + FileTimeToPRTime(&fileData.ftCreationTime, &aInfo->creationTime); + } + + FileTimeToPRTime(&fileData.ftLastAccessTime, &aInfo->accessTime); + FileTimeToPRTime(&fileData.ftLastWriteTime, &aInfo->modifyTime); + + return NS_OK; +} + +struct nsDir { + HANDLE handle; + WIN32_FIND_DATAW data; + bool firstEntry; +}; + +static nsresult OpenDir(const nsString& aName, nsDir** aDir) { + if (NS_WARN_IF(!aDir)) { + return NS_ERROR_INVALID_ARG; + } + + *aDir = nullptr; + + 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 nsSimpleEnumerator, + public nsIDirectoryEnumerator { + private: + ~nsDirEnumerator() { Close(); } + + public: + NS_DECL_ISUPPORTS_INHERITED + + NS_FORWARD_NSISIMPLEENUMERATORBASE(nsSimpleEnumerator::) + + nsDirEnumerator() : mDir(nullptr) {} + + const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); } + + 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) override { + 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 + rv = CloseDir(mDir); + if (NS_FAILED(rv)) { + return rv; + } + + *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 = file.forget(); + } + *aResult = mNext != nullptr; + if (!*aResult) { + Close(); + } + return NS_OK; + } + + NS_IMETHOD GetNext(nsISupports** aResult) override { + nsresult rv; + bool hasMore; + rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv)) { + return rv; + } + if (!hasMore) { + return NS_ERROR_FAILURE; + } + + mNext.forget(aResult); + return NS_OK; + } + + NS_IMETHOD GetNextFile(nsIFile** aResult) override { + *aResult = nullptr; + bool hasMore = false; + nsresult rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv) || !hasMore) { + return rv; + } + mNext.forget(aResult); + return NS_OK; + } + + NS_IMETHOD Close() override { + 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_INHERITED(nsDirEnumerator, nsSimpleEnumerator, + nsIDirectoryEnumerator) + +//----------------------------------------------------------------------------- +// nsLocalFile <public> +//----------------------------------------------------------------------------- + +nsLocalFile::nsLocalFile() + : mDirty(true), mResolveDirty(true), mUseDOSDevicePathSyntax(false) {} + +nsLocalFile::nsLocalFile(const nsAString& aFilePath) + : mUseDOSDevicePathSyntax(false) { + InitWithPath(aFilePath); +} + +nsresult nsLocalFile::nsLocalFileConstructor(const nsIID& aIID, + void** aInstancePtr) { + if (NS_WARN_IF(!aInstancePtr)) { + return NS_ERROR_INVALID_ARG; + } + + 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, nsIFile, nsILocalFileWin) + +//----------------------------------------------------------------------------- +// nsLocalFile <private> +//----------------------------------------------------------------------------- + +nsLocalFile::nsLocalFile(const nsLocalFile& aOther) + : mDirty(true), + mResolveDirty(true), + mUseDOSDevicePathSyntax(aOther.mUseDOSDevicePathSyntax), + mWorkingPath(aOther.mWorkingPath) {} + +nsresult nsLocalFile::ResolveSymlink() { + std::wstring workingPath(mWorkingPath.Data()); + if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(workingPath)) { + return NS_ERROR_FAILURE; + } + mResolvedPath.Assign(workingPath.c_str(), workingPath.length()); + return NS_OK; +} + +// Resolve any shortcuts and stat the resolved path. After a successful return +// the path is guaranteed valid and the members of mFileInfo can be used. +nsresult nsLocalFile::ResolveAndStat() { + // if we aren't dirty then we are already done + if (!mDirty) { + return NS_OK; + } + + AUTO_PROFILER_LABEL("nsLocalFile::ResolveAndStat", 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); + + // Make sure root paths have a trailing slash. + nsAutoString nsprPath(mWorkingPath); + if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == u':') { + 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, &mFileInfo); + if (NS_FAILED(rv)) { + return rv; + } + + if (mFileInfo.type != PR_FILE_OTHER) { + mResolveDirty = false; + mDirty = false; + return NS_OK; + } + + // OTHER from GetFileInfo currently means a symlink + rv = ResolveSymlink(); + // Even if it fails we need to have the resolved path equal to working path + // for those functions that always use the resolved path. + if (NS_FAILED(rv)) { + mResolvedPath.Assign(mWorkingPath); + return rv; + } + + mResolveDirty = false; + // get the details of the resolved path + rv = GetFileInfo(mResolvedPath, &mFileInfo); + 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); + + // TODO: Implement symlink support + + mResolveDirty = false; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsLocalFile::nsIFile +//----------------------------------------------------------------------------- + +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 (FilePreferences::IsBlockedUNCPath(aFilePath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + 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 (MozPathGetDriveNumber<wchar_t>(aFilePath.Data()) == -1) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + } + + if (IsSpecialNTFSPath(aFilePath)) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + mWorkingPath = aFilePath; + // kill any trailing '\' + if (mWorkingPath.Last() == L'\\') { + mWorkingPath.Truncate(mWorkingPath.Length() - 1); + } + + // Bug 1626514: make sure that we don't end up with multiple prefixes. + + // Prepend the "\\?\" prefix if the useDOSDevicePathSyntax is set and the path + // starts with a disk designator and backslash. + if (mUseDOSDevicePathSyntax && + FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath)) { + mWorkingPath = kDevicePathSpecifier + mWorkingPath; + } + + 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 + int32_t index = aPath.LowerCaseFindASCII(".exe "); + if (index == kNotFound) index = aPath.LowerCaseFindASCII(".dll "); + if (index == kNotFound) index = aPath.LowerCaseFindASCII(".cpl "); + + 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 + + constexpr auto rundllSegment = "rundll32.exe "_ns; + constexpr auto rundllSegmentShort = "rundll32 "_ns; + + // case insensitive + int32_t strLen = rundllSegment.Length(); + int32_t index = aCommandString.LowerCaseFindASCII(rundllSegment); + if (index == kNotFound) { + strLen = rundllSegmentShort.Length(); + index = aCommandString.LowerCaseFindASCII(rundllSegmentShort); + } + + 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(), nullptr, 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(u"\""); + + // 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) { + *aResult = _wfopen(mWorkingPath.get(), NS_ConvertASCIItoUTF16(aMode).get()); + if (*aResult) { + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +static nsresult do_create(nsIFile* aFile, const nsString& aPath, + uint32_t aAttributes) { + PRFileDesc* file; + nsresult rv = + OpenFile(aPath, 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(aFile->IsDirectory(&isdir)) && isdir) { + rv = NS_ERROR_FILE_ALREADY_EXISTS; + } + } + return rv; +} + +static nsresult do_mkdir(nsIFile*, const nsString& aPath, uint32_t) { + if (!::CreateDirectoryW(aPath.get(), nullptr)) { + return ConvertWinError(GetLastError()); + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Create(uint32_t aType, uint32_t aAttributes, bool aSkipAncestors) { + if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) { + return NS_ERROR_FILE_UNKNOWN_TYPE; + } + + auto* createFunc = (aType == NORMAL_FILE_TYPE ? do_create : do_mkdir); + + nsresult rv = createFunc(this, mWorkingPath, aAttributes); + + if (NS_SUCCEEDED(rv) || NS_ERROR_FILE_ALREADY_EXISTS == rv || + aSkipAncestors) { + 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 = char16ptr_t(mWorkingPath.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(mWorkingPath.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 our last CreateDirectory failed due to access, return that. + if (NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) { + return directoryCreateError; + } + + return createFunc(this, mWorkingPath, aAttributes); +} + +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 nsString& 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. + constexpr auto doubleDot = u"\\.."_ns; + 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, u"..\\"_ns)) { + 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); + + if (IsSpecialNTFSPath(mWorkingPath)) { + // Revert changes to mWorkingPath: + mWorkingPath.SetLength(mWorkingPath.Length() - aNode.Length() - 1); + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + return NS_OK; +} + +nsresult nsLocalFile::OpenNSPRFileDescMaybeShareDelete(int32_t aFlags, + int32_t aMode, + bool aShareDelete, + PRFileDesc** aResult) { + return OpenFile(mWorkingPath, 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 + u"\\"_ns); + } + } + + MOZ_ASSERT(0 < rootIdx && rootIdx < (int32_t)path.Length(), + "rootIdx is invalid"); + MOZ_ASSERT(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'\\'); + nsString newDir; + if (offset) { + newDir = Substring(mWorkingPath, 0, offset + 1) + aLeafName; + } else { + newDir = mWorkingPath + aLeafName; + } + if (IsSpecialNTFSPath(newDir)) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + + mWorkingPath.Assign(newDir); + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetDisplayName(nsAString& aLeafName) { + aLeafName.Truncate(); + + if (mWorkingPath.IsEmpty()) { + return NS_ERROR_FILE_UNRECOGNIZED_PATH; + } + SHFILEINFOW sfi = {}; + DWORD_PTR result = ::SHGetFileInfoW(mWorkingPath.get(), 0, &sfi, sizeof(sfi), + SHGFI_DISPLAYNAME); + // If we found a display name, return that: + if (result) { + aLeafName.Assign(sfi.szDisplayName); + return NS_OK; + } + // Nope - fall back to the regular leaf name. + return GetLeafName(aLeafName); +} + +NS_IMETHODIMP +nsLocalFile::GetPath(nsAString& aResult) { + MOZ_ASSERT_IF( + mUseDOSDevicePathSyntax, + !FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath)); + 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 = NS_ERROR_FAILURE; + + const WCHAR* path = 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, aField); + 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::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; + rv = aDestParent->GetTarget(destPath); + if (NS_FAILED(rv)) { + return rv; + } + + 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; + } + +#ifdef DEBUG + nsCOMPtr<nsILocalFileWin> srcWinFile = do_QueryInterface(aSourceFile); + MOZ_ASSERT(srcWinFile); + + bool srcUseDOSDevicePathSyntax; + srcWinFile->GetUseDOSDevicePathSyntax(&srcUseDOSDevicePathSyntax); + + nsCOMPtr<nsILocalFileWin> destWinFile = do_QueryInterface(aDestParent); + MOZ_ASSERT(destWinFile); + + bool destUseDOSDevicePathSyntax; + destWinFile->GetUseDOSDevicePathSyntax(&destUseDOSDevicePathSyntax); + + MOZ_ASSERT(srcUseDOSDevicePathSyntax == destUseDOSDevicePathSyntax, + "Copy or Move files with different values for " + "useDOSDevicePathSyntax would fail"); +#endif + + if (FilePreferences::IsBlockedUNCPath(destPath)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + int copyOK = 0; + if (move) { + copyOK = ::MoveFileExW(filePath.get(), destPath.get(), + MOVEFILE_REPLACE_EXISTING); + } + + // If we either failed to move the file, or this is a copy, try copying: + if (!copyOK && (!move || GetLastError() == ERROR_NOT_SAME_DEVICE)) { + // Failed renames here should just return access denied. + if (move && (aOptions & Rename)) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + // 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. + DWORD dwCopyFlags = COPY_FILE_ALLOW_DECRYPTED_DESTINATION; + bool path1Remote, path2Remote; + if (!IsRemoteFilePath(filePath.get(), path1Remote) || + !IsRemoteFilePath(destPath.get(), path2Remote) || path1Remote || + path2Remote) { + dwCopyFlags |= COPY_FILE_NO_BUFFERING; + } + + copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr, nullptr, + nullptr, dwCopyFlags); + // On Windows 10, copying without buffering has started failing, so try + // with buffering... + if (!copyOK && (dwCopyFlags & COPY_FILE_NO_BUFFERING) && + GetLastError() == ERROR_INVALID_PARAMETER) { + dwCopyFlags &= ~COPY_FILE_NO_BUFFERING; + copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr, nullptr, + nullptr, dwCopyFlags); + } + + if (move && 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); + UniquePtr<VOID, LocalFreeDeleter> autoFreeSecDesc(pSD); + if (pOldDACL) { + // Test the current DACL, if we find one that is inherited then we can + // skip the reset. This avoids a request for SeTcbPrivilege, which can + // cause a lot of audit events if enabled (Bug 1816694). + bool inherited = false; + for (DWORD i = 0; i < pOldDACL->AceCount; ++i) { + VOID* pAce = nullptr; + if (::GetAce(pOldDACL, i, &pAce) && + static_cast<PACE_HEADER>(pAce)->AceFlags & INHERITED_ACE) { + inherited = true; + break; + } + } + + if (!inherited) { + ::SetNamedSecurityInfoW( + (LPWSTR)destPath.get(), SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION, + nullptr, nullptr, pOldDACL, nullptr); + } + } + } + + return rv; +} + +nsresult nsLocalFile::CopyMove(nsIFile* aParentDir, const nsAString& aNewName, + uint32_t aOptions) { + bool move = aOptions & (Move | Rename); + bool followSymlinks = aOptions & FollowSymlinks; + // If we're not provided with a new parent, we're copying or moving to + // another file in the same directory and can safely skip checking if the + // destination directory exists: + bool targetInSameDirectory = !aParentDir; + + 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; + } + + if (!targetInSameDirectory) { + // make sure it exists and is a directory. Create it if not there. + bool exists = false; + rv = newParentDir->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + + if (!exists) { + rv = newParentDir->Create(DIRECTORY_TYPE, + 0644); // TODO, what permissions should we use + if (NS_FAILED(rv)) { + return rv; + } + } else { + bool isDir = false; + rv = newParentDir->IsDirectory(&isDir); + if (NS_FAILED(rv)) { + return rv; + } + + if (!isDir) { + if (followSymlinks) { + bool isLink = false; + rv = newParentDir->IsSymlink(&isLink); + if (NS_FAILED(rv)) { + return rv; + } + + if (isLink) { + nsAutoString target; + rv = newParentDir->GetTarget(target); + if (NS_FAILED(rv)) { + return rv; + } + + 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 = false; + rv = IsDirectory(&isDir); + if (NS_FAILED(rv)) { + return rv; + } + + bool isSymlink = false; + rv = IsSymlink(&isSymlink); + if (NS_FAILED(rv)) { + return rv; + } + + // 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 = false; + rv = IsSymlink(&isLink); + if (NS_FAILED(rv)) { + return rv; + } + + if (isLink) { + nsAutoString temp; + rv = GetTarget(temp); + if (NS_FAILED(rv)) { + return rv; + } + + 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(); + + bool exists = false; + // check if the destination directory already exists + rv = target->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + + 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 = false; + rv = target->IsWritable(&isWritable); + if (NS_FAILED(rv)) { + return rv; + } + + if (!isWritable) { + return NS_ERROR_FILE_ACCESS_DENIED; + } + + nsCOMPtr<nsIDirectoryEnumerator> 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; + } + + nsCOMPtr<nsIFile> file; + while (NS_SUCCEEDED(dirEnum->GetNextFile(getter_AddRefs(file))) && file) { + bool isDir = false; + rv = file->IsDirectory(&isDir); + if (NS_FAILED(rv)) { + return rv; + } + + bool isLink = false; + rv = file->IsSymlink(&isLink); + if (NS_FAILED(rv)) { + return rv; + } + + if (move) { + if (followSymlinks) { + return NS_ERROR_FAILURE; + } + + rv = file->MoveTo(target, u""_ns); + if (NS_FAILED(rv)) { + return rv; + } + } else { + if (followSymlinks) { + rv = file->CopyToFollowingLinks(target, u""_ns); + } else { + rv = file->CopyTo(target, u""_ns); + } + 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::MoveToFollowingLinks(nsIFile* aNewParentDir, + const nsAString& aNewName) { + return CopyMove(aNewParentDir, aNewName, Move | FollowSymlinks); +} + +NS_IMETHODIMP +nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName) { + // If we're not provided with a new parent, we're renaming inside one and + // the same directory and can safely skip checking if the destination + // directory exists: + bool targetInSameDirectory = !aNewParentDir; + + 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; + } + + if (!targetInSameDirectory) { + // make sure it exists and is a directory. Create it if not there. + bool exists = false; + rv = targetParentDir->Exists(&exists); + if (NS_FAILED(rv)) { + return rv; + } + + if (!exists) { + rv = targetParentDir->Create(DIRECTORY_TYPE, 0644); + if (NS_FAILED(rv)) { + return rv; + } + } else { + bool isDir = false; + rv = targetParentDir->IsDirectory(&isDir); + if (NS_FAILED(rv)) { + return rv; + } + 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(); + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + +#ifdef NS_BUILD_REFCNT_LOGGING + nsTraceRefcnt::SetActivityIsLegal(false); +#endif + + PRLibSpec libSpec; + libSpec.value.pathname_u = mWorkingPath.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, uint32_t* aRemoveCount) { + // 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(); + + nsresult rv = NS_OK; + + bool isLink = 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 + bool isDir = false; + if (!isLink) { + rv = IsDirectory(&isDir); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (isDir) { + if (aRecursive) { + // WARNING: neither the `SHFileOperation` nor `IFileOperation` APIs are + // appropriate here as neither handle long path names, i.e. paths prefixed + // with `\\?\` or longer than 260 characters on Windows 10+ system with + // long paths enabled. + + RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator(); + + rv = dirEnum->Init(this); + if (NS_FAILED(rv)) { + return rv; + } + + // XXX: We are ignoring the result of the removal here while + // nsLocalFileUnix does not. We should align the behavior. (bug 1779696) + nsCOMPtr<nsIFile> file; + while (NS_SUCCEEDED(dirEnum->GetNextFile(getter_AddRefs(file))) && file) { + file->Remove(aRecursive, aRemoveCount); + } + } + if (RemoveDirectoryW(mWorkingPath.get()) == 0) { + return ConvertWinError(GetLastError()); + } + } else { + if (DeleteFileW(mWorkingPath.get()) == 0) { + return ConvertWinError(GetLastError()); + } + } + + if (aRemoveCount) { + *aRemoveCount += 1; + } + + MakeDirty(); + return rv; +} + +nsresult nsLocalFile::GetDateImpl(PRTime* aTime, + nsLocalFile::TimeField aTimeField, + bool aFollowLinks) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aTime)) { + return NS_ERROR_INVALID_ARG; + } + + FileInfo symlinkInfo{}; + FileInfo* pInfo; + + if (aFollowLinks) { + if (nsresult rv = GetFileInfo(mWorkingPath, &symlinkInfo); NS_FAILED(rv)) { + return rv; + } + + pInfo = &symlinkInfo; + } else { + if (nsresult rv = ResolveAndStat(); NS_FAILED(rv)) { + return rv; + } + + pInfo = &mFileInfo; + } + + switch (aTimeField) { + case TimeField::AccessedTime: + *aTime = pInfo->accessTime / PR_USEC_PER_MSEC; + break; + + case TimeField::ModifiedTime: + *aTime = pInfo->modifyTime / PR_USEC_PER_MSEC; + break; + + default: + MOZ_CRASH("Unknown time field"); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetLastAccessedTime(PRTime* aLastAccessedTime) { + return GetDateImpl(aLastAccessedTime, TimeField::AccessedTime, + /* aFollowSymlinks = */ true); +} + +NS_IMETHODIMP +nsLocalFile::GetLastAccessedTimeOfLink(PRTime* aLastAccessedTime) { + return GetDateImpl(aLastAccessedTime, TimeField::AccessedTime, + /* aFollowSymlinks = */ false); +} + +NS_IMETHODIMP +nsLocalFile::SetLastAccessedTime(PRTime aLastAccessedTime) { + return SetDateImpl(aLastAccessedTime, TimeField::AccessedTime); +} + +NS_IMETHODIMP +nsLocalFile::SetLastAccessedTimeOfLink(PRTime aLastAccessedTime) { + return SetLastAccessedTime(aLastAccessedTime); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTime(PRTime* aLastModifiedTime) { + return GetDateImpl(aLastModifiedTime, TimeField::ModifiedTime, + /* aFollowSymlinks = */ true); +} + +NS_IMETHODIMP +nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModifiedTime) { + return GetDateImpl(aLastModifiedTime, TimeField::ModifiedTime, + /* aFollowSymlinks = */ false); +} + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime) { + return SetDateImpl(aLastModifiedTime, TimeField::ModifiedTime); +} + +NS_IMETHODIMP +nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime) { + return SetLastModifiedTime(aLastModifiedTime); +} + +NS_IMETHODIMP +nsLocalFile::GetCreationTime(PRTime* aCreationTime) { + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aCreationTime)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = ResolveAndStat(); + NS_ENSURE_SUCCESS(rv, rv); + + *aCreationTime = mFileInfo.creationTime / PR_USEC_PER_MSEC; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetCreationTimeOfLink(PRTime* aCreationTime) { + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aCreationTime)) { + return NS_ERROR_INVALID_ARG; + } + + FileInfo info; + nsresult rv = GetFileInfo(mWorkingPath, &info); + NS_ENSURE_SUCCESS(rv, rv); + + *aCreationTime = info.creationTime / PR_USEC_PER_MSEC; + + return NS_OK; +} + +nsresult nsLocalFile::SetDateImpl(PRTime aTime, + nsLocalFile::TimeField aTimeField) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + // The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the + // modification time for directories. + HANDLE file = + ::CreateFileW(mWorkingPath.get(), // 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; + + if (aTime == 0) { + aTime = PR_Now() / PR_USEC_PER_MSEC; + } + + // PR_ExplodeTime expects usecs... + PR_ExplodeTime(aTime * 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; + + const FILETIME* accessTime = nullptr; + const FILETIME* modifiedTime = nullptr; + + if (aTimeField == TimeField::AccessedTime) { + accessTime = &ft; + } else { + modifiedTime = &ft; + } + + nsresult rv = NS_OK; + + // if at least one of these fails... + if (!(SystemTimeToFileTime(&st, &ft) != 0 && + SetFileTime(file, nullptr, accessTime, modifiedTime) != 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 = false; + rv = IsWritable(&isWritable); + if (NS_FAILED(rv)) { + return rv; + } + + bool isExecutable = false; + rv = IsExecutable(&isExecutable); + if (NS_FAILED(rv)) { + return rv; + } + + *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 = Resolve(); + 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 = mFileInfo.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. + + FileInfo 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(); + + HANDLE hFile = + ::CreateFileW(mWorkingPath.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 + nsresult rv = NS_ERROR_FAILURE; + LARGE_INTEGER distance; + distance.QuadPart = aFileSize; + if (SetFilePointerEx(hFile, distance, nullptr, FILE_BEGIN) && + SetEndOfFile(hFile)) { + MakeDirty(); + rv = NS_OK; + } + + CloseHandle(hFile); + return rv; +} + +static nsresult GetDiskSpaceAttributes(const nsString& aResolvedPath, + int64_t* aFreeBytesAvailable, + int64_t* aTotalBytes) { + ULARGE_INTEGER liFreeBytesAvailableToCaller; + ULARGE_INTEGER liTotalNumberOfBytes; + if (::GetDiskFreeSpaceExW(aResolvedPath.get(), &liFreeBytesAvailableToCaller, + &liTotalNumberOfBytes, nullptr)) { + *aFreeBytesAvailable = liFreeBytesAvailableToCaller.QuadPart; + *aTotalBytes = liTotalNumberOfBytes.QuadPart; + + return NS_OK; + } + + return ConvertWinError(::GetLastError()); +} + +NS_IMETHODIMP +nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aDiskSpaceAvailable)) { + return NS_ERROR_INVALID_ARG; + } + + *aDiskSpaceAvailable = 0; + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + if (mFileInfo.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); + } + } + + int64_t dummy = 0; + return GetDiskSpaceAttributes(mResolvedPath, aDiskSpaceAvailable, &dummy); +} + +NS_IMETHODIMP +nsLocalFile::GetDiskCapacity(int64_t* aDiskCapacity) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (NS_WARN_IF(!aDiskCapacity)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = ResolveAndStat(); + if (NS_FAILED(rv)) { + return rv; + } + + if (mFileInfo.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->GetDiskCapacity(aDiskCapacity); + } + } + + int64_t dummy = 0; + return GetDiskSpaceAttributes(mResolvedPath, &dummy, aDiskCapacity); +} + +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 = NewLocalFile(parentPath, mUseDOSDevicePathSyntax, + 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; +} + +nsresult nsLocalFile::LookupExtensionIn(const char* const* aExtensionsArray, + size_t aArrayLength, 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 = false; + 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; + } + + nsDependentSubstring ext = Substring(path, dotIdx); + for (size_t i = 0; i < aArrayLength; ++i) { + if (ext.EqualsASCII(aExtensionsArray[i])) { + // Found a match. Set result and quit. + *aResult = true; + break; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::IsExecutable(bool* aResult) { + return LookupExtensionIn(sExecutableExts, ArrayLength(sExecutableExts), + aResult); +} + +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; + } + + // TODO: Implement symlink support + *aResult = false; + 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; + } + + nsCOMPtr<nsILocalFileWin> lf(do_QueryInterface(aInFile)); + if (!lf) { + *aResult = false; + return NS_OK; + } + + bool inUseDOSDevicePathSyntax; + lf->GetUseDOSDevicePathSyntax(&inUseDOSDevicePathSyntax); + + // If useDOSDevicePathSyntax are different remove the prefix from the one that + // might have it. This is added because of Omnijar. It compares files from + // different modules with itself. + bool removePathPrefix, removeInPathPrefix; + if (inUseDOSDevicePathSyntax != mUseDOSDevicePathSyntax) { + removeInPathPrefix = inUseDOSDevicePathSyntax; + removePathPrefix = mUseDOSDevicePathSyntax; + } else { + removePathPrefix = removeInPathPrefix = false; + } + + nsAutoString inFilePath, workingPath; + aInFile->GetPath(inFilePath); + workingPath = mWorkingPath; + + constexpr static auto equalPath = + [](nsAutoString& workingPath, nsAutoString& inFilePath, + bool removePathPrefix, bool removeInPathPrefix) { + if (removeInPathPrefix && + StringBeginsWith(inFilePath, kDevicePathSpecifier)) { + MOZ_ASSERT(!StringBeginsWith(workingPath, kDevicePathSpecifier)); + + inFilePath = Substring(inFilePath, kDevicePathSpecifier.Length()); + } else if (removePathPrefix && + StringBeginsWith(workingPath, kDevicePathSpecifier)) { + MOZ_ASSERT(!StringBeginsWith(inFilePath, kDevicePathSpecifier)); + + workingPath = Substring(workingPath, kDevicePathSpecifier.Length()); + } + + return _wcsicmp(workingPath.get(), inFilePath.get()) == 0; + }; + + if (equalPath(workingPath, inFilePath, removePathPrefix, + removeInPathPrefix)) { + *aResult = true; + return NS_OK; + } + + EnsureShortPath(); + lf->GetCanonicalPath(inFilePath); + workingPath = mShortWorkingPath; + *aResult = + equalPath(workingPath, inFilePath, removePathPrefix, removeInPathPrefix); + + 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(); + Resolve(); + + MOZ_ASSERT_IF( + mUseDOSDevicePathSyntax, + !FilePreferences::StartsWithDiskDesignatorAndBackslash(mResolvedPath)); + + aResult = mResolvedPath; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetDirectoryEntriesImpl(nsIDirectoryEnumerator** aEntries) { + nsresult rv; + + *aEntries = nullptr; + if (mWorkingPath.EqualsLiteral("\\\\.")) { + RefPtr<nsDriveEnumerator> drives = + new nsDriveEnumerator(mUseDOSDevicePathSyntax); + 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::GetReadOnly(bool* aReadOnly) { + NS_ENSURE_ARG_POINTER(aReadOnly); + + DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get()); + if (dwAttrs == INVALID_FILE_ATTRIBUTES) { + return NS_ERROR_FILE_INVALID_PATH; + } + + *aReadOnly = dwAttrs & FILE_ATTRIBUTE_READONLY; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetReadOnly(bool aReadOnly) { + DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get()); + if (dwAttrs == INVALID_FILE_ATTRIBUTES) { + return NS_ERROR_FILE_INVALID_PATH; + } + + if (aReadOnly) { + dwAttrs |= FILE_ATTRIBUTE_READONLY; + } else { + dwAttrs &= ~FILE_ATTRIBUTE_READONLY; + } + + if (SetFileAttributesW(mWorkingPath.get(), dwAttrs) == 0) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::GetUseDOSDevicePathSyntax(bool* aUseDOSDevicePathSyntax) { + MOZ_ASSERT(aUseDOSDevicePathSyntax); + + *aUseDOSDevicePathSyntax = mUseDOSDevicePathSyntax; + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetUseDOSDevicePathSyntax(bool aUseDOSDevicePathSyntax) { + if (mUseDOSDevicePathSyntax == aUseDOSDevicePathSyntax) { + return NS_OK; + } + + if (mUseDOSDevicePathSyntax) { + if (StringBeginsWith(mWorkingPath, kDevicePathSpecifier)) { + MakeDirty(); + // Remove the prefix + mWorkingPath = Substring(mWorkingPath, kDevicePathSpecifier.Length()); + } + } else { + if (FilePreferences::StartsWithDiskDesignatorAndBackslash(mWorkingPath)) { + MakeDirty(); + // Prepend the prefix + mWorkingPath = kDevicePathSpecifier + mWorkingPath; + } + } + + mUseDOSDevicePathSyntax = aUseDOSDevicePathSyntax; + 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; + } + + nsCOMPtr<nsIRunnable> task = + NS_NewRunnableFunction("nsLocalFile::Reveal", [path = mResolvedPath]() { + MOZ_ASSERT(!NS_IsMainThread(), "Don't run on the main thread"); + + bool doCoUninitialize = SUCCEEDED(CoInitializeEx( + nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)); + RevealFile(path); + if (doCoUninitialize) { + CoUninitialize(); + } + }); + + return NS_DispatchBackgroundTask(task, + nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK); +} + +NS_IMETHODIMP +nsLocalFile::GetWindowsFileAttributes(uint32_t* aAttrs) { + NS_ENSURE_ARG_POINTER(aAttrs); + + DWORD dwAttrs = ::GetFileAttributesW(mWorkingPath.get()); + if (dwAttrs == INVALID_FILE_ATTRIBUTES) { + return ConvertWinError(GetLastError()); + } + + *aAttrs = dwAttrs; + + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::SetWindowsFileAttributes(uint32_t aSetAttrs, + uint32_t aClearAttrs) { + DWORD dwAttrs = ::GetFileAttributesW(mWorkingPath.get()); + if (dwAttrs == INVALID_FILE_ATTRIBUTES) { + return ConvertWinError(GetLastError()); + } + + dwAttrs = (dwAttrs & ~aClearAttrs) | aSetAttrs; + + if (::SetFileAttributesW(mWorkingPath.get(), dwAttrs) == 0) { + return ConvertWinError(GetLastError()); + } + return NS_OK; +} + +NS_IMETHODIMP +nsLocalFile::Launch() { + // This API should be main thread only + MOZ_ASSERT(NS_IsMainThread()); + + // use the app registry name to launch a shell execute.... + _bstr_t execPath(mWorkingPath.get()); + + _variant_t args; + // Pass VT_ERROR/DISP_E_PARAMNOTFOUND to omit an optional RPC parameter + // to execute a file with the default verb. + _variant_t verbDefault(DISP_E_PARAMNOTFOUND, VT_ERROR); + _variant_t showCmd(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_t* workingDirectoryPtr = nullptr; + WCHAR workingDirectory[MAX_PATH + 1] = {L'\0'}; + wcsncpy(workingDirectory, mWorkingPath.get(), MAX_PATH); + if (PathRemoveFileSpecW(workingDirectory)) { + workingDirectoryPtr = workingDirectory; + } else { + NS_WARNING("Could not set working directory for launched file."); + } + + // We have two methods to launch a file: ShellExecuteExW and + // ShellExecuteByExplorer. ShellExecuteExW starts a new process as a child + // of the current process, while ShellExecuteByExplorer starts a new process + // as a child of explorer.exe. + // + // We prefer launching a process via ShellExecuteByExplorer because + // applications may not support the mitigation policies inherited from our + // process. For example, Skype for Business does not start correctly with + // the PreferSystem32Images policy which is one of the policies we use. + // + // If ShellExecuteByExplorer fails for some reason e.g. a system without + // running explorer.exe or VDI environment like Citrix, we fall back to + // ShellExecuteExW which still works in those special environments. + // + // There is an exception where we go straight to ShellExecuteExW without + // trying ShellExecuteByExplorer. When the extension of a downloaded file is + // "exe", we prefer security rather than compatibility. + // + // When a user launches a downloaded executable, the directory containing + // the downloaded file may contain a malicious DLL with a common name, which + // may have been downloaded before. If the downloaded executable is launched + // without the PreferSystem32Images policy, the process can be tricked into + // loading the malicious DLL in the same directory if its name is in the + // executable's dependent modules. Therefore, we always launch ".exe" + // executables via ShellExecuteExW so they inherit our process's mitigation + // policies including PreferSystem32Images. + // + // If the extension is not "exe", then we assume that we are launching an + // installed application, and therefore the security risk described above + // is lessened, as a malicious DLL is less likely to be installed in the + // application's directory. In that case, we attempt to preserve + // compatibility and try ShellExecuteByExplorer first. + + static const char* const onlyExeExt[] = {".exe"}; + bool isExecutable; + nsresult rv = + LookupExtensionIn(onlyExeExt, ArrayLength(onlyExeExt), &isExecutable); + if (NS_FAILED(rv)) { + isExecutable = false; + } + + // If the file is an executable, go straight to ShellExecuteExW. + // Otherwise try ShellExecuteByExplorer first, and if it fails, + // run ShellExecuteExW. + if (!isExecutable) { + mozilla::LauncherVoidResult shellExecuteOk = + mozilla::ShellExecuteByExplorer(execPath, args, verbDefault, + workingDirectoryPtr, showCmd); + if (shellExecuteOk.isOk()) { + return NS_OK; + } + } + + SHELLEXECUTEINFOW seinfo = {sizeof(SHELLEXECUTEINFOW)}; + seinfo.fMask = SEE_MASK_ASYNCOK; + seinfo.hwnd = GetMostRecentNavigatorHWND(); + seinfo.lpVerb = nullptr; + seinfo.lpFile = mWorkingPath.get(); + seinfo.lpParameters = nullptr; + seinfo.lpDirectory = workingDirectoryPtr; + seinfo.nShow = SW_SHOWNORMAL; + + if (!ShellExecuteExW(&seinfo)) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + return NS_OK; +} + +nsresult NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks, + nsIFile** aResult) { + RefPtr<nsLocalFile> file = new nsLocalFile(); + + 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; +} + +nsString nsLocalFile::NativePath() { return mWorkingPath; } + +nsCString nsIFile::HumanReadablePath() { + nsString path; + DebugOnly<nsresult> rv = GetPath(path); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return NS_ConvertUTF16toUTF8(path); +} + +NS_IMETHODIMP +nsLocalFile::CopyToNative(nsIFile* aNewParentDir, const nsACString& aNewName) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (aNewName.IsEmpty()) { + return CopyTo(aNewParentDir, u""_ns); + } + + 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, u""_ns); + } + + 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, u""_ns); + } + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return MoveTo(aNewParentDir, tmp); + } + + return rv; +} + +NS_IMETHODIMP +nsLocalFile::MoveToFollowingLinksNative(nsIFile* aNewParentDir, + const nsACString& aNewName) { + // Check we are correctly initialized. + CHECK_mWorkingPath(); + + if (aNewName.IsEmpty()) { + return MoveToFollowingLinks(aNewParentDir, u""_ns); + } + + nsAutoString tmp; + nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp); + if (NS_SUCCEEDED(rv)) { + return MoveToFollowingLinks(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); + } +} + +NS_IMPL_ISUPPORTS_INHERITED(nsDriveEnumerator, nsSimpleEnumerator, + nsIDirectoryEnumerator) + +nsDriveEnumerator::nsDriveEnumerator(bool aUseDOSDevicePathSyntax) + : mUseDOSDevicePathSyntax(aUseDOSDevicePathSyntax) {} + +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, mDrives.get())) { + 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_ERROR_FAILURE; + } + + nsAString::const_iterator driveEnd = mStartOfCurrentDrive; + FindCharInReadable(L'\0', driveEnd, mEndOfDrivesString); + nsString drive(Substring(mStartOfCurrentDrive, driveEnd)); + mStartOfCurrentDrive = ++driveEnd; + + nsIFile* file; + nsresult rv = NewLocalFile(drive, mUseDOSDevicePathSyntax, &file); + + *aNext = file; + return rv; +} diff --git a/xpcom/io/nsLocalFileWin.h b/xpcom/io/nsLocalFileWin.h new file mode 100644 index 0000000000..a3ea03a666 --- /dev/null +++ b/xpcom/io/nsLocalFileWin.h @@ -0,0 +1,127 @@ +/* -*- 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 "nsILocalFileWin.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: + NS_DEFINE_STATIC_CID_ACCESSOR(NS_LOCAL_FILE_CID) + + nsLocalFile(); + explicit nsLocalFile(const nsAString& aFilePath); + + static nsresult nsLocalFileConstructor(const nsIID& aIID, + void** aInstancePtr); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIFile interface + NS_DECL_NSIFILE + + // nsILocalFileWin interface + NS_DECL_NSILOCALFILEWIN + + public: + // Removes registry command handler parameters, quotes, and expands + // environment strings. + static bool CleanupCmdHandlerPath(nsAString& aCommandHandler); + // Called off the main thread to open the window revealing the file + static nsresult RevealFile(const nsString& aResolvedPath); + + // Checks if the filename is one of the windows reserved filenames + // (com1, com2, etc...) and returns true if so. + static bool CheckForReservedFileName(const nsString& aFileName); + + // PRFileInfo64 does not hvae an accessTime field; + struct FileInfo { + PRFileType type; + PROffset64 size; + PRTime creationTime; + PRTime accessTime; + PRTime modifyTime; + }; + + 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 mUseDOSDevicePathSyntax; + + // 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; + + FileInfo mFileInfo; + + void MakeDirty() { + mDirty = true; + mResolveDirty = true; + mShortWorkingPath.Truncate(); + } + + nsresult LookupExtensionIn(const char* const* aExtensionsArray, + size_t aArrayLength, bool* aResult); + + nsresult ResolveAndStat(); + nsresult Resolve(); + nsresult ResolveSymlink(); + + void EnsureShortPath(); + + nsresult CopyMove(nsIFile* aNewParentDir, const nsAString& aNewName, + uint32_t aOptions); + nsresult CopySingleFile(nsIFile* aSource, nsIFile* aDest, + const nsAString& aNewName, uint32_t aOptions); + + enum class TimeField { AccessedTime, ModifiedTime }; + + nsresult SetDateImpl(int64_t aTime, TimeField aTimeField); + nsresult GetDateImpl(PRTime* aTime, TimeField aTimeField, bool aFollowLinks); + nsresult HasFileAttribute(DWORD aFileAttrib, bool* aResult); + nsresult AppendInternal(const nsString& 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..bc8a67ed23 --- /dev/null +++ b/xpcom/io/nsMultiplexInputStream.cpp @@ -0,0 +1,1557 @@ +/* -*- 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/CheckedInt.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Mutex.h" + +#include "base/basictypes.h" + +#include "nsMultiplexInputStream.h" +#include "nsIBufferedStreams.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" +#include "nsIAsyncInputStream.h" +#include "nsIInputStreamLength.h" +#include "nsNetUtil.h" +#include "nsStreamUtils.h" + +using namespace mozilla; +using namespace mozilla::ipc; + +using mozilla::DeprecatedAbs; + +class nsMultiplexInputStream final : public nsIMultiplexInputStream, + public nsISeekableStream, + public nsIIPCSerializableInputStream, + public nsICloneableInputStream, + public nsIAsyncInputStream, + public nsIInputStreamCallback, + public nsIInputStreamLength, + public nsIAsyncInputStreamLength { + public: + nsMultiplexInputStream(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIMULTIPLEXINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSIINPUTSTREAMLENGTH + NS_DECL_NSIASYNCINPUTSTREAMLENGTH + + // This is used for nsIAsyncInputStream::AsyncWait + void AsyncWaitCompleted(); + + // This is used for nsIAsyncInputStreamLength::AsyncLengthWait + void AsyncWaitCompleted(int64_t aLength, const MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(mLock); + + struct StreamData { + nsresult Initialize(nsIInputStream* aOriginalStream) { + mCurrentPos = 0; + + mOriginalStream = aOriginalStream; + + mBufferedStream = aOriginalStream; + if (!NS_InputStreamIsBuffered(mBufferedStream)) { + nsCOMPtr<nsIInputStream> bufferedStream; + nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), + mBufferedStream.forget(), 4096); + NS_ENSURE_SUCCESS(rv, rv); + mBufferedStream = bufferedStream; + } + + mAsyncStream = do_QueryInterface(mBufferedStream); + mSeekableStream = do_QueryInterface(mBufferedStream); + + return NS_OK; + } + + nsCOMPtr<nsIInputStream> mOriginalStream; + + // Equal to mOriginalStream or a wrap around the original stream to make it + // buffered. + nsCOMPtr<nsIInputStream> mBufferedStream; + + // This can be null. + nsCOMPtr<nsIAsyncInputStream> mAsyncStream; + // This can be null. + nsCOMPtr<nsISeekableStream> mSeekableStream; + + uint64_t mCurrentPos; + }; + + Mutex& GetLock() MOZ_RETURN_CAPABILITY(mLock) { return mLock; } + + private: + ~nsMultiplexInputStream() = default; + + void NextStream() MOZ_REQUIRES(mLock) { + ++mCurrentStream; + mStartedReadingCurrent = false; + } + + nsresult AsyncWaitInternal(); + + // This method updates mSeekableStreams, mTellableStreams, + // mIPCSerializableStreams and mCloneableStreams values. + void UpdateQIMap(StreamData& aStream) MOZ_REQUIRES(mLock); + + struct MOZ_STACK_CLASS ReadSegmentsState { + nsCOMPtr<nsIInputStream> mThisStream; + uint32_t mOffset; + nsWriteSegmentFun mWriter; + void* mClosure; + bool mDone; + }; + + void SerializedComplexityInternal(uint32_t aMaxSize, uint32_t* aSizeUsed, + uint32_t* aPipes, uint32_t* aTransferables, + bool* aSerializeAsPipe); + + static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount); + + bool IsSeekable() const; + bool IsIPCSerializable() const; + bool IsCloneable() const; + bool IsAsyncInputStream() const; + bool IsInputStreamLength() const; + bool IsAsyncInputStreamLength() const; + + Mutex mLock; // Protects access to all data members. + + nsTArray<StreamData> mStreams MOZ_GUARDED_BY(mLock); + + uint32_t mCurrentStream MOZ_GUARDED_BY(mLock); + bool mStartedReadingCurrent MOZ_GUARDED_BY(mLock); + nsresult mStatus MOZ_GUARDED_BY(mLock); + nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback MOZ_GUARDED_BY(mLock); + uint32_t mAsyncWaitFlags MOZ_GUARDED_BY(mLock); + uint32_t mAsyncWaitRequestedCount MOZ_GUARDED_BY(mLock); + nsCOMPtr<nsIEventTarget> mAsyncWaitEventTarget MOZ_GUARDED_BY(mLock); + nsCOMPtr<nsIInputStreamLengthCallback> mAsyncWaitLengthCallback + MOZ_GUARDED_BY(mLock); + + class AsyncWaitLengthHelper; + RefPtr<AsyncWaitLengthHelper> mAsyncWaitLengthHelper MOZ_GUARDED_BY(mLock); + + uint32_t mSeekableStreams MOZ_GUARDED_BY(mLock); + uint32_t mIPCSerializableStreams MOZ_GUARDED_BY(mLock); + uint32_t mCloneableStreams MOZ_GUARDED_BY(mLock); + + // These are Atomics so that we can check them in QueryInterface without + // taking a lock (to look at mStreams.Length() and the numbers above) + // With no streams added yet, all of these are possible + Atomic<bool, Relaxed> mIsSeekableStream{true}; + Atomic<bool, Relaxed> mIsIPCSerializableStream{true}; + Atomic<bool, Relaxed> mIsCloneableStream{true}; + + Atomic<bool, Relaxed> mIsAsyncInputStream{false}; + Atomic<bool, Relaxed> mIsInputStreamLength{false}; + Atomic<bool, Relaxed> mIsAsyncInputStreamLength{false}; +}; + +NS_IMPL_ADDREF(nsMultiplexInputStream) +NS_IMPL_RELEASE(nsMultiplexInputStream) + +NS_IMPL_CLASSINFO(nsMultiplexInputStream, nullptr, nsIClassInfo::THREADSAFE, + NS_MULTIPLEXINPUTSTREAM_CID) + +NS_INTERFACE_MAP_BEGIN(nsMultiplexInputStream) + NS_INTERFACE_MAP_ENTRY(nsIMultiplexInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, IsSeekable()) + NS_INTERFACE_MAP_ENTRY(nsITellableStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream, + IsIPCSerializable()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, IsCloneable()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, IsAsyncInputStream()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, + IsAsyncInputStream()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength, + IsInputStreamLength()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength, + IsAsyncInputStreamLength()) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMultiplexInputStream) + NS_IMPL_QUERY_CLASSINFO(nsMultiplexInputStream) +NS_INTERFACE_MAP_END + +NS_IMPL_CI_INTERFACE_GETTER(nsMultiplexInputStream, nsIMultiplexInputStream, + nsIInputStream, nsISeekableStream, + nsITellableStream) + +static nsresult AvailableMaybeSeek(nsMultiplexInputStream::StreamData& aStream, + uint64_t* aResult) { + nsresult rv = aStream.mBufferedStream->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. + if (aStream.mSeekableStream) { + nsresult rvSeek = + aStream.mSeekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, 0); + if (NS_SUCCEEDED(rvSeek)) { + rv = aStream.mBufferedStream->Available(aResult); + } + } + } + return rv; +} + +nsMultiplexInputStream::nsMultiplexInputStream() + : mLock("nsMultiplexInputStream lock"), + mCurrentStream(0), + mStartedReadingCurrent(false), + mStatus(NS_OK), + mAsyncWaitFlags(0), + mAsyncWaitRequestedCount(0), + mSeekableStreams(0), + mIPCSerializableStreams(0), + mCloneableStreams(0) {} + +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); + + StreamData* streamData = mStreams.AppendElement(fallible); + if (NS_WARN_IF(!streamData)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = streamData->Initialize(aStream); + NS_ENSURE_SUCCESS(rv, rv); + + UpdateQIMap(*streamData); + + if (mStatus == NS_BASE_STREAM_CLOSED) { + // We were closed, but now we have more data to read. + mStatus = NS_OK; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::GetStream(uint32_t aIndex, nsIInputStream** aResult) { + MutexAutoLock lock(mLock); + + if (aIndex >= mStreams.Length()) { + return NS_ERROR_NOT_AVAILABLE; + } + + StreamData& streamData = mStreams.ElementAt(aIndex); + nsCOMPtr<nsIInputStream> stream = streamData.mOriginalStream; + stream.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Close() { + nsTArray<nsCOMPtr<nsIInputStream>> streams; + + // Let's take a copy of the streams becuase, calling close() it could trigger + // a nsIInputStreamCallback immediately and we don't want to create a deadlock + // with mutex. + { + MutexAutoLock lock(mLock); + uint32_t len = mStreams.Length(); + for (uint32_t i = 0; i < len; ++i) { + if (NS_WARN_IF( + !streams.AppendElement(mStreams[i].mBufferedStream, fallible))) { + mStatus = NS_BASE_STREAM_CLOSED; + return NS_ERROR_OUT_OF_MEMORY; + } + } + mStatus = NS_BASE_STREAM_CLOSED; + } + + nsresult rv = NS_OK; + + uint32_t len = streams.Length(); + for (uint32_t i = 0; i < len; ++i) { + nsresult rv2 = streams[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) { + *aResult = 0; + + MutexAutoLock lock(mLock); + if (NS_FAILED(mStatus)) { + return mStatus; + } + + uint64_t avail = 0; + nsresult rv = NS_BASE_STREAM_CLOSED; + + uint32_t len = mStreams.Length(); + for (uint32_t i = mCurrentStream; i < len; i++) { + uint64_t streamAvail; + rv = AvailableMaybeSeek(mStreams[i], &streamAvail); + if (rv == NS_BASE_STREAM_CLOSED) { + // If a stream is closed, we continue with the next one. + // If this is the current stream we move to the following stream. + if (mCurrentStream == i) { + NextStream(); + } + + // If this is the last stream, we want to return this error code. + continue; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + mStatus = rv; + return mStatus; + } + + // If the current stream is async, we have to return what we have so far + // without processing the following streams. This is needed because + // ::Available should return only what is currently available. In case of an + // nsIAsyncInputStream, we have to call AsyncWait() in order to read more. + if (mStreams[i].mAsyncStream) { + avail += streamAvail; + break; + } + + if (streamAvail == 0) { + // Nothing to read for this stream. Let's move to the next one. + continue; + } + + avail += streamAvail; + } + + // We still have something to read. We don't want to return an error code yet. + if (avail) { + *aResult = avail; + return NS_OK; + } + + // Let's propagate the last error message. + mStatus = rv; + return rv; +} + +NS_IMETHODIMP +nsMultiplexInputStream::StreamStatus() { + MutexAutoLock lock(mLock); + return mStatus; +} + +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].mBufferedStream->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) { + MOZ_ASSERT_UNREACHABLE( + "Input stream's Read method returned " + "NS_BASE_STREAM_CLOSED"); + rv = NS_OK; + read = 0; + } else if (NS_FAILED(rv)) { + break; + } + + if (read == 0) { + NextStream(); + } else { + NS_ASSERTION(aCount >= read, "Read more than requested"); + *aResult += read; + aCount -= read; + aBuf += read; + mStartedReadingCurrent = true; + + mStreams[mCurrentStream].mCurrentPos += read; + } + } + 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].mBufferedStream->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) { + MOZ_ASSERT_UNREACHABLE( + "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) { + NextStream(); + } else { + NS_ASSERTION(aCount >= read, "Read more than requested"); + state.mOffset += read; + aCount -= read; + mStartedReadingCurrent = true; + + mStreams[mCurrentStream].mCurrentPos += read; + } + } + + // 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. + *aNonBlocking = true; + return NS_OK; + } + + for (uint32_t i = 0; i < len; ++i) { + nsresult rv = mStreams[i].mBufferedStream->IsNonBlocking(aNonBlocking); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + // If one is blocking the entire stream becomes blocking. + 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 = mStreams[i].mSeekableStream; + 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; + } + + mStreams[i].mCurrentPos = 0; + continue; + } else { + break; + } + } + + // Get position in the current stream + int64_t streamPos; + if (i > oldCurrentStream || + (i == oldCurrentStream && !oldStartedReadingCurrent)) { + streamPos = 0; + } else { + streamPos = mStreams[i].mCurrentPos; + } + + // See if we need to seek the current stream forward or backward + if (remaining < streamPos) { + rv = stream->Seek(NS_SEEK_SET, remaining); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mStreams[i].mCurrentPos = remaining; + 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; + } + + mStreams[i].mCurrentPos = newPos; + mCurrentStream = i; + mStartedReadingCurrent = true; + + remaining -= newPos; + NS_ASSERTION(remaining >= 0, "Remaining invalid"); + } + } else { + NS_ASSERTION(remaining == streamPos, "Huh?"); + MOZ_ASSERT(remaining != 0, "Zero remaining should be handled earlier"); + remaining = 0; + mCurrentStream = i; + mStartedReadingCurrent = true; + } + } + + return NS_OK; + } + + if (aWhence == NS_SEEK_CUR && aOffset > 0) { + int64_t remaining = aOffset; + for (uint32_t i = mCurrentStream; remaining && i < mStreams.Length(); ++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 = mStreams[i].mSeekableStream->Seek(NS_SEEK_CUR, seek); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mStreams[i].mCurrentPos += seek; + 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) { + int64_t pos = mStreams[i].mCurrentPos; + + int64_t seek = XPCOM_MIN(pos, remaining); + + rv = mStreams[i].mSeekableStream->Seek(NS_SEEK_CUR, -seek); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mStreams[i].mCurrentPos -= seek; + 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; + int32_t i; + for (i = mStreams.Length() - 1; i >= 0; --i) { + nsCOMPtr<nsISeekableStream> stream = mStreams[i].mSeekableStream; + + uint64_t avail; + rv = AvailableMaybeSeek(mStreams[i], &avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t streamLength = avail + mStreams[i].mCurrentPos; + + // The seek(END) can be completed in the current stream. + if (streamLength >= DeprecatedAbs(remaining)) { + rv = stream->Seek(NS_SEEK_END, remaining); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mStreams[i].mCurrentPos = streamLength + remaining; + mCurrentStream = i; + mStartedReadingCurrent = true; + break; + } + + // We are at the beginning of this stream. + rv = stream->Seek(NS_SEEK_SET, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + remaining += streamLength; + mStreams[i].mCurrentPos = 0; + } + + // Any other stream must be set to the end. + for (--i; i >= 0; --i) { + nsCOMPtr<nsISeekableStream> stream = mStreams[i].mSeekableStream; + + uint64_t avail; + rv = AvailableMaybeSeek(mStreams[i], &avail); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t streamLength = avail + mStreams[i].mCurrentPos; + + rv = stream->Seek(NS_SEEK_END, 0); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mStreams[i].mCurrentPos = streamLength; + } + + 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; + } + + int64_t ret64 = 0; +#ifdef DEBUG + bool zeroFound = false; +#endif + + for (uint32_t i = 0; i < mStreams.Length(); ++i) { + ret64 += mStreams[i].mCurrentPos; + +#ifdef DEBUG + // When we see 1 stream with currentPos = 0, all the remaining streams must + // be set to 0 as well. + MOZ_ASSERT_IF(zeroFound, mStreams[i].mCurrentPos == 0); + if (mStreams[i].mCurrentPos == 0) { + zeroFound = true; + } +#endif + } + *aResult = ret64; + + return NS_OK; +} + +NS_IMETHODIMP +nsMultiplexInputStream::SetEOF() { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsMultiplexInputStream::CloseWithStatus(nsresult aStatus) { return Close(); } + +// This class is used to inform nsMultiplexInputStream that it's time to execute +// the asyncWait callback. +class AsyncWaitRunnable final : public DiscardableRunnable { + RefPtr<nsMultiplexInputStream> mStream; + + public: + static void Create(nsMultiplexInputStream* aStream, + nsIEventTarget* aEventTarget) { + RefPtr<AsyncWaitRunnable> runnable = new AsyncWaitRunnable(aStream); + if (aEventTarget) { + aEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); + } else { + runnable->Run(); + } + } + + NS_IMETHOD + Run() override { + mStream->AsyncWaitCompleted(); + return NS_OK; + } + + private: + explicit AsyncWaitRunnable(nsMultiplexInputStream* aStream) + : DiscardableRunnable("AsyncWaitRunnable"), mStream(aStream) { + MOZ_ASSERT(aStream); + } +}; + +NS_IMETHODIMP +nsMultiplexInputStream::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + { + MutexAutoLock lock(mLock); + + // We must execute the callback also when the stream is closed. + if (NS_FAILED(mStatus) && mStatus != NS_BASE_STREAM_CLOSED) { + return mStatus; + } + + if (NS_WARN_IF(mAsyncWaitCallback && aCallback && + mAsyncWaitCallback != aCallback)) { + return NS_ERROR_FAILURE; + } + + mAsyncWaitCallback = aCallback; + mAsyncWaitFlags = aFlags; + mAsyncWaitRequestedCount = aRequestedCount; + mAsyncWaitEventTarget = aEventTarget; + } + + return AsyncWaitInternal(); +} + +nsresult nsMultiplexInputStream::AsyncWaitInternal() { + nsCOMPtr<nsIAsyncInputStream> stream; + nsIInputStreamCallback* asyncWaitCallback = nullptr; + uint32_t asyncWaitFlags = 0; + uint32_t asyncWaitRequestedCount = 0; + nsCOMPtr<nsIEventTarget> asyncWaitEventTarget; + + { + MutexAutoLock lock(mLock); + + // Let's take the first async stream if we are not already closed, and if + // it has data to read or if it async. + if (mStatus != NS_BASE_STREAM_CLOSED) { + for (; mCurrentStream < mStreams.Length(); NextStream()) { + stream = mStreams[mCurrentStream].mAsyncStream; + if (stream) { + break; + } + + uint64_t avail = 0; + nsresult rv = AvailableMaybeSeek(mStreams[mCurrentStream], &avail); + if (rv == NS_BASE_STREAM_CLOSED || (NS_SUCCEEDED(rv) && avail == 0)) { + // Nothing to read here. Let's move on. + continue; + } + + if (NS_FAILED(rv)) { + return rv; + } + + break; + } + } + + asyncWaitCallback = mAsyncWaitCallback ? this : nullptr; + asyncWaitFlags = mAsyncWaitFlags; + asyncWaitRequestedCount = mAsyncWaitRequestedCount; + asyncWaitEventTarget = mAsyncWaitEventTarget; + + MOZ_ASSERT_IF(stream, NS_SUCCEEDED(mStatus)); + } + + // If we are here it's because we are already closed, or if the current stream + // is not async. In both case we have to execute the callback. + if (!stream) { + if (asyncWaitCallback) { + AsyncWaitRunnable::Create(this, asyncWaitEventTarget); + } + return NS_OK; + } + + return stream->AsyncWait(asyncWaitCallback, asyncWaitFlags, + asyncWaitRequestedCount, asyncWaitEventTarget); +} + +NS_IMETHODIMP +nsMultiplexInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) { + nsCOMPtr<nsIInputStreamCallback> callback; + + // When OnInputStreamReady is called, we could be in 2 scenarios: + // a. there is something to read; + // b. the stream is closed. + // But if the stream is closed and it was not the last one, we must proceed + // with the following stream in order to have something to read by the callee. + + { + MutexAutoLock lock(mLock); + + // The callback has been nullified in the meantime. + if (!mAsyncWaitCallback) { + return NS_OK; + } + + if (NS_SUCCEEDED(mStatus)) { + uint64_t avail = 0; + nsresult rv = NS_OK; + // Only check `Available()` if `aStream` is actually the current stream, + // otherwise we'll always want to re-poll, as we got the callback for the + // wrong stream. + if (mCurrentStream < mStreams.Length() && + aStream == mStreams[mCurrentStream].mAsyncStream) { + rv = aStream->Available(&avail); + } + if (rv == NS_BASE_STREAM_CLOSED || (NS_SUCCEEDED(rv) && avail == 0)) { + // This stream is either closed, has no data available, or is not the + // current stream. If it is closed and current, move to the next stream, + // otherwise re-wait on the current stream until it has data available + // or becomes closed. + // Unlike streams not implementing nsIAsyncInputStream, async streams + // cannot use `Available() == 0` to indicate EOF, so we re-poll in that + // situation. + if (NS_FAILED(rv)) { + NextStream(); + } + + // Unlock and invoke AsyncWaitInternal to wait again. If this succeeds, + // we'll be called again, otherwise fall through and notify. + MutexAutoUnlock unlock(mLock); + if (NS_SUCCEEDED(AsyncWaitInternal())) { + return NS_OK; + } + } + } + + mAsyncWaitCallback.swap(callback); + mAsyncWaitEventTarget = nullptr; + } + + return callback ? callback->OnInputStreamReady(this) : NS_OK; +} + +void nsMultiplexInputStream::AsyncWaitCompleted() { + nsCOMPtr<nsIInputStreamCallback> callback; + + { + MutexAutoLock lock(mLock); + + // The callback has been nullified in the meantime. + if (!mAsyncWaitCallback) { + return; + } + + mAsyncWaitCallback.swap(callback); + mAsyncWaitEventTarget = nullptr; + } + + callback->OnInputStreamReady(this); +} + +nsresult nsMultiplexInputStreamConstructor(REFNSIID aIID, void** aResult) { + *aResult = nullptr; + + RefPtr<nsMultiplexInputStream> inst = new nsMultiplexInputStream(); + + return inst->QueryInterface(aIID, aResult); +} + +void nsMultiplexInputStream::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + MutexAutoLock lock(mLock); + bool serializeAsPipe = false; + SerializedComplexityInternal(aMaxSize, aSizeUsed, aPipes, aTransferables, + &serializeAsPipe); +} + +void nsMultiplexInputStream::SerializedComplexityInternal( + uint32_t aMaxSize, uint32_t* aSizeUsed, uint32_t* aPipes, + uint32_t* aTransferables, bool* aSerializeAsPipe) { + mLock.AssertCurrentThreadOwns(); + CheckedUint32 totalSizeUsed = 0; + CheckedUint32 totalPipes = 0; + CheckedUint32 totalTransferables = 0; + CheckedUint32 maxSize = aMaxSize; + + uint32_t streamCount = mStreams.Length(); + + for (uint32_t index = 0; index < streamCount; index++) { + uint32_t sizeUsed = 0; + uint32_t pipes = 0; + uint32_t transferables = 0; + InputStreamHelper::SerializedComplexity(mStreams[index].mOriginalStream, + maxSize.value(), &sizeUsed, &pipes, + &transferables); + + MOZ_ASSERT(maxSize.value() >= sizeUsed); + + maxSize -= sizeUsed; + MOZ_DIAGNOSTIC_ASSERT(maxSize.isValid()); + totalSizeUsed += sizeUsed; + MOZ_DIAGNOSTIC_ASSERT(totalSizeUsed.isValid()); + totalPipes += pipes; + MOZ_DIAGNOSTIC_ASSERT(totalPipes.isValid()); + totalTransferables += transferables; + MOZ_DIAGNOSTIC_ASSERT(totalTransferables.isValid()); + } + + // If the combination of all streams when serialized independently is + // sufficiently complex, we may choose to serialize it as a pipe to limit the + // complexity of the payload. + if (totalTransferables.value() == 0) { + // If there are no transferables within our serialization, and it would + // contain at least one pipe, serialize the entire payload as a pipe for + // simplicity. + *aSerializeAsPipe = totalSizeUsed.value() > 0 && totalPipes.value() > 0; + } else { + // Otherwise, we may want to still serialize in segments to take advantage + // of the efficiency of serializing transferables. We'll only serialize as a + // pipe if the total attachment count exceeds kMaxAttachmentThreshold. + static constexpr uint32_t kMaxAttachmentThreshold = 8; + CheckedUint32 totalAttachments = totalPipes + totalTransferables; + *aSerializeAsPipe = !totalAttachments.isValid() || + totalAttachments.value() > kMaxAttachmentThreshold; + } + + if (*aSerializeAsPipe) { + NS_WARNING( + nsPrintfCString("Choosing to serialize multiplex stream as a pipe " + "(would be %u bytes, %u pipes, %u transferables)", + totalSizeUsed.value(), totalPipes.value(), + totalTransferables.value()) + .get()); + *aSizeUsed = 0; + *aPipes = 1; + *aTransferables = 0; + } else { + *aSizeUsed = totalSizeUsed.value(); + *aPipes = totalPipes.value(); + *aTransferables = totalTransferables.value(); + } +} + +void nsMultiplexInputStream::Serialize(InputStreamParams& aParams, + uint32_t aMaxSize, uint32_t* aSizeUsed) { + MutexAutoLock lock(mLock); + + // Check if we should serialize this stream as a pipe to reduce complexity. + uint32_t dummySizeUsed = 0, dummyPipes = 0, dummyTransferables = 0; + bool serializeAsPipe = false; + SerializedComplexityInternal(aMaxSize, &dummySizeUsed, &dummyPipes, + &dummyTransferables, &serializeAsPipe); + if (serializeAsPipe) { + *aSizeUsed = 0; + MutexAutoUnlock unlock(mLock); + InputStreamHelper::SerializeInputStreamAsPipe(this, aParams); + return; + } + + MultiplexInputStreamParams params; + + CheckedUint32 totalSizeUsed = 0; + CheckedUint32 maxSize = aMaxSize; + + uint32_t streamCount = mStreams.Length(); + if (streamCount) { + nsTArray<InputStreamParams>& streams = params.streams(); + + streams.SetCapacity(streamCount); + for (uint32_t index = 0; index < streamCount; index++) { + uint32_t sizeUsed = 0; + InputStreamHelper::SerializeInputStream(mStreams[index].mOriginalStream, + *streams.AppendElement(), + maxSize.value(), &sizeUsed); + + MOZ_ASSERT(maxSize.value() >= sizeUsed); + + maxSize -= sizeUsed; + MOZ_DIAGNOSTIC_ASSERT(maxSize.isValid()); + + totalSizeUsed += sizeUsed; + MOZ_DIAGNOSTIC_ASSERT(totalSizeUsed.isValid()); + } + } + + params.currentStream() = mCurrentStream; + params.status() = mStatus; + params.startedReadingCurrent() = mStartedReadingCurrent; + + aParams = std::move(params); + + MOZ_ASSERT(aSizeUsed); + *aSizeUsed = totalSizeUsed.value(); +} + +bool nsMultiplexInputStream::Deserialize(const InputStreamParams& aParams) { + if (aParams.type() != InputStreamParams::TMultiplexInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const MultiplexInputStreamParams& params = + aParams.get_MultiplexInputStreamParams(); + + const nsTArray<InputStreamParams>& streams = params.streams(); + + uint32_t streamCount = streams.Length(); + for (uint32_t index = 0; index < streamCount; index++) { + nsCOMPtr<nsIInputStream> stream = + InputStreamHelper::DeserializeInputStream(streams[index]); + if (!stream) { + NS_WARNING("Deserialize failed!"); + return false; + } + + if (NS_FAILED(AppendStream(stream))) { + NS_WARNING("AppendStream failed!"); + return false; + } + } + + MutexAutoLock lock(mLock); + mCurrentStream = params.currentStream(); + mStatus = params.status(); + mStartedReadingCurrent = params.startedReadingCurrent(); + + return true; +} + +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].mBufferedStream); + 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].mBufferedStream); + 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; +} + +NS_IMETHODIMP +nsMultiplexInputStream::Length(int64_t* aLength) { + MutexAutoLock lock(mLock); + + if (mCurrentStream > 0 || mStartedReadingCurrent) { + return NS_ERROR_NOT_AVAILABLE; + } + + CheckedInt64 length = 0; + nsresult retval = NS_OK; + + for (uint32_t i = 0, len = mStreams.Length(); i < len; ++i) { + nsCOMPtr<nsIInputStreamLength> substream = + do_QueryInterface(mStreams[i].mBufferedStream); + if (!substream) { + // Let's use available as fallback. + uint64_t streamAvail = 0; + nsresult rv = AvailableMaybeSeek(mStreams[i], &streamAvail); + if (rv == NS_BASE_STREAM_CLOSED) { + continue; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + mStatus = rv; + return mStatus; + } + + length += streamAvail; + if (!length.isValid()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + continue; + } + + int64_t size = 0; + nsresult rv = substream->Length(&size); + if (rv == NS_BASE_STREAM_CLOSED) { + continue; + } + + if (rv == NS_ERROR_NOT_AVAILABLE) { + return rv; + } + + // If one stream blocks, we all block. + if (rv != NS_BASE_STREAM_WOULD_BLOCK && NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // We want to return WOULD_BLOCK if there is 1 stream that blocks. But want + // to see if there are other streams with length = -1. + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + retval = NS_BASE_STREAM_WOULD_BLOCK; + continue; + } + + // If one of the stream doesn't know the size, we all don't know the size. + if (size == -1) { + *aLength = -1; + return NS_OK; + } + + length += size; + if (!length.isValid()) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + *aLength = length.value(); + return retval; +} + +class nsMultiplexInputStream::AsyncWaitLengthHelper final + : public nsIInputStreamLengthCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + AsyncWaitLengthHelper() + : mStreamNotified(false), mLength(0), mNegativeSize(false) {} + + bool AddStream(nsIAsyncInputStreamLength* aStream) { + return mPendingStreams.AppendElement(aStream, fallible); + } + + bool AddSize(int64_t aSize) { + MOZ_ASSERT(!mNegativeSize); + + mLength += aSize; + return mLength.isValid(); + } + + void NegativeSize() { + MOZ_ASSERT(!mNegativeSize); + mNegativeSize = true; + } + + nsresult Proceed(nsMultiplexInputStream* aParentStream, + nsIEventTarget* aEventTarget, + const MutexAutoLock& aProofOfLock) { + MOZ_ASSERT(!mStream); + + // If we don't need to wait, let's inform the callback immediately. + if (mPendingStreams.IsEmpty() || mNegativeSize) { + RefPtr<nsMultiplexInputStream> parentStream = aParentStream; + int64_t length = -1; + if (!mNegativeSize && mLength.isValid()) { + length = mLength.value(); + } + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + "AsyncWaitLengthHelper", [parentStream, length]() { + MutexAutoLock lock(parentStream->GetLock()); + parentStream->AsyncWaitCompleted(length, lock); + }); + return aEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + } + + // Let's store the callback and the parent stream until we have + // notifications from the async length streams. + + mStream = aParentStream; + + // Let's activate all the pending streams. + for (nsIAsyncInputStreamLength* stream : mPendingStreams) { + nsresult rv = stream->AsyncLengthWait(this, aEventTarget); + if (rv == NS_BASE_STREAM_CLOSED) { + continue; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; + } + + NS_IMETHOD + OnInputStreamLengthReady(nsIAsyncInputStreamLength* aStream, + int64_t aLength) override { + MutexAutoLock lock(mStream->GetLock()); + + MOZ_ASSERT(mPendingStreams.Contains(aStream)); + mPendingStreams.RemoveElement(aStream); + + // Already notified. + if (mStreamNotified) { + return NS_OK; + } + + if (aLength == -1) { + mNegativeSize = true; + } else { + mLength += aLength; + if (!mLength.isValid()) { + mNegativeSize = true; + } + } + + // We need to wait. + if (!mNegativeSize && !mPendingStreams.IsEmpty()) { + return NS_OK; + } + + // Let's notify the parent stream. + mStreamNotified = true; + mStream->AsyncWaitCompleted(mNegativeSize ? -1 : mLength.value(), lock); + return NS_OK; + } + + private: + ~AsyncWaitLengthHelper() = default; + + RefPtr<nsMultiplexInputStream> mStream; + bool mStreamNotified; + + CheckedInt64 mLength; + bool mNegativeSize; + + nsTArray<nsCOMPtr<nsIAsyncInputStreamLength>> mPendingStreams; +}; + +NS_IMPL_ISUPPORTS(nsMultiplexInputStream::AsyncWaitLengthHelper, + nsIInputStreamLengthCallback) + +NS_IMETHODIMP +nsMultiplexInputStream::AsyncLengthWait(nsIInputStreamLengthCallback* aCallback, + nsIEventTarget* aEventTarget) { + if (NS_WARN_IF(!aEventTarget)) { + return NS_ERROR_NULL_POINTER; + } + + MutexAutoLock lock(mLock); + + if (mCurrentStream > 0 || mStartedReadingCurrent) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (!aCallback) { + mAsyncWaitLengthCallback = nullptr; + return NS_OK; + } + + // We have a pending operation! Let's use this instead of creating a new one. + if (mAsyncWaitLengthHelper) { + mAsyncWaitLengthCallback = aCallback; + return NS_OK; + } + + RefPtr<AsyncWaitLengthHelper> helper = new AsyncWaitLengthHelper(); + + for (uint32_t i = 0, len = mStreams.Length(); i < len; ++i) { + nsCOMPtr<nsIAsyncInputStreamLength> asyncStream = + do_QueryInterface(mStreams[i].mBufferedStream); + if (asyncStream) { + if (NS_WARN_IF(!helper->AddStream(asyncStream))) { + return NS_ERROR_OUT_OF_MEMORY; + } + continue; + } + + nsCOMPtr<nsIInputStreamLength> stream = + do_QueryInterface(mStreams[i].mBufferedStream); + if (!stream) { + // Let's use available as fallback. + uint64_t streamAvail = 0; + nsresult rv = AvailableMaybeSeek(mStreams[i], &streamAvail); + if (rv == NS_BASE_STREAM_CLOSED) { + continue; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + mStatus = rv; + return mStatus; + } + + if (NS_WARN_IF(!helper->AddSize(streamAvail))) { + return NS_ERROR_OUT_OF_MEMORY; + } + + continue; + } + + int64_t size = 0; + nsresult rv = stream->Length(&size); + if (rv == NS_BASE_STREAM_CLOSED) { + continue; + } + + MOZ_ASSERT(rv != NS_BASE_STREAM_WOULD_BLOCK, + "A nsILengthInutStream returns NS_BASE_STREAM_WOULD_BLOCK but " + "it doesn't implement nsIAsyncInputStreamLength."); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (size == -1) { + helper->NegativeSize(); + break; + } + + if (NS_WARN_IF(!helper->AddSize(size))) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + nsresult rv = helper->Proceed(this, aEventTarget, lock); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mAsyncWaitLengthHelper = helper; + mAsyncWaitLengthCallback = aCallback; + return NS_OK; +} + +void nsMultiplexInputStream::AsyncWaitCompleted( + int64_t aLength, const MutexAutoLock& aProofOfLock) { + mLock.AssertCurrentThreadOwns(); + + nsCOMPtr<nsIInputStreamLengthCallback> callback; + callback.swap(mAsyncWaitLengthCallback); + + mAsyncWaitLengthHelper = nullptr; + + // Already canceled. + if (!callback) { + return; + } + + MutexAutoUnlock unlock(mLock); + callback->OnInputStreamLengthReady(this, aLength); +} + +#define MAYBE_UPDATE_VALUE_REAL(x, y) \ + if (y) { \ + ++x; \ + } + +#define MAYBE_UPDATE_VALUE(x, y) \ + { \ + nsCOMPtr<y> substream = do_QueryInterface(aStream.mBufferedStream); \ + MAYBE_UPDATE_VALUE_REAL(x, substream) \ + } + +#define MAYBE_UPDATE_BOOL(x, y) \ + if (!x) { \ + nsCOMPtr<y> substream = do_QueryInterface(aStream.mBufferedStream); \ + if (substream) { \ + x = true; \ + } \ + } + +void nsMultiplexInputStream::UpdateQIMap(StreamData& aStream) { + auto length = mStreams.Length(); + + MAYBE_UPDATE_VALUE_REAL(mSeekableStreams, aStream.mSeekableStream) + mIsSeekableStream = (mSeekableStreams == length); + MAYBE_UPDATE_VALUE(mIPCSerializableStreams, nsIIPCSerializableInputStream) + mIsIPCSerializableStream = (mIPCSerializableStreams == length); + MAYBE_UPDATE_VALUE(mCloneableStreams, nsICloneableInputStream) + mIsCloneableStream = (mCloneableStreams == length); + // nsMultiplexInputStream is nsIAsyncInputStream if at least 1 of the + // substream implements that interface + if (!mIsAsyncInputStream && aStream.mAsyncStream) { + mIsAsyncInputStream = true; + } + MAYBE_UPDATE_BOOL(mIsInputStreamLength, nsIInputStreamLength) + MAYBE_UPDATE_BOOL(mIsAsyncInputStreamLength, nsIAsyncInputStreamLength) +} + +#undef MAYBE_UPDATE_VALUE +#undef MAYBE_UPDATE_VALUE_REAL +#undef MAYBE_UPDATE_BOOL + +bool nsMultiplexInputStream::IsSeekable() const { return mIsSeekableStream; } + +bool nsMultiplexInputStream::IsIPCSerializable() const { + return mIsIPCSerializableStream; +} + +bool nsMultiplexInputStream::IsCloneable() const { return mIsCloneableStream; } + +bool nsMultiplexInputStream::IsAsyncInputStream() const { + // nsMultiplexInputStream is nsIAsyncInputStream if at least 1 of the + // substream implements that interface. + return mIsAsyncInputStream; +} + +bool nsMultiplexInputStream::IsInputStreamLength() const { + return mIsInputStreamLength; +} + +bool nsMultiplexInputStream::IsAsyncInputStreamLength() const { + return mIsAsyncInputStreamLength; +} diff --git a/xpcom/io/nsMultiplexInputStream.h b/xpcom/io/nsMultiplexInputStream.h new file mode 100644 index 0000000000..9b84697c34 --- /dev/null +++ b/xpcom/io/nsMultiplexInputStream.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/. */ + +/** + * The multiplex stream concatenates a list of input streams into a single + * stream. + */ + +#ifndef _nsMultiplexInputStream_h_ +#define _nsMultiplexInputStream_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(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..847ae3666c --- /dev/null +++ b/xpcom/io/nsNativeCharsetUtils.cpp @@ -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/. */ + +//----------------------------------------------------------------------------- +// Non-Windows +//----------------------------------------------------------------------------- +#ifndef XP_WIN + +# 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; +} + +//----------------------------------------------------------------------------- +// XP_WIN +//----------------------------------------------------------------------------- +#else + +# 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) { + char16ptr_t result = aOutput.BeginWriting(); + ::MultiByteToWideChar(CP_ACP, 0, buf, inputLen, 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) { + char* result = aOutput.BeginWriting(); + + // default "defaultChar" is '?', which is an illegal character on windows + // file system. That will cause file uncreatable. Change it to '_' + const char defaultChar = '_'; + + ::WideCharToMultiByte(CP_ACP, 0, buf, inputLen, result, resultLen, + &defaultChar, nullptr); + } + return NS_OK; +} + +#endif diff --git a/xpcom/io/nsNativeCharsetUtils.h b/xpcom/io/nsNativeCharsetUtils.h new file mode 100644 index 0000000000..dcbf60238f --- /dev/null +++ b/xpcom/io/nsNativeCharsetUtils.h @@ -0,0 +1,52 @@ +/* -*- 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 bytes and UTF-16. They are not * + * designed or tested for general encoding converter use. * + * * + * On Windows, these functions convert to and from the system's legacy * + * code page, which cannot represent all of Unicode. Elsewhere, these * + * convert to and from UTF-8. * + * * +\*****************************************************************************/ + +#include "nsError.h" +#include "nsStringFwd.h" + +/** + * 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. + */ +inline constexpr bool NS_IsNativeUTF8() { +#ifdef XP_WIN + return false; +#else + return true; +#endif +} + +#endif // nsNativeCharsetUtils_h__ diff --git a/xpcom/io/nsPipe.h b/xpcom/io/nsPipe.h new file mode 100644 index 0000000000..c9e8b1dfd4 --- /dev/null +++ b/xpcom/io/nsPipe.h @@ -0,0 +1,21 @@ +/* -*- 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(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..3d7486e673 --- /dev/null +++ b/xpcom/io/nsPipe3.cpp @@ -0,0 +1,1884 @@ +/* -*- 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/IntegerPrintfMacros.h" +#include "mozilla/ReentrantMonitor.h" +#include "nsIBufferedStreams.h" +#include "nsICloneableInputStream.h" +#include "nsIPipe.h" +#include "nsIEventTarget.h" +#include "nsITellableStream.h" +#include "mozilla/RefPtr.h" +#include "nsSegmentedBuffer.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsCRT.h" +#include "mozilla/Logging.h" +#include "nsIClassInfoImpl.h" +#include "nsAlgorithm.h" +#include "nsPipe.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIInputStreamPriority.h" +#include "nsThreadUtils.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 + +//----------------------------------------------------------------------------- + +class CallbackHolder { + public: + CallbackHolder() = default; + MOZ_IMPLICIT CallbackHolder(std::nullptr_t) {} + + CallbackHolder(nsIAsyncInputStream* aStream, + nsIInputStreamCallback* aCallback, uint32_t aFlags, + nsIEventTarget* aEventTarget) + : mRunnable(aCallback ? NS_NewCancelableRunnableFunction( + "nsPipeInputStream AsyncWait Callback", + [stream = nsCOMPtr{aStream}, + callback = nsCOMPtr{aCallback}]() { + callback->OnInputStreamReady(stream); + }) + : nullptr), + mEventTarget(aEventTarget), + mFlags(aFlags) {} + + CallbackHolder(nsIAsyncOutputStream* aStream, + nsIOutputStreamCallback* aCallback, uint32_t aFlags, + nsIEventTarget* aEventTarget) + : mRunnable(aCallback ? NS_NewCancelableRunnableFunction( + "nsPipeOutputStream AsyncWait Callback", + [stream = nsCOMPtr{aStream}, + callback = nsCOMPtr{aCallback}]() { + callback->OnOutputStreamReady(stream); + }) + : nullptr), + mEventTarget(aEventTarget), + mFlags(aFlags) {} + + CallbackHolder(const CallbackHolder&) = delete; + CallbackHolder(CallbackHolder&&) = default; + CallbackHolder& operator=(const CallbackHolder&) = delete; + CallbackHolder& operator=(CallbackHolder&&) = default; + + CallbackHolder& operator=(std::nullptr_t) { + mRunnable = nullptr; + mEventTarget = nullptr; + mFlags = 0; + return *this; + } + + MOZ_IMPLICIT operator bool() const { return mRunnable; } + + uint32_t Flags() const { + MOZ_ASSERT(mRunnable, "Should only be called when a callback is present"); + return mFlags; + } + + void Notify() { + nsCOMPtr<nsIRunnable> runnable = mRunnable.forget(); + nsCOMPtr<nsIEventTarget> eventTarget = mEventTarget.forget(); + if (runnable) { + if (eventTarget) { + eventTarget->Dispatch(runnable.forget()); + } else { + runnable->Run(); + } + } + } + + private: + nsCOMPtr<nsIRunnable> mRunnable; + nsCOMPtr<nsIEventTarget> mEventTarget; + uint32_t mFlags = 0; +}; + +//----------------------------------------------------------------------------- + +// 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() = default; + ~nsPipeEvents(); + + inline void NotifyReady(CallbackHolder aCallback) { + mCallbacks.AppendElement(std::move(aCallback)); + } + + private: + nsTArray<CallbackHolder> mCallbacks; +}; + +//----------------------------------------------------------------------------- + +// 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) {} + + // All members of this type are guarded by the pipe monitor, however it cannot + // be named from this type, so the less-reliable MOZ_GUARDED_VAR is used + // instead. In the future it would be nice to avoid this, especially as + // MOZ_GUARDED_VAR is deprecated. + char* mReadCursor MOZ_GUARDED_VAR; + char* mReadLimit MOZ_GUARDED_VAR; + int32_t mSegment MOZ_GUARDED_VAR; + uint32_t mAvailable MOZ_GUARDED_VAR; + + // This flag is managed using the AutoReadSegment RAII stack class. + bool mActiveRead MOZ_GUARDED_VAR; + + // 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 MOZ_GUARDED_VAR; +}; + +//----------------------------------------------------------------------------- + +// an input end of a pipe (maintained as a list of refs within the pipe) +class nsPipeInputStream final : public nsIAsyncInputStream, + public nsITellableStream, + public nsISearchableInputStream, + public nsICloneableInputStream, + public nsIClassInfo, + public nsIBufferedInputStream, + public nsIInputStreamPriority { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSITELLABLESTREAM + NS_DECL_NSISEARCHABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSICLASSINFO + NS_DECL_NSIBUFFEREDINPUTSTREAM + NS_DECL_NSIINPUTSTREAMPRIORITY + + explicit nsPipeInputStream(nsPipe* aPipe) + : mPipe(aPipe), + mLogicalOffset(0), + mInputStatus(NS_OK), + mBlocking(true), + mBlocked(false), + mPriority(nsIRunnablePriority::PRIORITY_NORMAL) {} + + nsPipeInputStream(const nsPipeInputStream& aOther) + : mPipe(aOther.mPipe), + mLogicalOffset(aOther.mLogicalOffset), + mInputStatus(aOther.mInputStatus), + mBlocking(aOther.mBlocking), + mBlocked(false), + mReadState(aOther.mReadState), + mPriority(nsIRunnablePriority::PRIORITY_NORMAL) {} + + void SetNonBlocking(bool aNonBlocking) { mBlocking = !aNonBlocking; } + + uint32_t Available() MOZ_REQUIRES(Monitor()); + + // 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) + MOZ_REQUIRES(Monitor()); + MonitorAction OnInputException(nsresult, nsPipeEvents&, + const ReentrantMonitorAutoEnter& ev) + MOZ_REQUIRES(Monitor()); + + 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 + MOZ_REQUIRES(Monitor()); + + // The status of this input stream, ignoring the status of the underlying + // monitor. If this status is errored, the input stream has either already + // been removed from the pipe, or will be removed from the pipe shortly. + nsresult InputStatus(const ReentrantMonitorAutoEnter&) const + MOZ_REQUIRES(Monitor()) { + return mInputStatus; + } + + ReentrantMonitor& Monitor() 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 MOZ_GUARDED_BY(Monitor()); + bool mBlocking; + + // these variables can only be accessed while inside the pipe's monitor + bool mBlocked MOZ_GUARDED_BY(Monitor()); + CallbackHolder mCallback MOZ_GUARDED_BY(Monitor()); + + // requires pipe's monitor to access members; usually treat as an opaque token + // to pass to nsPipe + nsPipeReadState mReadState; + Atomic<uint32_t, Relaxed> mPriority; +}; + +//----------------------------------------------------------------------------- + +// 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) {} + + void SetNonBlocking(bool aNonBlocking) { mBlocking = !aNonBlocking; } + void SetWritable(bool aWritable) MOZ_REQUIRES(Monitor()) { + mWritable = aWritable; + } + + // synchronously wait for the pipe to become writable. + nsresult Wait(); + + MonitorAction OnOutputWritable(nsPipeEvents&) MOZ_REQUIRES(Monitor()); + MonitorAction OnOutputException(nsresult, nsPipeEvents&) + MOZ_REQUIRES(Monitor()); + + ReentrantMonitor& Monitor() const; + + private: + nsPipe* mPipe; + + // separate refcnt so that we know when to close the producer + ThreadSafeAutoRefCnt mWriterRefCnt; + int64_t mLogicalOffset; + bool mBlocking; + + // these variables can only be accessed while inside the pipe's monitor + bool mBlocked MOZ_GUARDED_BY(Monitor()); + bool mWritable MOZ_GUARDED_BY(Monitor()); + CallbackHolder mCallback MOZ_GUARDED_BY(Monitor()); +}; + +//----------------------------------------------------------------------------- + +class nsPipe final { + public: + friend class nsPipeInputStream; + friend class nsPipeOutputStream; + friend class AutoReadSegment; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsPipe) + + // public constructor + friend void NS_NewPipe2(nsIAsyncInputStream**, nsIAsyncOutputStream**, bool, + bool, uint32_t, uint32_t); + + private: + nsPipe(uint32_t aSegmentSize, uint32_t aSegmentCount); + ~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) + MOZ_REQUIRES(mReentrantMonitor); + SegmentChangeResult AdvanceReadSegment(nsPipeReadState& aReadState, + const ReentrantMonitorAutoEnter& ev) + MOZ_REQUIRES(mReentrantMonitor); + bool ReadSegmentBeingWritten(nsPipeReadState& aReadState) + MOZ_REQUIRES(mReentrantMonitor); + uint32_t CountSegmentReferences(int32_t aSegment) + MOZ_REQUIRES(mReentrantMonitor); + void SetAllNullReadCursors() MOZ_REQUIRES(mReentrantMonitor); + bool AllReadCursorsMatchWriteCursor() MOZ_REQUIRES(mReentrantMonitor); + void RollBackAllReadCursors(char* aWriteCursor) + MOZ_REQUIRES(mReentrantMonitor); + void UpdateAllReadCursors(char* aWriteCursor) MOZ_REQUIRES(mReentrantMonitor); + void ValidateAllReadCursors() MOZ_REQUIRES(mReentrantMonitor); + uint32_t GetBufferSegmentCount(const nsPipeReadState& aReadState, + const ReentrantMonitorAutoEnter& ev) const + MOZ_REQUIRES(mReentrantMonitor); + bool IsAdvanceBufferFull(const ReentrantMonitorAutoEnter& ev) const + MOZ_REQUIRES(mReentrantMonitor); + + // + // 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 MOZ_GUARDED_BY(mReentrantMonitor); + + ReentrantMonitor mReentrantMonitor; + nsSegmentedBuffer mBuffer MOZ_GUARDED_BY(mReentrantMonitor); + + // 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 MOZ_GUARDED_BY(mReentrantMonitor); + + int32_t mWriteSegment MOZ_GUARDED_BY(mReentrantMonitor); + char* mWriteCursor MOZ_GUARDED_BY(mReentrantMonitor); + char* mWriteLimit MOZ_GUARDED_BY(mReentrantMonitor); + + // |mStatus| is protected by |mReentrantMonitor|. + nsresult mStatus MOZ_GUARDED_BY(mReentrantMonitor); +}; + +//----------------------------------------------------------------------------- + +// Declarations of Monitor() methods on the streams. +// +// These must be placed early to provide MOZ_RETURN_CAPABILITY annotations for +// the thread-safety analysis. This couldn't be done at the declaration due to +// nsPipe not yet being defined. + +ReentrantMonitor& nsPipeOutputStream::Monitor() const + MOZ_RETURN_CAPABILITY(mPipe->mReentrantMonitor) { + return mPipe->mReentrantMonitor; +} + +ReentrantMonitor& nsPipeInputStream::Monitor() const + MOZ_RETURN_CAPABILITY(mPipe->mReentrantMonitor) { + return mPipe->mReentrantMonitor; +} + +//----------------------------------------------------------------------------- + +// 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_DIAGNOSTIC_ASSERT(mPipe); + MOZ_DIAGNOSTIC_ASSERT(!mReadState.mActiveRead); + mStatus = mPipe->GetReadSegment(mReadState, mSegment, mLength); + if (NS_SUCCEEDED(mStatus)) { + MOZ_DIAGNOSTIC_ASSERT(mReadState.mActiveRead); + MOZ_DIAGNOSTIC_ASSERT(mSegment); + mLength = std::min(mLength, aMaxLength); + MOZ_DIAGNOSTIC_ASSERT(mLength); + } + } + + ~AutoReadSegment() { + if (NS_SUCCEEDED(mStatus)) { + if (mOffset) { + mPipe->AdvanceReadCursor(mReadState, mOffset); + } else { + nsPipeEvents events; + mPipe->ReleaseReadSegment(mReadState, events); + } + } + MOZ_DIAGNOSTIC_ASSERT(!mReadState.mActiveRead); + } + + nsresult Status() const { return mStatus; } + + const char* Data() const { + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mStatus)); + MOZ_DIAGNOSTIC_ASSERT(mSegment); + return mSegment + mOffset; + } + + uint32_t Length() const { + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mStatus)); + MOZ_DIAGNOSTIC_ASSERT(mLength >= mOffset); + return mLength - mOffset; + } + + void Advance(uint32_t aCount) { + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(mStatus)); + MOZ_DIAGNOSTIC_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(uint32_t aSegmentSize, uint32_t aSegmentCount) + : mOutput(this), + mReentrantMonitor("nsPipe.mReentrantMonitor"), + // protect against overflow + mMaxAdvanceBufferSegmentCount( + std::min(aSegmentCount, UINT32_MAX / aSegmentSize)), + mWriteSegment(-1), + mWriteCursor(nullptr), + mWriteLimit(nullptr), + mStatus(NS_OK) { + // 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(). + MOZ_ALWAYS_SUCCEEDS(mBuffer.Init(aSegmentSize)); +} + +nsPipe::~nsPipe() = default; + +void nsPipe::PeekSegment(const nsPipeReadState& aReadState, uint32_t aIndex, + char*& aCursor, char*& aLimit) { + if (aIndex == 0) { + MOZ_DIAGNOSTIC_ASSERT(!aReadState.mReadCursor || mBuffer.GetSegmentCount()); + 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_DIAGNOSTIC_ASSERT(!aReadState.mActiveRead); + aReadState.mActiveRead = true; + + aSegment = aReadState.mReadCursor; + aLength = aReadState.mReadLimit - aReadState.mReadCursor; + MOZ_DIAGNOSTIC_ASSERT(aLength <= aReadState.mAvailable); + + return NS_OK; +} + +void nsPipe::ReleaseReadSegment(nsPipeReadState& aReadState, + nsPipeEvents& aEvents) { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + MOZ_DIAGNOSTIC_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) { + MOZ_DIAGNOSTIC_ASSERT(aBytesRead > 0); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + LOG(("III advancing read cursor by %u\n", aBytesRead)); + MOZ_DIAGNOSTIC_ASSERT(aBytesRead <= mBuffer.GetSegmentSize()); + + aReadState.mReadCursor += aBytesRead; + MOZ_DIAGNOSTIC_ASSERT(aReadState.mReadCursor <= aReadState.mReadLimit); + + MOZ_DIAGNOSTIC_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. + mOutput.Monitor().AssertCurrentThreadIn(); + 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_DIAGNOSTIC_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_DIAGNOSTIC_ASSERT(!aReadState.mNeedDrain); + aReadState.mNeedDrain = true; + return; + } + + 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); + } + + // Force the stream into an empty state. Make sure mAvailable, mCursor, and + // mReadLimit are consistent with one another. + aReadState.mAvailable = 0; + aReadState.mReadCursor = nullptr; + aReadState.mReadLimit = nullptr; + + // Remove the input stream from the pipe's list of streams. This will + // prevent the pipe from holding the stream alive or trying to update + // its read state any further. + DebugOnly<uint32_t> numRemoved = 0; + mInputList.RemoveElementsBy([&](nsPipeInputStream* aEntry) { + bool result = &aReadState == &aEntry->ReadState(); + numRemoved += result ? 1 : 0; + return result; + }); + MOZ_ASSERT(numRemoved == 1); + + // If we have read any segments from the advance buffer then we can + // potentially notify blocked writers. + mOutput.Monitor().AssertCurrentThreadIn(); + if (!IsAdvanceBufferFull(mon) && + mOutput.OnOutputWritable(aEvents) == NotifyMonitor) { + mon.NotifyAll(); + } +} + +bool nsPipe::ReadSegmentBeingWritten(nsPipeReadState& aReadState) { + mReentrantMonitor.AssertCurrentThreadIn(); + bool beingWritten = + mWriteSegment == aReadState.mSegment && mWriteLimit > mWriteCursor; + MOZ_DIAGNOSTIC_ASSERT(!beingWritten || aReadState.mReadLimit == mWriteCursor); + 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 %" PRId64 " bytes\n", + static_cast<int64_t>(mWriteCursor - head))); + RollBackAllReadCursors(head); + mWriteCursor = head; + } + + aSegment = mWriteCursor; + aSegmentLen = mWriteLimit - mWriteCursor; + return NS_OK; +} + +void nsPipe::AdvanceWriteCursor(uint32_t aBytesWritten) { + MOZ_DIAGNOSTIC_ASSERT(aBytesWritten > 0); + + nsPipeEvents events; + { + ReentrantMonitorAutoEnter mon(mReentrantMonitor); + + LOG(("OOO advancing write cursor by %u\n", aBytesWritten)); + + char* newWriteCursor = mWriteCursor + aBytesWritten; + MOZ_DIAGNOSTIC_ASSERT(newWriteCursor <= mWriteLimit); + + // 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.Monitor().AssertCurrentThreadIn(); + 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) { + mInputList[i]->Monitor().AssertCurrentThreadIn(); + if (mInputList[i]->OnInputReadable(aBytesWritten, events, mon) == + NotifyMonitor) { + needNotify = true; + } + } + + if (needNotify) { + mon.NotifyAll(); + } + } +} + +void nsPipe::OnInputStreamException(nsPipeInputStream* aStream, + nsresult aReason) { + MOZ_DIAGNOSTIC_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; + } + + mInputList[i]->Monitor().AssertCurrentThreadIn(); + MonitorAction action = + mInputList[i]->OnInputException(aReason, events, mon); + + // 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=%" PRIx32 " output-only=%d]\n", + static_cast<uint32_t>(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; + + // OnInputException() can drain the stream and remove it from + // mInputList. So iterate over a temp list instead. + nsTArray<nsPipeInputStream*> list = mInputList.Clone(); + for (uint32_t i = 0; i < list.Length(); ++i) { + // an output-only exception applies to the input end if the pipe has + // zero bytes available. + list[i]->Monitor().AssertCurrentThreadIn(); + if (aOutputOnly && list[i]->Available()) { + continue; + } + + if (list[i]->OnInputException(aReason, events, mon) == NotifyMonitor) { + needNotify = true; + } + } + + mOutput.Monitor().AssertCurrentThreadIn(); + 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); + // don't add clones of closed pipes to mInputList. + ref->Monitor().AssertCurrentThreadIn(); + if (NS_SUCCEEDED(ref->InputStatus(mon))) { + mInputList.AppendElement(ref); + } + nsCOMPtr<nsIAsyncInputStream> upcast = std::move(ref); + upcast.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) { + MOZ_DIAGNOSTIC_ASSERT(mWriteSegment == readState.mSegment); + 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_DIAGNOSTIC_ASSERT(mWriteSegment == readState.mSegment); + MOZ_DIAGNOSTIC_ASSERT(mWriteCursor == readState.mReadCursor); + MOZ_DIAGNOSTIC_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(); + MOZ_ASSERT(state.mReadCursor != mWriteCursor || + (mBuffer.GetSegment(state.mSegment) == state.mReadCursor && + mWriteCursor == mWriteLimit)); + } +#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_DIAGNOSTIC_ASSERT(mWriteSegment >= 0); + MOZ_DIAGNOSTIC_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. + mInputList[i]->Monitor().AssertCurrentThreadIn(); + 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 (auto& callback : mCallbacks) { + callback.Notify(); + } + mCallbacks.Clear(); +} + +//----------------------------------------------------------------------------- +// 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, nsITellableStream) + 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(nsPipeInputStream, nsIInputStreamPriority) + 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, nsITellableStream, + 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"); +} + +NS_IMETHODIMP +nsPipeInputStream::GetData(nsIInputStream** aResult) { + // as this was not created with init() we are not + // wrapping anything + return NS_ERROR_NOT_IMPLEMENTED; +} + +uint32_t nsPipeInputStream::Available() { + mPipe->mReentrantMonitor.AssertCurrentThreadIn(); + return mReadState.mAvailable; +} + +nsresult nsPipeInputStream::Wait() { + MOZ_DIAGNOSTIC_ASSERT(mBlocking); + + 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=%" PRIx32 " available=%u]\n", + static_cast<uint32_t>(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 && !(mCallback.Flags() & WAIT_CLOSURE_ONLY)) { + aEvents.NotifyReady(std::move(mCallback)); + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + +MonitorAction nsPipeInputStream::OnInputException( + nsresult aReason, nsPipeEvents& aEvents, + const ReentrantMonitorAutoEnter& ev) { + LOG(("nsPipeInputStream::OnInputException [this=%p reason=%" PRIx32 "]\n", + this, static_cast<uint32_t>(aReason))); + + MonitorAction result = DoNotNotifyMonitor; + + MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aReason)); + + if (NS_SUCCEEDED(mInputStatus)) { + mInputStatus = aReason; + } + + // force count of available bytes to zero. + mPipe->DrainInputStream(mReadState, aEvents); + + if (mCallback) { + aEvents.NotifyReady(std::move(mCallback)); + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + +NS_IMETHODIMP +nsPipeInputStream::CloseWithStatus(nsresult aReason) { + LOG(("III CloseWithStatus [this=%p reason=%" PRIx32 "]\n", this, + static_cast<uint32_t>(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::SetPriority(uint32_t priority) { + mPriority = priority; + return NS_OK; +} + +NS_IMETHODIMP +nsPipeInputStream::GetPriority(uint32_t* priority) { + *priority = mPriority; + 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::StreamStatus() { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + return mReadState.mAvailable ? NS_OK : Status(mon); +} + +NS_IMETHODIMP +nsPipeInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aReadCount) { + LOG(("III ReadSegments [this=%p 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; + } + + MOZ_DIAGNOSTIC_ASSERT(writeCount <= segment.Length()); + 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=%p]\n", this)); + + nsPipeEvents pipeEvents; + { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // replace a pending callback + mCallback = nullptr; + + if (!aCallback) { + return NS_OK; + } + + CallbackHolder callback(this, aCallback, aFlags, aTarget); + + if (NS_FAILED(Status(mon)) || + (mReadState.mAvailable && !(aFlags & WAIT_CLOSURE_ONLY))) { + // stream is already closed or readable; post event. + pipeEvents.NotifyReady(std::move(callback)); + } else { + // queue up callback object to be notified when data becomes available + mCallback = std::move(callback); + } + } + return NS_OK; +} + +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; +} + +static bool strings_equal(bool aIgnoreCase, const char* aS1, const char* aS2, + uint32_t aLen) { + return aIgnoreCase ? !nsCRT::strncasecmp(aS1, aS2, aLen) + : !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; + } + + MOZ_ASSERT_UNREACHABLE("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() { + MOZ_DIAGNOSTIC_ASSERT(mBlocking); + + 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=%" PRIx32 " writable=%u]\n", + static_cast<uint32_t>(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 && !(mCallback.Flags() & WAIT_CLOSURE_ONLY)) { + aEvents.NotifyReady(std::move(mCallback)); + } else if (mBlocked) { + result = NotifyMonitor; + } + + return result; +} + +MonitorAction nsPipeOutputStream::OnOutputException(nsresult aReason, + nsPipeEvents& aEvents) { + LOG(("nsPipeOutputStream::OnOutputException [this=%p reason=%" PRIx32 "]\n", + this, static_cast<uint32_t>(aReason))); + + MonitorAction result = DoNotNotifyMonitor; + + MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aReason)); + mWritable = false; + + if (mCallback) { + aEvents.NotifyReady(std::move(mCallback)); + } 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=%p reason=%" PRIx32 "]\n", this, + static_cast<uint32_t>(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=%p 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; + } + + MOZ_DIAGNOSTIC_ASSERT(readCount <= segmentLen); + segment += readCount; + segmentLen -= readCount; + aCount -= readCount; + *aWriteCount += readCount; + mLogicalOffset += readCount; + } + + if (segmentLen < originalLen) { + mPipe->AdvanceWriteCursor(originalLen - segmentLen); + } + } + + return rv; +} + +NS_IMETHODIMP +nsPipeOutputStream::Write(const char* aFromBuf, uint32_t aBufLen, + uint32_t* aWriteCount) { + return WriteSegments(NS_CopyBufferToSegment, (void*)aFromBuf, aBufLen, + aWriteCount); +} + +NS_IMETHODIMP +nsPipeOutputStream::Flush() { + // nothing to do + return NS_OK; +} + +NS_IMETHODIMP +nsPipeOutputStream::StreamStatus() { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + return mPipe->mStatus; +} + +NS_IMETHODIMP +nsPipeOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* aWriteCount) { + return WriteSegments(NS_CopyStreamToSegment, 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=%p]\n", this)); + + nsPipeEvents pipeEvents; + { + ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor); + + // replace a pending callback + mCallback = nullptr; + + if (!aCallback) { + return NS_OK; + } + + CallbackHolder callback(this, aCallback, aFlags, aTarget); + + if (NS_FAILED(mPipe->mStatus) || + (mWritable && !(aFlags & WAIT_CLOSURE_ONLY))) { + // stream is already closed or writable; post event. + pipeEvents.NotifyReady(std::move(callback)); + } else { + // queue up callback object to be notified when data becomes available + mCallback = std::move(callback); + } + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +void 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; + NS_NewPipe2(&in, &out, aNonBlockingInput, aNonBlockingOutput, aSegmentSize, + segmentCount); + + *aPipeIn = in; + *aPipeOut = out; +} + +// Disable thread safety analysis as this is logically a constructor, and no +// additional threads can observe these objects yet. +void NS_NewPipe2(nsIAsyncInputStream** aPipeIn, nsIAsyncOutputStream** aPipeOut, + bool aNonBlockingInput, bool aNonBlockingOutput, + uint32_t aSegmentSize, + uint32_t aSegmentCount) MOZ_NO_THREAD_SAFETY_ANALYSIS { + RefPtr<nsPipe> pipe = + new nsPipe(aSegmentSize ? aSegmentSize : DEFAULT_SEGMENT_SIZE, + aSegmentCount ? aSegmentCount : DEFAULT_SEGMENT_COUNT); + + RefPtr<nsPipeInputStream> pipeIn = new nsPipeInputStream(pipe); + pipe->mInputList.AppendElement(pipeIn); + RefPtr<nsPipeOutputStream> pipeOut = &pipe->mOutput; + + pipeIn->SetNonBlocking(aNonBlockingInput); + pipeOut->SetNonBlocking(aNonBlockingOutput); + + pipeIn.forget(aPipeIn); + pipeOut.forget(aPipeOut); +} + +//////////////////////////////////////////////////////////////////////////////// + +// Thin nsIPipe implementation for consumers of the component manager interface +// for creating pipes. Acts as a thin wrapper around NS_NewPipe2 for JS callers. +class nsPipeHolder final : public nsIPipe { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPIPE + + private: + ~nsPipeHolder() = default; + + nsCOMPtr<nsIAsyncInputStream> mInput; + nsCOMPtr<nsIAsyncOutputStream> mOutput; +}; + +NS_IMPL_ISUPPORTS(nsPipeHolder, nsIPipe) + +NS_IMETHODIMP +nsPipeHolder::Init(bool aNonBlockingInput, bool aNonBlockingOutput, + uint32_t aSegmentSize, uint32_t aSegmentCount) { + if (mInput || mOutput) { + return NS_ERROR_ALREADY_INITIALIZED; + } + NS_NewPipe2(getter_AddRefs(mInput), getter_AddRefs(mOutput), + aNonBlockingInput, aNonBlockingOutput, aSegmentSize, + aSegmentCount); + return NS_OK; +} + +NS_IMETHODIMP +nsPipeHolder::GetInputStream(nsIAsyncInputStream** aInputStream) { + if (mInput) { + *aInputStream = do_AddRef(mInput).take(); + return NS_OK; + } + return NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP +nsPipeHolder::GetOutputStream(nsIAsyncOutputStream** aOutputStream) { + if (mOutput) { + *aOutputStream = do_AddRef(mOutput).take(); + return NS_OK; + } + return NS_ERROR_NOT_INITIALIZED; +} + +nsresult nsPipeConstructor(REFNSIID aIID, void** aResult) { + RefPtr<nsPipeHolder> pipe = new nsPipeHolder(); + nsresult rv = pipe->QueryInterface(aIID, aResult); + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/io/nsScriptableBase64Encoder.cpp b/xpcom/io/nsScriptableBase64Encoder.cpp new file mode 100644 index 0000000000..8513a95380 --- /dev/null +++ b/xpcom/io/nsScriptableBase64Encoder.cpp @@ -0,0 +1,26 @@ +/* -*- 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..8982657e59 --- /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() = default; +}; + +#endif diff --git a/xpcom/io/nsScriptableInputStream.cpp b/xpcom/io/nsScriptableInputStream.cpp new file mode 100644 index 0000000000..5f8b139c6f --- /dev/null +++ b/xpcom/io/nsScriptableInputStream.cpp @@ -0,0 +1,117 @@ +/* -*- 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 "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, mozilla::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(REFNSIID aIID, void** aResult) { + 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..1dc8a80962 --- /dev/null +++ b/xpcom/io/nsScriptableInputStream.h @@ -0,0 +1,46 @@ +/* -*- 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() = default; + + static nsresult Create(REFNSIID aIID, void** aResult); + + private: + ~nsScriptableInputStream() = default; + + 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..01d075368a --- /dev/null +++ b/xpcom/io/nsSegmentedBuffer.cpp @@ -0,0 +1,170 @@ +/* -*- 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 "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/ScopeExit.h" + +nsresult nsSegmentedBuffer::Init(uint32_t aSegmentSize) { + if (mSegmentArrayCount != 0) { + return NS_ERROR_FAILURE; // initialized more than once + } + mSegmentSize = aSegmentSize; + mSegmentArrayCount = NS_SEGMENTARRAY_INITIAL_COUNT; + return NS_OK; +} + +char* nsSegmentedBuffer::AppendNewSegment() { + if (!mSegmentArray) { + uint32_t bytes = mSegmentArrayCount * sizeof(char*); + mSegmentArray = (char**)moz_xmalloc(bytes); + memset(mSegmentArray, 0, bytes); + } + + if (IsFull()) { + mozilla::CheckedInt<uint32_t> newArraySize = + mozilla::CheckedInt<uint32_t>(mSegmentArrayCount) * 2; + mozilla::CheckedInt<uint32_t> bytes = newArraySize * sizeof(char*); + if (!bytes.isValid()) { + return nullptr; + } + + mSegmentArray = (char**)moz_xrealloc(mSegmentArray, bytes.value()); + // 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.value() - mLastSegmentIndex) * sizeof(char*)); + } else { + memset(&mSegmentArray[mLastSegmentIndex], 0, + (newArraySize.value() - mLastSegmentIndex) * sizeof(char*)); + } + mSegmentArrayCount = newArraySize.value(); + } + + 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"); + FreeOMT(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"); + FreeOMT(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() { + auto clearMembers = mozilla::MakeScopeExit([&] { + mSegmentArray = nullptr; + mSegmentArrayCount = NS_SEGMENTARRAY_INITIAL_COUNT; + mFirstSegmentIndex = mLastSegmentIndex = 0; + }); + + // If mSegmentArray is null, there's no need to actually free anything + if (!mSegmentArray) { + return; + } + + // Dispatch a task that frees up the array. This may run immediately or on + // a background thread. + FreeOMT([segmentArray = mSegmentArray, arrayCount = mSegmentArrayCount]() { + for (uint32_t i = 0; i < arrayCount; i++) { + if (segmentArray[i]) { + free(segmentArray[i]); + } + } + free(segmentArray); + }); +} + +void nsSegmentedBuffer::FreeOMT(void* aPtr) { + FreeOMT([aPtr]() { free(aPtr); }); +} + +void nsSegmentedBuffer::FreeOMT(std::function<void()>&& aTask) { + if (!NS_IsMainThread()) { + aTask(); + return; + } + + if (mFreeOMT) { + // There is a runnable pending which will handle this object + if (mFreeOMT->AddTask(std::move(aTask)) > 1) { + return; + } + } else { + mFreeOMT = mozilla::MakeRefPtr<FreeOMTPointers>(); + mFreeOMT->AddTask(std::move(aTask)); + } + + if (!mIOThread) { + mIOThread = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + } + + // During the shutdown we are not able to obtain the IOThread and/or the + // dispatching of runnable fails. + if (!mIOThread || NS_FAILED(mIOThread->Dispatch(NS_NewRunnableFunction( + "nsSegmentedBuffer::FreeOMT", + [obj = mFreeOMT]() { obj->FreeAll(); })))) { + mFreeOMT->FreeAll(); + } +} + +void nsSegmentedBuffer::FreeOMTPointers::FreeAll() { + // Take all the tasks from the object. If AddTask is called after we release + // the lock, then another runnable will be dispatched for that task. This is + // necessary to avoid blocking the main thread while memory is being freed. + nsTArray<std::function<void()>> tasks = [this]() { + auto t = mTasks.Lock(); + return std::move(*t); + }(); + + // Finally run all the tasks to free memory. + for (auto& task : tasks) { + task(); + } +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/io/nsSegmentedBuffer.h b/xpcom/io/nsSegmentedBuffer.h new file mode 100644 index 0000000000..c654956df9 --- /dev/null +++ b/xpcom/io/nsSegmentedBuffer.h @@ -0,0 +1,126 @@ +/* -*- 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 <stddef.h> +#include <functional> + +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsTArray.h" +#include "mozilla/DataMutex.h" + +class nsIEventTarget; + +class nsSegmentedBuffer { + public: + nsSegmentedBuffer() + : mSegmentSize(0), + mSegmentArray(nullptr), + mSegmentArrayCount(0), + mFirstSegmentIndex(0), + mLastSegmentIndex(0) {} + + ~nsSegmentedBuffer() { Empty(); } + + nsresult Init(uint32_t aSegmentSize); + + 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 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; + char** mSegmentArray; + uint32_t mSegmentArrayCount; + int32_t mFirstSegmentIndex; + int32_t mLastSegmentIndex; + + private: + class FreeOMTPointers { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FreeOMTPointers) + + public: + FreeOMTPointers() : mTasks("nsSegmentedBuffer::FreeOMTPointers") {} + + void FreeAll(); + + // Adds a task to the array. Returns the size of the array. + size_t AddTask(std::function<void()>&& aTask) { + auto tasks = mTasks.Lock(); + tasks->AppendElement(std::move(aTask)); + return tasks->Length(); + } + + private: + ~FreeOMTPointers() = default; + + mozilla::DataMutex<nsTArray<std::function<void()>>> mTasks; + }; + + void FreeOMT(void* aPtr); + void FreeOMT(std::function<void()>&& aTask); + + nsCOMPtr<nsIEventTarget> mIOThread; + + // This object is created the first time we need to dispatch to another thread + // to free segments. It is only freed when the nsSegmentedBufer is destroyed + // or when the runnable is finally handled and its refcount goes to 0. + RefPtr<FreeOMTPointers> mFreeOMT; +}; + +// 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..cd3dfd6645 --- /dev/null +++ b/xpcom/io/nsStorageStream.cpp @@ -0,0 +1,680 @@ +/* -*- 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 "mozilla/Mutex.h" +#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::MutexAutoLock; +using mozilla::ipc::InputStreamParams; +using mozilla::ipc::StringInputStreamParams; + +// +// 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 mozilla::LazyLogModule sStorageStreamLog("nsStorageStream"); +#ifdef LOG +# undef LOG +#endif +#define LOG(args) MOZ_LOG(sStorageStreamLog, mozilla::LogLevel::Debug, args) + +nsStorageStream::nsStorageStream() { + 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) { + MutexAutoLock lock(mMutex); + mSegmentedBuffer = new nsSegmentedBuffer(); + mSegmentSize = aSegmentSize; + mSegmentSizeLog2 = mozilla::FloorLog2(aSegmentSize); + mMaxLogicalLength = aMaxSize; + + // Segment size must be a power of two + if (mSegmentSize != ((uint32_t)1 << mSegmentSizeLog2)) { + return NS_ERROR_INVALID_ARG; + } + + return mSegmentedBuffer->Init(aSegmentSize); +} + +NS_IMETHODIMP +nsStorageStream::GetOutputStream(int32_t aStartingOffset, + nsIOutputStream** aOutputStream) { + if (NS_WARN_IF(!aOutputStream)) { + return NS_ERROR_INVALID_ARG; + } + + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (mWriteInProgress) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (mActiveSegmentBorrows > 0) { + 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() { + MutexAutoLock lock(mMutex); + 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 && !mActiveSegmentBorrows) { + mSegmentedBuffer->ReallocLastSegment(segmentOffset); + } + + mWriteCursor = 0; + mSegmentEnd = 0; + + LOG(("nsStorageStream [%p] Close mWriteCursor=%p mSegmentEnd=%p\n", this, + mWriteCursor, mSegmentEnd)); + + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::Flush() { return NS_OK; } + +NS_IMETHODIMP +nsStorageStream::StreamStatus() { + MutexAutoLock lock(mMutex); + if (!mSegmentedBuffer) { + return NS_ERROR_NOT_INITIALIZED; + } + 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; + } + + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (NS_WARN_IF(mLogicalLength >= mMaxLogicalLength)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + LOG(("nsStorageStream [%p] Write mWriteCursor=%p mSegmentEnd=%p aCount=%d\n", + this, mWriteCursor, mSegmentEnd, aCount)); + + uint32_t remaining = aCount; + const char* readCursor = aBuffer; + + remaining = std::min(remaining, mMaxLogicalLength - mLogicalLength); + + auto onExit = mozilla::MakeScopeExit([&] { + mMutex.AssertCurrentThreadOwns(); + *aNumWritten = aCount - remaining; + mLogicalLength += *aNumWritten; + + LOG( + ("nsStorageStream [%p] Wrote mWriteCursor=%p mSegmentEnd=%p " + "numWritten=%d\n", + this, mWriteCursor, mSegmentEnd, *aNumWritten)); + }); + + // 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; + uint32_t availableInSegment = mSegmentEnd - mWriteCursor; + if (!availableInSegment) { + mWriteCursor = mSegmentedBuffer->AppendNewSegment(); + if (!mWriteCursor) { + mSegmentEnd = 0; + return NS_ERROR_OUT_OF_MEMORY; + } + mLastSegmentNum++; + mSegmentEnd = mWriteCursor + mSegmentSize; + availableInSegment = mSegmentEnd - mWriteCursor; + LOG( + ("nsStorageStream [%p] Write (new seg) mWriteCursor=%p " + "mSegmentEnd=%p\n", + this, mWriteCursor, mSegmentEnd)); + } + + uint32_t count = XPCOM_MIN(availableInSegment, remaining); + memcpy(mWriteCursor, readCursor, count); + remaining -= count; + readCursor += count; + mWriteCursor += count; + LOG( + ("nsStorageStream [%p] Writing mWriteCursor=%p mSegmentEnd=%p " + "count=%d\n", + this, mWriteCursor, mSegmentEnd, count)); + } + + return NS_OK; +} + +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) { + MutexAutoLock lock(mMutex); + *aLength = mLogicalLength; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageStream::SetLength(uint32_t aLength) { + MutexAutoLock lock(mMutex); + return SetLengthLocked(aLength); +} + +// Truncate the buffer by deleting the end segments +nsresult nsStorageStream::SetLengthLocked(uint32_t aLength) { + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (mWriteInProgress) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (mActiveSegmentBorrows) { + 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) { + MutexAutoLock lock(mMutex); + *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 + SetLengthLocked(aPosition); + + // Special handling for seek to start-of-buffer + if (aPosition == 0) { + mWriteCursor = 0; + mSegmentEnd = 0; + LOG(("nsStorageStream [%p] Seek mWriteCursor=%p mSegmentEnd=%p\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=%p mSegmentEnd=%p\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_NSITELLABLESTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + + private: + ~nsStorageInputStream() = default; + + protected: + nsresult Seek(uint32_t aPosition) MOZ_REQUIRES(mStorageStream->mMutex); + + 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) MOZ_REQUIRES(mStorageStream->mMutex) { + return aPosition >> mStorageStream->mSegmentSizeLog2; + } + uint32_t SegOffset(uint32_t aPosition) { + return aPosition & (mSegmentSize - 1); + } +}; + +NS_IMPL_ISUPPORTS(nsStorageInputStream, nsIInputStream, nsISeekableStream, + nsITellableStream, nsIIPCSerializableInputStream, + nsICloneableInputStream) + +NS_IMETHODIMP +nsStorageStream::NewInputStream(int32_t aStartingOffset, + nsIInputStream** aInputStream) { + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(!mSegmentedBuffer)) { + return NS_ERROR_NOT_INITIALIZED; + } + + RefPtr<nsStorageInputStream> inputStream = + new nsStorageInputStream(this, mSegmentSize); + + inputStream->mStorageStream->mMutex.AssertCurrentThreadOwns(); + 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; + } + + MutexAutoLock lock(mStorageStream->mMutex); + *aAvailable = mStorageStream->mLogicalLength - mLogicalCursor; + return NS_OK; +} + +NS_IMETHODIMP +nsStorageInputStream::StreamStatus() { return mStatus; } + +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) { + const char* cur = nullptr; + { + MutexAutoLock lock(mStorageStream->mMutex); + availableInSegment = mSegmentEnd - mReadCursor; + if (!availableInSegment) { + uint32_t available = mStorageStream->mLogicalLength - mLogicalCursor; + if (!available) { + break; + } + + // 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; + } + cur = mStorageStream->mSegmentedBuffer->GetSegment(mSegmentNum); + mStorageStream->mActiveSegmentBorrows++; + } + auto dropBorrow = mozilla::MakeScopeExit([&] { + MutexAutoLock lock(mStorageStream->mMutex); + mStorageStream->mActiveSegmentBorrows--; + }); + + 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; + } + + *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; + } + + MutexAutoLock lock(mStorageStream->mMutex); + 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: + MOZ_ASSERT_UNREACHABLE("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() { + MOZ_ASSERT_UNREACHABLE("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::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + uint64_t remaining = 0; + mozilla::DebugOnly<nsresult> rv = Available(&remaining); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + if (remaining >= aMaxSize) { + *aPipes = 1; + } else { + *aSizeUsed = remaining; + } +} + +void nsStorageInputStream::Serialize(InputStreamParams& aParams, + uint32_t aMaxSize, uint32_t* aSizeUsed) { + MOZ_ASSERT(aSizeUsed); + *aSizeUsed = 0; + + uint64_t remaining = 0; + mozilla::DebugOnly<nsresult> rv = Available(&remaining); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + if (remaining >= aMaxSize) { + mozilla::ipc::InputStreamHelper::SerializeInputStreamAsPipe(this, aParams); + return; + } + + *aSizeUsed = remaining; + + nsCString combined; + int64_t offset; + rv = Tell(&offset); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + auto handleOrErr = combined.BulkWrite(remaining, 0, false); + MOZ_ASSERT(!handleOrErr.isErr()); + + auto handle = handleOrErr.unwrap(); + + uint32_t numRead = 0; + + rv = Read(handle.Elements(), remaining, &numRead); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + MOZ_ASSERT(numRead == remaining); + handle.Finish(numRead, false); + + rv = Seek(NS_SEEK_SET, offset); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + StringInputStreamParams params; + params.data() = combined; + aParams = params; +} + +bool nsStorageInputStream::Deserialize(const InputStreamParams& aParams) { + MOZ_ASSERT_UNREACHABLE( + "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..78265b4c6b --- /dev/null +++ b/xpcom/io/nsStorageStream.h @@ -0,0 +1,79 @@ +/* -*- 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 "mozilla/Attributes.h" +#include "mozilla/Mutex.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(); + + mozilla::Mutex mMutex{"nsStorageStream"}; + nsSegmentedBuffer* mSegmentedBuffer MOZ_GUARDED_BY(mMutex) = nullptr; + // All segments, except possibly the last, are of this size. Must be + // power-of-2 + uint32_t mSegmentSize MOZ_GUARDED_BY(mMutex) = 0; + // log2(mSegmentSize) + uint32_t mSegmentSizeLog2 MOZ_GUARDED_BY(mMutex) = 0; + // true, if an un-Close'ed output stream exists + bool mWriteInProgress MOZ_GUARDED_BY(mMutex) = false; + // Last segment # in use, -1 initially + int32_t mLastSegmentNum MOZ_GUARDED_BY(mMutex) = -1; + // Pointer to next byte to be written + char* mWriteCursor MOZ_GUARDED_BY(mMutex) = nullptr; + // Pointer to one byte after end of segment containing the write cursor + char* mSegmentEnd MOZ_GUARDED_BY(mMutex) = nullptr; + // Maximum number of bytes which may be written to the stream + uint32_t mMaxLogicalLength MOZ_GUARDED_BY(mMutex) = 0; + // Number of bytes written to stream + uint32_t mLogicalLength MOZ_GUARDED_BY(mMutex) = 0; + // number of input streams actively reading a segment. + uint32_t mActiveSegmentBorrows MOZ_GUARDED_BY(mMutex) = 0; + + nsresult SetLengthLocked(uint32_t aLength) MOZ_REQUIRES(mMutex); + nsresult Seek(int32_t aPosition) MOZ_REQUIRES(mMutex); + uint32_t SegNum(uint32_t aPosition) MOZ_REQUIRES(mMutex) { + return aPosition >> mSegmentSizeLog2; + } + uint32_t SegOffset(uint32_t aPosition) MOZ_REQUIRES(mMutex) { + 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..94669b71dc --- /dev/null +++ b/xpcom/io/nsStreamUtils.cpp @@ -0,0 +1,976 @@ +/* -*- 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 "mozilla/InputStreamLengthWrapper.h" +#include "nsIInputStreamLength.h" +#include "nsStreamUtils.h" +#include "nsCOMPtr.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 "nsIPipe.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsITransport.h" +#include "nsIStreamTransportService.h" +#include "NonBlockingAsyncInputStream.h" + +using namespace mozilla; + +static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID); + +//----------------------------------------------------------------------------- + +// 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 nsIRunnablePriority { + public: + NS_DECL_ISUPPORTS_INHERITED + + nsInputStreamReadyEvent(const char* aName, nsIInputStreamCallback* aCallback, + nsIEventTarget* aTarget, uint32_t aPriority) + : CancelableRunnable(aName), + mCallback(aCallback), + mTarget(aTarget), + mPriority(aPriority) {} + + 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( + "~nsInputStreamReadyEvent", mCallback, mTarget, mPriority); + mCallback = nullptr; + if (event) { + rv = event->OnInputStreamReady(nullptr); + if (NS_FAILED(rv)) { + MOZ_ASSERT_UNREACHABLE("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; + } + + NS_IMETHOD GetPriority(uint32_t* aPriority) override { + *aPriority = mPriority; + return NS_OK; + } + + private: + nsCOMPtr<nsIAsyncInputStream> mStream; + nsCOMPtr<nsIInputStreamCallback> mCallback; + nsCOMPtr<nsIEventTarget> mTarget; + uint32_t mPriority; +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsInputStreamReadyEvent, CancelableRunnable, + nsIInputStreamCallback, nsIRunnablePriority) + +//----------------------------------------------------------------------------- + +// 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) + : CancelableRunnable("nsOutputStreamReadyEvent"), + 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)) { + MOZ_ASSERT_UNREACHABLE("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( + const char* aName, nsIInputStreamCallback* aCallback, + nsIEventTarget* aTarget, uint32_t aPriority) { + NS_ASSERTION(aCallback, "null callback"); + NS_ASSERTION(aTarget, "null target"); + RefPtr<nsInputStreamReadyEvent> ev = + new nsInputStreamReadyEvent(aName, aCallback, aTarget, aPriority); + 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() + : CancelableRunnable("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; + } + 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 (mAsyncSource) { + // cancel any previously-registered AsyncWait callbacks to avoid leaks + mAsyncSource->AsyncWait(nullptr, 0, 0, nullptr); + } + if (mCloseSource) { + // close source + if (mAsyncSource) { + mAsyncSource->CloseWithStatus(canceled ? cancelStatus + : sinkCondition); + } else { + mSource->Close(); + } + } + mAsyncSource = nullptr; + mSource = nullptr; + + if (mAsyncSink) { + // cancel any previously-registered AsyncWait callbacks to avoid leaks + mAsyncSink->AsyncWait(nullptr, 0, 0, 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() MOZ_REQUIRES(mLock) { + 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 MOZ_GUARDED_BY(mLock); + bool mEventIsPending MOZ_GUARDED_BY(mLock); + bool mCloseSource; + bool mCloseSink; + bool mCanceled MOZ_GUARDED_BY(mLock); + nsresult mCancelStatus MOZ_GUARDED_BY(mLock); + + // virtual since subclasses call superclass Release() + virtual ~nsAStreamCopier() = default; +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsAStreamCopier, CancelableRunnable, + nsIInputStreamCallback, nsIOutputStreamCallback) + +class nsStreamCopierIB final : public nsAStreamCopier { + public: + nsStreamCopierIB() : nsAStreamCopier() {} + virtual ~nsStreamCopierIB() = default; + + 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 = NS_SUCCEEDED(state.mSinkCondition) && n == 0 + ? mSink->StreamStatus() + : state.mSinkCondition; + return n; + } + + nsresult Cancel() override { return NS_OK; } +}; + +class nsStreamCopierOB final : public nsAStreamCopier { + public: + nsStreamCopierOB() : nsAStreamCopier() {} + virtual ~nsStreamCopierOB() = default; + + 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 = NS_SUCCEEDED(state.mSourceCondition) && n == 0 + ? mSource->StreamStatus() + : 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); +} + +//----------------------------------------------------------------------------- + +namespace { +template <typename T> +struct ResultTraits {}; + +template <> +struct ResultTraits<nsACString> { + static void Clear(nsACString& aString) { aString.Truncate(); } + + static char* GetStorage(nsACString& aString) { + return aString.BeginWriting(); + } +}; + +template <> +struct ResultTraits<nsTArray<uint8_t>> { + static void Clear(nsTArray<uint8_t>& aArray) { aArray.Clear(); } + + static char* GetStorage(nsTArray<uint8_t>& aArray) { + return reinterpret_cast<char*>(aArray.Elements()); + } +}; +} // namespace + +template <typename T> +nsresult DoConsumeStream(nsIInputStream* aStream, uint32_t aMaxCount, + T& aResult) { + nsresult rv = NS_OK; + ResultTraits<T>::Clear(aResult); + + 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(); + CheckedInt<uint32_t> newLength = CheckedInt<uint32_t>(length) + avail; + if (!newLength.isValid()) { + return NS_ERROR_FILE_TOO_BIG; + } + + if (!aResult.SetLength(newLength.value(), fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + char* buf = ResultTraits<T>::GetStorage(aResult) + length; + + uint32_t n; + rv = aStream->Read(buf, avail, &n); + if (NS_FAILED(rv)) { + break; + } + if (n != avail) { + MOZ_ASSERT(n < avail, "What happened there???"); + aResult.SetLength(length + n); + } + if (n == 0) { + break; + } + aMaxCount -= n; + } + + return rv; +} + +nsresult NS_ConsumeStream(nsIInputStream* aStream, uint32_t aMaxCount, + nsACString& aResult) { + return DoConsumeStream(aStream, aMaxCount, aResult); +} + +nsresult NS_ConsumeStream(nsIInputStream* aStream, uint32_t aMaxCount, + nsTArray<uint8_t>& aResult) { + return DoConsumeStream(aStream, aMaxCount, aResult); +} + +//----------------------------------------------------------------------------- + +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; + *aCountWritten = 0; + 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 || rv != NS_ERROR_NOT_IMPLEMENTED; +} + +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; + *aCountRead = 0; + 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_CopyBufferToSegment(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_CopyStreamToSegment(nsIOutputStream* aOutputStream, void* aClosure, + char* aToSegment, uint32_t aFromOffset, + uint32_t aCount, uint32_t* aReadCount) { + nsIInputStream* fromStream = static_cast<nsIInputStream*>(aClosure); + return fromStream->Read(aToSegment, aCount, aReadCount); +} + +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; + + NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), 0, + 0, // default segment size and max size + true, true); // non-blocking + + // Propagate length information provided by nsIInputStreamLength. We don't use + // InputStreamLengthHelper::GetSyncLength to avoid the risk of blocking when + // called off-main-thread. + int64_t length = -1; + if (nsCOMPtr<nsIInputStreamLength> streamLength = do_QueryInterface(aSource); + streamLength && NS_SUCCEEDED(streamLength->Length(&length)) && + length != -1) { + reader = new mozilla::InputStreamLengthWrapper(reader.forget(), length); + } + + cloneable = do_QueryInterface(reader); + MOZ_ASSERT(cloneable && cloneable->GetCloneable()); + + nsresult 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; +} + +nsresult NS_MakeAsyncNonBlockingInputStream( + already_AddRefed<nsIInputStream> aSource, + nsIAsyncInputStream** aAsyncInputStream, bool aCloseWhenDone, + uint32_t aFlags, uint32_t aSegmentSize, uint32_t aSegmentCount) { + nsCOMPtr<nsIInputStream> source = std::move(aSource); + if (NS_WARN_IF(!aAsyncInputStream)) { + return NS_ERROR_FAILURE; + } + + bool nonBlocking = false; + nsresult rv = source->IsNonBlocking(&nonBlocking); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(source); + + if (nonBlocking && asyncStream) { + // This stream is perfect! + asyncStream.forget(aAsyncInputStream); + return NS_OK; + } + + if (nonBlocking) { + // If the stream is non-blocking but not async, we wrap it. + return NonBlockingAsyncInputStream::Create(source.forget(), + aAsyncInputStream); + } + + nsCOMPtr<nsIStreamTransportService> sts = + do_GetService(kStreamTransportServiceCID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsITransport> transport; + rv = sts->CreateInputTransport(source, aCloseWhenDone, + getter_AddRefs(transport)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIInputStream> wrapper; + rv = transport->OpenInputStream(aFlags, aSegmentSize, aSegmentCount, + getter_AddRefs(wrapper)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + asyncStream = do_QueryInterface(wrapper); + MOZ_ASSERT(asyncStream); + + asyncStream.forget(aAsyncInputStream); + return NS_OK; +} diff --git a/xpcom/io/nsStreamUtils.h b/xpcom/io/nsStreamUtils.h new file mode 100644 index 0000000000..f274d9134a --- /dev/null +++ b/xpcom/io/nsStreamUtils.h @@ -0,0 +1,332 @@ +/* -*- 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" +#include "nsIRunnable.h" + +class nsIAsyncInputStream; +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. + * + * The optional aPriority parameter allows the input stream runnable events + * to be dispatched with a different priority than normal. + */ +extern already_AddRefed<nsIInputStreamCallback> NS_NewInputStreamReadyEvent( + const char* aName, nsIInputStreamCallback* aNotify, nsIEventTarget* aTarget, + uint32_t aPriority = nsIRunnablePriority::PRIORITY_NORMAL); + +/** + * 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); + +/** + * Just like the above, but consumes into an nsTArray<uint8_t>. + */ +extern nsresult NS_ConsumeStream(nsIInputStream* aSource, uint32_t aMaxCount, + nsTArray<uint8_t>& 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_CopyBufferToSegment(nsIOutputStream* aOutputStream, + void* aClosure, char* aToSegment, + uint32_t aFromOffset, uint32_t aCount, + uint32_t* aReadCount); + +/** + * This function is intended to be passed to nsIOutputStream::WriteSegments to + * copy data into the nsIOutputStream from a nsIInputStream passed as the + * aClosure parameter to the WriteSegments function. + * + * @see nsIOutputStream.idl for a description of this function's parameters. + */ +extern nsresult NS_CopyStreamToSegment(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); + +/* + * This function returns a non-blocking nsIAsyncInputStream. Internally, + * different approaches are used based on what |aSource| is and what it + * implements. + * + * Note that this component takes the owninship of aSource. + * + * If the |aSource| is already a non-blocking and async stream, + * |aAsyncInputStream| will be equal to |aSource|. + * + * Otherwise, if |aSource| is just non-blocking, NonBlockingAsyncInputStream + * class is used in order to make it async. + * + * The last step is to use nsIStreamTransportService and create a pipe in order + * to expose a non-blocking async inputStream and read |aSource| data from + * a separate thread. + * + * In case we need to create a pipe, |aCloseWhenDone| will be used to create the + * inputTransport, |aFlags|, |aSegmentSize|, |asegmentCount| will be used to + * open the inputStream. If true, the input stream will be closed after it has + * been read. Read more in nsITransport.idl. + */ +extern nsresult NS_MakeAsyncNonBlockingInputStream( + already_AddRefed<nsIInputStream> aSource, + nsIAsyncInputStream** aAsyncInputStream, bool aCloseWhenDone = true, + uint32_t aFlags = 0, uint32_t aSegmentSize = 0, uint32_t aSegmentCount = 0); + +#endif // !nsStreamUtils_h__ diff --git a/xpcom/io/nsStringStream.cpp b/xpcom/io/nsStringStream.cpp new file mode 100644 index 0000000000..b3e83a58ba --- /dev/null +++ b/xpcom/io/nsStringStream.cpp @@ -0,0 +1,591 @@ +/* -*- 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 "nsIClassInfoImpl.h" +#include "mozilla/Attributes.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/Maybe.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/StreamBufferSourceImpl.h" +#include "nsIIPCSerializableInputStream.h" +#include "XPCOMModule.h" + +using namespace mozilla::ipc; +using mozilla::fallible; +using mozilla::MakeRefPtr; +using mozilla::MallocSizeOf; +using mozilla::nsBorrowedSource; +using mozilla::nsCStringSource; +using mozilla::nsTArraySource; +using mozilla::ReentrantMonitorAutoEnter; +using mozilla::Span; +using mozilla::StreamBufferSource; + +//----------------------------------------------------------------------------- +// 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_NSITELLABLESTREAM + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCSTRING + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + + nsStringInputStream() = default; + + nsresult Init(nsCString&& aString); + + nsresult Init(nsTArray<uint8_t>&& aArray); + + private: + ~nsStringInputStream() = default; + + size_t Length() const MOZ_REQUIRES(mMon) { + return mSource ? mSource->Data().Length() : 0; + } + + size_t LengthRemaining() const MOZ_REQUIRES(mMon) { + return Length() - mOffset; + } + + void Clear() MOZ_REQUIRES(mMon) { mSource = nullptr; } + + bool Closed() MOZ_REQUIRES(mMon) { return !mSource; } + + RefPtr<StreamBufferSource> mSource MOZ_GUARDED_BY(mMon); + size_t mOffset MOZ_GUARDED_BY(mMon) = 0; + + mutable mozilla::ReentrantMonitor mMon{"nsStringInputStream"}; +}; + +nsresult nsStringInputStream::Init(nsCString&& aString) { + nsCString string; + if (!string.Assign(std::move(aString), fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + auto source = MakeRefPtr<nsCStringSource>(std::move(string)); + return SetDataSource(source); +} + +nsresult nsStringInputStream::Init(nsTArray<uint8_t>&& aArray) { + auto source = MakeRefPtr<nsTArraySource>(std::move(aArray)); + return SetDataSource(source); +} + +// 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, nsITellableStream, + nsIIPCSerializableInputStream, + nsICloneableInputStream) +NS_IMPL_CI_INTERFACE_GETTER(nsStringInputStream, nsIStringInputStream, + nsIInputStream, nsISupportsCString, + nsISeekableStream, nsITellableStream, + nsICloneableInputStream) + +///////// +// nsISupportsCString implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::GetType(uint16_t* aType) { + *aType = TYPE_CSTRING; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::GetData(nsACString& data) { + ReentrantMonitorAutoEnter lock(mMon); + + // 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; + } + + return mSource->GetData(data); +} + +NS_IMETHODIMP +nsStringInputStream::SetData(const nsACString& aData) { + nsCString string; + if (!string.Assign(aData, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + auto source = MakeRefPtr<nsCStringSource>(std::move(string)); + return SetDataSource(source); +} + +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; + } + + nsCString string; + if (NS_WARN_IF(!string.Assign(aData, aDataLen, fallible))) { + return NS_ERROR_OUT_OF_MEMORY; + } + auto source = MakeRefPtr<nsCStringSource>(std::move(string)); + return SetDataSource(source); +} + +NS_IMETHODIMP +nsStringInputStream::SetUTF8Data(const nsACString& aData) { + return nsStringInputStream::SetData(aData); +} + +NS_IMETHODIMP +nsStringInputStream::AdoptData(char* aData, int32_t aDataLen) { + if (NS_WARN_IF(!aData)) { + return NS_ERROR_INVALID_ARG; + } + + nsCString string; + string.Adopt(aData, aDataLen); + auto source = MakeRefPtr<nsCStringSource>(std::move(string)); + return SetDataSource(source); +} + +NS_IMETHODIMP +nsStringInputStream::ShareData(const char* aData, int32_t aDataLen) { + if (NS_WARN_IF(!aData)) { + return NS_ERROR_INVALID_ARG; + } + + size_t length = aDataLen < 0 ? strlen(aData) : size_t(aDataLen); + auto source = MakeRefPtr<nsBorrowedSource>(Span{aData, length}); + return SetDataSource(source); +} + +NS_IMETHODIMP +nsStringInputStream::SetDataSource(StreamBufferSource* aSource) { + ReentrantMonitorAutoEnter lock(mMon); + + if (NS_WARN_IF(!aSource)) { + return NS_ERROR_INVALID_ARG; + } + + mSource = aSource; + mOffset = 0; + return NS_OK; +} + +NS_IMETHODIMP_(size_t) +nsStringInputStream::SizeOfIncludingThisIfUnshared(MallocSizeOf aMallocSizeOf) { + ReentrantMonitorAutoEnter lock(mMon); + + size_t n = aMallocSizeOf(this); + if (mSource) { + n += mSource->SizeOfIncludingThisIfUnshared(aMallocSizeOf); + } + return n; +} + +NS_IMETHODIMP_(size_t) +nsStringInputStream::SizeOfIncludingThisEvenIfShared( + MallocSizeOf aMallocSizeOf) { + ReentrantMonitorAutoEnter lock(mMon); + + size_t n = aMallocSizeOf(this); + if (mSource) { + n += mSource->SizeOfIncludingThisEvenIfShared(aMallocSizeOf); + } + return n; +} + +///////// +// nsIInputStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::Close() { + ReentrantMonitorAutoEnter lock(mMon); + + Clear(); + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::Available(uint64_t* aLength) { + ReentrantMonitorAutoEnter lock(mMon); + + NS_ASSERTION(aLength, "null ptr"); + + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + *aLength = LengthRemaining(); + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::StreamStatus() { + ReentrantMonitorAutoEnter lock(mMon); + return Closed() ? NS_BASE_STREAM_CLOSED : 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) { + ReentrantMonitorAutoEnter lock(mMon); + + 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 + size_t maxCount = LengthRemaining(); + if (maxCount == 0) { + *aResult = 0; + return NS_OK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + + RefPtr<StreamBufferSource> source = mSource; + size_t offset = mOffset; + + nsresult rv = aWriter(this, aClosure, source->Data().Elements() + offset, 0, + aCount, aResult); + + if (Closed()) { + NS_WARNING("nsStringInputStream was closed during ReadSegments"); + return NS_OK; + } + + MOZ_RELEASE_ASSERT(mSource == source, "String was replaced!"); + MOZ_RELEASE_ASSERT(mOffset == offset, "Nested read operation!"); + + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(*aResult <= aCount, + "writer should not write more than we asked it to write"); + mOffset = offset + *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) { + ReentrantMonitorAutoEnter lock(mMon); + + 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 += (int64_t)mOffset; + break; + case NS_SEEK_END: + newPos += (int64_t)Length(); + break; + default: + NS_ERROR("invalid aWhence"); + return NS_ERROR_INVALID_ARG; + } + + if (NS_WARN_IF(newPos < 0) || NS_WARN_IF(newPos > (int64_t)Length())) { + return NS_ERROR_INVALID_ARG; + } + + mOffset = (size_t)newPos; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::SetEOF() { + ReentrantMonitorAutoEnter lock(mMon); + + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + mOffset = Length(); + return NS_OK; +} + +///////// +// nsITellableStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::Tell(int64_t* aOutWhere) { + ReentrantMonitorAutoEnter lock(mMon); + + if (Closed()) { + return NS_BASE_STREAM_CLOSED; + } + + *aOutWhere = (int64_t)mOffset; + return NS_OK; +} + +///////// +// nsIIPCSerializableInputStream implementation +///////// + +void nsStringInputStream::SerializedComplexity(uint32_t aMaxSize, + uint32_t* aSizeUsed, + uint32_t* aPipes, + uint32_t* aTransferables) { + ReentrantMonitorAutoEnter lock(mMon); + + if (Length() >= aMaxSize) { + *aPipes = 1; + } else { + *aSizeUsed = Length(); + } +} + +void nsStringInputStream::Serialize(InputStreamParams& aParams, + uint32_t aMaxSize, uint32_t* aSizeUsed) { + ReentrantMonitorAutoEnter lock(mMon); + + MOZ_DIAGNOSTIC_ASSERT(!Closed(), "cannot send a closed stream!"); + MOZ_ASSERT(aSizeUsed); + *aSizeUsed = 0; + + if (Length() >= aMaxSize) { + // If the input stream is non-owning (i.e. it was initialized with + // `ShareData`), create a new owning source so that it doesn't go away while + // async copying. + if (!mSource->Owning()) { + auto source = + MakeRefPtr<nsCStringSource>(nsDependentCSubstring(mSource->Data())); + mSource = source; + } + + InputStreamHelper::SerializeInputStreamAsPipe(this, aParams); + return; + } + + *aSizeUsed = Length(); + + StringInputStreamParams params; + mSource->GetData(params.data()); + aParams = params; +} + +bool nsStringInputStream::Deserialize(const InputStreamParams& aParams) { + if (aParams.type() != InputStreamParams::TStringInputStreamParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const StringInputStreamParams& params = aParams.get_StringInputStreamParams(); + + if (NS_FAILED(SetData(params.data()))) { + NS_WARNING("SetData failed!"); + return false; + } + + return true; +} + +///////// +// nsICloneableInputStream implementation +///////// + +NS_IMETHODIMP +nsStringInputStream::GetCloneable(bool* aCloneableOut) { + *aCloneableOut = true; + return NS_OK; +} + +NS_IMETHODIMP +nsStringInputStream::Clone(nsIInputStream** aCloneOut) { + ReentrantMonitorAutoEnter lock(mMon); + + RefPtr<nsStringInputStream> ref = new nsStringInputStream(); + // Nothing else can access this yet, but suppress static analysis warnings + ReentrantMonitorAutoEnter reflock(ref->mMon); + if (mSource && !mSource->Owning()) { + auto data = mSource->Data(); + nsresult rv = ref->SetData(data.Elements(), data.Length()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + ref->mSource = mSource; + } + + // mOffset is overwritten by SetData(). + ref->mOffset = mOffset; + + ref.forget(aCloneOut); + return NS_OK; +} + +nsresult NS_NewByteInputStream(nsIInputStream** aStreamResult, + mozilla::Span<const char> aStringToRead, + nsAssignmentType aAssignment) { + MOZ_ASSERT(aStreamResult, "null out ptr"); + + RefPtr<nsStringInputStream> stream = new nsStringInputStream(); + + nsresult rv; + switch (aAssignment) { + case NS_ASSIGNMENT_COPY: + rv = stream->SetData(aStringToRead.Elements(), aStringToRead.Length()); + break; + case NS_ASSIGNMENT_DEPEND: + rv = stream->ShareData(aStringToRead.Elements(), aStringToRead.Length()); + break; + case NS_ASSIGNMENT_ADOPT: + rv = stream->AdoptData(const_cast<char*>(aStringToRead.Elements()), + aStringToRead.Length()); + 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_NewByteInputStream(nsIInputStream** aStreamResult, + nsTArray<uint8_t>&& aArray) { + MOZ_ASSERT(aStreamResult, "null out ptr"); + + RefPtr<nsStringInputStream> stream = new nsStringInputStream(); + + nsresult rv = stream->Init(std::move(aArray)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + stream.forget(aStreamResult); + return NS_OK; +} + +extern nsresult NS_NewByteInputStream(nsIInputStream** aStreamResult, + mozilla::StreamBufferSource* aSource) { + MOZ_ASSERT(aStreamResult, "null out ptr"); + + RefPtr<nsStringInputStream> stream = new nsStringInputStream(); + + nsresult rv = stream->SetDataSource(aSource); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + stream.forget(aStreamResult); + return NS_OK; +} + +nsresult NS_NewCStringInputStream(nsIInputStream** aStreamResult, + const nsACString& aStringToRead) { + MOZ_ASSERT(aStreamResult, "null out ptr"); + + RefPtr<nsStringInputStream> stream = new nsStringInputStream(); + + nsresult rv = stream->SetData(aStringToRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + stream.forget(aStreamResult); + return NS_OK; +} + +nsresult NS_NewCStringInputStream(nsIInputStream** aStreamResult, + nsCString&& aStringToRead) { + MOZ_ASSERT(aStreamResult, "null out ptr"); + + RefPtr<nsStringInputStream> stream = new nsStringInputStream(); + + nsresult rv = stream->Init(std::move(aStringToRead)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + stream.forget(aStreamResult); + return NS_OK; +} + +// factory method for constructing a nsStringInputStream object +nsresult nsStringInputStreamConstructor(REFNSIID aIID, void** aResult) { + *aResult = nullptr; + + 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..8568cc33f8 --- /dev/null +++ b/xpcom/io/nsStringStream.h @@ -0,0 +1,89 @@ +/* -*- 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 "nsString.h" +#include "nsTArray.h" + +/** + * Implements: + * nsIStringInputStream + * nsIInputStream + * nsISeekableStream + * nsITellableStream + * 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 \ + } \ + } + +/** + * An enumeration type used to represent a method of assignment. + */ +enum nsAssignmentType { + NS_ASSIGNMENT_COPY, // copy by value + NS_ASSIGNMENT_DEPEND, // copy by reference + NS_ASSIGNMENT_ADOPT // copy by reference (take ownership of resource) +}; + +/** + * Factory method to get an nsInputStream from a byte buffer. Result will + * implement nsIStringInputStream, nsITellableStream 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. + */ +extern nsresult NS_NewByteInputStream(nsIInputStream** aStreamResult, + mozilla::Span<const char> aStringToRead, + nsAssignmentType aAssignment); + +/** + * Factory method to get an nsIInputStream from an nsTArray representing a byte + * buffer. This will take ownership of the data and empty out the nsTArray. + * + * Result will implement nsIStringInputStream, nsITellableStream and + * nsISeekableStream. + */ +extern nsresult NS_NewByteInputStream(nsIInputStream** aStreamResult, + nsTArray<uint8_t>&& aArray); + +/** + * Factory method to get an nsIInputStream from an arbitrary StreamBufferSource. + * This will take a strong reference to the source. + * + * Result will implement nsIStringInputStream, nsITellableStream and + * nsISeekableStream. + */ +extern nsresult NS_NewByteInputStream(nsIInputStream** aStreamResult, + mozilla::StreamBufferSource* aSource); + +/** + * Factory method to get an nsInputStream from an nsACString. Result will + * implement nsIStringInputStream, nsTellableStream and nsISeekableStream. + */ +extern nsresult NS_NewCStringInputStream(nsIInputStream** aStreamResult, + const nsACString& aStringToRead); +extern nsresult NS_NewCStringInputStream(nsIInputStream** aStreamResult, + nsCString&& aStringToRead); + +#endif // nsStringStream_h__ diff --git a/xpcom/io/nsUnicharInputStream.cpp b/xpcom/io/nsUnicharInputStream.cpp new file mode 100644 index 0000000000..1366ab1e34 --- /dev/null +++ b/xpcom/io/nsUnicharInputStream.cpp @@ -0,0 +1,132 @@ +/* -*- 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 "nsString.h" +#include "nsTArray.h" +#include "nsCRT.h" +#include "nsStreamUtils.h" +#include "nsConverterInputStream.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() = default; +}; + +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<uint32_t>(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) + +//---------------------------------------------------------------------- + +nsresult NS_NewUnicharInputStream(nsIInputStream* aStreamToWrap, + nsIUnicharInputStream** aResult) { + *aResult = nullptr; + + // Create converter input stream + RefPtr<nsConverterInputStream> it = new nsConverterInputStream(); + nsresult rv = it->Init(aStreamToWrap, "UTF-8", STRING_BUFFER_SIZE, + nsIConverterInputStream::ERRORS_ARE_FATAL); + 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..de0534f01c --- /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..64546ca676 --- /dev/null +++ b/xpcom/io/nsWildCard.cpp @@ -0,0 +1,435 @@ +/* -*- 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; + [[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)); + 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; + [[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)); + 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..e3205fa7a1 --- /dev/null +++ b/xpcom/io/nsWildCard.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/. */ + +/* + * 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__ */ |