summaryrefslogtreecommitdiffstats
path: root/xpcom/io
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xpcom/io/Base64.cpp780
-rw-r--r--xpcom/io/Base64.h93
-rw-r--r--xpcom/io/CocoaFileUtils.h46
-rw-r--r--xpcom/io/CocoaFileUtils.mm289
-rw-r--r--xpcom/io/FileDescriptorFile.cpp442
-rw-r--r--xpcom/io/FileDescriptorFile.h48
-rw-r--r--xpcom/io/FilePreferences.cpp373
-rw-r--r--xpcom/io/FilePreferences.h40
-rw-r--r--xpcom/io/FileUtilsWin.cpp139
-rw-r--r--xpcom/io/FileUtilsWin.h147
-rw-r--r--xpcom/io/FixedBufferOutputStream.cpp161
-rw-r--r--xpcom/io/FixedBufferOutputStream.h78
-rw-r--r--xpcom/io/InputStreamLengthHelper.cpp258
-rw-r--r--xpcom/io/InputStreamLengthHelper.h57
-rw-r--r--xpcom/io/InputStreamLengthWrapper.cpp345
-rw-r--r--xpcom/io/InputStreamLengthWrapper.h84
-rw-r--r--xpcom/io/NonBlockingAsyncInputStream.cpp388
-rw-r--r--xpcom/io/NonBlockingAsyncInputStream.h85
-rw-r--r--xpcom/io/SlicedInputStream.cpp668
-rw-r--r--xpcom/io/SlicedInputStream.h102
-rw-r--r--xpcom/io/SnappyCompressOutputStream.cpp259
-rw-r--r--xpcom/io/SnappyCompressOutputStream.h68
-rw-r--r--xpcom/io/SnappyFrameUtils.cpp241
-rw-r--r--xpcom/io/SnappyFrameUtils.h80
-rw-r--r--xpcom/io/SnappyUncompressInputStream.cpp386
-rw-r--r--xpcom/io/SnappyUncompressInputStream.h89
-rw-r--r--xpcom/io/SpecialSystemDirectory.cpp748
-rw-r--r--xpcom/io/SpecialSystemDirectory.h62
-rw-r--r--xpcom/io/StreamBufferSink.h29
-rw-r--r--xpcom/io/StreamBufferSinkImpl.h49
-rw-r--r--xpcom/io/StreamBufferSource.h61
-rw-r--r--xpcom/io/StreamBufferSourceImpl.h82
-rw-r--r--xpcom/io/components.conf42
-rw-r--r--xpcom/io/crc32c.c154
-rw-r--r--xpcom/io/crc32c.h26
-rw-r--r--xpcom/io/moz.build162
-rw-r--r--xpcom/io/nsAnonymousTemporaryFile.cpp264
-rw-r--r--xpcom/io/nsAnonymousTemporaryFile.h39
-rw-r--r--xpcom/io/nsAppDirectoryServiceDefs.h102
-rw-r--r--xpcom/io/nsAppFileLocationProvider.cpp333
-rw-r--r--xpcom/io/nsAppFileLocationProvider.h45
-rw-r--r--xpcom/io/nsBinaryStream.cpp1007
-rw-r--r--xpcom/io/nsBinaryStream.h99
-rw-r--r--xpcom/io/nsDirectoryService.cpp448
-rw-r--r--xpcom/io/nsDirectoryService.h59
-rw-r--r--xpcom/io/nsDirectoryServiceDefs.h96
-rw-r--r--xpcom/io/nsDirectoryServiceUtils.h29
-rw-r--r--xpcom/io/nsEscape.cpp664
-rw-r--r--xpcom/io/nsEscape.h243
-rw-r--r--xpcom/io/nsIAsyncInputStream.idl105
-rw-r--r--xpcom/io/nsIAsyncOutputStream.idl104
-rw-r--r--xpcom/io/nsIBinaryInputStream.idl118
-rw-r--r--xpcom/io/nsIBinaryOutputStream.idl103
-rw-r--r--xpcom/io/nsICloneableInputStream.idl31
-rw-r--r--xpcom/io/nsIConverterInputStream.idl45
-rw-r--r--xpcom/io/nsIConverterOutputStream.idl30
-rw-r--r--xpcom/io/nsIDirectoryEnumerator.idl34
-rw-r--r--xpcom/io/nsIDirectoryService.idl101
-rw-r--r--xpcom/io/nsIFile.idl597
-rw-r--r--xpcom/io/nsIIOUtil.idl34
-rw-r--r--xpcom/io/nsIInputStream.idl172
-rw-r--r--xpcom/io/nsIInputStreamLength.idl85
-rw-r--r--xpcom/io/nsIInputStreamPriority.idl17
-rw-r--r--xpcom/io/nsIInputStreamTee.idl42
-rw-r--r--xpcom/io/nsILineInputStream.idl26
-rw-r--r--xpcom/io/nsILocalFileMac.idl212
-rw-r--r--xpcom/io/nsILocalFileWin.idl98
-rw-r--r--xpcom/io/nsIMultiplexInputStream.idl35
-rw-r--r--xpcom/io/nsIOUtil.cpp30
-rw-r--r--xpcom/io/nsIOUtil.h28
-rw-r--r--xpcom/io/nsIObjectInputStream.idl56
-rw-r--r--xpcom/io/nsIObjectOutputStream.idl99
-rw-r--r--xpcom/io/nsIOutputStream.idl164
-rw-r--r--xpcom/io/nsIPipe.idl171
-rw-r--r--xpcom/io/nsIRandomAccessStream.idl62
-rw-r--r--xpcom/io/nsISafeOutputStream.idl39
-rw-r--r--xpcom/io/nsIScriptableBase64Encoder.idl32
-rw-r--r--xpcom/io/nsIScriptableInputStream.idl67
-rw-r--r--xpcom/io/nsISeekableStream.idl65
-rw-r--r--xpcom/io/nsIStorageStream.idl69
-rw-r--r--xpcom/io/nsIStreamBufferAccess.idl88
-rw-r--r--xpcom/io/nsIStringStream.idl92
-rw-r--r--xpcom/io/nsITellableStream.idl34
-rw-r--r--xpcom/io/nsIUnicharInputStream.idl97
-rw-r--r--xpcom/io/nsIUnicharLineInputStream.idl26
-rw-r--r--xpcom/io/nsIUnicharOutputStream.idl45
-rw-r--r--xpcom/io/nsInputStreamTee.cpp341
-rw-r--r--xpcom/io/nsLinebreakConverter.cpp452
-rw-r--r--xpcom/io/nsLinebreakConverter.h141
-rw-r--r--xpcom/io/nsLocalFile.h124
-rw-r--r--xpcom/io/nsLocalFileCommon.cpp438
-rw-r--r--xpcom/io/nsLocalFileCommon.h16
-rw-r--r--xpcom/io/nsLocalFileUnix.cpp2916
-rw-r--r--xpcom/io/nsLocalFileUnix.h104
-rw-r--r--xpcom/io/nsLocalFileWin.cpp3697
-rw-r--r--xpcom/io/nsLocalFileWin.h127
-rw-r--r--xpcom/io/nsMultiplexInputStream.cpp1557
-rw-r--r--xpcom/io/nsMultiplexInputStream.h27
-rw-r--r--xpcom/io/nsNativeCharsetUtils.cpp98
-rw-r--r--xpcom/io/nsNativeCharsetUtils.h52
-rw-r--r--xpcom/io/nsPipe.h21
-rw-r--r--xpcom/io/nsPipe3.cpp1884
-rw-r--r--xpcom/io/nsScriptableBase64Encoder.cpp26
-rw-r--r--xpcom/io/nsScriptableBase64Encoder.h30
-rw-r--r--xpcom/io/nsScriptableInputStream.cpp117
-rw-r--r--xpcom/io/nsScriptableInputStream.h46
-rw-r--r--xpcom/io/nsSegmentedBuffer.cpp170
-rw-r--r--xpcom/io/nsSegmentedBuffer.h126
-rw-r--r--xpcom/io/nsStorageStream.cpp680
-rw-r--r--xpcom/io/nsStorageStream.h79
-rw-r--r--xpcom/io/nsStreamUtils.cpp976
-rw-r--r--xpcom/io/nsStreamUtils.h332
-rw-r--r--xpcom/io/nsStringStream.cpp591
-rw-r--r--xpcom/io/nsStringStream.h89
-rw-r--r--xpcom/io/nsUnicharInputStream.cpp132
-rw-r--r--xpcom/io/nsUnicharInputStream.h15
-rw-r--r--xpcom/io/nsWildCard.cpp435
-rw-r--r--xpcom/io/nsWildCard.h63
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("&lt;");
+ } else if (*cur == '>') {
+ aDst.AppendLiteral("&gt;");
+ } else if (*cur == '&') {
+ aDst.AppendLiteral("&amp;");
+ } else if (*cur == '"') {
+ aDst.AppendLiteral("&quot;");
+ } else if (*cur == '\'') {
+ aDst.AppendLiteral("&#39;");
+ } 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__ */