summaryrefslogtreecommitdiffstats
path: root/xpcom/io
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /xpcom/io
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'xpcom/io')
-rw-r--r--xpcom/io/Base64.cpp645
-rw-r--r--xpcom/io/Base64.h73
-rw-r--r--xpcom/io/CocoaFileUtils.h35
-rw-r--r--xpcom/io/CocoaFileUtils.mm267
-rw-r--r--xpcom/io/FileUtilsWin.cpp75
-rw-r--r--xpcom/io/FileUtilsWin.h144
-rw-r--r--xpcom/io/SlicedInputStream.cpp209
-rw-r--r--xpcom/io/SlicedInputStream.h50
-rw-r--r--xpcom/io/SnappyCompressOutputStream.cpp256
-rw-r--r--xpcom/io/SnappyCompressOutputStream.h69
-rw-r--r--xpcom/io/SnappyFrameUtils.cpp258
-rw-r--r--xpcom/io/SnappyFrameUtils.h85
-rw-r--r--xpcom/io/SnappyUncompressInputStream.cpp362
-rw-r--r--xpcom/io/SnappyUncompressInputStream.h90
-rw-r--r--xpcom/io/SpecialSystemDirectory.cpp830
-rw-r--r--xpcom/io/SpecialSystemDirectory.h107
-rw-r--r--xpcom/io/crc32c.c154
-rw-r--r--xpcom/io/crc32c.h23
-rw-r--r--xpcom/io/moz.build140
-rw-r--r--xpcom/io/nsAnonymousTemporaryFile.cpp314
-rw-r--r--xpcom/io/nsAnonymousTemporaryFile.h31
-rw-r--r--xpcom/io/nsAppDirectoryServiceDefs.h118
-rw-r--r--xpcom/io/nsAppFileLocationProvider.cpp609
-rw-r--r--xpcom/io/nsAppFileLocationProvider.h55
-rw-r--r--xpcom/io/nsBinaryStream.cpp1016
-rw-r--r--xpcom/io/nsBinaryStream.h101
-rw-r--r--xpcom/io/nsDirectoryService.cpp766
-rw-r--r--xpcom/io/nsDirectoryService.h66
-rw-r--r--xpcom/io/nsDirectoryServiceAtomList.h98
-rw-r--r--xpcom/io/nsDirectoryServiceDefs.h168
-rw-r--r--xpcom/io/nsDirectoryServiceUtils.h31
-rw-r--r--xpcom/io/nsEscape.cpp633
-rw-r--r--xpcom/io/nsEscape.h224
-rw-r--r--xpcom/io/nsIAsyncInputStream.idl104
-rw-r--r--xpcom/io/nsIAsyncOutputStream.idl104
-rw-r--r--xpcom/io/nsIBinaryInputStream.idl119
-rw-r--r--xpcom/io/nsIBinaryOutputStream.idl90
-rw-r--r--xpcom/io/nsICloneableInputStream.idl22
-rw-r--r--xpcom/io/nsIConverterInputStream.idl40
-rw-r--r--xpcom/io/nsIConverterOutputStream.idl44
-rw-r--r--xpcom/io/nsIDirectoryEnumerator.idl34
-rw-r--r--xpcom/io/nsIDirectoryService.idl103
-rw-r--r--xpcom/io/nsIFile.idl521
-rw-r--r--xpcom/io/nsIIOUtil.idl34
-rw-r--r--xpcom/io/nsIInputStream.idl147
-rw-r--r--xpcom/io/nsIInputStreamTee.idl42
-rw-r--r--xpcom/io/nsILineInputStream.idl26
-rw-r--r--xpcom/io/nsILocalFile.idl17
-rw-r--r--xpcom/io/nsILocalFileMac.idl179
-rw-r--r--xpcom/io/nsILocalFileWin.idl121
-rw-r--r--xpcom/io/nsIMultiplexInputStream.idl55
-rw-r--r--xpcom/io/nsIOUtil.cpp32
-rw-r--r--xpcom/io/nsIOUtil.h27
-rw-r--r--xpcom/io/nsIObjectInputStream.idl53
-rw-r--r--xpcom/io/nsIObjectOutputStream.idl97
-rw-r--r--xpcom/io/nsIOutputStream.idl145
-rw-r--r--xpcom/io/nsIPipe.idl171
-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.idl74
-rw-r--r--xpcom/io/nsIStorageStream.idl69
-rw-r--r--xpcom/io/nsIStreamBufferAccess.idl88
-rw-r--r--xpcom/io/nsIStringStream.idl66
-rw-r--r--xpcom/io/nsIUnicharInputStream.idl95
-rw-r--r--xpcom/io/nsIUnicharLineInputStream.idl26
-rw-r--r--xpcom/io/nsIUnicharOutputStream.idl47
-rw-r--r--xpcom/io/nsInputStreamTee.cpp366
-rw-r--r--xpcom/io/nsLinebreakConverter.cpp488
-rw-r--r--xpcom/io/nsLinebreakConverter.h131
-rw-r--r--xpcom/io/nsLocalFile.h124
-rw-r--r--xpcom/io/nsLocalFileCommon.cpp328
-rw-r--r--xpcom/io/nsLocalFileUnix.cpp2715
-rw-r--r--xpcom/io/nsLocalFileUnix.h138
-rw-r--r--xpcom/io/nsLocalFileWin.cpp3787
-rw-r--r--xpcom/io/nsLocalFileWin.h123
-rw-r--r--xpcom/io/nsMultiplexInputStream.cpp835
-rw-r--r--xpcom/io/nsMultiplexInputStream.h30
-rw-r--r--xpcom/io/nsNativeCharsetUtils.cpp1044
-rw-r--r--xpcom/io/nsNativeCharsetUtils.h63
-rw-r--r--xpcom/io/nsPipe.h24
-rw-r--r--xpcom/io/nsPipe3.cpp2007
-rw-r--r--xpcom/io/nsScriptableBase64Encoder.cpp28
-rw-r--r--xpcom/io/nsScriptableBase64Encoder.h30
-rw-r--r--xpcom/io/nsScriptableInputStream.cpp134
-rw-r--r--xpcom/io/nsScriptableInputStream.h47
-rw-r--r--xpcom/io/nsSegmentedBuffer.cpp169
-rw-r--r--xpcom/io/nsSegmentedBuffer.h109
-rw-r--r--xpcom/io/nsStorageStream.cpp648
-rw-r--r--xpcom/io/nsStorageStream.h73
-rw-r--r--xpcom/io/nsStreamUtils.cpp957
-rw-r--r--xpcom/io/nsStreamUtils.h295
-rw-r--r--xpcom/io/nsStringStream.cpp452
-rw-r--r--xpcom/io/nsStringStream.h63
-rw-r--r--xpcom/io/nsUnicharInputStream.cpp398
-rw-r--r--xpcom/io/nsUnicharInputStream.h15
-rw-r--r--xpcom/io/nsWildCard.cpp481
-rw-r--r--xpcom/io/nsWildCard.h64
98 files changed, 27198 insertions, 0 deletions
diff --git a/xpcom/io/Base64.cpp b/xpcom/io/Base64.cpp
new file mode 100644
index 000000000..911c0595a
--- /dev/null
+++ b/xpcom/io/Base64.cpp
@@ -0,0 +1,645 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Base64.h"
+
+#include "mozilla/UniquePtrExtensions.h"
+#include "nsIInputStream.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include "plbase64.h"
+
+namespace {
+
+// BEGIN base64 encode code copied and modified from NSPR
+const unsigned char* base =
+ (unsigned char*)"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+
+template<typename T>
+static void
+Encode3to4(const unsigned char* aSrc, T* aDest)
+{
+ uint32_t b32 = (uint32_t)0;
+ int i, j = 18;
+
+ for (i = 0; i < 3; ++i) {
+ b32 <<= 8;
+ b32 |= (uint32_t)aSrc[i];
+ }
+
+ for (i = 0; i < 4; ++i) {
+ aDest[i] = base[(uint32_t)((b32 >> j) & 0x3F)];
+ j -= 6;
+ }
+}
+
+template<typename T>
+static void
+Encode2to4(const unsigned char* aSrc, T* aDest)
+{
+ aDest[0] = base[(uint32_t)((aSrc[0] >> 2) & 0x3F)];
+ aDest[1] = base[(uint32_t)(((aSrc[0] & 0x03) << 4) | ((aSrc[1] >> 4) & 0x0F))];
+ aDest[2] = base[(uint32_t)((aSrc[1] & 0x0F) << 2)];
+ aDest[3] = (unsigned char)'=';
+}
+
+template<typename T>
+static void
+Encode1to4(const unsigned char* aSrc, T* aDest)
+{
+ aDest[0] = base[(uint32_t)((aSrc[0] >> 2) & 0x3F)];
+ aDest[1] = base[(uint32_t)((aSrc[0] & 0x03) << 4)];
+ aDest[2] = (unsigned char)'=';
+ aDest[3] = (unsigned char)'=';
+}
+
+template<typename T>
+static void
+Encode(const unsigned char* aSrc, uint32_t aSrcLen, T* aDest)
+{
+ while (aSrcLen >= 3) {
+ Encode3to4(aSrc, aDest);
+ aSrc += 3;
+ aDest += 4;
+ aSrcLen -= 3;
+ }
+
+ switch (aSrcLen) {
+ case 2:
+ Encode2to4(aSrc, aDest);
+ break;
+ case 1:
+ Encode1to4(aSrc, aDest);
+ break;
+ case 0:
+ break;
+ default:
+ NS_NOTREACHED("coding error");
+ }
+}
+
+// END base64 encode code copied and modified from NSPR.
+
+template<typename T>
+struct EncodeInputStream_State
+{
+ unsigned char c[3];
+ uint8_t charsOnStack;
+ typename T::char_type* buffer;
+};
+
+template<typename T>
+nsresult
+EncodeInputStream_Encoder(nsIInputStream* aStream,
+ void* aClosure,
+ const char* aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ NS_ASSERTION(aCount > 0, "Er, what?");
+
+ EncodeInputStream_State<T>* state =
+ static_cast<EncodeInputStream_State<T>*>(aClosure);
+
+ // If we have any data left from last time, encode it now.
+ uint32_t countRemaining = aCount;
+ const unsigned char* src = (const unsigned char*)aFromSegment;
+ if (state->charsOnStack) {
+ unsigned char firstSet[4];
+ if (state->charsOnStack == 1) {
+ firstSet[0] = state->c[0];
+ firstSet[1] = src[0];
+ firstSet[2] = (countRemaining > 1) ? src[1] : '\0';
+ firstSet[3] = '\0';
+ } else /* state->charsOnStack == 2 */ {
+ firstSet[0] = state->c[0];
+ firstSet[1] = state->c[1];
+ firstSet[2] = src[0];
+ firstSet[3] = '\0';
+ }
+ Encode(firstSet, 3, state->buffer);
+ state->buffer += 4;
+ countRemaining -= (3 - state->charsOnStack);
+ src += (3 - state->charsOnStack);
+ state->charsOnStack = 0;
+ }
+
+ // Encode the bulk of the
+ uint32_t encodeLength = countRemaining - countRemaining % 3;
+ MOZ_ASSERT(encodeLength % 3 == 0,
+ "Should have an exact number of triplets!");
+ Encode(src, encodeLength, state->buffer);
+ state->buffer += (encodeLength / 3) * 4;
+ src += encodeLength;
+ countRemaining -= encodeLength;
+
+ // We must consume all data, so if there's some data left stash it
+ *aWriteCount = aCount;
+
+ if (countRemaining) {
+ // We should never have a full triplet left at this point.
+ MOZ_ASSERT(countRemaining < 3, "We should have encoded more!");
+ state->c[0] = src[0];
+ state->c[1] = (countRemaining == 2) ? src[1] : '\0';
+ state->charsOnStack = countRemaining;
+ }
+
+ return NS_OK;
+}
+
+template<typename T>
+nsresult
+EncodeInputStream(nsIInputStream* aInputStream,
+ T& aDest,
+ uint32_t aCount,
+ uint32_t aOffset)
+{
+ nsresult rv;
+ uint64_t count64 = aCount;
+
+ if (!aCount) {
+ rv = aInputStream->Available(&count64);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ // if count64 is over 4GB, it will be failed at the below condition,
+ // then will return NS_ERROR_OUT_OF_MEMORY
+ aCount = (uint32_t)count64;
+ }
+
+ uint64_t countlong =
+ (count64 + 2) / 3 * 4; // +2 due to integer math.
+ if (countlong + aOffset > UINT32_MAX) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t count = uint32_t(countlong);
+
+ if (!aDest.SetLength(count + aOffset, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ EncodeInputStream_State<T> state;
+ state.charsOnStack = 0;
+ state.c[2] = '\0';
+ state.buffer = aOffset + aDest.BeginWriting();
+
+ while (1) {
+ uint32_t read = 0;
+
+ rv = aInputStream->ReadSegments(&EncodeInputStream_Encoder<T>,
+ (void*)&state,
+ aCount,
+ &read);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ NS_RUNTIMEABORT("Not implemented for async streams!");
+ }
+ if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ NS_RUNTIMEABORT("Requires a stream that implements ReadSegments!");
+ }
+ return rv;
+ }
+
+ if (!read) {
+ break;
+ }
+ }
+
+ // Finish encoding if anything is left
+ if (state.charsOnStack) {
+ Encode(state.c, state.charsOnStack, state.buffer);
+ }
+
+ if (aDest.Length()) {
+ // May belong to an nsCString with an unallocated buffer, so only null
+ // terminate if there is a need to.
+ *aDest.EndWriting() = '\0';
+ }
+
+ return NS_OK;
+}
+
+static const char kBase64URLAlphabet[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+// Maps an encoded character to a value in the Base64 URL alphabet, per
+// RFC 4648, Table 2. Invalid input characters map to UINT8_MAX.
+static const uint8_t kBase64URLDecodeTable[] = {
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255,
+ 62 /* - */,
+ 255, 255,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, /* 0 - 9 */
+ 255, 255, 255, 255, 255, 255, 255,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, /* A - Z */
+ 255, 255, 255, 255,
+ 63 /* _ */,
+ 255,
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
+ 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, /* a - z */
+ 255, 255, 255, 255,
+};
+
+bool
+Base64URLCharToValue(char aChar, uint8_t* aValue) {
+ uint8_t index = static_cast<uint8_t>(aChar);
+ *aValue = kBase64URLDecodeTable[index & 0x7f];
+ return (*aValue != 255) && !(index & ~0x7f);
+}
+
+} // namespace
+
+namespace mozilla {
+
+nsresult
+Base64EncodeInputStream(nsIInputStream* aInputStream,
+ nsACString& aDest,
+ uint32_t aCount,
+ uint32_t aOffset)
+{
+ return EncodeInputStream<nsACString>(aInputStream, aDest, aCount, aOffset);
+}
+
+nsresult
+Base64EncodeInputStream(nsIInputStream* aInputStream,
+ nsAString& aDest,
+ uint32_t aCount,
+ uint32_t aOffset)
+{
+ return EncodeInputStream<nsAString>(aInputStream, aDest, aCount, aOffset);
+}
+
+nsresult
+Base64Encode(const char* aBinary, uint32_t aBinaryLen, char** aBase64)
+{
+ // Check for overflow.
+ if (aBinaryLen > (UINT32_MAX / 4) * 3) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Don't ask PR_Base64Encode to encode empty strings.
+ if (aBinaryLen == 0) {
+ *aBase64 = (char*)moz_xmalloc(1);
+ (*aBase64)[0] = '\0';
+ return NS_OK;
+ }
+
+ *aBase64 = nullptr;
+ uint32_t base64Len = ((aBinaryLen + 2) / 3) * 4;
+
+ // Add one byte for null termination.
+ UniqueFreePtr<char[]> base64((char*)malloc(base64Len + 1));
+ if (!base64) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!PL_Base64Encode(aBinary, aBinaryLen, base64.get())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // PL_Base64Encode doesn't null terminate the buffer for us when we pass
+ // the buffer in. Do that manually.
+ base64[base64Len] = '\0';
+
+ *aBase64 = base64.release();
+ return NS_OK;
+}
+
+nsresult
+Base64Encode(const nsACString& aBinary, nsACString& aBase64)
+{
+ // Check for overflow.
+ if (aBinary.Length() > (UINT32_MAX / 4) * 3) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Don't ask PR_Base64Encode to encode empty strings.
+ if (aBinary.IsEmpty()) {
+ aBase64.Truncate();
+ return NS_OK;
+ }
+
+ uint32_t base64Len = ((aBinary.Length() + 2) / 3) * 4;
+
+ // Add one byte for null termination.
+ if (!aBase64.SetCapacity(base64Len + 1, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ char* base64 = aBase64.BeginWriting();
+ if (!PL_Base64Encode(aBinary.BeginReading(), aBinary.Length(), base64)) {
+ aBase64.Truncate();
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // PL_Base64Encode doesn't null terminate the buffer for us when we pass
+ // the buffer in. Do that manually.
+ base64[base64Len] = '\0';
+
+ aBase64.SetLength(base64Len);
+ return NS_OK;
+}
+
+nsresult
+Base64Encode(const nsAString& aBinary, nsAString& aBase64)
+{
+ NS_LossyConvertUTF16toASCII binary(aBinary);
+ nsAutoCString base64;
+
+ nsresult rv = Base64Encode(binary, base64);
+ if (NS_SUCCEEDED(rv)) {
+ CopyASCIItoUTF16(base64, aBase64);
+ } else {
+ aBase64.Truncate();
+ }
+
+ return rv;
+}
+
+static nsresult
+Base64DecodeHelper(const char* aBase64, uint32_t aBase64Len, char* aBinary,
+ uint32_t* aBinaryLen)
+{
+ MOZ_ASSERT(aBinary);
+ if (!PL_Base64Decode(aBase64, aBase64Len, aBinary)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // PL_Base64Decode doesn't null terminate the buffer for us when we pass
+ // the buffer in. Do that manually, taking into account the number of '='
+ // characters we were passed.
+ if (aBase64Len != 0 && aBase64[aBase64Len - 1] == '=') {
+ if (aBase64Len > 1 && aBase64[aBase64Len - 2] == '=') {
+ *aBinaryLen -= 2;
+ } else {
+ *aBinaryLen -= 1;
+ }
+ }
+ aBinary[*aBinaryLen] = '\0';
+ return NS_OK;
+}
+
+nsresult
+Base64Decode(const char* aBase64, uint32_t aBase64Len, char** aBinary,
+ uint32_t* aBinaryLen)
+{
+ // Check for overflow.
+ if (aBase64Len > UINT32_MAX / 3) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Don't ask PR_Base64Decode to decode the empty string.
+ if (aBase64Len == 0) {
+ *aBinary = (char*)moz_xmalloc(1);
+ (*aBinary)[0] = '\0';
+ *aBinaryLen = 0;
+ return NS_OK;
+ }
+
+ *aBinary = nullptr;
+ *aBinaryLen = (aBase64Len * 3) / 4;
+
+ // Add one byte for null termination.
+ UniqueFreePtr<char[]> binary((char*)malloc(*aBinaryLen + 1));
+ if (!binary) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsresult rv =
+ Base64DecodeHelper(aBase64, aBase64Len, binary.get(), aBinaryLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *aBinary = binary.release();
+ return NS_OK;
+}
+
+nsresult
+Base64Decode(const nsACString& aBase64, nsACString& aBinary)
+{
+ // Check for overflow.
+ if (aBase64.Length() > UINT32_MAX / 3) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Don't ask PR_Base64Decode to decode the empty string
+ if (aBase64.IsEmpty()) {
+ aBinary.Truncate();
+ return NS_OK;
+ }
+
+ uint32_t binaryLen = ((aBase64.Length() * 3) / 4);
+
+ // Add one byte for null termination.
+ if (!aBinary.SetCapacity(binaryLen + 1, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ char* binary = aBinary.BeginWriting();
+ nsresult rv = Base64DecodeHelper(aBase64.BeginReading(), aBase64.Length(),
+ binary, &binaryLen);
+ if (NS_FAILED(rv)) {
+ aBinary.Truncate();
+ return rv;
+ }
+
+ aBinary.SetLength(binaryLen);
+ return NS_OK;
+}
+
+nsresult
+Base64Decode(const nsAString& aBase64, nsAString& aBinary)
+{
+ NS_LossyConvertUTF16toASCII base64(aBase64);
+ nsAutoCString binary;
+
+ nsresult rv = Base64Decode(base64, binary);
+ if (NS_SUCCEEDED(rv)) {
+ CopyASCIItoUTF16(binary, aBinary);
+ } else {
+ aBinary.Truncate();
+ }
+
+ return rv;
+}
+
+nsresult
+Base64URLDecode(const nsACString& aBase64,
+ Base64URLDecodePaddingPolicy aPaddingPolicy,
+ FallibleTArray<uint8_t>& aBinary)
+{
+ // Don't decode empty strings.
+ if (aBase64.IsEmpty()) {
+ aBinary.Clear();
+ return NS_OK;
+ }
+
+ // Check for overflow.
+ uint32_t base64Len = aBase64.Length();
+ if (base64Len > UINT32_MAX / 3) {
+ return NS_ERROR_FAILURE;
+ }
+ const char* base64 = aBase64.BeginReading();
+
+ // The decoded length may be 1-2 bytes over, depending on the final quantum.
+ uint32_t binaryLen = (base64Len * 3) / 4;
+
+ // Determine whether to check for and ignore trailing padding.
+ bool maybePadded = false;
+ switch (aPaddingPolicy) {
+ case Base64URLDecodePaddingPolicy::Require:
+ if (base64Len % 4) {
+ // Padded input length must be a multiple of 4.
+ return NS_ERROR_INVALID_ARG;
+ }
+ maybePadded = true;
+ break;
+
+ case Base64URLDecodePaddingPolicy::Ignore:
+ // Check for padding only if the length is a multiple of 4.
+ maybePadded = !(base64Len % 4);
+ break;
+
+ // If we're expecting unpadded input, no need for additional checks.
+ // `=` isn't in the decode table, so padded strings will fail to decode.
+ default:
+ MOZ_FALLTHROUGH_ASSERT("Invalid decode padding policy");
+ case Base64URLDecodePaddingPolicy::Reject:
+ break;
+ }
+ if (maybePadded && base64[base64Len - 1] == '=') {
+ if (base64[base64Len - 2] == '=') {
+ base64Len -= 2;
+ } else {
+ base64Len -= 1;
+ }
+ }
+
+ if (NS_WARN_IF(!aBinary.SetCapacity(binaryLen, mozilla::fallible))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ aBinary.SetLengthAndRetainStorage(binaryLen);
+ uint8_t* binary = aBinary.Elements();
+
+ for (; base64Len >= 4; base64Len -= 4) {
+ uint8_t w, x, y, z;
+ if (!Base64URLCharToValue(*base64++, &w) ||
+ !Base64URLCharToValue(*base64++, &x) ||
+ !Base64URLCharToValue(*base64++, &y) ||
+ !Base64URLCharToValue(*base64++, &z)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *binary++ = w << 2 | x >> 4;
+ *binary++ = x << 4 | y >> 2;
+ *binary++ = y << 6 | z;
+ }
+
+ if (base64Len == 3) {
+ uint8_t w, x, y;
+ if (!Base64URLCharToValue(*base64++, &w) ||
+ !Base64URLCharToValue(*base64++, &x) ||
+ !Base64URLCharToValue(*base64++, &y)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *binary++ = w << 2 | x >> 4;
+ *binary++ = x << 4 | y >> 2;
+ } else if (base64Len == 2) {
+ uint8_t w, x;
+ if (!Base64URLCharToValue(*base64++, &w) ||
+ !Base64URLCharToValue(*base64++, &x)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *binary++ = w << 2 | x >> 4;
+ } else if (base64Len) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Set the length to the actual number of decoded bytes.
+ aBinary.TruncateLength(binary - aBinary.Elements());
+ return NS_OK;
+}
+
+nsresult
+Base64URLEncode(uint32_t aBinaryLen, const uint8_t* aBinary,
+ Base64URLEncodePaddingPolicy aPaddingPolicy,
+ nsACString& aBase64)
+{
+ // Don't encode empty strings.
+ if (aBinaryLen == 0) {
+ aBase64.Truncate();
+ return NS_OK;
+ }
+
+ // Check for overflow.
+ if (aBinaryLen > (UINT32_MAX / 4) * 3) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Allocate a buffer large enough to hold the encoded string with padding.
+ // Add one byte for null termination.
+ uint32_t base64Len = ((aBinaryLen + 2) / 3) * 4;
+ if (NS_WARN_IF(!aBase64.SetCapacity(base64Len + 1, fallible))) {
+ aBase64.Truncate();
+ return NS_ERROR_FAILURE;
+ }
+
+ char* base64 = aBase64.BeginWriting();
+
+ uint32_t index = 0;
+ for (; index + 3 <= aBinaryLen; index += 3) {
+ *base64++ = kBase64URLAlphabet[aBinary[index] >> 2];
+ *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4) |
+ (aBinary[index + 1] >> 4)];
+ *base64++ = kBase64URLAlphabet[((aBinary[index + 1] & 0xf) << 2) |
+ (aBinary[index + 2] >> 6)];
+ *base64++ = kBase64URLAlphabet[aBinary[index + 2] & 0x3f];
+ }
+
+ uint32_t remaining = aBinaryLen - index;
+ if (remaining == 1) {
+ *base64++ = kBase64URLAlphabet[aBinary[index] >> 2];
+ *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4)];
+ } else if (remaining == 2) {
+ *base64++ = kBase64URLAlphabet[aBinary[index] >> 2];
+ *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4) |
+ (aBinary[index + 1] >> 4)];
+ *base64++ = kBase64URLAlphabet[((aBinary[index + 1] & 0xf) << 2)];
+ }
+
+ uint32_t length = base64 - aBase64.BeginWriting();
+ if (aPaddingPolicy == Base64URLEncodePaddingPolicy::Include) {
+ if (length % 4 == 2) {
+ *base64++ = '=';
+ *base64++ = '=';
+ length += 2;
+ } else if (length % 4 == 3) {
+ *base64++ = '=';
+ length += 1;
+ }
+ } else {
+ MOZ_ASSERT(aPaddingPolicy == Base64URLEncodePaddingPolicy::Omit,
+ "Invalid encode padding policy");
+ }
+
+ // Null terminate and truncate to the actual number of characters.
+ *base64 = '\0';
+ aBase64.SetLength(length);
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/xpcom/io/Base64.h b/xpcom/io/Base64.h
new file mode 100644
index 000000000..0c3f1e140
--- /dev/null
+++ b/xpcom/io/Base64.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_Base64_h__
+#define mozilla_Base64_h__
+
+#include "nsString.h"
+
+class nsIInputStream;
+
+namespace mozilla {
+
+MOZ_MUST_USE nsresult
+Base64EncodeInputStream(nsIInputStream* aInputStream,
+ nsACString& aDest,
+ uint32_t aCount,
+ uint32_t aOffset = 0);
+MOZ_MUST_USE nsresult
+Base64EncodeInputStream(nsIInputStream* aInputStream,
+ nsAString& aDest,
+ uint32_t aCount,
+ uint32_t aOffset = 0);
+
+MOZ_MUST_USE nsresult
+Base64Encode(const char* aBinary, uint32_t aBinaryLen, char** aBase64);
+MOZ_MUST_USE nsresult
+Base64Encode(const nsACString& aBinary, nsACString& aBase64);
+MOZ_MUST_USE nsresult
+Base64Encode(const nsAString& aBinary, nsAString& aBase64);
+
+MOZ_MUST_USE nsresult
+Base64Decode(const char* aBase64, uint32_t aBase64Len, char** aBinary,
+ uint32_t* aBinaryLen);
+MOZ_MUST_USE nsresult
+Base64Decode(const nsACString& aBase64, nsACString& aBinary);
+MOZ_MUST_USE nsresult
+Base64Decode(const nsAString& aBase64, nsAString& aBinary);
+
+enum class Base64URLEncodePaddingPolicy {
+ Include,
+ Omit,
+};
+
+/**
+ * Converts |aBinary| to an unpadded, Base64 URL-encoded string per RFC 4648.
+ * Aims to encode the data in constant time. The caller retains ownership
+ * of |aBinary|.
+ */
+MOZ_MUST_USE nsresult
+Base64URLEncode(uint32_t aBinaryLen, const uint8_t* aBinary,
+ Base64URLEncodePaddingPolicy aPaddingPolicy,
+ nsACString& aBase64);
+
+enum class Base64URLDecodePaddingPolicy {
+ Require,
+ Ignore,
+ Reject,
+};
+
+/**
+ * Decodes a Base64 URL-encoded |aBase64| into |aBinary|.
+ */
+MOZ_MUST_USE nsresult
+Base64URLDecode(const nsACString& aBase64,
+ Base64URLDecodePaddingPolicy aPaddingPolicy,
+ FallibleTArray<uint8_t>& aBinary);
+
+} // namespace mozilla
+
+#endif
diff --git a/xpcom/io/CocoaFileUtils.h b/xpcom/io/CocoaFileUtils.h
new file mode 100644
index 000000000..7127cb65d
--- /dev/null
+++ b/xpcom/io/CocoaFileUtils.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This namespace contains methods with Obj-C/Cocoa implementations. The header
+// is C/C++ for inclusion in C/C++-only files.
+
+#ifndef CocoaFileUtils_h_
+#define CocoaFileUtils_h_
+
+#include "nscore.h"
+#include <CoreFoundation/CoreFoundation.h>
+
+namespace CocoaFileUtils {
+
+nsresult RevealFileInFinder(CFURLRef aUrl);
+nsresult OpenURL(CFURLRef aUrl);
+nsresult GetFileCreatorCode(CFURLRef aUrl, OSType* aCreatorCode);
+nsresult SetFileCreatorCode(CFURLRef aUrl, OSType aCreatorCode);
+nsresult GetFileTypeCode(CFURLRef aUrl, OSType* aTypeCode);
+nsresult SetFileTypeCode(CFURLRef aUrl, OSType aTypeCode);
+void AddOriginMetadataToFile(const CFStringRef filePath,
+ const CFURLRef sourceURL,
+ const CFURLRef referrerURL);
+void AddQuarantineMetadataToFile(const CFStringRef filePath,
+ const CFURLRef sourceURL,
+ const CFURLRef referrerURL,
+ const bool isFromWeb);
+CFURLRef GetTemporaryFolderCFURLRef();
+
+} // namespace CocoaFileUtils
+
+#endif
diff --git a/xpcom/io/CocoaFileUtils.mm b/xpcom/io/CocoaFileUtils.mm
new file mode 100644
index 000000000..a02b82ac1
--- /dev/null
+++ b/xpcom/io/CocoaFileUtils.mm
@@ -0,0 +1,267 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:set ts=2 sts=2 sw=2 et cin:
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CocoaFileUtils.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include <Cocoa/Cocoa.h>
+#include "nsObjCExceptions.h"
+#include "nsDebug.h"
+
+// Need to cope with us using old versions of the SDK and needing this on 10.10+
+#if !defined(MAC_OS_X_VERSION_10_10) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10)
+const CFStringRef kCFURLQuarantinePropertiesKey = CFSTR("NSURLQuarantinePropertiesKey");
+#endif
+
+namespace CocoaFileUtils {
+
+nsresult RevealFileInFinder(CFURLRef url)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!url))
+ return NS_ERROR_INVALID_ARG;
+
+ NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
+ BOOL success = [[NSWorkspace sharedWorkspace] selectFile:[(NSURL*)url path] inFileViewerRootedAtPath:@""];
+ [ap release];
+
+ return (success ? NS_OK : NS_ERROR_FAILURE);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult OpenURL(CFURLRef url)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!url))
+ return NS_ERROR_INVALID_ARG;
+
+ NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
+ BOOL success = [[NSWorkspace sharedWorkspace] openURL:(NSURL*)url];
+ [ap release];
+
+ return (success ? NS_OK : NS_ERROR_FAILURE);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult GetFileCreatorCode(CFURLRef url, OSType *creatorCode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!url) || NS_WARN_IF(!creatorCode))
+ return NS_ERROR_INVALID_ARG;
+
+ nsAutoreleasePool localPool;
+
+ NSString *resolvedPath = [[(NSURL*)url path] stringByResolvingSymlinksInPath];
+ if (!resolvedPath) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSDictionary* dict = [[NSFileManager defaultManager] attributesOfItemAtPath:resolvedPath error:nil];
+ if (!dict) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSNumber* creatorNum = (NSNumber*)[dict objectForKey:NSFileHFSCreatorCode];
+ if (!creatorNum) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *creatorCode = [creatorNum unsignedLongValue];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult SetFileCreatorCode(CFURLRef url, OSType creatorCode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!url))
+ return NS_ERROR_INVALID_ARG;
+
+ NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
+ NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:creatorCode] forKey:NSFileHFSCreatorCode];
+ BOOL success = [[NSFileManager defaultManager] setAttributes:dict ofItemAtPath:[(NSURL*)url path] error:nil];
+ [ap release];
+ return (success ? NS_OK : NS_ERROR_FAILURE);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult GetFileTypeCode(CFURLRef url, OSType *typeCode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!url) || NS_WARN_IF(!typeCode))
+ return NS_ERROR_INVALID_ARG;
+
+ nsAutoreleasePool localPool;
+
+ NSString *resolvedPath = [[(NSURL*)url path] stringByResolvingSymlinksInPath];
+ if (!resolvedPath) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSDictionary* dict = [[NSFileManager defaultManager] attributesOfItemAtPath:resolvedPath error:nil];
+ if (!dict) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NSNumber* typeNum = (NSNumber*)[dict objectForKey:NSFileHFSTypeCode];
+ if (!typeNum) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *typeCode = [typeNum unsignedLongValue];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsresult SetFileTypeCode(CFURLRef url, OSType typeCode)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ if (NS_WARN_IF(!url))
+ return NS_ERROR_INVALID_ARG;
+
+ NSAutoreleasePool* ap = [[NSAutoreleasePool alloc] init];
+ NSDictionary* dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:typeCode] forKey:NSFileHFSTypeCode];
+ BOOL success = [[NSFileManager defaultManager] setAttributes:dict ofItemAtPath:[(NSURL*)url path] error:nil];
+ [ap release];
+ return (success ? NS_OK : NS_ERROR_FAILURE);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+void AddOriginMetadataToFile(const CFStringRef filePath,
+ const CFURLRef sourceURL,
+ const CFURLRef referrerURL) {
+ typedef OSStatus (*MDItemSetAttribute_type)(MDItemRef, CFStringRef, CFTypeRef);
+ static MDItemSetAttribute_type mdItemSetAttributeFunc = NULL;
+
+ static bool did_symbol_lookup = false;
+ if (!did_symbol_lookup) {
+ did_symbol_lookup = true;
+
+ CFBundleRef metadata_bundle = ::CFBundleGetBundleWithIdentifier(CFSTR("com.apple.Metadata"));
+ if (!metadata_bundle) {
+ return;
+ }
+
+ mdItemSetAttributeFunc = (MDItemSetAttribute_type)
+ ::CFBundleGetFunctionPointerForName(metadata_bundle, CFSTR("MDItemSetAttribute"));
+ }
+ if (!mdItemSetAttributeFunc) {
+ return;
+ }
+
+ MDItemRef mdItem = ::MDItemCreate(NULL, filePath);
+ if (!mdItem) {
+ return;
+ }
+
+ CFMutableArrayRef list = ::CFArrayCreateMutable(kCFAllocatorDefault, 2, NULL);
+ if (!list) {
+ ::CFRelease(mdItem);
+ return;
+ }
+
+ // The first item in the list is the source URL of the downloaded file.
+ if (sourceURL) {
+ ::CFArrayAppendValue(list, ::CFURLGetString(sourceURL));
+ }
+
+ // If the referrer is known, store that in the second position.
+ if (referrerURL) {
+ ::CFArrayAppendValue(list, ::CFURLGetString(referrerURL));
+ }
+
+ mdItemSetAttributeFunc(mdItem, kMDItemWhereFroms, list);
+
+ ::CFRelease(list);
+ ::CFRelease(mdItem);
+}
+
+void AddQuarantineMetadataToFile(const CFStringRef filePath,
+ const CFURLRef sourceURL,
+ const CFURLRef referrerURL,
+ const bool isFromWeb) {
+ CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
+ filePath,
+ kCFURLPOSIXPathStyle,
+ false);
+
+ // The properties key changed in 10.10:
+ CFStringRef quarantinePropKey;
+ if (nsCocoaFeatures::OnYosemiteOrLater()) {
+ quarantinePropKey = kCFURLQuarantinePropertiesKey;
+ } else {
+ quarantinePropKey = kLSItemQuarantineProperties;
+ }
+ CFDictionaryRef quarantineProps = NULL;
+ Boolean success = ::CFURLCopyResourcePropertyForKey(fileURL,
+ quarantinePropKey,
+ &quarantineProps,
+ NULL);
+
+ // If there aren't any quarantine properties then the user probably
+ // set up an exclusion and we don't need to add metadata.
+ if (!success || !quarantineProps) {
+ ::CFRelease(fileURL);
+ return;
+ }
+
+ // We don't know what to do if the props aren't a dictionary.
+ if (::CFGetTypeID(quarantineProps) != ::CFDictionaryGetTypeID()) {
+ ::CFRelease(fileURL);
+ ::CFRelease(quarantineProps);
+ return;
+ }
+
+ // Make a mutable copy of the properties.
+ CFMutableDictionaryRef mutQuarantineProps =
+ ::CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, (CFDictionaryRef)quarantineProps);
+ ::CFRelease(quarantineProps);
+
+ // Add metadata that the OS couldn't infer.
+
+ if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineTypeKey)) {
+ CFStringRef type = isFromWeb ? kLSQuarantineTypeWebDownload : kLSQuarantineTypeOtherDownload;
+ ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineTypeKey, type);
+ }
+
+ if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineOriginURLKey) && referrerURL) {
+ ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineOriginURLKey, referrerURL);
+ }
+
+ if (!::CFDictionaryGetValue(mutQuarantineProps, kLSQuarantineDataURLKey) && sourceURL) {
+ ::CFDictionarySetValue(mutQuarantineProps, kLSQuarantineDataURLKey, sourceURL);
+ }
+
+ // Set quarantine properties on file.
+ ::CFURLSetResourcePropertyForKey(fileURL,
+ quarantinePropKey,
+ mutQuarantineProps,
+ NULL);
+
+ ::CFRelease(fileURL);
+ ::CFRelease(mutQuarantineProps);
+}
+
+CFURLRef GetTemporaryFolderCFURLRef()
+{
+ NSString* tempDir = ::NSTemporaryDirectory();
+ return tempDir == nil ? NULL : (CFURLRef)[NSURL fileURLWithPath:tempDir
+ isDirectory:YES];
+}
+
+} // namespace CocoaFileUtils
diff --git a/xpcom/io/FileUtilsWin.cpp b/xpcom/io/FileUtilsWin.cpp
new file mode 100644
index 000000000..732c074f7
--- /dev/null
+++ b/xpcom/io/FileUtilsWin.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FileUtilsWin.h"
+
+#include <windows.h>
+#include <psapi.h>
+
+#include "mozilla/Unused.h"
+#include "nsWindowsHelpers.h"
+#include "GeckoProfiler.h"
+
+namespace {
+
+// Scoped type used by HandleToFilename
+struct ScopedMappedViewTraits
+{
+ typedef void* type;
+ static void* empty()
+ {
+ return nullptr;
+ }
+ static void release(void* aPtr)
+ {
+ if (aPtr) {
+ mozilla::Unused << UnmapViewOfFile(aPtr);
+ }
+ }
+};
+typedef mozilla::Scoped<ScopedMappedViewTraits> ScopedMappedView;
+
+} // namespace
+
+namespace mozilla {
+
+bool
+HandleToFilename(HANDLE aHandle, const LARGE_INTEGER& aOffset,
+ nsAString& aFilename)
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::NETWORK);
+
+ aFilename.Truncate();
+ // This implementation is nice because it uses fully documented APIs that
+ // are available on all Windows versions that we support.
+ nsAutoHandle fileMapping(CreateFileMapping(aHandle, nullptr, PAGE_READONLY,
+ 0, 1, nullptr));
+ if (!fileMapping) {
+ return false;
+ }
+ ScopedMappedView view(MapViewOfFile(fileMapping, FILE_MAP_READ,
+ aOffset.HighPart, aOffset.LowPart, 1));
+ if (!view) {
+ return false;
+ }
+ nsAutoString mappedFilename;
+ DWORD len = 0;
+ SetLastError(ERROR_SUCCESS);
+ do {
+ mappedFilename.SetLength(mappedFilename.Length() + MAX_PATH);
+ len = GetMappedFileNameW(GetCurrentProcess(), view,
+ wwc(mappedFilename.BeginWriting()),
+ mappedFilename.Length());
+ } while (!len && GetLastError() == ERROR_INSUFFICIENT_BUFFER);
+ if (!len) {
+ return false;
+ }
+ mappedFilename.Truncate(len);
+ return NtPathToDosPath(mappedFilename, aFilename);
+}
+
+} // namespace mozilla
+
diff --git a/xpcom/io/FileUtilsWin.h b/xpcom/io/FileUtilsWin.h
new file mode 100644
index 000000000..e32e9fd6e
--- /dev/null
+++ b/xpcom/io/FileUtilsWin.h
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_FileUtilsWin_h
+#define mozilla_FileUtilsWin_h
+
+#include <windows.h>
+
+#include "mozilla/Scoped.h"
+#include "nsStringGlue.h"
+
+namespace mozilla {
+
+inline bool
+EnsureLongPath(nsAString& aDosPath)
+{
+ uint32_t aDosPathOriginalLen = aDosPath.Length();
+ auto inputPath = PromiseFlatString(aDosPath);
+ // Try to get the long path, or else get the required length of the long path
+ DWORD longPathLen = GetLongPathNameW(inputPath.get(),
+ reinterpret_cast<wchar_t*>(aDosPath.BeginWriting()),
+ aDosPathOriginalLen);
+ if (longPathLen == 0) {
+ return false;
+ }
+ aDosPath.SetLength(longPathLen);
+ if (longPathLen <= aDosPathOriginalLen) {
+ // Our string happened to be long enough for the first call to succeed.
+ return true;
+ }
+ // Now we have a large enough buffer, get the actual string
+ longPathLen = GetLongPathNameW(inputPath.get(),
+ reinterpret_cast<wchar_t*>(aDosPath.BeginWriting()), aDosPath.Length());
+ if (longPathLen == 0) {
+ return false;
+ }
+ // This success check should always be less-than because longPathLen excludes
+ // the null terminator on success, but includes it in the first call that
+ // returned the required size.
+ if (longPathLen < aDosPath.Length()) {
+ aDosPath.SetLength(longPathLen);
+ return true;
+ }
+ // We shouldn't reach this, but if we do then it's a failure!
+ return false;
+}
+
+inline bool
+NtPathToDosPath(const nsAString& aNtPath, nsAString& aDosPath)
+{
+ aDosPath.Truncate();
+ if (aNtPath.IsEmpty()) {
+ return true;
+ }
+ NS_NAMED_LITERAL_STRING(symLinkPrefix, "\\??\\");
+ uint32_t ntPathLen = aNtPath.Length();
+ uint32_t symLinkPrefixLen = symLinkPrefix.Length();
+ if (ntPathLen >= 6 && aNtPath.CharAt(5) == L':' &&
+ ntPathLen >= symLinkPrefixLen &&
+ Substring(aNtPath, 0, symLinkPrefixLen).Equals(symLinkPrefix)) {
+ // Symbolic link for DOS device. Just strip off the prefix.
+ aDosPath = aNtPath;
+ aDosPath.Cut(0, 4);
+ return true;
+ }
+ nsAutoString logicalDrives;
+ DWORD len = 0;
+ while (true) {
+ len = GetLogicalDriveStringsW(
+ len, reinterpret_cast<wchar_t*>(logicalDrives.BeginWriting()));
+ if (!len) {
+ return false;
+ } else if (len > logicalDrives.Length()) {
+ logicalDrives.SetLength(len);
+ } else {
+ break;
+ }
+ }
+ const char16_t* cur = logicalDrives.BeginReading();
+ const char16_t* end = logicalDrives.EndReading();
+ nsString targetPath;
+ targetPath.SetLength(MAX_PATH);
+ wchar_t driveTemplate[] = L" :";
+ do {
+ // Unfortunately QueryDosDevice doesn't support the idiom for querying the
+ // output buffer size, so it may require retries.
+ driveTemplate[0] = *cur;
+ DWORD targetPathLen = 0;
+ SetLastError(ERROR_SUCCESS);
+ while (true) {
+ targetPathLen = QueryDosDeviceW(driveTemplate,
+ reinterpret_cast<wchar_t*>(targetPath.BeginWriting()),
+ targetPath.Length());
+ if (targetPathLen || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ break;
+ }
+ targetPath.SetLength(targetPath.Length() * 2);
+ }
+ if (targetPathLen) {
+ // Need to use wcslen here because targetPath contains embedded NULL chars
+ size_t firstTargetPathLen = wcslen(targetPath.get());
+ const char16_t* pathComponent = aNtPath.BeginReading() +
+ firstTargetPathLen;
+ bool found = _wcsnicmp(char16ptr_t(aNtPath.BeginReading()), targetPath.get(),
+ firstTargetPathLen) == 0 &&
+ *pathComponent == L'\\';
+ if (found) {
+ aDosPath = driveTemplate;
+ aDosPath += pathComponent;
+ return EnsureLongPath(aDosPath);
+ }
+ }
+ // Advance to the next NUL character in logicalDrives
+ while (*cur++);
+ } while (cur != end);
+ // Try to handle UNC paths. NB: This must happen after we've checked drive
+ // mappings in case a UNC path is mapped to a drive!
+ NS_NAMED_LITERAL_STRING(uncPrefix, "\\\\");
+ NS_NAMED_LITERAL_STRING(deviceMupPrefix, "\\Device\\Mup\\");
+ if (StringBeginsWith(aNtPath, deviceMupPrefix)) {
+ aDosPath = uncPrefix;
+ aDosPath += Substring(aNtPath, deviceMupPrefix.Length());
+ return true;
+ }
+ NS_NAMED_LITERAL_STRING(deviceLanmanRedirectorPrefix,
+ "\\Device\\LanmanRedirector\\");
+ if (StringBeginsWith(aNtPath, deviceLanmanRedirectorPrefix)) {
+ aDosPath = uncPrefix;
+ aDosPath += Substring(aNtPath, deviceLanmanRedirectorPrefix.Length());
+ return true;
+ }
+ return false;
+}
+
+bool
+HandleToFilename(HANDLE aHandle, const LARGE_INTEGER& aOffset,
+ nsAString& aFilename);
+
+} // namespace mozilla
+
+#endif // mozilla_FileUtilsWin_h
diff --git a/xpcom/io/SlicedInputStream.cpp b/xpcom/io/SlicedInputStream.cpp
new file mode 100644
index 000000000..7d5fc2b05
--- /dev/null
+++ b/xpcom/io/SlicedInputStream.cpp
@@ -0,0 +1,209 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SlicedInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsStreamUtils.h"
+
+NS_IMPL_ISUPPORTS(SlicedInputStream, nsIInputStream,
+ nsICloneableInputStream, nsIAsyncInputStream)
+
+SlicedInputStream::SlicedInputStream(nsIInputStream* aInputStream,
+ uint64_t aStart, uint64_t aLength)
+ : mInputStream(aInputStream)
+ , mStart(aStart)
+ , mLength(aLength)
+ , mCurPos(0)
+ , mClosed(false)
+{
+ MOZ_ASSERT(aInputStream);
+}
+
+SlicedInputStream::~SlicedInputStream()
+{}
+
+NS_IMETHODIMP
+SlicedInputStream::Close()
+{
+ mClosed = true;
+ return NS_OK;
+}
+
+// nsIInputStream interface
+
+NS_IMETHODIMP
+SlicedInputStream::Available(uint64_t* aLength)
+{
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ nsresult rv = mInputStream->Available(aLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Let's remove extra length from the end.
+ if (*aLength + mCurPos > mStart + mLength) {
+ *aLength -= XPCOM_MIN(*aLength, (*aLength + mCurPos) - (mStart + mLength));
+ }
+
+ // Let's remove extra length from the begin.
+ if (mCurPos < mStart) {
+ *aLength -= XPCOM_MIN(*aLength, mStart - mCurPos);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SlicedInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount)
+{
+ return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aReadCount);
+}
+
+NS_IMETHODIMP
+SlicedInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t *aResult)
+{
+ uint32_t result;
+
+ if (!aResult) {
+ aResult = &result;
+ }
+
+ *aResult = 0;
+
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ if (mCurPos < mStart) {
+ nsCOMPtr<nsISeekableStream> seekableStream =
+ do_QueryInterface(mInputStream);
+ if (seekableStream) {
+ nsresult rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_SET,
+ mStart);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mCurPos = mStart;
+ } else {
+ char buf[4096];
+ while (mCurPos < mStart) {
+ uint32_t bytesRead;
+ uint64_t bufCount = XPCOM_MIN(mStart - mCurPos, (uint64_t)sizeof(buf));
+ nsresult rv = mInputStream->Read(buf, bufCount, &bytesRead);
+ if (NS_WARN_IF(NS_FAILED(rv)) || bytesRead == 0) {
+ return rv;
+ }
+
+ mCurPos += bytesRead;
+ }
+ }
+ }
+
+ // Let's reduce aCount in case it's too big.
+ if (mCurPos + aCount > mStart + mLength) {
+ aCount = mStart + mLength - mCurPos;
+ }
+
+ char buf[4096];
+ while (mCurPos < mStart + mLength && *aResult < aCount) {
+ uint32_t bytesRead;
+ uint64_t bufCount = XPCOM_MIN(aCount - *aResult, (uint32_t)sizeof(buf));
+ nsresult rv = mInputStream->Read(buf, bufCount, &bytesRead);
+ if (NS_WARN_IF(NS_FAILED(rv)) || bytesRead == 0) {
+ return rv;
+ }
+
+ mCurPos += bytesRead;
+
+ uint32_t bytesWritten = 0;
+ while (bytesWritten < bytesRead) {
+ uint32_t writerCount = 0;
+ rv = aWriter(this, aClosure, buf + bytesWritten, *aResult,
+ bytesRead - bytesWritten, &writerCount);
+ if (NS_FAILED(rv) || writerCount == 0) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(writerCount <= bytesRead - bytesWritten);
+ bytesWritten += writerCount;
+ *aResult += writerCount;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SlicedInputStream::IsNonBlocking(bool* aNonBlocking)
+{
+ return mInputStream->IsNonBlocking(aNonBlocking);
+}
+
+// nsICloneableInputStream interface
+
+NS_IMETHODIMP
+SlicedInputStream::GetCloneable(bool* aCloneable)
+{
+ *aCloneable = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SlicedInputStream::Clone(nsIInputStream** aResult)
+{
+ nsCOMPtr<nsIInputStream> clonedStream;
+ nsCOMPtr<nsIInputStream> replacementStream;
+
+ nsresult rv = NS_CloneInputStream(mInputStream, getter_AddRefs(clonedStream),
+ getter_AddRefs(replacementStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (replacementStream) {
+ mInputStream = replacementStream.forget();
+ }
+
+ nsCOMPtr<nsIInputStream> sis =
+ new SlicedInputStream(clonedStream, mStart, mLength);
+
+ sis.forget(aResult);
+ return NS_OK;
+}
+
+// nsIAsyncInputStream interface
+
+NS_IMETHODIMP
+SlicedInputStream::CloseWithStatus(nsresult aStatus)
+{
+ nsCOMPtr<nsIAsyncInputStream> asyncStream =
+ do_QueryInterface(mInputStream);
+ if (!asyncStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return asyncStream->CloseWithStatus(aStatus);
+}
+
+NS_IMETHODIMP
+SlicedInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget)
+{
+ nsCOMPtr<nsIAsyncInputStream> asyncStream =
+ do_QueryInterface(mInputStream);
+ if (!asyncStream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return asyncStream->AsyncWait(aCallback, aFlags, aRequestedCount,
+ aEventTarget);
+}
diff --git a/xpcom/io/SlicedInputStream.h b/xpcom/io/SlicedInputStream.h
new file mode 100644
index 000000000..6c38fc39f
--- /dev/null
+++ b/xpcom/io/SlicedInputStream.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SlicedInputStream_h
+#define SlicedInputStream_h
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsIAsyncInputStream.h"
+#include "nsICloneableInputStream.h"
+
+// A wrapper for a slice of an underlying input stream.
+
+class SlicedInputStream final : public nsIAsyncInputStream
+ , public nsICloneableInputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSICLONEABLEINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ // Create an input stream whose data comes from a slice of aInputStream. The
+ // slice begins at aStart bytes beyond aInputStream's current position, and
+ // extends for a maximum of aLength bytes. If aInputStream contains fewer
+ // than aStart bytes, reading from SlicedInputStream returns no data. If
+ // aInputStream contains more than aStart bytes, but fewer than aStart +
+ // aLength bytes, reading from SlicedInputStream returns as many bytes as can
+ // be consumed from aInputStream after reading aLength bytes.
+ //
+ // aInputStream should not be read from after constructing a
+ // SlicedInputStream wrapper around it.
+
+ SlicedInputStream(nsIInputStream* aInputStream,
+ uint64_t aStart, uint64_t aLength);
+
+private:
+ ~SlicedInputStream();
+
+ nsCOMPtr<nsIInputStream> mInputStream;
+ uint64_t mStart;
+ uint64_t mLength;
+ uint64_t mCurPos;
+
+ bool mClosed;
+};
+
+#endif // SlicedInputStream_h
diff --git a/xpcom/io/SnappyCompressOutputStream.cpp b/xpcom/io/SnappyCompressOutputStream.cpp
new file mode 100644
index 000000000..89b8a0808
--- /dev/null
+++ b/xpcom/io/SnappyCompressOutputStream.cpp
@@ -0,0 +1,256 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/SnappyCompressOutputStream.h"
+
+#include <algorithm>
+#include "nsStreamUtils.h"
+#include "snappy/snappy.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(SnappyCompressOutputStream, nsIOutputStream);
+
+// static
+const size_t
+SnappyCompressOutputStream::kMaxBlockSize = snappy::kBlockSize;
+
+SnappyCompressOutputStream::SnappyCompressOutputStream(nsIOutputStream* aBaseStream,
+ size_t aBlockSize)
+ : mBaseStream(aBaseStream)
+ , mBlockSize(std::min(aBlockSize, kMaxBlockSize))
+ , mNextByte(0)
+ , mCompressedBufferLength(0)
+ , mStreamIdentifierWritten(false)
+{
+ MOZ_ASSERT(mBlockSize > 0);
+
+ // This implementation only supports sync base streams. Verify this in debug
+ // builds. Note, this can be simpler than the check in
+ // SnappyUncompressInputStream because we don't have to deal with the
+ // nsStringInputStream oddness of being non-blocking and sync.
+#ifdef DEBUG
+ bool baseNonBlocking;
+ nsresult rv = mBaseStream->IsNonBlocking(&baseNonBlocking);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(!baseNonBlocking);
+#endif
+}
+
+size_t
+SnappyCompressOutputStream::BlockSize() const
+{
+ return mBlockSize;
+}
+
+NS_IMETHODIMP
+SnappyCompressOutputStream::Close()
+{
+ if (!mBaseStream) {
+ return NS_OK;
+ }
+
+ nsresult rv = Flush();
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ mBaseStream->Close();
+ mBaseStream = nullptr;
+
+ mBuffer = nullptr;
+ mCompressedBuffer = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SnappyCompressOutputStream::Flush()
+{
+ if (!mBaseStream) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ nsresult rv = FlushToBaseStream();
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ mBaseStream->Flush();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SnappyCompressOutputStream::Write(const char* aBuf, uint32_t aCount,
+ uint32_t* aResultOut)
+{
+ return WriteSegments(NS_CopySegmentToBuffer, const_cast<char*>(aBuf), aCount,
+ aResultOut);
+}
+
+NS_IMETHODIMP
+SnappyCompressOutputStream::WriteFrom(nsIInputStream*, uint32_t, uint32_t*)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SnappyCompressOutputStream::WriteSegments(nsReadSegmentFun aReader,
+ void* aClosure,
+ uint32_t aCount,
+ uint32_t* aBytesWrittenOut)
+{
+ *aBytesWrittenOut = 0;
+
+ if (!mBaseStream) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ if (!mBuffer) {
+ mBuffer.reset(new (fallible) char[mBlockSize]);
+ if (NS_WARN_IF(!mBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ while (aCount > 0) {
+ // Determine how much space is left in our flat, uncompressed buffer.
+ MOZ_ASSERT(mNextByte <= mBlockSize);
+ uint32_t remaining = mBlockSize - mNextByte;
+
+ // If it is full, then compress and flush the data to the base stream.
+ if (remaining == 0) {
+ nsresult rv = FlushToBaseStream();
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ // Now the entire buffer should be available for copying.
+ MOZ_ASSERT(!mNextByte);
+ remaining = mBlockSize;
+ }
+
+ uint32_t numToRead = std::min(remaining, aCount);
+ uint32_t numRead = 0;
+
+ nsresult rv = aReader(this, aClosure, &mBuffer[mNextByte],
+ *aBytesWrittenOut, numToRead, &numRead);
+
+ // As defined in nsIOutputStream.idl, do not pass reader func errors.
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ // End-of-file
+ if (numRead == 0) {
+ return NS_OK;
+ }
+
+ mNextByte += numRead;
+ *aBytesWrittenOut += numRead;
+ aCount -= numRead;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SnappyCompressOutputStream::IsNonBlocking(bool* aNonBlockingOut)
+{
+ *aNonBlockingOut = false;
+ return NS_OK;
+}
+
+SnappyCompressOutputStream::~SnappyCompressOutputStream()
+{
+ Close();
+}
+
+nsresult
+SnappyCompressOutputStream::FlushToBaseStream()
+{
+ MOZ_ASSERT(mBaseStream);
+
+ // Lazily create the compressed buffer on our first flush. This
+ // allows us to report OOM during stream operation. This buffer
+ // will then get re-used until the stream is closed.
+ if (!mCompressedBuffer) {
+ mCompressedBufferLength = MaxCompressedBufferLength(mBlockSize);
+ mCompressedBuffer.reset(new (fallible) char[mCompressedBufferLength]);
+ if (NS_WARN_IF(!mCompressedBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ // The first chunk must be a StreamIdentifier chunk. Write it out
+ // if we have not done so already.
+ nsresult rv = MaybeFlushStreamIdentifier();
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ // Compress the data to our internal compressed buffer.
+ size_t compressedLength;
+ rv = WriteCompressedData(mCompressedBuffer.get(), mCompressedBufferLength,
+ mBuffer.get(), mNextByte, &compressedLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ MOZ_ASSERT(compressedLength > 0);
+
+ mNextByte = 0;
+
+ // Write the compressed buffer out to the base stream.
+ uint32_t numWritten = 0;
+ rv = WriteAll(mCompressedBuffer.get(), compressedLength, &numWritten);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ MOZ_ASSERT(compressedLength == numWritten);
+
+ return NS_OK;
+}
+
+nsresult
+SnappyCompressOutputStream::MaybeFlushStreamIdentifier()
+{
+ MOZ_ASSERT(mCompressedBuffer);
+
+ if (mStreamIdentifierWritten) {
+ return NS_OK;
+ }
+
+ // Build the StreamIdentifier in our compressed buffer.
+ size_t compressedLength;
+ nsresult rv = WriteStreamIdentifier(mCompressedBuffer.get(),
+ mCompressedBufferLength,
+ &compressedLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ // Write the compressed buffer out to the base stream.
+ uint32_t numWritten = 0;
+ rv = WriteAll(mCompressedBuffer.get(), compressedLength, &numWritten);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ MOZ_ASSERT(compressedLength == numWritten);
+
+ mStreamIdentifierWritten = true;
+
+ return NS_OK;
+}
+
+nsresult
+SnappyCompressOutputStream::WriteAll(const char* aBuf, uint32_t aCount,
+ uint32_t* aBytesWrittenOut)
+{
+ *aBytesWrittenOut = 0;
+
+ if (!mBaseStream) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ uint32_t offset = 0;
+ while (aCount > 0) {
+ uint32_t numWritten = 0;
+ nsresult rv = mBaseStream->Write(aBuf + offset, aCount, &numWritten);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ offset += numWritten;
+ aCount -= numWritten;
+ *aBytesWrittenOut += numWritten;
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/xpcom/io/SnappyCompressOutputStream.h b/xpcom/io/SnappyCompressOutputStream.h
new file mode 100644
index 000000000..36c47e66e
--- /dev/null
+++ b/xpcom/io/SnappyCompressOutputStream.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SnappyCompressOutputStream_h__
+#define mozilla_SnappyCompressOutputStream_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsIOutputStream.h"
+#include "nsISupportsImpl.h"
+#include "SnappyFrameUtils.h"
+
+namespace mozilla {
+
+class SnappyCompressOutputStream final : public nsIOutputStream
+ , protected detail::SnappyFrameUtils
+{
+public:
+ // Maximum compression block size.
+ static const size_t kMaxBlockSize;
+
+ // Construct a new blocking output stream to compress data to
+ // the given base stream. The base stream must also be blocking.
+ // The compression block size may optionally be set to a value
+ // up to kMaxBlockSize.
+ explicit SnappyCompressOutputStream(nsIOutputStream* aBaseStream,
+ size_t aBlockSize = kMaxBlockSize);
+
+ // The compression block size. To optimize stream performance
+ // try to write to the stream in segments at least this size.
+ size_t BlockSize() const;
+
+private:
+ virtual ~SnappyCompressOutputStream();
+
+ nsresult FlushToBaseStream();
+ nsresult MaybeFlushStreamIdentifier();
+ nsresult WriteAll(const char* aBuf, uint32_t aCount,
+ uint32_t* aBytesWrittenOut);
+
+ nsCOMPtr<nsIOutputStream> mBaseStream;
+ const size_t mBlockSize;
+
+ // Buffer holding copied uncompressed data. This must be copied here
+ // so that the compression can be performed on a single flat buffer.
+ mozilla::UniquePtr<char[]> mBuffer;
+
+ // The next byte in the uncompressed data to copy incoming data to.
+ size_t mNextByte;
+
+ // Buffer holding the resulting compressed data.
+ mozilla::UniquePtr<char[]> mCompressedBuffer;
+ size_t mCompressedBufferLength;
+
+ // The first thing written to the stream must be a stream identifier.
+ bool mStreamIdentifierWritten;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SnappyCompressOutputStream_h__
diff --git a/xpcom/io/SnappyFrameUtils.cpp b/xpcom/io/SnappyFrameUtils.cpp
new file mode 100644
index 000000000..97883a362
--- /dev/null
+++ b/xpcom/io/SnappyFrameUtils.cpp
@@ -0,0 +1,258 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/SnappyFrameUtils.h"
+
+#include "crc32c.h"
+#include "mozilla/EndianUtils.h"
+#include "nsDebug.h"
+#include "snappy/snappy.h"
+
+namespace {
+
+using mozilla::detail::SnappyFrameUtils;
+using mozilla::NativeEndian;
+
+SnappyFrameUtils::ChunkType ReadChunkType(uint8_t aByte)
+{
+ if (aByte == 0xff) {
+ return SnappyFrameUtils::StreamIdentifier;
+ } else if (aByte == 0x00) {
+ return SnappyFrameUtils::CompressedData;
+ } else if (aByte == 0x01) {
+ return SnappyFrameUtils::UncompressedData;
+ } else if (aByte == 0xfe) {
+ return SnappyFrameUtils::Padding;
+ }
+
+ return SnappyFrameUtils::Reserved;
+}
+
+void WriteChunkType(char* aDest, SnappyFrameUtils::ChunkType aType)
+{
+ unsigned char* dest = reinterpret_cast<unsigned char*>(aDest);
+ if (aType == SnappyFrameUtils::StreamIdentifier) {
+ *dest = 0xff;
+ } else if (aType == SnappyFrameUtils::CompressedData) {
+ *dest = 0x00;
+ } else if (aType == SnappyFrameUtils::UncompressedData) {
+ *dest = 0x01;
+ } else if (aType == SnappyFrameUtils::Padding) {
+ *dest = 0xfe;
+ } else {
+ *dest = 0x02;
+ }
+}
+
+void WriteUInt24(char* aBuf, uint32_t aVal)
+{
+ MOZ_ASSERT(!(aVal & 0xff000000));
+ uint32_t tmp = NativeEndian::swapToLittleEndian(aVal);
+ memcpy(aBuf, &tmp, 3);
+}
+
+uint32_t ReadUInt24(const char* aBuf)
+{
+ uint32_t val = 0;
+ memcpy(&val, aBuf, 3);
+ return NativeEndian::swapFromLittleEndian(val);
+}
+
+// This mask is explicitly defined in the snappy framing_format.txt file.
+uint32_t MaskChecksum(uint32_t aValue)
+{
+ return ((aValue >> 15) | (aValue << 17)) + 0xa282ead8;
+}
+
+} // namespace
+
+namespace mozilla {
+namespace detail {
+
+using mozilla::LittleEndian;
+
+// static
+nsresult
+SnappyFrameUtils::WriteStreamIdentifier(char* aDest, size_t aDestLength,
+ size_t* aBytesWrittenOut)
+{
+ if (NS_WARN_IF(aDestLength < (kHeaderLength + kStreamIdentifierDataLength))) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ WriteChunkType(aDest, StreamIdentifier);
+ aDest[1] = 0x06; // Data length
+ aDest[2] = 0x00;
+ aDest[3] = 0x00;
+ aDest[4] = 0x73; // "sNaPpY"
+ aDest[5] = 0x4e;
+ aDest[6] = 0x61;
+ aDest[7] = 0x50;
+ aDest[8] = 0x70;
+ aDest[9] = 0x59;
+
+ static_assert(kHeaderLength + kStreamIdentifierDataLength == 10,
+ "StreamIdentifier chunk should be exactly 10 bytes long");
+ *aBytesWrittenOut = kHeaderLength + kStreamIdentifierDataLength;
+
+ return NS_OK;
+}
+
+// static
+nsresult
+SnappyFrameUtils::WriteCompressedData(char* aDest, size_t aDestLength,
+ const char* aData, size_t aDataLength,
+ size_t* aBytesWrittenOut)
+{
+ *aBytesWrittenOut = 0;
+
+ size_t neededLength = MaxCompressedBufferLength(aDataLength);
+ if (NS_WARN_IF(aDestLength < neededLength)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ size_t offset = 0;
+
+ WriteChunkType(aDest, CompressedData);
+ offset += kChunkTypeLength;
+
+ // Skip length for now and write it out after we know the compressed length.
+ size_t lengthOffset = offset;
+ offset += kChunkLengthLength;
+
+ uint32_t crc = ComputeCrc32c(~0, reinterpret_cast<const unsigned char*>(aData),
+ aDataLength);
+ uint32_t maskedCrc = MaskChecksum(crc);
+ LittleEndian::writeUint32(aDest + offset, maskedCrc);
+ offset += kCRCLength;
+
+ size_t compressedLength;
+ snappy::RawCompress(aData, aDataLength, aDest + offset, &compressedLength);
+
+ // Go back and write the data length.
+ size_t dataLength = compressedLength + kCRCLength;
+ WriteUInt24(aDest + lengthOffset, dataLength);
+
+ *aBytesWrittenOut = kHeaderLength + dataLength;
+
+ return NS_OK;
+}
+
+// static
+nsresult
+SnappyFrameUtils::ParseHeader(const char* aSource, size_t aSourceLength,
+ ChunkType* aTypeOut, size_t* aDataLengthOut)
+{
+ if (NS_WARN_IF(aSourceLength < kHeaderLength)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ *aTypeOut = ReadChunkType(aSource[0]);
+ *aDataLengthOut = ReadUInt24(aSource + kChunkTypeLength);
+
+ return NS_OK;
+}
+
+// static
+nsresult
+SnappyFrameUtils::ParseData(char* aDest, size_t aDestLength,
+ ChunkType aType, const char* aData,
+ size_t aDataLength,
+ size_t* aBytesWrittenOut, size_t* aBytesReadOut)
+{
+ switch(aType) {
+ case StreamIdentifier:
+ return ParseStreamIdentifier(aDest, aDestLength, aData, aDataLength,
+ aBytesWrittenOut, aBytesReadOut);
+
+ case CompressedData:
+ return ParseCompressedData(aDest, aDestLength, aData, aDataLength,
+ aBytesWrittenOut, aBytesReadOut);
+
+ // TODO: support other snappy chunk types
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsupported snappy framing chunk type.");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+}
+
+// static
+nsresult
+SnappyFrameUtils::ParseStreamIdentifier(char*, size_t,
+ const char* aData, size_t aDataLength,
+ size_t* aBytesWrittenOut,
+ size_t* aBytesReadOut)
+{
+ *aBytesWrittenOut = 0;
+ *aBytesReadOut = 0;
+ if (NS_WARN_IF(aDataLength != kStreamIdentifierDataLength ||
+ aData[0] != 0x73 ||
+ aData[1] != 0x4e ||
+ aData[2] != 0x61 ||
+ aData[3] != 0x50 ||
+ aData[4] != 0x70 ||
+ aData[5] != 0x59)) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ *aBytesReadOut = aDataLength;
+ return NS_OK;
+}
+
+// static
+nsresult
+SnappyFrameUtils::ParseCompressedData(char* aDest, size_t aDestLength,
+ const char* aData, size_t aDataLength,
+ size_t* aBytesWrittenOut,
+ size_t* aBytesReadOut)
+{
+ *aBytesWrittenOut = 0;
+ *aBytesReadOut = 0;
+ size_t offset = 0;
+
+ uint32_t readCrc = LittleEndian::readUint32(aData + offset);
+ offset += kCRCLength;
+
+ size_t uncompressedLength;
+ if (NS_WARN_IF(!snappy::GetUncompressedLength(aData + offset,
+ aDataLength - offset,
+ &uncompressedLength))) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (NS_WARN_IF(aDestLength < uncompressedLength)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (NS_WARN_IF(!snappy::RawUncompress(aData + offset, aDataLength - offset,
+ aDest))) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ uint32_t crc = ComputeCrc32c(~0, reinterpret_cast<const unsigned char*>(aDest),
+ uncompressedLength);
+ uint32_t maskedCrc = MaskChecksum(crc);
+ if (NS_WARN_IF(readCrc != maskedCrc)) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ *aBytesWrittenOut = uncompressedLength;
+ *aBytesReadOut = aDataLength;
+
+ return NS_OK;
+}
+
+// static
+size_t
+SnappyFrameUtils::MaxCompressedBufferLength(size_t aSourceLength)
+{
+ size_t neededLength = kHeaderLength;
+ neededLength += kCRCLength;
+ neededLength += snappy::MaxCompressedLength(aSourceLength);
+ return neededLength;
+}
+
+} // namespace detail
+} // namespace mozilla
diff --git a/xpcom/io/SnappyFrameUtils.h b/xpcom/io/SnappyFrameUtils.h
new file mode 100644
index 000000000..41479b14d
--- /dev/null
+++ b/xpcom/io/SnappyFrameUtils.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SnappyFrameUtils_h__
+#define mozilla_SnappyFrameUtils_h__
+
+#include "mozilla/Attributes.h"
+#include "nsError.h"
+
+namespace mozilla {
+namespace detail {
+
+//
+// Utility class providing primitives necessary to build streams based
+// on the snappy compressor. This essentially abstracts the framing format
+// defined in:
+//
+// other-licences/snappy/src/framing_format.txt
+//
+// NOTE: Currently only the StreamIdentifier and CompressedData chunks are
+// supported.
+//
+class SnappyFrameUtils
+{
+public:
+ enum ChunkType
+ {
+ Unknown,
+ StreamIdentifier,
+ CompressedData,
+ UncompressedData,
+ Padding,
+ Reserved,
+ ChunkTypeCount
+ };
+
+ static const size_t kChunkTypeLength = 1;
+ static const size_t kChunkLengthLength = 3;
+ static const size_t kHeaderLength = kChunkTypeLength + kChunkLengthLength;
+ static const size_t kStreamIdentifierDataLength = 6;
+ static const size_t kCRCLength = 4;
+
+ static nsresult
+ WriteStreamIdentifier(char* aDest, size_t aDestLength,
+ size_t* aBytesWrittenOut);
+
+ static nsresult
+ WriteCompressedData(char* aDest, size_t aDestLength,
+ const char* aData, size_t aDataLength,
+ size_t* aBytesWrittenOut);
+
+ static nsresult
+ ParseHeader(const char* aSource, size_t aSourceLength, ChunkType* aTypeOut,
+ size_t* aDataLengthOut);
+
+ static nsresult
+ ParseData(char* aDest, size_t aDestLength,
+ ChunkType aType, const char* aData, size_t aDataLength,
+ size_t* aBytesWrittenOut, size_t* aBytesReadOut);
+
+ static nsresult
+ ParseStreamIdentifier(char* aDest, size_t aDestLength,
+ const char* aData, size_t aDataLength,
+ size_t* aBytesWrittenOut, size_t* aBytesReadOut);
+
+ static nsresult
+ ParseCompressedData(char* aDest, size_t aDestLength,
+ const char* aData, size_t aDataLength,
+ size_t* aBytesWrittenOut, size_t* aBytesReadOut);
+
+ static size_t
+ MaxCompressedBufferLength(size_t aSourceLength);
+
+protected:
+ SnappyFrameUtils() { }
+ virtual ~SnappyFrameUtils() { }
+};
+
+} // namespace detail
+} // namespace mozilla
+
+#endif // mozilla_SnappyFrameUtils_h__
diff --git a/xpcom/io/SnappyUncompressInputStream.cpp b/xpcom/io/SnappyUncompressInputStream.cpp
new file mode 100644
index 000000000..e0a1b6f4c
--- /dev/null
+++ b/xpcom/io/SnappyUncompressInputStream.cpp
@@ -0,0 +1,362 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/SnappyUncompressInputStream.h"
+
+#include <algorithm>
+#include "nsIAsyncInputStream.h"
+#include "nsStreamUtils.h"
+#include "snappy/snappy.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(SnappyUncompressInputStream,
+ nsIInputStream);
+
+// Putting kCompressedBufferLength inside a function avoids a static
+// constructor.
+static size_t CompressedBufferLength()
+{
+ static size_t kCompressedBufferLength =
+ detail::SnappyFrameUtils::MaxCompressedBufferLength(snappy::kBlockSize);
+
+ MOZ_ASSERT(kCompressedBufferLength > 0);
+ return kCompressedBufferLength;
+}
+
+SnappyUncompressInputStream::SnappyUncompressInputStream(nsIInputStream* aBaseStream)
+ : mBaseStream(aBaseStream)
+ , mUncompressedBytes(0)
+ , mNextByte(0)
+ , mNextChunkType(Unknown)
+ , mNextChunkDataLength(0)
+ , mNeedFirstStreamIdentifier(true)
+{
+ // This implementation only supports sync base streams. Verify this in debug
+ // builds. Note, this is a bit complicated because the streams we support
+ // advertise different capabilities:
+ // - nsFileInputStream - blocking and sync
+ // - nsStringInputStream - non-blocking and sync
+ // - nsPipeInputStream - can be blocking, but provides async interface
+#ifdef DEBUG
+ bool baseNonBlocking;
+ nsresult rv = mBaseStream->IsNonBlocking(&baseNonBlocking);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (baseNonBlocking) {
+ nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(mBaseStream);
+ MOZ_ASSERT(!async);
+ }
+#endif
+}
+
+NS_IMETHODIMP
+SnappyUncompressInputStream::Close()
+{
+ if (!mBaseStream) {
+ return NS_OK;
+ }
+
+ mBaseStream->Close();
+ mBaseStream = nullptr;
+
+ mUncompressedBuffer = nullptr;
+ mCompressedBuffer = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SnappyUncompressInputStream::Available(uint64_t* aLengthOut)
+{
+ if (!mBaseStream) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ // If we have uncompressed bytes, then we are done.
+ *aLengthOut = UncompressedLength();
+ if (*aLengthOut > 0) {
+ return NS_OK;
+ }
+
+ // Otherwise, attempt to uncompress bytes until we get something or the
+ // underlying stream is drained. We loop here because some chunks can
+ // be StreamIdentifiers, padding, etc with no data.
+ uint32_t bytesRead;
+ do {
+ nsresult rv = ParseNextChunk(&bytesRead);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ *aLengthOut = UncompressedLength();
+ } while(*aLengthOut == 0 && bytesRead);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SnappyUncompressInputStream::Read(char* aBuf, uint32_t aCount,
+ uint32_t* aBytesReadOut)
+{
+ return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aBytesReadOut);
+}
+
+NS_IMETHODIMP
+SnappyUncompressInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure, uint32_t aCount,
+ uint32_t* aBytesReadOut)
+{
+ *aBytesReadOut = 0;
+
+ if (!mBaseStream) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ nsresult rv;
+
+ // Do not try to use the base stream's ReadSegements here. Its very
+ // unlikely we will get a single buffer that contains all of the compressed
+ // data and therefore would have to copy into our own buffer anyways.
+ // Instead, focus on making efficient use of the Read() interface.
+
+ while (aCount > 0) {
+ // We have some decompressed data in our buffer. Provide it to the
+ // callers writer function.
+ if (mUncompressedBytes > 0) {
+ MOZ_ASSERT(mUncompressedBuffer);
+ uint32_t remaining = UncompressedLength();
+ uint32_t numToWrite = std::min(aCount, remaining);
+ uint32_t numWritten;
+ rv = aWriter(this, aClosure, &mUncompressedBuffer[mNextByte], *aBytesReadOut,
+ numToWrite, &numWritten);
+
+ // As defined in nsIInputputStream.idl, do not pass writer func errors.
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ // End-of-file
+ if (numWritten == 0) {
+ return NS_OK;
+ }
+
+ *aBytesReadOut += numWritten;
+ mNextByte += numWritten;
+ MOZ_ASSERT(mNextByte <= mUncompressedBytes);
+
+ if (mNextByte == mUncompressedBytes) {
+ mNextByte = 0;
+ mUncompressedBytes = 0;
+ }
+
+ aCount -= numWritten;
+
+ continue;
+ }
+
+ // Otherwise uncompress the next chunk and loop. Any resulting data
+ // will set mUncompressedBytes which we check at the top of the loop.
+ uint32_t bytesRead;
+ rv = ParseNextChunk(&bytesRead);
+ if (NS_FAILED(rv)) { return rv; }
+
+ // If we couldn't read anything and there is no more data to provide
+ // to the caller, then this is eof.
+ if (bytesRead == 0 && mUncompressedBytes == 0) {
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SnappyUncompressInputStream::IsNonBlocking(bool* aNonBlockingOut)
+{
+ *aNonBlockingOut = false;
+ return NS_OK;
+}
+
+SnappyUncompressInputStream::~SnappyUncompressInputStream()
+{
+ Close();
+}
+
+nsresult
+SnappyUncompressInputStream::ParseNextChunk(uint32_t* aBytesReadOut)
+{
+ // There must not be any uncompressed data already in mUncompressedBuffer.
+ MOZ_ASSERT(mUncompressedBytes == 0);
+ MOZ_ASSERT(mNextByte == 0);
+
+ nsresult rv;
+ *aBytesReadOut = 0;
+
+ // Lazily create our two buffers so we can report OOM during stream
+ // operation. These allocations only happens once. The buffers are reused
+ // until the stream is closed.
+ if (!mUncompressedBuffer) {
+ mUncompressedBuffer.reset(new (fallible) char[snappy::kBlockSize]);
+ if (NS_WARN_IF(!mUncompressedBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ if (!mCompressedBuffer) {
+ mCompressedBuffer.reset(new (fallible) char[CompressedBufferLength()]);
+ if (NS_WARN_IF(!mCompressedBuffer)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ // We have no decompressed data and we also have not seen the start of stream
+ // yet. Read and validate the StreamIdentifier chunk. Also read the next
+ // header to determine the size of the first real data chunk.
+ if (mNeedFirstStreamIdentifier) {
+ const uint32_t firstReadLength = kHeaderLength +
+ kStreamIdentifierDataLength +
+ kHeaderLength;
+ MOZ_ASSERT(firstReadLength <= CompressedBufferLength());
+
+ rv = ReadAll(mCompressedBuffer.get(), firstReadLength, firstReadLength,
+ aBytesReadOut);
+ if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; }
+
+ rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength,
+ &mNextChunkType, &mNextChunkDataLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ if (NS_WARN_IF(mNextChunkType != StreamIdentifier ||
+ mNextChunkDataLength != kStreamIdentifierDataLength)) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+ size_t offset = kHeaderLength;
+
+ mNeedFirstStreamIdentifier = false;
+
+ size_t numRead;
+ size_t numWritten;
+ rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, mNextChunkType,
+ &mCompressedBuffer[offset],
+ mNextChunkDataLength, &numWritten, &numRead);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ MOZ_ASSERT(numWritten == 0);
+ MOZ_ASSERT(numRead == mNextChunkDataLength);
+ offset += numRead;
+
+ rv = ParseHeader(&mCompressedBuffer[offset], *aBytesReadOut - offset,
+ &mNextChunkType, &mNextChunkDataLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ return NS_OK;
+ }
+
+ // We have no compressed data and we don't know how big the next chunk is.
+ // This happens when we get an EOF pause in the middle of a stream and also
+ // at the end of the stream. Simply read the next header and return. The
+ // chunk body will be read on the next entry into this method.
+ if (mNextChunkType == Unknown) {
+ rv = ReadAll(mCompressedBuffer.get(), kHeaderLength, kHeaderLength,
+ aBytesReadOut);
+ if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; }
+
+ rv = ParseHeader(mCompressedBuffer.get(), kHeaderLength,
+ &mNextChunkType, &mNextChunkDataLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ return NS_OK;
+ }
+
+ // We have no decompressed data, but we do know the size of the next chunk.
+ // Read at least that much from the base stream.
+ uint32_t readLength = mNextChunkDataLength;
+ MOZ_ASSERT(readLength <= CompressedBufferLength());
+
+ // However, if there is enough data in the base stream, also read the next
+ // chunk header. This helps optimize the stream by avoiding many small reads.
+ uint64_t avail;
+ rv = mBaseStream->Available(&avail);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ if (avail >= (readLength + kHeaderLength)) {
+ readLength += kHeaderLength;
+ MOZ_ASSERT(readLength <= CompressedBufferLength());
+ }
+
+ rv = ReadAll(mCompressedBuffer.get(), readLength, mNextChunkDataLength,
+ aBytesReadOut);
+ if (NS_WARN_IF(NS_FAILED(rv)) || *aBytesReadOut == 0) { return rv; }
+
+ size_t numRead;
+ size_t numWritten;
+ rv = ParseData(mUncompressedBuffer.get(), snappy::kBlockSize, mNextChunkType,
+ mCompressedBuffer.get(), mNextChunkDataLength,
+ &numWritten, &numRead);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ MOZ_ASSERT(numRead == mNextChunkDataLength);
+
+ mUncompressedBytes = numWritten;
+
+ // If we were unable to directly read the next chunk header, then clear
+ // our internal state. We will have to perform a small read to get the
+ // header the next time we enter this method.
+ if (*aBytesReadOut <= mNextChunkDataLength) {
+ mNextChunkType = Unknown;
+ mNextChunkDataLength = 0;
+ return NS_OK;
+ }
+
+ // We got the next chunk header. Parse it so that we are ready to for the
+ // next call into this method.
+ rv = ParseHeader(&mCompressedBuffer[numRead], *aBytesReadOut - numRead,
+ &mNextChunkType, &mNextChunkDataLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ return NS_OK;
+}
+
+nsresult
+SnappyUncompressInputStream::ReadAll(char* aBuf, uint32_t aCount,
+ uint32_t aMinValidCount,
+ uint32_t* aBytesReadOut)
+{
+ MOZ_ASSERT(aCount >= aMinValidCount);
+
+ *aBytesReadOut = 0;
+
+ if (!mBaseStream) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ uint32_t offset = 0;
+ while (aCount > 0) {
+ uint32_t bytesRead = 0;
+ nsresult rv = mBaseStream->Read(aBuf + offset, aCount, &bytesRead);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ // EOF, but don't immediately return. We need to validate min read bytes
+ // below.
+ if (bytesRead == 0) {
+ break;
+ }
+
+ *aBytesReadOut += bytesRead;
+ offset += bytesRead;
+ aCount -= bytesRead;
+ }
+
+ // Reading zero bytes is not an error. Its the expected EOF condition.
+ // Only compare to the minimum valid count if we read at least one byte.
+ if (*aBytesReadOut != 0 && *aBytesReadOut < aMinValidCount) {
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ return NS_OK;
+}
+
+size_t
+SnappyUncompressInputStream::UncompressedLength() const
+{
+ MOZ_ASSERT(mNextByte <= mUncompressedBytes);
+ return mUncompressedBytes - mNextByte;
+}
+
+} // namespace mozilla
diff --git a/xpcom/io/SnappyUncompressInputStream.h b/xpcom/io/SnappyUncompressInputStream.h
new file mode 100644
index 000000000..0d24c0d11
--- /dev/null
+++ b/xpcom/io/SnappyUncompressInputStream.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SnappyUncompressInputStream_h__
+#define mozilla_SnappyUncompressInputStream_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+#include "nsISupportsImpl.h"
+#include "SnappyFrameUtils.h"
+
+namespace mozilla {
+
+class SnappyUncompressInputStream final : public nsIInputStream
+ , protected detail::SnappyFrameUtils
+{
+public:
+ // Construct a new blocking stream to uncompress the given base stream. The
+ // base stream must also be blocking. The base stream does not have to be
+ // buffered.
+ explicit SnappyUncompressInputStream(nsIInputStream* aBaseStream);
+
+private:
+ virtual ~SnappyUncompressInputStream();
+
+ // Parse the next chunk of data. This may populate mBuffer and set
+ // mBufferFillSize. This should not be called when mBuffer already
+ // contains data.
+ nsresult ParseNextChunk(uint32_t* aBytesReadOut);
+
+ // Convenience routine to Read() from the base stream until we get
+ // the given number of bytes or reach EOF.
+ //
+ // aBuf - The buffer to write the bytes into.
+ // aCount - Max number of bytes to read. If the stream closes
+ // fewer bytes my be read.
+ // aMinValidCount - A minimum expected number of bytes. If we find
+ // fewer than this many bytes, then return
+ // NS_ERROR_CORRUPTED_CONTENT. If nothing was read due
+ // due to EOF (aBytesReadOut == 0), then NS_OK is returned.
+ // aBytesReadOut - An out parameter indicating how many bytes were read.
+ nsresult ReadAll(char* aBuf, uint32_t aCount, uint32_t aMinValidCount,
+ uint32_t* aBytesReadOut);
+
+ // Convenience routine to determine how many bytes of uncompressed data
+ // we currently have in our buffer.
+ size_t UncompressedLength() const;
+
+ nsCOMPtr<nsIInputStream> mBaseStream;
+
+ // Buffer to hold compressed data. Must copy here since we need a large
+ // flat buffer to run the uncompress process on. Always the same length
+ // of SnappyFrameUtils::MaxCompressedBufferLength(snappy::kBlockSize)
+ // bytes long.
+ mozilla::UniquePtr<char[]> mCompressedBuffer;
+
+ // Buffer storing the resulting uncompressed data. Exactly snappy::kBlockSize
+ // bytes long.
+ mozilla::UniquePtr<char[]> mUncompressedBuffer;
+
+ // Number of bytes of uncompressed data in mBuffer.
+ size_t mUncompressedBytes;
+
+ // Next byte of mBuffer to return in ReadSegments(). Must be less than
+ // mBufferFillSize
+ size_t mNextByte;
+
+ // Next chunk in the stream that has been parsed during read-ahead.
+ ChunkType mNextChunkType;
+
+ // Length of next chunk's length that has been determined during read-ahead.
+ size_t mNextChunkDataLength;
+
+ // The stream must begin with a StreamIdentifier chunk. Are we still
+ // expecting it?
+ bool mNeedFirstStreamIdentifier;
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SnappyUncompressInputStream_h__
diff --git a/xpcom/io/SpecialSystemDirectory.cpp b/xpcom/io/SpecialSystemDirectory.cpp
new file mode 100644
index 000000000..9ce8eb85b
--- /dev/null
+++ b/xpcom/io/SpecialSystemDirectory.cpp
@@ -0,0 +1,830 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SpecialSystemDirectory.h"
+#include "nsString.h"
+#include "nsDependentString.h"
+#include "nsAutoPtr.h"
+
+#if defined(XP_WIN)
+
+#include <windows.h>
+#include <shlobj.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <direct.h>
+#include <shlobj.h>
+#include <knownfolders.h>
+#include <guiddef.h>
+#include "mozilla/WindowsVersion.h"
+
+using mozilla::IsWin7OrLater;
+
+#elif defined(XP_UNIX)
+
+#include <limits.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/param.h>
+#include "prenv.h"
+#if defined(MOZ_WIDGET_COCOA)
+#include "CocoaFileUtils.h"
+#endif
+
+#endif
+
+#ifndef MAXPATHLEN
+#ifdef PATH_MAX
+#define MAXPATHLEN PATH_MAX
+#elif defined(MAX_PATH)
+#define MAXPATHLEN MAX_PATH
+#elif defined(_MAX_PATH)
+#define MAXPATHLEN _MAX_PATH
+#elif defined(CCHMAXPATH)
+#define MAXPATHLEN CCHMAXPATH
+#else
+#define MAXPATHLEN 1024
+#endif
+#endif
+
+#ifdef XP_WIN
+typedef HRESULT (WINAPI* nsGetKnownFolderPath)(GUID& rfid,
+ DWORD dwFlags,
+ HANDLE hToken,
+ PWSTR* ppszPath);
+
+static nsGetKnownFolderPath gGetKnownFolderPath = nullptr;
+#endif
+
+void
+StartupSpecialSystemDirectory()
+{
+#if defined (XP_WIN)
+ // SHGetKnownFolderPath is only available on Windows Vista
+ // so that we need to use GetProcAddress to get the pointer.
+ HMODULE hShell32DLLInst = GetModuleHandleW(L"shell32.dll");
+ if (hShell32DLLInst) {
+ gGetKnownFolderPath = (nsGetKnownFolderPath)
+ GetProcAddress(hShell32DLLInst, "SHGetKnownFolderPath");
+ }
+#endif
+}
+
+#if defined (XP_WIN)
+
+static nsresult
+GetKnownFolder(GUID* aGuid, nsIFile** aFile)
+{
+ if (!aGuid || !gGetKnownFolderPath) {
+ return NS_ERROR_FAILURE;
+ }
+
+ PWSTR path = nullptr;
+ gGetKnownFolderPath(*aGuid, 0, nullptr, &path);
+
+ if (!path) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = NS_NewLocalFile(nsDependentString(path),
+ true,
+ aFile);
+
+ CoTaskMemFree(path);
+ return rv;
+}
+
+static nsresult
+GetWindowsFolder(int aFolder, nsIFile** aFile)
+{
+ WCHAR path_orig[MAX_PATH + 3];
+ WCHAR* path = path_orig + 1;
+ HRESULT result = SHGetSpecialFolderPathW(nullptr, path, aFolder, true);
+
+ if (!SUCCEEDED(result)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Append the trailing slash
+ int len = wcslen(path);
+ if (len > 1 && path[len - 1] != L'\\') {
+ path[len] = L'\\';
+ path[++len] = L'\0';
+ }
+
+ return NS_NewLocalFile(nsDependentString(path, len), true, aFile);
+}
+
+__inline HRESULT
+SHLoadLibraryFromKnownFolder(REFKNOWNFOLDERID aFolderId, DWORD aMode,
+ REFIID riid, void** ppv)
+{
+ *ppv = nullptr;
+ IShellLibrary* plib;
+ HRESULT hr = CoCreateInstance(CLSID_ShellLibrary, nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(&plib));
+ if (SUCCEEDED(hr)) {
+ hr = plib->LoadLibraryFromKnownFolder(aFolderId, aMode);
+ if (SUCCEEDED(hr)) {
+ hr = plib->QueryInterface(riid, ppv);
+ }
+ plib->Release();
+ }
+ return hr;
+}
+
+/*
+ * Check to see if we're on Win7 and up, and if so, returns the default
+ * save-to location for the Windows Library passed in through aFolderId.
+ * Otherwise falls back on pre-win7 GetWindowsFolder.
+ */
+static nsresult
+GetLibrarySaveToPath(int aFallbackFolderId, REFKNOWNFOLDERID aFolderId,
+ nsIFile** aFile)
+{
+ // Skip off checking for library support if the os is Vista or lower.
+ if (!IsWin7OrLater()) {
+ return GetWindowsFolder(aFallbackFolderId, aFile);
+ }
+
+ RefPtr<IShellLibrary> shellLib;
+ RefPtr<IShellItem> savePath;
+ HRESULT hr =
+ SHLoadLibraryFromKnownFolder(aFolderId, STGM_READ,
+ IID_IShellLibrary, getter_AddRefs(shellLib));
+
+ if (shellLib &&
+ SUCCEEDED(shellLib->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem,
+ getter_AddRefs(savePath)))) {
+ wchar_t* str = nullptr;
+ if (SUCCEEDED(savePath->GetDisplayName(SIGDN_FILESYSPATH, &str))) {
+ nsAutoString path;
+ path.Assign(str);
+ path.Append('\\');
+ nsresult rv =
+ NS_NewLocalFile(path, false, aFile);
+ CoTaskMemFree(str);
+ return rv;
+ }
+ }
+
+ return GetWindowsFolder(aFallbackFolderId, aFile);
+}
+
+/**
+ * Provides a fallback for getting the path to APPDATA or LOCALAPPDATA by
+ * querying the registry when the call to SHGetSpecialFolderPathW is unable to
+ * provide these paths (Bug 513958).
+ */
+static nsresult
+GetRegWindowsAppDataFolder(bool aLocal, nsIFile** aFile)
+{
+ HKEY key;
+ NS_NAMED_LITERAL_STRING(keyName,
+ "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders");
+ DWORD res = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName.get(), 0, KEY_READ,
+ &key);
+ if (res != ERROR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WCHAR path[MAX_PATH + 2];
+ DWORD type, size;
+ res = RegQueryValueExW(key, (aLocal ? L"Local AppData" : L"AppData"),
+ nullptr, &type, (LPBYTE)&path, &size);
+ ::RegCloseKey(key);
+ // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the
+ // buffer size must not equal 0, and the buffer size be a multiple of 2.
+ if (res != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Append the trailing slash
+ int len = wcslen(path);
+ if (len > 1 && path[len - 1] != L'\\') {
+ path[len] = L'\\';
+ path[++len] = L'\0';
+ }
+
+ return NS_NewLocalFile(nsDependentString(path, len), true, aFile);
+}
+
+#endif // XP_WIN
+
+#if defined(XP_UNIX)
+static nsresult
+GetUnixHomeDir(nsIFile** aFile)
+{
+#if defined(ANDROID)
+ // XXX no home dir on android; maybe we should return the sdcard if present?
+ return NS_ERROR_FAILURE;
+#else
+ return NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")),
+ true, aFile);
+#endif
+}
+
+/*
+ The following license applies to the xdg_user_dir_lookup function:
+
+ Copyright (c) 2007 Red Hat, Inc.
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+static char*
+xdg_user_dir_lookup(const char* aType)
+{
+ FILE* file;
+ char* home_dir;
+ char* config_home;
+ char* config_file;
+ char buffer[512];
+ char* user_dir;
+ char* p;
+ char* d;
+ int len;
+ int relative;
+
+ home_dir = getenv("HOME");
+
+ if (!home_dir) {
+ goto error;
+ }
+
+ config_home = getenv("XDG_CONFIG_HOME");
+ if (!config_home || config_home[0] == 0) {
+ config_file = (char*)malloc(strlen(home_dir) +
+ strlen("/.config/user-dirs.dirs") + 1);
+ if (!config_file) {
+ goto error;
+ }
+
+ strcpy(config_file, home_dir);
+ strcat(config_file, "/.config/user-dirs.dirs");
+ } else {
+ config_file = (char*)malloc(strlen(config_home) +
+ strlen("/user-dirs.dirs") + 1);
+ if (!config_file) {
+ goto error;
+ }
+
+ strcpy(config_file, config_home);
+ strcat(config_file, "/user-dirs.dirs");
+ }
+
+ file = fopen(config_file, "r");
+ free(config_file);
+ if (!file) {
+ goto error;
+ }
+
+ user_dir = nullptr;
+ while (fgets(buffer, sizeof(buffer), file)) {
+ /* Remove newline at end */
+ len = strlen(buffer);
+ if (len > 0 && buffer[len - 1] == '\n') {
+ buffer[len - 1] = 0;
+ }
+
+ p = buffer;
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+
+ if (strncmp(p, "XDG_", 4) != 0) {
+ continue;
+ }
+ p += 4;
+ if (strncmp(p, aType, strlen(aType)) != 0) {
+ continue;
+ }
+ p += strlen(aType);
+ if (strncmp(p, "_DIR", 4) != 0) {
+ continue;
+ }
+ p += 4;
+
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+
+ if (*p != '=') {
+ continue;
+ }
+ p++;
+
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+
+ if (*p != '"') {
+ continue;
+ }
+ p++;
+
+ relative = 0;
+ if (strncmp(p, "$HOME/", 6) == 0) {
+ p += 6;
+ relative = 1;
+ } else if (*p != '/') {
+ continue;
+ }
+
+ if (relative) {
+ user_dir = (char*)malloc(strlen(home_dir) + 1 + strlen(p) + 1);
+ if (!user_dir) {
+ goto error2;
+ }
+
+ strcpy(user_dir, home_dir);
+ strcat(user_dir, "/");
+ } else {
+ user_dir = (char*)malloc(strlen(p) + 1);
+ if (!user_dir) {
+ goto error2;
+ }
+
+ *user_dir = 0;
+ }
+
+ d = user_dir + strlen(user_dir);
+ while (*p && *p != '"') {
+ if ((*p == '\\') && (*(p + 1) != 0)) {
+ p++;
+ }
+ *d++ = *p++;
+ }
+ *d = 0;
+ }
+error2:
+ fclose(file);
+
+ if (user_dir) {
+ return user_dir;
+ }
+
+error:
+ return nullptr;
+}
+
+static const char xdg_user_dirs[] =
+ "DESKTOP\0"
+ "DOCUMENTS\0"
+ "DOWNLOAD\0"
+ "MUSIC\0"
+ "PICTURES\0"
+ "PUBLICSHARE\0"
+ "TEMPLATES\0"
+ "VIDEOS";
+
+static const uint8_t xdg_user_dir_offsets[] = {
+ 0,
+ 8,
+ 18,
+ 27,
+ 33,
+ 42,
+ 54,
+ 64
+};
+
+static nsresult
+GetUnixXDGUserDirectory(SystemDirectories aSystemDirectory,
+ nsIFile** aFile)
+{
+ char* dir = xdg_user_dir_lookup(
+ xdg_user_dirs + xdg_user_dir_offsets[aSystemDirectory - Unix_XDG_Desktop]);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> file;
+ if (dir) {
+ rv = NS_NewNativeLocalFile(nsDependentCString(dir), true,
+ getter_AddRefs(file));
+ free(dir);
+ } else if (Unix_XDG_Desktop == aSystemDirectory) {
+ // for the XDG desktop dir, fall back to HOME/Desktop
+ // (for historical compatibility)
+ rv = GetUnixHomeDir(getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = file->AppendNative(NS_LITERAL_CSTRING("Desktop"));
+ } else {
+ // no fallback for the other XDG dirs
+ rv = NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool exists;
+ rv = file->Exists(&exists);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!exists) {
+ rv = file->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ *aFile = nullptr;
+ file.swap(*aFile);
+
+ return NS_OK;
+}
+#endif
+
+nsresult
+GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory,
+ nsIFile** aFile)
+{
+#if defined(XP_WIN)
+ WCHAR path[MAX_PATH];
+#else
+ char path[MAXPATHLEN];
+#endif
+
+ switch (aSystemSystemDirectory) {
+ case OS_CurrentWorkingDirectory:
+#if defined(XP_WIN)
+ if (!_wgetcwd(path, MAX_PATH)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_NewLocalFile(nsDependentString(path),
+ true,
+ aFile);
+#else
+ if (!getcwd(path, MAXPATHLEN)) {
+ return NS_ERROR_FAILURE;
+ }
+#endif
+
+#if !defined(XP_WIN)
+ return NS_NewNativeLocalFile(nsDependentCString(path),
+ true,
+ aFile);
+#endif
+
+ case OS_DriveDirectory:
+#if defined (XP_WIN)
+ {
+ int32_t len = ::GetWindowsDirectoryW(path, MAX_PATH);
+ if (len == 0) {
+ break;
+ }
+ if (path[1] == char16_t(':') && path[2] == char16_t('\\')) {
+ path[3] = 0;
+ }
+
+ return NS_NewLocalFile(nsDependentString(path),
+ true,
+ aFile);
+ }
+#else
+ return NS_NewNativeLocalFile(nsDependentCString("/"),
+ true,
+ aFile);
+
+#endif
+
+ case OS_TemporaryDirectory:
+#if defined (XP_WIN)
+ {
+ DWORD len = ::GetTempPathW(MAX_PATH, path);
+ if (len == 0) {
+ break;
+ }
+ return NS_NewLocalFile(nsDependentString(path, len),
+ true,
+ aFile);
+ }
+#elif defined(MOZ_WIDGET_COCOA)
+ {
+ return GetOSXFolderType(kUserDomain, kTemporaryFolderType, aFile);
+ }
+
+#elif defined(XP_UNIX)
+ {
+ static const char* tPath = nullptr;
+ if (!tPath) {
+ tPath = PR_GetEnv("TMPDIR");
+ if (!tPath || !*tPath) {
+ tPath = PR_GetEnv("TMP");
+ if (!tPath || !*tPath) {
+ tPath = PR_GetEnv("TEMP");
+ if (!tPath || !*tPath) {
+ tPath = "/tmp/";
+ }
+ }
+ }
+ }
+ return NS_NewNativeLocalFile(nsDependentCString(tPath),
+ true,
+ aFile);
+ }
+#else
+ break;
+#endif
+#if defined (XP_WIN)
+ case Win_SystemDirectory: {
+ int32_t len = ::GetSystemDirectoryW(path, MAX_PATH);
+
+ // Need enough space to add the trailing backslash
+ if (!len || len > MAX_PATH - 2) {
+ break;
+ }
+ path[len] = L'\\';
+ path[++len] = L'\0';
+
+ return NS_NewLocalFile(nsDependentString(path, len),
+ true,
+ aFile);
+ }
+
+ case Win_WindowsDirectory: {
+ int32_t len = ::GetWindowsDirectoryW(path, MAX_PATH);
+
+ // Need enough space to add the trailing backslash
+ if (!len || len > MAX_PATH - 2) {
+ break;
+ }
+
+ path[len] = L'\\';
+ path[++len] = L'\0';
+
+ return NS_NewLocalFile(nsDependentString(path, len),
+ true,
+ aFile);
+ }
+
+ case Win_ProgramFiles: {
+ return GetWindowsFolder(CSIDL_PROGRAM_FILES, aFile);
+ }
+
+ case Win_HomeDirectory: {
+ nsresult rv = GetWindowsFolder(CSIDL_PROFILE, aFile);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+
+ int32_t len;
+ if ((len = ::GetEnvironmentVariableW(L"HOME", path, MAX_PATH)) > 0) {
+ // Need enough space to add the trailing backslash
+ if (len > MAX_PATH - 2) {
+ break;
+ }
+
+ path[len] = L'\\';
+ path[++len] = L'\0';
+
+ rv = NS_NewLocalFile(nsDependentString(path, len),
+ true,
+ aFile);
+ if (NS_SUCCEEDED(rv)) {
+ return rv;
+ }
+ }
+
+ len = ::GetEnvironmentVariableW(L"HOMEDRIVE", path, MAX_PATH);
+ if (0 < len && len < MAX_PATH) {
+ WCHAR temp[MAX_PATH];
+ DWORD len2 = ::GetEnvironmentVariableW(L"HOMEPATH", temp, MAX_PATH);
+ if (0 < len2 && len + len2 < MAX_PATH) {
+ wcsncat(path, temp, len2);
+ }
+
+ len = wcslen(path);
+
+ // Need enough space to add the trailing backslash
+ if (len > MAX_PATH - 2) {
+ break;
+ }
+
+ path[len] = L'\\';
+ path[++len] = L'\0';
+
+ return NS_NewLocalFile(nsDependentString(path, len),
+ true,
+ aFile);
+ }
+ }
+ case Win_Desktop: {
+ return GetWindowsFolder(CSIDL_DESKTOP, aFile);
+ }
+ case Win_Programs: {
+ return GetWindowsFolder(CSIDL_PROGRAMS, aFile);
+ }
+
+ case Win_Downloads: {
+ // Defined in KnownFolders.h.
+ GUID folderid_downloads = {
+ 0x374de290, 0x123f, 0x4565,
+ { 0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b }
+ };
+ nsresult rv = GetKnownFolder(&folderid_downloads, aFile);
+ // On WinXP, there is no downloads folder, default
+ // to 'Desktop'.
+ if (NS_ERROR_FAILURE == rv) {
+ rv = GetWindowsFolder(CSIDL_DESKTOP, aFile);
+ }
+ return rv;
+ }
+
+ case Win_Controls: {
+ return GetWindowsFolder(CSIDL_CONTROLS, aFile);
+ }
+ case Win_Printers: {
+ return GetWindowsFolder(CSIDL_PRINTERS, aFile);
+ }
+ case Win_Personal: {
+ return GetWindowsFolder(CSIDL_PERSONAL, aFile);
+ }
+ case Win_Favorites: {
+ return GetWindowsFolder(CSIDL_FAVORITES, aFile);
+ }
+ case Win_Startup: {
+ return GetWindowsFolder(CSIDL_STARTUP, aFile);
+ }
+ case Win_Recent: {
+ return GetWindowsFolder(CSIDL_RECENT, aFile);
+ }
+ case Win_Sendto: {
+ return GetWindowsFolder(CSIDL_SENDTO, aFile);
+ }
+ case Win_Bitbucket: {
+ return GetWindowsFolder(CSIDL_BITBUCKET, aFile);
+ }
+ case Win_Startmenu: {
+ return GetWindowsFolder(CSIDL_STARTMENU, aFile);
+ }
+ case Win_Desktopdirectory: {
+ return GetWindowsFolder(CSIDL_DESKTOPDIRECTORY, aFile);
+ }
+ case Win_Drives: {
+ return GetWindowsFolder(CSIDL_DRIVES, aFile);
+ }
+ case Win_Network: {
+ return GetWindowsFolder(CSIDL_NETWORK, aFile);
+ }
+ case Win_Nethood: {
+ return GetWindowsFolder(CSIDL_NETHOOD, aFile);
+ }
+ case Win_Fonts: {
+ return GetWindowsFolder(CSIDL_FONTS, aFile);
+ }
+ case Win_Templates: {
+ return GetWindowsFolder(CSIDL_TEMPLATES, aFile);
+ }
+ case Win_Common_Startmenu: {
+ return GetWindowsFolder(CSIDL_COMMON_STARTMENU, aFile);
+ }
+ case Win_Common_Programs: {
+ return GetWindowsFolder(CSIDL_COMMON_PROGRAMS, aFile);
+ }
+ case Win_Common_Startup: {
+ return GetWindowsFolder(CSIDL_COMMON_STARTUP, aFile);
+ }
+ case Win_Common_Desktopdirectory: {
+ return GetWindowsFolder(CSIDL_COMMON_DESKTOPDIRECTORY, aFile);
+ }
+ case Win_Common_AppData: {
+ return GetWindowsFolder(CSIDL_COMMON_APPDATA, aFile);
+ }
+ case Win_Printhood: {
+ return GetWindowsFolder(CSIDL_PRINTHOOD, aFile);
+ }
+ case Win_Cookies: {
+ return GetWindowsFolder(CSIDL_COOKIES, aFile);
+ }
+ case Win_Appdata: {
+ nsresult rv = GetWindowsFolder(CSIDL_APPDATA, aFile);
+ if (NS_FAILED(rv)) {
+ rv = GetRegWindowsAppDataFolder(false, aFile);
+ }
+ return rv;
+ }
+ case Win_LocalAppdata: {
+ nsresult rv = GetWindowsFolder(CSIDL_LOCAL_APPDATA, aFile);
+ if (NS_FAILED(rv)) {
+ rv = GetRegWindowsAppDataFolder(true, aFile);
+ }
+ return rv;
+ }
+#if defined(MOZ_CONTENT_SANDBOX)
+ case Win_LocalAppdataLow: {
+ // This should only really fail on versions pre-Vista, in which case this
+ // shouldn't have been used in the first place.
+ GUID localAppDataLowGuid = FOLDERID_LocalAppDataLow;
+ return GetKnownFolder(&localAppDataLowGuid, aFile);
+ }
+#endif
+ case Win_Documents: {
+ return GetLibrarySaveToPath(CSIDL_MYDOCUMENTS,
+ FOLDERID_DocumentsLibrary,
+ aFile);
+ }
+ case Win_Pictures: {
+ return GetLibrarySaveToPath(CSIDL_MYPICTURES,
+ FOLDERID_PicturesLibrary,
+ aFile);
+ }
+ case Win_Music: {
+ return GetLibrarySaveToPath(CSIDL_MYMUSIC,
+ FOLDERID_MusicLibrary,
+ aFile);
+ }
+ case Win_Videos: {
+ return GetLibrarySaveToPath(CSIDL_MYVIDEO,
+ FOLDERID_VideosLibrary,
+ aFile);
+ }
+#endif // XP_WIN
+
+#if defined(XP_UNIX)
+ case Unix_LocalDirectory:
+ return NS_NewNativeLocalFile(nsDependentCString("/usr/local/netscape/"),
+ true,
+ aFile);
+ case Unix_LibDirectory:
+ return NS_NewNativeLocalFile(nsDependentCString("/usr/local/lib/netscape/"),
+ true,
+ aFile);
+
+ case Unix_HomeDirectory:
+ return GetUnixHomeDir(aFile);
+
+ case Unix_XDG_Desktop:
+ case Unix_XDG_Documents:
+ case Unix_XDG_Download:
+ case Unix_XDG_Music:
+ case Unix_XDG_Pictures:
+ case Unix_XDG_PublicShare:
+ case Unix_XDG_Templates:
+ case Unix_XDG_Videos:
+ return GetUnixXDGUserDirectory(aSystemSystemDirectory, aFile);
+#endif
+
+ default:
+ break;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+#if defined (MOZ_WIDGET_COCOA)
+nsresult
+GetOSXFolderType(short aDomain, OSType aFolderType, nsIFile** aLocalFile)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (aFolderType == kTemporaryFolderType) {
+ NS_NewLocalFile(EmptyString(), true, aLocalFile);
+ nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*aLocalFile));
+ if (localMacFile) {
+ rv = localMacFile->InitWithCFURL(
+ CocoaFileUtils::GetTemporaryFolderCFURLRef());
+ }
+ return rv;
+ }
+
+ OSErr err;
+ FSRef fsRef;
+ err = ::FSFindFolder(aDomain, aFolderType, kCreateFolder, &fsRef);
+ if (err == noErr) {
+ NS_NewLocalFile(EmptyString(), true, aLocalFile);
+ nsCOMPtr<nsILocalFileMac> localMacFile(do_QueryInterface(*aLocalFile));
+ if (localMacFile) {
+ rv = localMacFile->InitWithFSRef(&fsRef);
+ }
+ }
+ return rv;
+}
+#endif
+
diff --git a/xpcom/io/SpecialSystemDirectory.h b/xpcom/io/SpecialSystemDirectory.h
new file mode 100644
index 000000000..dd3d88379
--- /dev/null
+++ b/xpcom/io/SpecialSystemDirectory.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SPECIALSYSTEMDIRECTORY_H_
+#define _SPECIALSYSTEMDIRECTORY_H_
+
+#include "nscore.h"
+#include "nsIFile.h"
+
+#ifdef MOZ_WIDGET_COCOA
+#include <Carbon/Carbon.h>
+#include "nsILocalFileMac.h"
+#include "prenv.h"
+#endif
+
+extern void StartupSpecialSystemDirectory();
+
+
+enum SystemDirectories {
+ OS_DriveDirectory = 1,
+ OS_TemporaryDirectory = 2,
+ OS_CurrentProcessDirectory = 3,
+ OS_CurrentWorkingDirectory = 4,
+ XPCOM_CurrentProcessComponentDirectory = 5,
+ XPCOM_CurrentProcessComponentRegistry = 6,
+
+ Moz_BinDirectory = 100 ,
+ Mac_SystemDirectory = 101,
+ Mac_DesktopDirectory = 102,
+ Mac_TrashDirectory = 103,
+ Mac_StartupDirectory = 104,
+ Mac_ShutdownDirectory = 105,
+ Mac_AppleMenuDirectory = 106,
+ Mac_ControlPanelDirectory = 107,
+ Mac_ExtensionDirectory = 108,
+ Mac_FontsDirectory = 109,
+ Mac_ClassicPreferencesDirectory = 110,
+ Mac_DocumentsDirectory = 111,
+ Mac_InternetSearchDirectory = 112,
+ Mac_DefaultDownloadDirectory = 113,
+ Mac_UserLibDirectory = 114,
+ Mac_PreferencesDirectory = 115,
+
+ Win_SystemDirectory = 201,
+ Win_WindowsDirectory = 202,
+ Win_HomeDirectory = 203,
+ Win_Desktop = 204,
+ Win_Programs = 205,
+ Win_Controls = 206,
+ Win_Printers = 207,
+ Win_Personal = 208,
+ Win_Favorites = 209,
+ Win_Startup = 210,
+ Win_Recent = 211,
+ Win_Sendto = 212,
+ Win_Bitbucket = 213,
+ Win_Startmenu = 214,
+ Win_Desktopdirectory = 215,
+ Win_Drives = 216,
+ Win_Network = 217,
+ Win_Nethood = 218,
+ Win_Fonts = 219,
+ Win_Templates = 220,
+ Win_Common_Startmenu = 221,
+ Win_Common_Programs = 222,
+ Win_Common_Startup = 223,
+ Win_Common_Desktopdirectory = 224,
+ Win_Appdata = 225,
+ Win_Printhood = 226,
+ Win_Cookies = 227,
+ Win_LocalAppdata = 228,
+ Win_ProgramFiles = 229,
+ Win_Downloads = 230,
+ Win_Common_AppData = 231,
+ Win_Documents = 232,
+ Win_Pictures = 233,
+ Win_Music = 234,
+ Win_Videos = 235,
+#if defined(MOZ_CONTENT_SANDBOX)
+ Win_LocalAppdataLow = 236,
+#endif
+
+ Unix_LocalDirectory = 301,
+ Unix_LibDirectory = 302,
+ Unix_HomeDirectory = 303,
+ Unix_XDG_Desktop = 304,
+ Unix_XDG_Documents = 305,
+ Unix_XDG_Download = 306,
+ Unix_XDG_Music = 307,
+ Unix_XDG_Pictures = 308,
+ Unix_XDG_PublicShare = 309,
+ Unix_XDG_Templates = 310,
+ Unix_XDG_Videos = 311
+};
+
+nsresult
+GetSpecialSystemDirectory(SystemDirectories aSystemSystemDirectory,
+ nsIFile** aFile);
+#ifdef MOZ_WIDGET_COCOA
+nsresult
+GetOSXFolderType(short aDomain, OSType aFolderType, nsIFile** aLocalFile);
+#endif
+
+#endif
diff --git a/xpcom/io/crc32c.c b/xpcom/io/crc32c.c
new file mode 100644
index 000000000..3494945c0
--- /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 000000000..f7035fa15
--- /dev/null
+++ b/xpcom/io/crc32c.h
@@ -0,0 +1,23 @@
+#ifndef crc32c_h
+#define crc32c_h
+
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Compute a CRC32c as defined in RFC3720. This is a different polynomial than
+// what is used in the crc for zlib, etc. Typical usage to calculate a new CRC:
+//
+// ComputeCrc32c(~0, buffer, bufferLength);
+//
+uint32_t
+ComputeCrc32c(uint32_t aCrc, const void *aBuf, size_t aSize);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // crc32c_h
diff --git a/xpcom/io/moz.build b/xpcom/io/moz.build
new file mode 100644
index 000000000..6f21e0a72
--- /dev/null
+++ b/xpcom/io/moz.build
@@ -0,0 +1,140 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ 'nsIAsyncInputStream.idl',
+ 'nsIAsyncOutputStream.idl',
+ 'nsIBinaryInputStream.idl',
+ 'nsIBinaryOutputStream.idl',
+ 'nsICloneableInputStream.idl',
+ 'nsIConverterInputStream.idl',
+ 'nsIConverterOutputStream.idl',
+ 'nsIDirectoryEnumerator.idl',
+ 'nsIDirectoryService.idl',
+ 'nsIFile.idl',
+ 'nsIInputStream.idl',
+ 'nsIInputStreamTee.idl',
+ 'nsIIOUtil.idl',
+ 'nsILineInputStream.idl',
+ 'nsILocalFile.idl',
+ 'nsILocalFileWin.idl',
+ 'nsIMultiplexInputStream.idl',
+ 'nsIObjectInputStream.idl',
+ 'nsIObjectOutputStream.idl',
+ 'nsIOutputStream.idl',
+ 'nsIPipe.idl',
+ 'nsISafeOutputStream.idl',
+ 'nsIScriptableBase64Encoder.idl',
+ 'nsIScriptableInputStream.idl',
+ 'nsISeekableStream.idl',
+ 'nsIStorageStream.idl',
+ 'nsIStreamBufferAccess.idl',
+ 'nsIStringStream.idl',
+ 'nsIUnicharInputStream.idl',
+ 'nsIUnicharLineInputStream.idl',
+ 'nsIUnicharOutputStream.idl',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ XPIDL_SOURCES += [
+ 'nsILocalFileMac.idl',
+ ]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ EXPORTS += ['nsLocalFileWin.h']
+ EXPORTS.mozilla += [
+ 'FileUtilsWin.h',
+ ]
+ SOURCES += [
+ 'FileUtilsWin.cpp',
+ 'nsLocalFileWin.cpp',
+ ]
+else:
+ EXPORTS += ['nsLocalFileUnix.h']
+ SOURCES += [
+ 'nsLocalFileUnix.cpp',
+ ]
+
+XPIDL_MODULE = 'xpcom_io'
+
+EXPORTS += [
+ 'nsAnonymousTemporaryFile.h',
+ 'nsAppDirectoryServiceDefs.h',
+ 'nsDirectoryService.h',
+ 'nsDirectoryServiceAtomList.h',
+ 'nsDirectoryServiceDefs.h',
+ 'nsDirectoryServiceUtils.h',
+ 'nsEscape.h',
+ 'nsLinebreakConverter.h',
+ 'nsLocalFile.h',
+ 'nsMultiplexInputStream.h',
+ 'nsNativeCharsetUtils.h',
+ 'nsScriptableInputStream.h',
+ 'nsStorageStream.h',
+ 'nsStreamUtils.h',
+ 'nsStringStream.h',
+ 'nsUnicharInputStream.h',
+ 'nsWildCard.h',
+ 'SlicedInputStream.h',
+ 'SpecialSystemDirectory.h',
+]
+
+EXPORTS.mozilla += [
+ 'Base64.h',
+ 'SnappyCompressOutputStream.h',
+ 'SnappyFrameUtils.h',
+ 'SnappyUncompressInputStream.h',
+]
+
+UNIFIED_SOURCES += [
+ 'Base64.cpp',
+ 'crc32c.c',
+ 'nsAnonymousTemporaryFile.cpp',
+ 'nsAppFileLocationProvider.cpp',
+ 'nsBinaryStream.cpp',
+ 'nsDirectoryService.cpp',
+ 'nsEscape.cpp',
+ 'nsInputStreamTee.cpp',
+ 'nsIOUtil.cpp',
+ 'nsLinebreakConverter.cpp',
+ 'nsLocalFileCommon.cpp',
+ 'nsMultiplexInputStream.cpp',
+ 'nsNativeCharsetUtils.cpp',
+ 'nsPipe3.cpp',
+ 'nsScriptableBase64Encoder.cpp',
+ 'nsScriptableInputStream.cpp',
+ 'nsSegmentedBuffer.cpp',
+ 'nsStorageStream.cpp',
+ 'nsStreamUtils.cpp',
+ 'nsStringStream.cpp',
+ 'nsUnicharInputStream.cpp',
+ 'nsWildCard.cpp',
+ 'SlicedInputStream.cpp',
+ 'SnappyCompressOutputStream.cpp',
+ 'SnappyFrameUtils.cpp',
+ 'SnappyUncompressInputStream.cpp',
+ 'SpecialSystemDirectory.cpp',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += [
+ 'CocoaFileUtils.mm',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['OS_ARCH'] == 'Linux' and 'lib64' in CONFIG['libdir']:
+ DEFINES['HAVE_USR_LIB64_DIR'] = True
+
+LOCAL_INCLUDES += ['!..']
+
+if CONFIG['_MSC_VER']:
+ # This is intended as a temporary hack to support building with VS2015.
+ # '_snwprintf' : format string '%s' requires an argument of type 'wchar_t *',
+ # but variadic argument 3 has type 'char16ptr_t'
+ CXXFLAGS += ['-wd4477']
diff --git a/xpcom/io/nsAnonymousTemporaryFile.cpp b/xpcom/io/nsAnonymousTemporaryFile.cpp
new file mode 100644
index 000000000..586e552c4
--- /dev/null
+++ b/xpcom/io/nsAnonymousTemporaryFile.cpp
@@ -0,0 +1,314 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsAnonymousTemporaryFile.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsXULAppAPI.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "prio.h"
+#include "private/pprio.h"
+
+#ifdef XP_WIN
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "nsIIdleService.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIFile.h"
+#include "nsAutoPtr.h"
+#include "nsITimer.h"
+#include "nsCRT.h"
+
+#endif
+
+using namespace mozilla;
+
+// We store the temp files in the system temp dir.
+//
+// On Windows systems in particular we use a sub-directory of the temp
+// directory, because:
+// 1. DELETE_ON_CLOSE is unreliable on Windows, in particular if we power
+// cycle (and perhaps if we crash) the files are not deleted. We store
+// the temporary files in a known sub-dir so that we can find and delete
+// them easily and quickly.
+// 2. On Windows NT the system temp dir is in the user's $HomeDir/AppData,
+// so we can be sure the user always has write privileges to that directory;
+// if the sub-dir for our temp files was in some shared location and
+// was created by a privileged user, it's possible that other users
+// wouldn't have write access to that sub-dir. (Non-Windows systems
+// don't store their temp files in a sub-dir, so this isn't an issue on
+// those platforms).
+// 3. Content processes can access the system temp dir
+// (NS_GetSpecialDirectory fails on NS_APP_USER_PROFILE_LOCAL_50_DIR
+// for content process for example, which is where we previously stored
+// temp files on Windows). This argument applies to all platforms, not
+// just Windows.
+static nsresult
+GetTempDir(nsIFile** aTempDir)
+{
+ if (NS_WARN_IF(!aTempDir)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifdef XP_WIN
+ // On windows DELETE_ON_CLOSE is unreliable, so we store temporary files
+ // in a subdir of the temp dir and delete that in an idle service observer
+ // to ensure it's been cleared.
+ rv = tmpFile->AppendNative(nsDependentCString("mozilla-temp-files"));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+#endif
+
+ tmpFile.forget(aTempDir);
+
+ return NS_OK;
+}
+
+namespace {
+
+class nsRemoteAnonymousTemporaryFileRunnable : public Runnable
+{
+public:
+ dom::FileDescOrError *mResultPtr;
+ explicit nsRemoteAnonymousTemporaryFileRunnable(dom::FileDescOrError *aResultPtr)
+ : mResultPtr(aResultPtr)
+ { }
+
+protected:
+ NS_IMETHOD Run() override {
+ dom::ContentChild* child = dom::ContentChild::GetSingleton();
+ MOZ_ASSERT(child);
+ child->SendOpenAnonymousTemporaryFile(mResultPtr);
+ return NS_OK;
+ }
+};
+
+} // namespace
+
+nsresult
+NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc)
+{
+ if (NS_WARN_IF(!aOutFileDesc)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (dom::ContentChild* child = dom::ContentChild::GetSingleton()) {
+ dom::FileDescOrError fd = NS_OK;
+ if (NS_IsMainThread()) {
+ child->SendOpenAnonymousTemporaryFile(&fd);
+ } else {
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ MOZ_ASSERT(mainThread);
+ SyncRunnable::DispatchToThread(mainThread,
+ new nsRemoteAnonymousTemporaryFileRunnable(&fd));
+ }
+ if (fd.type() == dom::FileDescOrError::Tnsresult) {
+ nsresult rv = fd.get_nsresult();
+ MOZ_ASSERT(NS_FAILED(rv));
+ return rv;
+ }
+ auto rawFD = fd.get_FileDescriptor().ClonePlatformHandle();
+ *aOutFileDesc = PR_ImportFile(PROsfd(rawFD.release()));
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> tmpFile;
+ rv = GetTempDir(getter_AddRefs(tmpFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Give the temp file a name with a random element. CreateUnique will also
+ // append a counter to the name if it encounters a name collision. Adding
+ // a random element to the name reduces the likelihood of a name collision,
+ // so that CreateUnique() doesn't end up trying a lot of name variants in
+ // its "try appending an incrementing counter" loop, as file IO can be
+ // expensive on some mobile flash drives.
+ nsAutoCString name("mozilla-temp-");
+ name.AppendInt(rand());
+
+ rv = tmpFile->AppendNative(name);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = tmpFile->OpenNSPRFileDesc(PR_RDWR | nsIFile::DELETE_ON_CLOSE,
+ PR_IRWXU, aOutFileDesc);
+
+ return rv;
+}
+
+#ifdef XP_WIN
+
+// On Windows we have an idle service observer that runs some time after
+// startup and deletes any stray anonymous temporary files...
+
+// Duration of idle time before we'll get a callback whereupon we attempt to
+// remove any stray and unused anonymous temp files.
+#define TEMP_FILE_IDLE_TIME_S 30
+
+// The nsAnonTempFileRemover is created in a timer, which sets an idle observer.
+// This is expiration time (in ms) which initial timer is set for (3 minutes).
+#define SCHEDULE_TIMEOUT_MS 3 * 60 * 1000
+
+#define XPCOM_SHUTDOWN_TOPIC "xpcom-shutdown"
+
+// This class adds itself as an idle observer. When the application has
+// been idle for about 30 seconds we'll get a notification, whereupon we'll
+// attempt to delete ${TempDir}/mozilla-temp-files/. This is to ensure all
+// temp files that were supposed to be deleted on application exit were actually
+// deleted, as they may not be if we previously crashed. See bugs 572579 and
+// 785662. This is only needed on some versions of Windows,
+// nsIFile::DELETE_ON_CLOSE works on other platforms.
+// This class adds itself as a shutdown observer so that it can cancel the
+// idle observer and its timer on shutdown. Note: the observer and idle
+// services hold references to instances of this object, and those references
+// are what keep this object alive.
+class nsAnonTempFileRemover final : public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ nsAnonTempFileRemover() {}
+
+ nsresult Init()
+ {
+ // We add the idle observer in a timer, so that the app has enough
+ // time to start up before we add the idle observer. If we register the
+ // idle observer too early, it will be registered before the fake idle
+ // service is installed when running in xpcshell, and this interferes with
+ // the fake idle service, causing xpcshell-test failures.
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ if (NS_WARN_IF(!mTimer)) {
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv = mTimer->Init(this,
+ SCHEDULE_TIMEOUT_MS,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Register shutdown observer so we can cancel the timer if we shutdown before
+ // the timer runs.
+ nsCOMPtr<nsIObserverService> obsSrv = services::GetObserverService();
+ if (NS_WARN_IF(!obsSrv)) {
+ return NS_ERROR_FAILURE;
+ }
+ return obsSrv->AddObserver(this, XPCOM_SHUTDOWN_TOPIC, false);
+ }
+
+ void Cleanup()
+ {
+ // Cancel timer.
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ // Remove idle service observer.
+ nsCOMPtr<nsIIdleService> idleSvc =
+ do_GetService("@mozilla.org/widget/idleservice;1");
+ if (idleSvc) {
+ idleSvc->RemoveIdleObserver(this, TEMP_FILE_IDLE_TIME_S);
+ }
+ // Remove shutdown observer.
+ nsCOMPtr<nsIObserverService> obsSrv = services::GetObserverService();
+ if (obsSrv) {
+ obsSrv->RemoveObserver(this, XPCOM_SHUTDOWN_TOPIC);
+ }
+ }
+
+ NS_IMETHODIMP Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+ {
+ if (nsCRT::strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0 &&
+ NS_FAILED(RegisterIdleObserver())) {
+ Cleanup();
+ } else if (nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0) {
+ // The user has been idle for a while, clean up the temp files.
+ // The idle service will drop its reference to this object after
+ // we exit, destroying this object.
+ RemoveAnonTempFileFiles();
+ Cleanup();
+ } else if (nsCRT::strcmp(aTopic, XPCOM_SHUTDOWN_TOPIC) == 0) {
+ Cleanup();
+ }
+ return NS_OK;
+ }
+
+ nsresult RegisterIdleObserver()
+ {
+ // Add this as an idle observer. When we've been idle for
+ // TEMP_FILE_IDLE_TIME_S seconds, we'll get a notification, and we'll then
+ // try to delete any stray temp files.
+ nsCOMPtr<nsIIdleService> idleSvc =
+ do_GetService("@mozilla.org/widget/idleservice;1");
+ if (!idleSvc) {
+ return NS_ERROR_FAILURE;
+ }
+ return idleSvc->AddIdleObserver(this, TEMP_FILE_IDLE_TIME_S);
+ }
+
+ void RemoveAnonTempFileFiles()
+ {
+ nsCOMPtr<nsIFile> tmpDir;
+ nsresult rv = GetTempDir(getter_AddRefs(tmpDir));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ // Remove the directory recursively.
+ tmpDir->Remove(true);
+ }
+
+private:
+ ~nsAnonTempFileRemover() {}
+
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+NS_IMPL_ISUPPORTS(nsAnonTempFileRemover, nsIObserver)
+
+nsresult
+CreateAnonTempFileRemover()
+{
+ // Create a temp file remover. If Init() succeeds, the temp file remover is kept
+ // alive by a reference held by the observer service, since the temp file remover
+ // is a shutdown observer. We only create the temp file remover if we're running
+ // in the main process; there's no point in doing the temp file removal multiple
+ // times per startup.
+ if (!XRE_IsParentProcess()) {
+ return NS_OK;
+ }
+ RefPtr<nsAnonTempFileRemover> tempRemover = new nsAnonTempFileRemover();
+ return tempRemover->Init();
+}
+
+#endif
+
diff --git a/xpcom/io/nsAnonymousTemporaryFile.h b/xpcom/io/nsAnonymousTemporaryFile.h
new file mode 100644
index 000000000..d2f39528f
--- /dev/null
+++ b/xpcom/io/nsAnonymousTemporaryFile.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#pragma once
+
+#include "prio.h"
+#include "nscore.h"
+
+/**
+ * OpenAnonymousTemporaryFile
+ *
+ * Creates and opens a temporary file which has a random name. Callers have no
+ * control over the file name, and the file is opened in a temporary location
+ * which is appropriate for the platform.
+ *
+ * Upon success, aOutFileDesc contains an opened handle to the temporary file.
+ * The caller is responsible for closing the file when they're finished with it.
+ *
+ * The file will be deleted when the file handle is closed. On non-Windows
+ * platforms the file will be unlinked before this function returns. On Windows
+ * the OS supplied delete-on-close mechanism is unreliable if the application
+ * crashes or the computer power cycles unexpectedly, so unopened temporary
+ * files are purged at some time after application startup.
+ *
+ */
+nsresult
+NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc);
+
diff --git a/xpcom/io/nsAppDirectoryServiceDefs.h b/xpcom/io/nsAppDirectoryServiceDefs.h
new file mode 100644
index 000000000..aa0a68816
--- /dev/null
+++ b/xpcom/io/nsAppDirectoryServiceDefs.h
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAppDirectoryServiceDefs_h___
+#define nsAppDirectoryServiceDefs_h___
+
+//========================================================================================
+//
+// Defines property names for directories available from standard nsIDirectoryServiceProviders.
+// These keys are not guaranteed to exist because the nsIDirectoryServiceProviders which
+// provide them are optional.
+//
+// Keys whose definition ends in "DIR" or "FILE" return a single nsIFile (or subclass).
+// Keys whose definition ends in "LIST" return an nsISimpleEnumerator which enumerates a
+// list of file objects.
+//
+// System and XPCOM level properties are defined in nsDirectoryServiceDefs.h.
+//
+//========================================================================================
+
+
+// --------------------------------------------------------------------------------------
+// Files and directories which exist on a per-product basis
+// --------------------------------------------------------------------------------------
+
+#define NS_APP_APPLICATION_REGISTRY_FILE "AppRegF"
+#define NS_APP_APPLICATION_REGISTRY_DIR "AppRegD"
+
+#define NS_APP_DEFAULTS_50_DIR "DefRt" // The root dir of all defaults dirs
+#define NS_APP_PREF_DEFAULTS_50_DIR "PrfDef"
+
+#define NS_APP_USER_PROFILES_ROOT_DIR "DefProfRt" // The dir where user profile dirs live.
+#define NS_APP_USER_PROFILES_LOCAL_ROOT_DIR "DefProfLRt" // The dir where user profile temp dirs live.
+
+#define NS_APP_RES_DIR "ARes"
+#define NS_APP_CHROME_DIR "AChrom"
+#define NS_APP_PLUGINS_DIR "APlugns" // Deprecated - use NS_APP_PLUGINS_DIR_LIST
+#define NS_APP_SEARCH_DIR "SrchPlugns"
+
+#define NS_APP_CHROME_DIR_LIST "AChromDL"
+#define NS_APP_PLUGINS_DIR_LIST "APluginsDL"
+#define NS_APP_SEARCH_DIR_LIST "SrchPluginsDL"
+#define NS_APP_DISTRIBUTION_SEARCH_DIR_LIST "SrchPluginsDistDL"
+
+// --------------------------------------------------------------------------------------
+// Files and directories which exist on a per-profile basis
+// These locations are typically provided by the profile mgr
+// --------------------------------------------------------------------------------------
+
+// In a shared profile environment, prefixing a profile-relative
+// key with NS_SHARED returns a location that is shared by
+// other users of the profile. Without this prefix, the consumer
+// has exclusive access to this location.
+
+#define NS_SHARED "SHARED"
+
+#define NS_APP_PREFS_50_DIR "PrefD" // Directory which contains user prefs
+#define NS_APP_PREFS_50_FILE "PrefF"
+#define NS_APP_PREFS_DEFAULTS_DIR_LIST "PrefDL"
+#define NS_EXT_PREFS_DEFAULTS_DIR_LIST "ExtPrefDL"
+#define NS_APP_PREFS_OVERRIDE_DIR "PrefDOverride" // Directory for per-profile defaults
+
+#define NS_APP_USER_PROFILE_50_DIR "ProfD"
+#define NS_APP_USER_PROFILE_LOCAL_50_DIR "ProfLD"
+
+#define NS_APP_USER_CHROME_DIR "UChrm"
+#define NS_APP_USER_SEARCH_DIR "UsrSrchPlugns"
+
+#define NS_APP_LOCALSTORE_50_FILE "LclSt"
+#define NS_APP_USER_PANELS_50_FILE "UPnls"
+#define NS_APP_USER_MIMETYPES_50_FILE "UMimTyp"
+#define NS_APP_CACHE_PARENT_DIR "cachePDir"
+
+#define NS_APP_DOWNLOADS_50_FILE "DLoads"
+
+#define NS_APP_SEARCH_50_FILE "SrchF"
+
+#define NS_APP_INSTALL_CLEANUP_DIR "XPIClnupD" //location of xpicleanup.dat xpicleanup.exe
+
+#define NS_APP_INDEXEDDB_PARENT_DIR "indexedDBPDir"
+
+#define NS_APP_PERMISSION_PARENT_DIR "permissionDBPDir"
+
+#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
+//
+// NS_APP_CONTENT_PROCESS_TEMP_DIR refers to a directory that is read and
+// write accessible from a sandboxed content process. The key may be used in
+// either process, but the directory is intended to be used for short-lived
+// files that need to be saved to the filesystem by the content process and
+// don't need to survive browser restarts. The directory is reset on startup.
+// The key is only valid when MOZ_CONTENT_SANDBOX is defined. When
+// MOZ_CONTENT_SANDBOX is defined, the directory the key refers to differs
+// depending on whether or not content sandboxing is enabled.
+//
+// When MOZ_CONTENT_SANDBOX is defined and sandboxing is enabled (versus
+// manually disabled via prefs), the content process replaces NS_OS_TEMP_DIR
+// with NS_APP_CONTENT_PROCESS_TEMP_DIR so that legacy code in content
+// attempting to write to NS_OS_TEMP_DIR will write to
+// NS_APP_CONTENT_PROCESS_TEMP_DIR instead. When MOZ_CONTENT_SANDBOX is
+// defined but sandboxing is disabled, NS_APP_CONTENT_PROCESS_TEMP_DIR
+// falls back to NS_OS_TEMP_DIR in both content and chrome processes.
+//
+// New code should avoid writing to the filesystem from the content process
+// and should instead proxy through the parent process whenever possible.
+//
+// At present, all sandboxed content processes use the same directory for
+// NS_APP_CONTENT_PROCESS_TEMP_DIR, but that should not be relied upon.
+//
+#define NS_APP_CONTENT_PROCESS_TEMP_DIR "ContentTmpD"
+#else
+// Otherwise NS_APP_CONTENT_PROCESS_TEMP_DIR must match NS_OS_TEMP_DIR.
+#define NS_APP_CONTENT_PROCESS_TEMP_DIR "TmpD"
+#endif // (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_CONTENT_SANDBOX)
+
+#endif // nsAppDirectoryServiceDefs_h___
diff --git a/xpcom/io/nsAppFileLocationProvider.cpp b/xpcom/io/nsAppFileLocationProvider.cpp
new file mode 100644
index 000000000..27ccd56dc
--- /dev/null
+++ b/xpcom/io/nsAppFileLocationProvider.cpp
@@ -0,0 +1,609 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAppFileLocationProvider.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsEnumeratorUtils.h"
+#include "nsIAtom.h"
+#include "nsIFile.h"
+#include "nsString.h"
+#include "nsXPIDLString.h"
+#include "nsISimpleEnumerator.h"
+#include "prenv.h"
+#include "nsCRT.h"
+
+#if defined(MOZ_WIDGET_COCOA)
+#include <Carbon/Carbon.h>
+#include "nsILocalFileMac.h"
+#elif defined(XP_WIN)
+#include <windows.h>
+#include <shlobj.h>
+#elif defined(XP_UNIX)
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/param.h>
+#endif
+
+
+// WARNING: These hard coded names need to go away. They need to
+// come from localizable resources
+
+#if defined(MOZ_WIDGET_COCOA)
+#define APP_REGISTRY_NAME NS_LITERAL_CSTRING("Application Registry")
+#define ESSENTIAL_FILES NS_LITERAL_CSTRING("Essential Files")
+#elif defined(XP_WIN)
+#define APP_REGISTRY_NAME NS_LITERAL_CSTRING("registry.dat")
+#else
+#define APP_REGISTRY_NAME NS_LITERAL_CSTRING("appreg")
+#endif
+
+// define default product directory
+#define DEFAULT_PRODUCT_DIR NS_LITERAL_CSTRING(MOZ_USER_DIR)
+
+// Locally defined keys used by nsAppDirectoryEnumerator
+#define NS_ENV_PLUGINS_DIR "EnvPlugins" // env var MOZ_PLUGIN_PATH
+#define NS_USER_PLUGINS_DIR "UserPlugins"
+
+#ifdef MOZ_WIDGET_COCOA
+#define NS_MACOSX_USER_PLUGIN_DIR "OSXUserPlugins"
+#define NS_MACOSX_LOCAL_PLUGIN_DIR "OSXLocalPlugins"
+#define NS_MACOSX_JAVA2_PLUGIN_DIR "OSXJavaPlugins"
+#elif XP_UNIX
+#define NS_SYSTEM_PLUGINS_DIR "SysPlugins"
+#endif
+
+#define DEFAULTS_DIR_NAME NS_LITERAL_CSTRING("defaults")
+#define DEFAULTS_PREF_DIR_NAME NS_LITERAL_CSTRING("pref")
+#define RES_DIR_NAME NS_LITERAL_CSTRING("res")
+#define CHROME_DIR_NAME NS_LITERAL_CSTRING("chrome")
+#define PLUGINS_DIR_NAME NS_LITERAL_CSTRING("plugins")
+#define SEARCH_DIR_NAME NS_LITERAL_CSTRING("searchplugins")
+
+//*****************************************************************************
+// nsAppFileLocationProvider::Constructor/Destructor
+//*****************************************************************************
+
+nsAppFileLocationProvider::nsAppFileLocationProvider()
+{
+}
+
+//*****************************************************************************
+// nsAppFileLocationProvider::nsISupports
+//*****************************************************************************
+
+NS_IMPL_ISUPPORTS(nsAppFileLocationProvider,
+ nsIDirectoryServiceProvider,
+ nsIDirectoryServiceProvider2)
+
+//*****************************************************************************
+// nsAppFileLocationProvider::nsIDirectoryServiceProvider
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsAppFileLocationProvider::GetFile(const char* aProp, bool* aPersistent,
+ nsIFile** aResult)
+{
+ if (NS_WARN_IF(!aProp)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ *aResult = nullptr;
+ *aPersistent = true;
+
+#ifdef MOZ_WIDGET_COCOA
+ FSRef fileRef;
+ nsCOMPtr<nsILocalFileMac> macFile;
+#endif
+
+ if (nsCRT::strcmp(aProp, NS_APP_APPLICATION_REGISTRY_DIR) == 0) {
+ rv = GetProductDirectory(getter_AddRefs(localFile));
+ } else if (nsCRT::strcmp(aProp, NS_APP_APPLICATION_REGISTRY_FILE) == 0) {
+ rv = GetProductDirectory(getter_AddRefs(localFile));
+ if (NS_SUCCEEDED(rv)) {
+ rv = localFile->AppendNative(APP_REGISTRY_NAME);
+ }
+ } else if (nsCRT::strcmp(aProp, NS_APP_DEFAULTS_50_DIR) == 0) {
+ rv = CloneMozBinDirectory(getter_AddRefs(localFile));
+ if (NS_SUCCEEDED(rv)) {
+ rv = localFile->AppendRelativeNativePath(DEFAULTS_DIR_NAME);
+ }
+ } else if (nsCRT::strcmp(aProp, NS_APP_PREF_DEFAULTS_50_DIR) == 0) {
+ rv = CloneMozBinDirectory(getter_AddRefs(localFile));
+ if (NS_SUCCEEDED(rv)) {
+ rv = localFile->AppendRelativeNativePath(DEFAULTS_DIR_NAME);
+ if (NS_SUCCEEDED(rv)) {
+ rv = localFile->AppendRelativeNativePath(DEFAULTS_PREF_DIR_NAME);
+ }
+ }
+ } else if (nsCRT::strcmp(aProp, NS_APP_USER_PROFILES_ROOT_DIR) == 0) {
+ rv = GetDefaultUserProfileRoot(getter_AddRefs(localFile));
+ } else if (nsCRT::strcmp(aProp, NS_APP_USER_PROFILES_LOCAL_ROOT_DIR) == 0) {
+ rv = GetDefaultUserProfileRoot(getter_AddRefs(localFile), true);
+ } else if (nsCRT::strcmp(aProp, NS_APP_RES_DIR) == 0) {
+ rv = CloneMozBinDirectory(getter_AddRefs(localFile));
+ if (NS_SUCCEEDED(rv)) {
+ rv = localFile->AppendRelativeNativePath(RES_DIR_NAME);
+ }
+ } else if (nsCRT::strcmp(aProp, NS_APP_CHROME_DIR) == 0) {
+ rv = CloneMozBinDirectory(getter_AddRefs(localFile));
+ if (NS_SUCCEEDED(rv)) {
+ rv = localFile->AppendRelativeNativePath(CHROME_DIR_NAME);
+ }
+ } else if (nsCRT::strcmp(aProp, NS_APP_PLUGINS_DIR) == 0) {
+ rv = CloneMozBinDirectory(getter_AddRefs(localFile));
+ if (NS_SUCCEEDED(rv)) {
+ rv = localFile->AppendRelativeNativePath(PLUGINS_DIR_NAME);
+ }
+ }
+#ifdef MOZ_WIDGET_COCOA
+ else if (nsCRT::strcmp(aProp, NS_MACOSX_USER_PLUGIN_DIR) == 0) {
+ if (::FSFindFolder(kUserDomain, kInternetPlugInFolderType, false,
+ &fileRef) == noErr) {
+ rv = NS_NewLocalFileWithFSRef(&fileRef, true, getter_AddRefs(macFile));
+ if (NS_SUCCEEDED(rv)) {
+ localFile = macFile;
+ }
+ }
+ } else if (nsCRT::strcmp(aProp, NS_MACOSX_LOCAL_PLUGIN_DIR) == 0) {
+ if (::FSFindFolder(kLocalDomain, kInternetPlugInFolderType, false,
+ &fileRef) == noErr) {
+ rv = NS_NewLocalFileWithFSRef(&fileRef, true, getter_AddRefs(macFile));
+ if (NS_SUCCEEDED(rv)) {
+ localFile = macFile;
+ }
+ }
+ } else if (nsCRT::strcmp(aProp, NS_MACOSX_JAVA2_PLUGIN_DIR) == 0) {
+ static const char* const java2PluginDirPath =
+ "/System/Library/Java/Support/Deploy.bundle/Contents/Resources/";
+ rv = NS_NewNativeLocalFile(nsDependentCString(java2PluginDirPath), true,
+ getter_AddRefs(localFile));
+ }
+#else
+ else if (nsCRT::strcmp(aProp, NS_ENV_PLUGINS_DIR) == 0) {
+ NS_ERROR("Don't use nsAppFileLocationProvider::GetFile(NS_ENV_PLUGINS_DIR, ...). "
+ "Use nsAppFileLocationProvider::GetFiles(...).");
+ const char* pathVar = PR_GetEnv("MOZ_PLUGIN_PATH");
+ if (pathVar && *pathVar)
+ rv = NS_NewNativeLocalFile(nsDependentCString(pathVar), true,
+ getter_AddRefs(localFile));
+ } else if (nsCRT::strcmp(aProp, NS_USER_PLUGINS_DIR) == 0) {
+#ifdef ENABLE_SYSTEM_EXTENSION_DIRS
+ rv = GetProductDirectory(getter_AddRefs(localFile));
+ if (NS_SUCCEEDED(rv)) {
+ rv = localFile->AppendRelativeNativePath(PLUGINS_DIR_NAME);
+ }
+#else
+ rv = NS_ERROR_FAILURE;
+#endif
+ }
+#ifdef XP_UNIX
+ else if (nsCRT::strcmp(aProp, NS_SYSTEM_PLUGINS_DIR) == 0) {
+#ifdef ENABLE_SYSTEM_EXTENSION_DIRS
+ static const char* const sysLPlgDir =
+#if defined(HAVE_USR_LIB64_DIR) && defined(__LP64__)
+ "/usr/lib64/mozilla/plugins";
+#elif defined(__OpenBSD__) || defined (__FreeBSD__)
+ "/usr/local/lib/mozilla/plugins";
+#else
+ "/usr/lib/mozilla/plugins";
+#endif
+ rv = NS_NewNativeLocalFile(nsDependentCString(sysLPlgDir),
+ false, getter_AddRefs(localFile));
+#else
+ rv = NS_ERROR_FAILURE;
+#endif
+ }
+#endif
+#endif
+ else if (nsCRT::strcmp(aProp, NS_APP_SEARCH_DIR) == 0) {
+ rv = CloneMozBinDirectory(getter_AddRefs(localFile));
+ if (NS_SUCCEEDED(rv)) {
+ rv = localFile->AppendRelativeNativePath(SEARCH_DIR_NAME);
+ }
+ } else if (nsCRT::strcmp(aProp, NS_APP_USER_SEARCH_DIR) == 0) {
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, aResult);
+ if (NS_SUCCEEDED(rv)) {
+ rv = (*aResult)->AppendNative(SEARCH_DIR_NAME);
+ }
+ } else if (nsCRT::strcmp(aProp, NS_APP_INSTALL_CLEANUP_DIR) == 0) {
+ // This is cloned so that embeddors will have a hook to override
+ // with their own cleanup dir. See bugzilla bug #105087
+ rv = CloneMozBinDirectory(getter_AddRefs(localFile));
+ }
+
+ if (localFile && NS_SUCCEEDED(rv)) {
+ localFile.forget(aResult);
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+
+nsresult
+nsAppFileLocationProvider::CloneMozBinDirectory(nsIFile** aLocalFile)
+{
+ if (NS_WARN_IF(!aLocalFile)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsresult rv;
+
+ if (!mMozBinDirectory) {
+ // Get the mozilla bin directory
+ // 1. Check the directory service first for NS_XPCOM_CURRENT_PROCESS_DIR
+ // This will be set if a directory was passed to NS_InitXPCOM
+ // 2. If that doesn't work, set it to be the current process directory
+ nsCOMPtr<nsIProperties>
+ directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = directoryService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(mMozBinDirectory));
+ if (NS_FAILED(rv)) {
+ rv = directoryService->Get(NS_OS_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(mMozBinDirectory));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ nsCOMPtr<nsIFile> aFile;
+ rv = mMozBinDirectory->Clone(getter_AddRefs(aFile));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ NS_IF_ADDREF(*aLocalFile = aFile);
+ return NS_OK;
+}
+
+
+//----------------------------------------------------------------------------------------
+// GetProductDirectory - Gets the directory which contains the application data folder
+//
+// UNIX : ~/.mozilla/
+// WIN : <Application Data folder on user's machine>\Mozilla
+// Mac : :Documents:Mozilla:
+//----------------------------------------------------------------------------------------
+nsresult
+nsAppFileLocationProvider::GetProductDirectory(nsIFile** aLocalFile,
+ bool aLocal)
+{
+ if (NS_WARN_IF(!aLocalFile)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult rv;
+ bool exists;
+ nsCOMPtr<nsIFile> localDir;
+
+#if defined(MOZ_WIDGET_COCOA)
+ FSRef fsRef;
+ OSType folderType = aLocal ? (OSType)kCachedDataFolderType :
+ (OSType)kDomainLibraryFolderType;
+ OSErr err = ::FSFindFolder(kUserDomain, folderType, kCreateFolder, &fsRef);
+ if (err) {
+ return NS_ERROR_FAILURE;
+ }
+ NS_NewLocalFile(EmptyString(), true, getter_AddRefs(localDir));
+ if (!localDir) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsILocalFileMac> localDirMac(do_QueryInterface(localDir));
+ rv = localDirMac->InitWithFSRef(&fsRef);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+#elif defined(XP_WIN)
+ nsCOMPtr<nsIProperties> directoryService =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ const char* prop = aLocal ? NS_WIN_LOCAL_APPDATA_DIR : NS_WIN_APPDATA_DIR;
+ rv = directoryService->Get(prop, NS_GET_IID(nsIFile), getter_AddRefs(localDir));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+#elif defined(XP_UNIX)
+ rv = NS_NewNativeLocalFile(nsDependentCString(PR_GetEnv("HOME")), true,
+ getter_AddRefs(localDir));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+#else
+#error dont_know_how_to_get_product_dir_on_your_platform
+#endif
+
+ rv = localDir->AppendRelativeNativePath(DEFAULT_PRODUCT_DIR);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = localDir->Exists(&exists);
+
+ if (NS_SUCCEEDED(rv) && !exists) {
+ rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ localDir.forget(aLocalFile);
+
+ return rv;
+}
+
+
+//----------------------------------------------------------------------------------------
+// GetDefaultUserProfileRoot - Gets the directory which contains each user profile dir
+//
+// UNIX : ~/.mozilla/
+// WIN : <Application Data folder on user's machine>\Mozilla\Profiles
+// Mac : :Documents:Mozilla:Profiles:
+//----------------------------------------------------------------------------------------
+nsresult
+nsAppFileLocationProvider::GetDefaultUserProfileRoot(nsIFile** aLocalFile,
+ bool aLocal)
+{
+ if (NS_WARN_IF(!aLocalFile)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> localDir;
+
+ rv = GetProductDirectory(getter_AddRefs(localDir), aLocal);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+#if defined(MOZ_WIDGET_COCOA) || defined(XP_WIN)
+ // These 3 platforms share this part of the path - do them as one
+ rv = localDir->AppendRelativeNativePath(NS_LITERAL_CSTRING("Profiles"));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool exists;
+ rv = localDir->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists) {
+ rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+#endif
+
+ localDir.forget(aLocalFile);
+
+ return rv;
+}
+
+//*****************************************************************************
+// nsAppFileLocationProvider::nsIDirectoryServiceProvider2
+//*****************************************************************************
+
+class nsAppDirectoryEnumerator : public nsISimpleEnumerator
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ /**
+ * aKeyList is a null-terminated list of properties which are provided by aProvider
+ * They do not need to be publicly defined keys.
+ */
+ nsAppDirectoryEnumerator(nsIDirectoryServiceProvider* aProvider,
+ const char* aKeyList[]) :
+ mProvider(aProvider),
+ mCurrentKey(aKeyList)
+ {
+ }
+
+ NS_IMETHOD HasMoreElements(bool* aResult) override
+ {
+ while (!mNext && *mCurrentKey) {
+ bool dontCare;
+ nsCOMPtr<nsIFile> testFile;
+ (void)mProvider->GetFile(*mCurrentKey++, &dontCare, getter_AddRefs(testFile));
+ // Don't return a file which does not exist.
+ bool exists;
+ if (testFile && NS_SUCCEEDED(testFile->Exists(&exists)) && exists) {
+ mNext = testFile;
+ }
+ }
+ *aResult = mNext != nullptr;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetNext(nsISupports** aResult) override
+ {
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aResult = nullptr;
+
+ bool hasMore;
+ HasMoreElements(&hasMore);
+ if (!hasMore) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = mNext;
+ NS_IF_ADDREF(*aResult);
+ mNext = nullptr;
+
+ return *aResult ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+protected:
+ nsCOMPtr<nsIDirectoryServiceProvider> mProvider;
+ const char** mCurrentKey;
+ nsCOMPtr<nsIFile> mNext;
+
+ // Virtual destructor since subclass nsPathsDirectoryEnumerator
+ // does not re-implement Release()
+ virtual ~nsAppDirectoryEnumerator()
+ {
+ }
+};
+
+NS_IMPL_ISUPPORTS(nsAppDirectoryEnumerator, nsISimpleEnumerator)
+
+/* nsPathsDirectoryEnumerator and PATH_SEPARATOR
+ * are not used on MacOS/X. */
+
+#if defined(XP_WIN) /* Win32 */
+#define PATH_SEPARATOR ';'
+#else
+#define PATH_SEPARATOR ':'
+#endif
+
+class nsPathsDirectoryEnumerator final
+ : public nsAppDirectoryEnumerator
+{
+ ~nsPathsDirectoryEnumerator() {}
+
+public:
+ /**
+ * aKeyList is a null-terminated list.
+ * The first element is a path list.
+ * The remainder are properties provided by aProvider.
+ * They do not need to be publicly defined keys.
+ */
+ nsPathsDirectoryEnumerator(nsIDirectoryServiceProvider* aProvider,
+ const char* aKeyList[]) :
+ nsAppDirectoryEnumerator(aProvider, aKeyList + 1),
+ mEndPath(aKeyList[0])
+ {
+ }
+
+ NS_IMETHOD HasMoreElements(bool* aResult)
+ {
+ if (mEndPath)
+ while (!mNext && *mEndPath) {
+ const char* pathVar = mEndPath;
+
+ // skip PATH_SEPARATORs at the begining of the mEndPath
+ while (*pathVar == PATH_SEPARATOR) {
+ ++pathVar;
+ }
+
+ do {
+ ++mEndPath;
+ } while (*mEndPath && *mEndPath != PATH_SEPARATOR);
+
+ nsCOMPtr<nsIFile> localFile;
+ NS_NewNativeLocalFile(Substring(pathVar, mEndPath),
+ true,
+ getter_AddRefs(localFile));
+ if (*mEndPath == PATH_SEPARATOR) {
+ ++mEndPath;
+ }
+ // Don't return a "file" (directory) which does not exist.
+ bool exists;
+ if (localFile &&
+ NS_SUCCEEDED(localFile->Exists(&exists)) &&
+ exists) {
+ mNext = localFile;
+ }
+ }
+ if (mNext) {
+ *aResult = true;
+ } else {
+ nsAppDirectoryEnumerator::HasMoreElements(aResult);
+ }
+
+ return NS_OK;
+ }
+
+protected:
+ const char* mEndPath;
+};
+
+NS_IMETHODIMP
+nsAppFileLocationProvider::GetFiles(const char* aProp,
+ nsISimpleEnumerator** aResult)
+{
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aResult = nullptr;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (!nsCRT::strcmp(aProp, NS_APP_PLUGINS_DIR_LIST)) {
+#ifdef MOZ_WIDGET_COCOA
+ // As of Java for Mac OS X 10.5 Update 10, Apple has (in effect) deprecated Java Plugin2 on
+ // on OS X 10.5, and removed the soft link to it from /Library/Internet Plug-Ins/. Java
+ // Plugin2 is still present and usable, but there are no longer any links to it in the
+ // "normal" locations. So we won't be able to find it unless we look in the "non-normal"
+ // location where it actually is. Safari can use the WebKit-specific JavaPluginCocoa.bundle,
+ // which (of course) is still fully supported on OS X 10.5. But we have no alternative to
+ // using Java Plugin2. For more information see bug 668639.
+ static const char* keys[] = {
+ NS_APP_PLUGINS_DIR,
+ NS_MACOSX_USER_PLUGIN_DIR,
+ NS_MACOSX_LOCAL_PLUGIN_DIR,
+ IsOSXLeopard() ? NS_MACOSX_JAVA2_PLUGIN_DIR : nullptr,
+ nullptr
+ };
+ *aResult = new nsAppDirectoryEnumerator(this, keys);
+#else
+#ifdef XP_UNIX
+ static const char* keys[] = { nullptr, NS_USER_PLUGINS_DIR, NS_APP_PLUGINS_DIR, NS_SYSTEM_PLUGINS_DIR, nullptr };
+#else
+ static const char* keys[] = { nullptr, NS_USER_PLUGINS_DIR, NS_APP_PLUGINS_DIR, nullptr };
+#endif
+ if (!keys[0] && !(keys[0] = PR_GetEnv("MOZ_PLUGIN_PATH"))) {
+ static const char nullstr = 0;
+ keys[0] = &nullstr;
+ }
+ *aResult = new nsPathsDirectoryEnumerator(this, keys);
+#endif
+ NS_ADDREF(*aResult);
+ rv = NS_OK;
+ }
+ if (!nsCRT::strcmp(aProp, NS_APP_SEARCH_DIR_LIST)) {
+ static const char* keys[] = { nullptr, NS_APP_USER_SEARCH_DIR, nullptr };
+ if (!keys[0] && !(keys[0] = PR_GetEnv("MOZ_SEARCH_ENGINE_PATH"))) {
+ static const char nullstr = 0;
+ keys[0] = &nullstr;
+ }
+ *aResult = new nsPathsDirectoryEnumerator(this, keys);
+ NS_ADDREF(*aResult);
+ rv = NS_OK;
+ }
+ if (!strcmp(aProp, NS_APP_DISTRIBUTION_SEARCH_DIR_LIST)) {
+ return NS_NewEmptyEnumerator(aResult);
+ }
+ return rv;
+}
+
+#if defined(MOZ_WIDGET_COCOA)
+bool
+nsAppFileLocationProvider::IsOSXLeopard()
+{
+ static SInt32 version = 0;
+
+ if (!version) {
+ OSErr err = ::Gestalt(gestaltSystemVersion, &version);
+ if (err != noErr) {
+ version = 0;
+ } else {
+ version &= 0xFFFF; // The system version is in the low order word
+ }
+ }
+
+ return ((version >= 0x1050) && (version < 0x1060));
+}
+#endif
diff --git a/xpcom/io/nsAppFileLocationProvider.h b/xpcom/io/nsAppFileLocationProvider.h
new file mode 100644
index 000000000..248c5b828
--- /dev/null
+++ b/xpcom/io/nsAppFileLocationProvider.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAppFileLocationProvider_h
+#define nsAppFileLocationProvider_h
+
+#include "nsIDirectoryService.h"
+#include "nsIFile.h"
+#include "mozilla/Attributes.h"
+
+class nsIFile;
+
+//*****************************************************************************
+// class nsAppFileLocationProvider
+//*****************************************************************************
+
+class nsAppFileLocationProvider final : public nsIDirectoryServiceProvider2
+{
+public:
+ nsAppFileLocationProvider();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER2
+
+private:
+ ~nsAppFileLocationProvider()
+ {
+ }
+
+protected:
+ nsresult CloneMozBinDirectory(nsIFile** aLocalFile);
+ /**
+ * Get the product directory. This is a user-specific directory for storing
+ * application settings (e.g. the Application Data directory on windows
+ * systems).
+ * @param aLocal If true, should try to get a directory that is only stored
+ * locally (ie not transferred with roaming profiles)
+ */
+ nsresult GetProductDirectory(nsIFile** aLocalFile,
+ bool aLocal = false);
+ nsresult GetDefaultUserProfileRoot(nsIFile** aLocalFile,
+ bool aLocal = false);
+
+#if defined(MOZ_WIDGET_COCOA)
+ static bool IsOSXLeopard();
+#endif
+
+ nsCOMPtr<nsIFile> mMozBinDirectory;
+};
+
+#endif
diff --git a/xpcom/io/nsBinaryStream.cpp b/xpcom/io/nsBinaryStream.cpp
new file mode 100644
index 000000000..82159952e
--- /dev/null
+++ b/xpcom/io/nsBinaryStream.cpp
@@ -0,0 +1,1016 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file contains implementations of the nsIBinaryInputStream and
+ * nsIBinaryOutputStream interfaces. Together, these interfaces allows reading
+ * and writing of primitive data types (integers, floating-point values,
+ * booleans, etc.) to a stream in a binary, untagged, fixed-endianness format.
+ * This might be used, for example, to implement network protocols or to
+ * produce architecture-neutral binary disk files, i.e. ones that can be read
+ * and written by both big-endian and little-endian platforms. Output is
+ * written in big-endian order (high-order byte first), as this is traditional
+ * network order.
+ *
+ * @See nsIBinaryInputStream
+ * @See nsIBinaryOutputStream
+ */
+#include <algorithm>
+#include <string.h>
+
+#include "nsBinaryStream.h"
+
+#include "mozilla/EndianUtils.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsCRT.h"
+#include "nsString.h"
+#include "nsISerializable.h"
+#include "nsIClassInfo.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIURI.h" // for NS_IURI_IID
+#include "nsIX509Cert.h" // for NS_IX509CERT_IID
+
+#include "jsfriendapi.h"
+
+using mozilla::MakeUnique;
+using mozilla::PodCopy;
+using mozilla::UniquePtr;
+
+NS_IMPL_ISUPPORTS(nsBinaryOutputStream,
+ nsIObjectOutputStream,
+ nsIBinaryOutputStream,
+ nsIOutputStream)
+
+NS_IMETHODIMP
+nsBinaryOutputStream::Flush()
+{
+ if (NS_WARN_IF(!mOutputStream)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return mOutputStream->Flush();
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::Close()
+{
+ if (NS_WARN_IF(!mOutputStream)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return mOutputStream->Close();
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::Write(const char* aBuf, uint32_t aCount,
+ uint32_t* aActualBytes)
+{
+ if (NS_WARN_IF(!mOutputStream)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return mOutputStream->Write(aBuf, aCount, aActualBytes);
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::WriteFrom(nsIInputStream* aInStr, uint32_t aCount,
+ uint32_t* aResult)
+{
+ NS_NOTREACHED("WriteFrom");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* aResult)
+{
+ NS_NOTREACHED("WriteSegments");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::IsNonBlocking(bool* aNonBlocking)
+{
+ if (NS_WARN_IF(!mOutputStream)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return mOutputStream->IsNonBlocking(aNonBlocking);
+}
+
+nsresult
+nsBinaryOutputStream::WriteFully(const char* aBuf, uint32_t aCount)
+{
+ if (NS_WARN_IF(!mOutputStream)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv;
+ uint32_t bytesWritten;
+
+ rv = mOutputStream->Write(aBuf, aCount, &bytesWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (bytesWritten != aCount) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::SetOutputStream(nsIOutputStream* aOutputStream)
+{
+ if (NS_WARN_IF(!aOutputStream)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ mOutputStream = aOutputStream;
+ mBufferAccess = do_QueryInterface(aOutputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::WriteBoolean(bool aBoolean)
+{
+ return Write8(aBoolean);
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::Write8(uint8_t aByte)
+{
+ return WriteFully((const char*)&aByte, sizeof(aByte));
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::Write16(uint16_t aNum)
+{
+ aNum = mozilla::NativeEndian::swapToBigEndian(aNum);
+ return WriteFully((const char*)&aNum, sizeof(aNum));
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::Write32(uint32_t aNum)
+{
+ aNum = mozilla::NativeEndian::swapToBigEndian(aNum);
+ return WriteFully((const char*)&aNum, sizeof(aNum));
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::Write64(uint64_t aNum)
+{
+ nsresult rv;
+ uint32_t bytesWritten;
+
+ aNum = mozilla::NativeEndian::swapToBigEndian(aNum);
+ rv = Write(reinterpret_cast<char*>(&aNum), sizeof(aNum), &bytesWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (bytesWritten != sizeof(aNum)) {
+ return NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::WriteFloat(float aFloat)
+{
+ NS_ASSERTION(sizeof(float) == sizeof(uint32_t),
+ "False assumption about sizeof(float)");
+ return Write32(*reinterpret_cast<uint32_t*>(&aFloat));
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::WriteDouble(double aDouble)
+{
+ NS_ASSERTION(sizeof(double) == sizeof(uint64_t),
+ "False assumption about sizeof(double)");
+ return Write64(*reinterpret_cast<uint64_t*>(&aDouble));
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::WriteStringZ(const char* aString)
+{
+ uint32_t length;
+ nsresult rv;
+
+ length = strlen(aString);
+ rv = Write32(length);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return WriteFully(aString, length);
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::WriteWStringZ(const char16_t* aString)
+{
+ uint32_t length, byteCount;
+ nsresult rv;
+
+ length = NS_strlen(aString);
+ rv = Write32(length);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (length == 0) {
+ return NS_OK;
+ }
+ byteCount = length * sizeof(char16_t);
+
+#ifdef IS_BIG_ENDIAN
+ rv = WriteBytes(reinterpret_cast<const char*>(aString), byteCount);
+#else
+ // XXX use WriteSegments here to avoid copy!
+ char16_t* copy;
+ char16_t temp[64];
+ if (length <= 64) {
+ copy = temp;
+ } else {
+ copy = reinterpret_cast<char16_t*>(malloc(byteCount));
+ if (!copy) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ NS_ASSERTION((uintptr_t(aString) & 0x1) == 0, "aString not properly aligned");
+ mozilla::NativeEndian::copyAndSwapToBigEndian(copy, aString, length);
+ rv = WriteBytes(reinterpret_cast<const char*>(copy), byteCount);
+ if (copy != temp) {
+ free(copy);
+ }
+#endif
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::WriteUtf8Z(const char16_t* aString)
+{
+ return WriteStringZ(NS_ConvertUTF16toUTF8(aString).get());
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::WriteBytes(const char* aString, uint32_t aLength)
+{
+ nsresult rv;
+ uint32_t bytesWritten;
+
+ rv = Write(aString, aLength, &bytesWritten);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (bytesWritten != aLength) {
+ return NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::WriteByteArray(uint8_t* aBytes, uint32_t aLength)
+{
+ return WriteBytes(reinterpret_cast<char*>(aBytes), aLength);
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::WriteObject(nsISupports* aObject, bool aIsStrongRef)
+{
+ return WriteCompoundObject(aObject, NS_GET_IID(nsISupports),
+ aIsStrongRef);
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::WriteSingleRefObject(nsISupports* aObject)
+{
+ return WriteCompoundObject(aObject, NS_GET_IID(nsISupports),
+ true);
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::WriteCompoundObject(nsISupports* aObject,
+ const nsIID& aIID,
+ bool aIsStrongRef)
+{
+ nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(aObject);
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(aObject);
+
+ // Can't deal with weak refs
+ if (NS_WARN_IF(!aIsStrongRef)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (NS_WARN_IF(!classInfo) || NS_WARN_IF(!serializable)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCID cid;
+ nsresult rv = classInfo->GetClassIDNoAlloc(&cid);
+ if (NS_SUCCEEDED(rv)) {
+ rv = WriteID(cid);
+ } else {
+ nsCID* cidptr = nullptr;
+ rv = classInfo->GetClassID(&cidptr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = WriteID(*cidptr);
+
+ free(cidptr);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = WriteID(aIID);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return serializable->Write(this);
+}
+
+NS_IMETHODIMP
+nsBinaryOutputStream::WriteID(const nsIID& aIID)
+{
+ nsresult rv = Write32(aIID.m0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = Write16(aIID.m1);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = Write16(aIID.m2);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ for (int i = 0; i < 8; ++i) {
+ rv = Write8(aIID.m3[i]);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(char*)
+nsBinaryOutputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask)
+{
+ if (mBufferAccess) {
+ return mBufferAccess->GetBuffer(aLength, aAlignMask);
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP_(void)
+nsBinaryOutputStream::PutBuffer(char* aBuffer, uint32_t aLength)
+{
+ if (mBufferAccess) {
+ mBufferAccess->PutBuffer(aBuffer, aLength);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsBinaryInputStream,
+ nsIObjectInputStream,
+ nsIBinaryInputStream,
+ nsIInputStream)
+
+NS_IMETHODIMP
+nsBinaryInputStream::Available(uint64_t* aResult)
+{
+ if (NS_WARN_IF(!mInputStream)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return mInputStream->Available(aResult);
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aNumRead)
+{
+ if (NS_WARN_IF(!mInputStream)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // mInputStream might give us short reads, so deal with that.
+ uint32_t totalRead = 0;
+
+ uint32_t bytesRead;
+ do {
+ nsresult rv = mInputStream->Read(aBuffer, aCount, &bytesRead);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK && totalRead != 0) {
+ // We already read some data. Return it.
+ break;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ totalRead += bytesRead;
+ aBuffer += bytesRead;
+ aCount -= bytesRead;
+ } while (aCount != 0 && bytesRead != 0);
+
+ *aNumRead = totalRead;
+
+ return NS_OK;
+}
+
+
+// when forwarding ReadSegments to mInputStream, we need to make sure
+// 'this' is being passed to the writer each time. To do this, we need
+// a thunking function which keeps the real input stream around.
+
+// the closure wrapper
+struct MOZ_STACK_CLASS ReadSegmentsClosure
+{
+ nsCOMPtr<nsIInputStream> mRealInputStream;
+ void* mRealClosure;
+ nsWriteSegmentFun mRealWriter;
+ nsresult mRealResult;
+ uint32_t mBytesRead; // to properly implement aToOffset
+};
+
+// the thunking function
+static nsresult
+ReadSegmentForwardingThunk(nsIInputStream* aStream,
+ void* aClosure,
+ const char* aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ ReadSegmentsClosure* thunkClosure =
+ reinterpret_cast<ReadSegmentsClosure*>(aClosure);
+
+ NS_ASSERTION(NS_SUCCEEDED(thunkClosure->mRealResult),
+ "How did this get to be a failure status?");
+
+ thunkClosure->mRealResult =
+ thunkClosure->mRealWriter(thunkClosure->mRealInputStream,
+ thunkClosure->mRealClosure,
+ aFromSegment,
+ thunkClosure->mBytesRead + aToOffset,
+ aCount, aWriteCount);
+
+ return thunkClosure->mRealResult;
+}
+
+
+NS_IMETHODIMP
+nsBinaryInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult)
+{
+ if (NS_WARN_IF(!mInputStream)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ ReadSegmentsClosure thunkClosure = { this, aClosure, aWriter, NS_OK, 0 };
+
+ // mInputStream might give us short reads, so deal with that.
+ uint32_t bytesRead;
+ do {
+ nsresult rv = mInputStream->ReadSegments(ReadSegmentForwardingThunk,
+ &thunkClosure,
+ aCount, &bytesRead);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK && thunkClosure.mBytesRead != 0) {
+ // We already read some data. Return it.
+ break;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ thunkClosure.mBytesRead += bytesRead;
+ aCount -= bytesRead;
+ } while (aCount != 0 && bytesRead != 0 &&
+ NS_SUCCEEDED(thunkClosure.mRealResult));
+
+ *aResult = thunkClosure.mBytesRead;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::IsNonBlocking(bool* aNonBlocking)
+{
+ if (NS_WARN_IF(!mInputStream)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return mInputStream->IsNonBlocking(aNonBlocking);
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::Close()
+{
+ if (NS_WARN_IF(!mInputStream)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ return mInputStream->Close();
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::SetInputStream(nsIInputStream* aInputStream)
+{
+ if (NS_WARN_IF(!aInputStream)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ mInputStream = aInputStream;
+ mBufferAccess = do_QueryInterface(aInputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::ReadBoolean(bool* aBoolean)
+{
+ uint8_t byteResult;
+ nsresult rv = Read8(&byteResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aBoolean = !!byteResult;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::Read8(uint8_t* aByte)
+{
+ nsresult rv;
+ uint32_t bytesRead;
+
+ rv = Read(reinterpret_cast<char*>(aByte), sizeof(*aByte), &bytesRead);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (bytesRead != 1) {
+ return NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::Read16(uint16_t* aNum)
+{
+ uint32_t bytesRead;
+ nsresult rv = Read(reinterpret_cast<char*>(aNum), sizeof(*aNum), &bytesRead);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (bytesRead != sizeof(*aNum)) {
+ return NS_ERROR_FAILURE;
+ }
+ *aNum = mozilla::NativeEndian::swapFromBigEndian(*aNum);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::Read32(uint32_t* aNum)
+{
+ uint32_t bytesRead;
+ nsresult rv = Read(reinterpret_cast<char*>(aNum), sizeof(*aNum), &bytesRead);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (bytesRead != sizeof(*aNum)) {
+ return NS_ERROR_FAILURE;
+ }
+ *aNum = mozilla::NativeEndian::swapFromBigEndian(*aNum);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::Read64(uint64_t* aNum)
+{
+ uint32_t bytesRead;
+ nsresult rv = Read(reinterpret_cast<char*>(aNum), sizeof(*aNum), &bytesRead);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (bytesRead != sizeof(*aNum)) {
+ return NS_ERROR_FAILURE;
+ }
+ *aNum = mozilla::NativeEndian::swapFromBigEndian(*aNum);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::ReadFloat(float* aFloat)
+{
+ NS_ASSERTION(sizeof(float) == sizeof(uint32_t),
+ "False assumption about sizeof(float)");
+ return Read32(reinterpret_cast<uint32_t*>(aFloat));
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::ReadDouble(double* aDouble)
+{
+ NS_ASSERTION(sizeof(double) == sizeof(uint64_t),
+ "False assumption about sizeof(double)");
+ return Read64(reinterpret_cast<uint64_t*>(aDouble));
+}
+
+static nsresult
+WriteSegmentToCString(nsIInputStream* aStream,
+ void* aClosure,
+ const char* aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ nsACString* outString = static_cast<nsACString*>(aClosure);
+
+ outString->Append(aFromSegment, aCount);
+
+ *aWriteCount = aCount;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::ReadCString(nsACString& aString)
+{
+ nsresult rv;
+ uint32_t length, bytesRead;
+
+ rv = Read32(&length);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ aString.Truncate();
+ rv = ReadSegments(WriteSegmentToCString, &aString, length, &bytesRead);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (bytesRead != length) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+
+// sometimes, WriteSegmentToString will be handed an odd-number of
+// bytes, which means we only have half of the last char16_t
+struct WriteStringClosure
+{
+ char16_t* mWriteCursor;
+ bool mHasCarryoverByte;
+ char mCarryoverByte;
+};
+
+// there are a few cases we have to account for here:
+// * even length buffer, no carryover - easy, just append
+// * odd length buffer, no carryover - the last byte needs to be saved
+// for carryover
+// * odd length buffer, with carryover - first byte needs to be used
+// with the carryover byte, and
+// the rest of the even length
+// buffer is appended as normal
+// * even length buffer, with carryover - the first byte needs to be
+// used with the previous carryover byte.
+// this gives you an odd length buffer,
+// so you have to save the last byte for
+// the next carryover
+
+
+// same version of the above, but with correct casting and endian swapping
+static nsresult
+WriteSegmentToString(nsIInputStream* aStream,
+ void* aClosure,
+ const char* aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ NS_PRECONDITION(aCount > 0, "Why are we being told to write 0 bytes?");
+ NS_PRECONDITION(sizeof(char16_t) == 2, "We can't handle other sizes!");
+
+ WriteStringClosure* closure = static_cast<WriteStringClosure*>(aClosure);
+ char16_t* cursor = closure->mWriteCursor;
+
+ // we're always going to consume the whole buffer no matter what
+ // happens, so take care of that right now.. that allows us to
+ // tweak aCount later. Do NOT move this!
+ *aWriteCount = aCount;
+
+ // if the last Write had an odd-number of bytes read, then
+ if (closure->mHasCarryoverByte) {
+ // re-create the two-byte sequence we want to work with
+ char bytes[2] = { closure->mCarryoverByte, *aFromSegment };
+ *cursor = *(char16_t*)bytes;
+ // Now the little endianness dance
+ mozilla::NativeEndian::swapToBigEndianInPlace(cursor, 1);
+ ++cursor;
+
+ // now skip past the first byte of the buffer.. code from here
+ // can assume normal operations, but should not assume aCount
+ // is relative to the ORIGINAL buffer
+ ++aFromSegment;
+ --aCount;
+
+ closure->mHasCarryoverByte = false;
+ }
+
+ // this array is possibly unaligned... be careful how we access it!
+ const char16_t* unicodeSegment =
+ reinterpret_cast<const char16_t*>(aFromSegment);
+
+ // calculate number of full characters in segment (aCount could be odd!)
+ uint32_t segmentLength = aCount / sizeof(char16_t);
+
+ // copy all data into our aligned buffer. byte swap if necessary.
+ // cursor may be unaligned, so we cannot use copyAndSwapToBigEndian directly
+ memcpy(cursor, unicodeSegment, segmentLength * sizeof(char16_t));
+ char16_t* end = cursor + segmentLength;
+ mozilla::NativeEndian::swapToBigEndianInPlace(cursor, segmentLength);
+ closure->mWriteCursor = end;
+
+ // remember this is the modifed aCount and aFromSegment,
+ // so that will take into account the fact that we might have
+ // skipped the first byte in the buffer
+ if (aCount % sizeof(char16_t) != 0) {
+ // we must have had a carryover byte, that we'll need the next
+ // time around
+ closure->mCarryoverByte = aFromSegment[aCount - 1];
+ closure->mHasCarryoverByte = true;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsBinaryInputStream::ReadString(nsAString& aString)
+{
+ nsresult rv;
+ uint32_t length, bytesRead;
+
+ rv = Read32(&length);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (length == 0) {
+ aString.Truncate();
+ return NS_OK;
+ }
+
+ // pre-allocate output buffer, and get direct access to buffer...
+ if (!aString.SetLength(length, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsAString::iterator start;
+ aString.BeginWriting(start);
+
+ WriteStringClosure closure;
+ closure.mWriteCursor = start.get();
+ closure.mHasCarryoverByte = false;
+
+ rv = ReadSegments(WriteSegmentToString, &closure,
+ length * sizeof(char16_t), &bytesRead);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ NS_ASSERTION(!closure.mHasCarryoverByte, "some strange stream corruption!");
+
+ if (bytesRead != length * sizeof(char16_t)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::ReadBytes(uint32_t aLength, char** aResult)
+{
+ nsresult rv;
+ uint32_t bytesRead;
+ char* s;
+
+ s = reinterpret_cast<char*>(malloc(aLength));
+ if (!s) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = Read(s, aLength, &bytesRead);
+ if (NS_FAILED(rv)) {
+ free(s);
+ return rv;
+ }
+ if (bytesRead != aLength) {
+ free(s);
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = s;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::ReadByteArray(uint32_t aLength, uint8_t** aResult)
+{
+ return ReadBytes(aLength, reinterpret_cast<char**>(aResult));
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::ReadArrayBuffer(uint32_t aLength,
+ JS::Handle<JS::Value> aBuffer,
+ JSContext* aCx, uint32_t* aReadLength)
+{
+ if (!aBuffer.isObject()) {
+ return NS_ERROR_FAILURE;
+ }
+ JS::RootedObject buffer(aCx, &aBuffer.toObject());
+ if (!JS_IsArrayBufferObject(buffer)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t bufferLength = JS_GetArrayBufferByteLength(buffer);
+ if (bufferLength < aLength) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t bufSize = std::min<uint32_t>(aLength, 4096);
+ UniquePtr<char[]> buf = MakeUnique<char[]>(bufSize);
+
+ uint32_t pos = 0;
+ *aReadLength = 0;
+ do {
+ // Read data into temporary buffer.
+ uint32_t bytesRead;
+ uint32_t amount = std::min(aLength - pos, bufSize);
+ nsresult rv = Read(buf.get(), amount, &bytesRead);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ MOZ_ASSERT(bytesRead <= amount);
+
+ if (bytesRead == 0) {
+ break;
+ }
+
+ // Copy data into actual buffer.
+
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ if (bufferLength != JS_GetArrayBufferByteLength(buffer)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ char* data = reinterpret_cast<char*>(JS_GetArrayBufferData(buffer, &isShared, nogc));
+ MOZ_ASSERT(!isShared); // Implied by JS_GetArrayBufferData()
+ if (!data) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aReadLength += bytesRead;
+ PodCopy(data + pos, buf.get(), bytesRead);
+
+ pos += bytesRead;
+ } while (pos < aLength);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::ReadObject(bool aIsStrongRef, nsISupports** aObject)
+{
+ nsCID cid;
+ nsIID iid;
+ nsresult rv = ReadID(&cid);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = ReadID(&iid);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // HACK: Intercept old (pre-gecko6) nsIURI IID, and replace with
+ // the updated IID, so that we're QI'ing to an actual interface.
+ // (As soon as we drop support for upgrading from pre-gecko6, we can
+ // remove this chunk.)
+ static const nsIID oldURIiid = {
+ 0x7a22cc0, 0xce5, 0x11d3,
+ { 0x93, 0x31, 0x0, 0x10, 0x4b, 0xa0, 0xfd, 0x40 }
+ };
+
+ // hackaround for bug 670542
+ static const nsIID oldURIiid2 = {
+ 0xd6d04c36, 0x0fa4, 0x4db3,
+ { 0xbe, 0x05, 0x4a, 0x18, 0x39, 0x71, 0x03, 0xe2 }
+ };
+
+ // hackaround for bug 682031
+ static const nsIID oldURIiid3 = {
+ 0x12120b20, 0x0929, 0x40e9,
+ { 0x88, 0xcf, 0x6e, 0x08, 0x76, 0x6e, 0x8b, 0x23 }
+ };
+
+ // hackaround for bug 1195415
+ static const nsIID oldURIiid4 = {
+ 0x395fe045, 0x7d18, 0x4adb,
+ { 0xa3, 0xfd, 0xaf, 0x98, 0xc8, 0xa1, 0xaf, 0x11 }
+ };
+
+ if (iid.Equals(oldURIiid) ||
+ iid.Equals(oldURIiid2) ||
+ iid.Equals(oldURIiid3) ||
+ iid.Equals(oldURIiid4)) {
+ const nsIID newURIiid = NS_IURI_IID;
+ iid = newURIiid;
+ }
+ // END HACK
+
+ // HACK: Service workers store resource security info on disk in the dom
+ // Cache API. When the uuid of the nsIX509Cert interface changes
+ // these serialized objects cannot be loaded any more. This hack
+ // works around this issue.
+
+ // hackaround for bug 1247580 (FF45 to FF46 transition)
+ static const nsIID oldCertIID = {
+ 0xf8ed8364, 0xced9, 0x4c6e,
+ { 0x86, 0xba, 0x48, 0xaf, 0x53, 0xc3, 0x93, 0xe6 }
+ };
+
+ if (iid.Equals(oldCertIID)) {
+ const nsIID newCertIID = NS_IX509CERT_IID;
+ iid = newCertIID;
+ }
+ // END HACK
+
+ nsCOMPtr<nsISupports> object = do_CreateInstance(cid, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISerializable> serializable = do_QueryInterface(object);
+ if (NS_WARN_IF(!serializable)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = serializable->Read(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return object->QueryInterface(iid, reinterpret_cast<void**>(aObject));
+}
+
+NS_IMETHODIMP
+nsBinaryInputStream::ReadID(nsID* aResult)
+{
+ nsresult rv = Read32(&aResult->m0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = Read16(&aResult->m1);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = Read16(&aResult->m2);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ for (int i = 0; i < 8; ++i) {
+ rv = Read8(&aResult->m3[i]);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(char*)
+nsBinaryInputStream::GetBuffer(uint32_t aLength, uint32_t aAlignMask)
+{
+ if (mBufferAccess) {
+ return mBufferAccess->GetBuffer(aLength, aAlignMask);
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP_(void)
+nsBinaryInputStream::PutBuffer(char* aBuffer, uint32_t aLength)
+{
+ if (mBufferAccess) {
+ mBufferAccess->PutBuffer(aBuffer, aLength);
+ }
+}
diff --git a/xpcom/io/nsBinaryStream.h b/xpcom/io/nsBinaryStream.h
new file mode 100644
index 000000000..2520d9201
--- /dev/null
+++ b/xpcom/io/nsBinaryStream.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsBinaryStream_h___
+#define nsBinaryStream_h___
+
+#include "nsCOMPtr.h"
+#include "nsAString.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIStreamBufferAccess.h"
+
+#define NS_BINARYOUTPUTSTREAM_CID \
+{ /* 86c37b9a-74e7-4672-844e-6e7dd83ba484 */ \
+ 0x86c37b9a, \
+ 0x74e7, \
+ 0x4672, \
+ {0x84, 0x4e, 0x6e, 0x7d, 0xd8, 0x3b, 0xa4, 0x84} \
+}
+
+#define NS_BINARYOUTPUTSTREAM_CONTRACTID "@mozilla.org/binaryoutputstream;1"
+
+// Derive from nsIObjectOutputStream so this class can be used as a superclass
+// by nsObjectOutputStream.
+class nsBinaryOutputStream final : public nsIObjectOutputStream
+{
+public:
+ nsBinaryOutputStream()
+ {
+ }
+
+protected:
+ // nsISupports methods
+ NS_DECL_ISUPPORTS
+
+ // nsIOutputStream methods
+ NS_DECL_NSIOUTPUTSTREAM
+
+ // nsIBinaryOutputStream methods
+ NS_DECL_NSIBINARYOUTPUTSTREAM
+
+ // nsIObjectOutputStream methods
+ NS_DECL_NSIOBJECTOUTPUTSTREAM
+
+ // Call Write(), ensuring that all proffered data is written
+ nsresult WriteFully(const char* aBuf, uint32_t aCount);
+
+ nsCOMPtr<nsIOutputStream> mOutputStream;
+ nsCOMPtr<nsIStreamBufferAccess> mBufferAccess;
+
+private:
+ // virtual dtor since subclasses call our Release()
+ virtual ~nsBinaryOutputStream()
+ {
+ }
+};
+
+#define NS_BINARYINPUTSTREAM_CID \
+{ /* c521a612-2aad-46db-b6ab-3b821fb150b1 */ \
+ 0xc521a612, \
+ 0x2aad, \
+ 0x46db, \
+ {0xb6, 0xab, 0x3b, 0x82, 0x1f, 0xb1, 0x50, 0xb1} \
+}
+
+#define NS_BINARYINPUTSTREAM_CONTRACTID "@mozilla.org/binaryinputstream;1"
+
+class nsBinaryInputStream final : public nsIObjectInputStream
+{
+public:
+ nsBinaryInputStream()
+ {
+ }
+
+protected:
+ // nsISupports methods
+ NS_DECL_ISUPPORTS
+
+ // nsIInputStream methods
+ NS_DECL_NSIINPUTSTREAM
+
+ // nsIBinaryInputStream methods
+ NS_DECL_NSIBINARYINPUTSTREAM
+
+ // nsIObjectInputStream methods
+ NS_DECL_NSIOBJECTINPUTSTREAM
+
+ nsCOMPtr<nsIInputStream> mInputStream;
+ nsCOMPtr<nsIStreamBufferAccess> mBufferAccess;
+
+private:
+ // virtual dtor since subclasses call our Release()
+ virtual ~nsBinaryInputStream()
+ {
+ }
+};
+
+#endif // nsBinaryStream_h___
diff --git a/xpcom/io/nsDirectoryService.cpp b/xpcom/io/nsDirectoryService.cpp
new file mode 100644
index 000000000..a4d962395
--- /dev/null
+++ b/xpcom/io/nsDirectoryService.cpp
@@ -0,0 +1,766 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsDirectoryService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsLocalFile.h"
+#include "nsDebug.h"
+#include "nsStaticAtom.h"
+#include "nsEnumeratorUtils.h"
+
+#include "nsICategoryManager.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIStringEnumerator.h"
+
+#if defined(XP_WIN)
+#include <windows.h>
+#include <shlobj.h>
+#include <stdlib.h>
+#include <stdio.h>
+#elif defined(XP_UNIX)
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/param.h>
+#include "prenv.h"
+#ifdef MOZ_WIDGET_COCOA
+#include <CoreServices/CoreServices.h>
+#include <Carbon/Carbon.h>
+#endif
+#endif
+
+#include "SpecialSystemDirectory.h"
+#include "nsAppFileLocationProvider.h"
+
+using namespace mozilla;
+
+// define home directory
+// For Windows platform, We are choosing Appdata folder as HOME
+#if defined (XP_WIN)
+#define HOME_DIR NS_WIN_APPDATA_DIR
+#elif defined (MOZ_WIDGET_COCOA)
+#define HOME_DIR NS_OSX_HOME_DIR
+#elif defined (XP_UNIX)
+#define HOME_DIR NS_UNIX_HOME_DIR
+#endif
+
+//----------------------------------------------------------------------------------------
+nsresult
+nsDirectoryService::GetCurrentProcessDirectory(nsIFile** aFile)
+//----------------------------------------------------------------------------------------
+{
+ if (NS_WARN_IF(!aFile)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aFile = nullptr;
+
+ // Set the component registry location:
+ if (!gService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+ nsCOMPtr<nsIProperties> dirService;
+ rv = nsDirectoryService::Create(nullptr,
+ NS_GET_IID(nsIProperties),
+ getter_AddRefs(dirService)); // needs to be around for life of product
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (dirService) {
+ nsCOMPtr<nsIFile> localFile;
+ dirService->Get(NS_XPCOM_INIT_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(localFile));
+ if (localFile) {
+ localFile.forget(aFile);
+ return NS_OK;
+ }
+ }
+
+ RefPtr<nsLocalFile> localFile = new nsLocalFile;
+
+#ifdef XP_WIN
+ wchar_t buf[MAX_PATH + 1];
+ SetLastError(ERROR_SUCCESS);
+ if (GetModuleFileNameW(0, buf, mozilla::ArrayLength(buf)) &&
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ // chop off the executable name by finding the rightmost backslash
+ wchar_t* lastSlash = wcsrchr(buf, L'\\');
+ if (lastSlash) {
+ *(lastSlash + 1) = L'\0';
+ }
+
+ localFile->InitWithPath(nsDependentString(buf));
+ localFile.forget(aFile);
+ return NS_OK;
+ }
+
+#elif defined(MOZ_WIDGET_COCOA)
+ // Works even if we're not bundled.
+ CFBundleRef appBundle = CFBundleGetMainBundle();
+ if (appBundle) {
+ CFURLRef bundleURL = CFBundleCopyExecutableURL(appBundle);
+ if (bundleURL) {
+ CFURLRef parentURL = CFURLCreateCopyDeletingLastPathComponent(
+ kCFAllocatorDefault, bundleURL);
+ if (parentURL) {
+ // Pass true for the "resolveAgainstBase" arg to CFURLGetFileSystemRepresentation.
+ // This will resolve the relative portion of the CFURL against it base, giving a full
+ // path, which CFURLCopyFileSystemPath doesn't do.
+ char buffer[PATH_MAX];
+ if (CFURLGetFileSystemRepresentation(parentURL, true,
+ (UInt8*)buffer, sizeof(buffer))) {
+#ifdef DEBUG_conrad
+ printf("nsDirectoryService - CurrentProcessDir is: %s\n", buffer);
+#endif
+ rv = localFile->InitWithNativePath(nsDependentCString(buffer));
+ if (NS_SUCCEEDED(rv)) {
+ localFile.forget(aFile);
+ }
+ }
+ CFRelease(parentURL);
+ }
+ CFRelease(bundleURL);
+ }
+ }
+
+ NS_ASSERTION(*aFile, "nsDirectoryService - Could not determine CurrentProcessDir.\n");
+ if (*aFile) {
+ return NS_OK;
+ }
+
+#elif defined(XP_UNIX)
+
+ // In the absence of a good way to get the executable directory let
+ // us try this for unix:
+ // - if MOZILLA_FIVE_HOME is defined, that is it
+ // - else give the current directory
+ char buf[MAXPATHLEN];
+
+ // The MOZ_DEFAULT_MOZILLA_FIVE_HOME variable can be set at configure time with
+ // a --with-default-mozilla-five-home=foo autoconf flag.
+ //
+ // The idea here is to allow for builds that have a default MOZILLA_FIVE_HOME
+ // regardless of the environment. This makes it easier to write apps that
+ // embed mozilla without having to worry about setting up the environment
+ //
+ // We do this by putenv()ing the default value into the environment. Note that
+ // we only do this if it is not already set.
+#ifdef MOZ_DEFAULT_MOZILLA_FIVE_HOME
+ const char* home = PR_GetEnv("MOZILLA_FIVE_HOME");
+ if (!home || !*home) {
+ putenv("MOZILLA_FIVE_HOME=" MOZ_DEFAULT_MOZILLA_FIVE_HOME);
+ }
+#endif
+
+ char* moz5 = PR_GetEnv("MOZILLA_FIVE_HOME");
+ if (moz5 && *moz5) {
+ if (realpath(moz5, buf)) {
+ localFile->InitWithNativePath(nsDependentCString(buf));
+ localFile.forget(aFile);
+ return NS_OK;
+ }
+ }
+#if defined(DEBUG)
+ static bool firstWarning = true;
+
+ if ((!moz5 || !*moz5) && firstWarning) {
+ // Warn that MOZILLA_FIVE_HOME not set, once.
+ printf("Warning: MOZILLA_FIVE_HOME not set.\n");
+ firstWarning = false;
+ }
+#endif /* DEBUG */
+
+ // Fall back to current directory.
+ if (getcwd(buf, sizeof(buf))) {
+ localFile->InitWithNativePath(nsDependentCString(buf));
+ localFile.forget(aFile);
+ return NS_OK;
+ }
+
+#endif
+
+ NS_ERROR("unable to get current process directory");
+ return NS_ERROR_FAILURE;
+} // GetCurrentProcessDirectory()
+
+StaticRefPtr<nsDirectoryService> nsDirectoryService::gService;
+
+nsDirectoryService::nsDirectoryService()
+ : mHashtable(128)
+{
+}
+
+nsresult
+nsDirectoryService::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult)
+{
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aOuter)) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ if (!gService) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ return gService->QueryInterface(aIID, aResult);
+}
+
+#define DIR_ATOM(name_, value_) nsIAtom* nsDirectoryService::name_ = nullptr;
+#include "nsDirectoryServiceAtomList.h"
+#undef DIR_ATOM
+
+#define DIR_ATOM(name_, value_) NS_STATIC_ATOM_BUFFER(name_##_buffer, value_)
+#include "nsDirectoryServiceAtomList.h"
+#undef DIR_ATOM
+
+static const nsStaticAtom directory_atoms[] = {
+#define DIR_ATOM(name_, value_) NS_STATIC_ATOM(name_##_buffer, &nsDirectoryService::name_),
+#include "nsDirectoryServiceAtomList.h"
+#undef DIR_ATOM
+};
+
+NS_IMETHODIMP
+nsDirectoryService::Init()
+{
+ NS_NOTREACHED("nsDirectoryService::Init() for internal use only!");
+ return NS_OK;
+}
+
+void
+nsDirectoryService::RealInit()
+{
+ NS_ASSERTION(!gService,
+ "nsDirectoryService::RealInit Mustn't initialize twice!");
+
+ gService = new nsDirectoryService();
+
+ NS_RegisterStaticAtoms(directory_atoms);
+
+ // Let the list hold the only reference to the provider.
+ nsAppFileLocationProvider* defaultProvider = new nsAppFileLocationProvider;
+ gService->mProviders.AppendElement(defaultProvider);
+}
+
+nsDirectoryService::~nsDirectoryService()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsDirectoryService,
+ nsIProperties,
+ nsIDirectoryService,
+ nsIDirectoryServiceProvider,
+ nsIDirectoryServiceProvider2)
+
+
+NS_IMETHODIMP
+nsDirectoryService::Undefine(const char* aProp)
+{
+ if (NS_WARN_IF(!aProp)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsDependentCString key(aProp);
+ if (!mHashtable.Get(key, nullptr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mHashtable.Remove(key);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryService::GetKeys(uint32_t* aCount, char*** aKeys)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+struct MOZ_STACK_CLASS FileData
+{
+ FileData(const char* aProperty, const nsIID& aUUID)
+ : property(aProperty)
+ , data(nullptr)
+ , persistent(true)
+ , uuid(aUUID)
+ {
+ }
+
+ const char* property;
+ nsCOMPtr<nsISupports> data;
+ bool persistent;
+ const nsIID& uuid;
+};
+
+static bool
+FindProviderFile(nsIDirectoryServiceProvider* aElement, FileData* aData)
+{
+ nsresult rv;
+ if (aData->uuid.Equals(NS_GET_IID(nsISimpleEnumerator))) {
+ // Not all providers implement this iface
+ nsCOMPtr<nsIDirectoryServiceProvider2> prov2 = do_QueryInterface(aElement);
+ if (prov2) {
+ nsCOMPtr<nsISimpleEnumerator> newFiles;
+ rv = prov2->GetFiles(aData->property, getter_AddRefs(newFiles));
+ if (NS_SUCCEEDED(rv) && newFiles) {
+ if (aData->data) {
+ nsCOMPtr<nsISimpleEnumerator> unionFiles;
+
+ NS_NewUnionEnumerator(getter_AddRefs(unionFiles),
+ (nsISimpleEnumerator*)aData->data.get(), newFiles);
+
+ if (unionFiles) {
+ unionFiles.swap(*(nsISimpleEnumerator**)&aData->data);
+ }
+ } else {
+ aData->data = newFiles;
+ }
+
+ aData->persistent = false; // Enumerators can never be persistent
+ return rv == NS_SUCCESS_AGGREGATE_RESULT;
+ }
+ }
+ } else {
+ rv = aElement->GetFile(aData->property, &aData->persistent,
+ (nsIFile**)&aData->data);
+ if (NS_SUCCEEDED(rv) && aData->data) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsDirectoryService::Get(const char* aProp, const nsIID& aUuid, void** aResult)
+{
+ if (NS_WARN_IF(!aProp)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsDependentCString key(aProp);
+
+ nsCOMPtr<nsIFile> cachedFile = mHashtable.Get(key);
+
+ if (cachedFile) {
+ nsCOMPtr<nsIFile> cloneFile;
+ cachedFile->Clone(getter_AddRefs(cloneFile));
+ return cloneFile->QueryInterface(aUuid, aResult);
+ }
+
+ // it is not one of our defaults, lets check any providers
+ FileData fileData(aProp, aUuid);
+
+ for (int32_t i = mProviders.Length() - 1; i >= 0; i--) {
+ if (!FindProviderFile(mProviders[i], &fileData)) {
+ break;
+ }
+ }
+ if (fileData.data) {
+ if (fileData.persistent) {
+ Set(aProp, static_cast<nsIFile*>(fileData.data.get()));
+ }
+ nsresult rv = (fileData.data)->QueryInterface(aUuid, aResult);
+ fileData.data = nullptr; // AddRef occurs in FindProviderFile()
+ return rv;
+ }
+
+ FindProviderFile(static_cast<nsIDirectoryServiceProvider*>(this), &fileData);
+ if (fileData.data) {
+ if (fileData.persistent) {
+ Set(aProp, static_cast<nsIFile*>(fileData.data.get()));
+ }
+ nsresult rv = (fileData.data)->QueryInterface(aUuid, aResult);
+ fileData.data = nullptr; // AddRef occurs in FindProviderFile()
+ return rv;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDirectoryService::Set(const char* aProp, nsISupports* aValue)
+{
+ if (NS_WARN_IF(!aProp)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsDependentCString key(aProp);
+ if (mHashtable.Get(key, nullptr) || !aValue) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> ourFile = do_QueryInterface(aValue);
+ if (ourFile) {
+ nsCOMPtr<nsIFile> cloneFile;
+ ourFile->Clone(getter_AddRefs(cloneFile));
+ mHashtable.Put(key, cloneFile);
+
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDirectoryService::Has(const char* aProp, bool* aResult)
+{
+ if (NS_WARN_IF(!aProp)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = false;
+ nsCOMPtr<nsIFile> value;
+ nsresult rv = Get(aProp, NS_GET_IID(nsIFile), getter_AddRefs(value));
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ if (value) {
+ *aResult = true;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDirectoryService::RegisterProvider(nsIDirectoryServiceProvider* aProv)
+{
+ if (!aProv) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mProviders.AppendElement(aProv);
+ return NS_OK;
+}
+
+void
+nsDirectoryService::RegisterCategoryProviders()
+{
+ nsCOMPtr<nsICategoryManager> catman
+ (do_GetService(NS_CATEGORYMANAGER_CONTRACTID));
+ if (!catman) {
+ return;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> entries;
+ catman->EnumerateCategory(XPCOM_DIRECTORY_PROVIDER_CATEGORY,
+ getter_AddRefs(entries));
+
+ nsCOMPtr<nsIUTF8StringEnumerator> strings(do_QueryInterface(entries));
+ if (!strings) {
+ return;
+ }
+
+ bool more;
+ while (NS_SUCCEEDED(strings->HasMore(&more)) && more) {
+ nsAutoCString entry;
+ strings->GetNext(entry);
+
+ nsXPIDLCString contractID;
+ catman->GetCategoryEntry(XPCOM_DIRECTORY_PROVIDER_CATEGORY, entry.get(),
+ getter_Copies(contractID));
+
+ if (contractID) {
+ nsCOMPtr<nsIDirectoryServiceProvider> provider = do_GetService(contractID.get());
+ if (provider) {
+ RegisterProvider(provider);
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDirectoryService::UnregisterProvider(nsIDirectoryServiceProvider* aProv)
+{
+ if (!aProv) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mProviders.RemoveElement(aProv);
+ return NS_OK;
+}
+
+#if defined(MOZ_CONTENT_SANDBOX) && defined(XP_WIN)
+static nsresult
+GetLowIntegrityTempBase(nsIFile** aLowIntegrityTempBase)
+{
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = GetSpecialSystemDirectory(Win_LocalAppdataLow,
+ getter_AddRefs(localFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = localFile->Append(NS_LITERAL_STRING(MOZ_USER_DIR));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ localFile.forget(aLowIntegrityTempBase);
+ return rv;
+}
+#endif
+
+// DO NOT ADD ANY LOCATIONS TO THIS FUNCTION UNTIL YOU TALK TO: dougt@netscape.com.
+// This is meant to be a place of xpcom or system specific file locations, not
+// application specific locations. If you need the later, register a callback for
+// your application.
+
+NS_IMETHODIMP
+nsDirectoryService::GetFile(const char* aProp, bool* aPersistent,
+ nsIFile** aResult)
+{
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ *aResult = nullptr;
+ *aPersistent = true;
+
+ nsCOMPtr<nsIAtom> inAtom = NS_Atomize(aProp);
+
+ // check to see if it is one of our defaults
+
+ if (inAtom == nsDirectoryService::sCurrentProcess ||
+ inAtom == nsDirectoryService::sOS_CurrentProcessDirectory) {
+ rv = GetCurrentProcessDirectory(getter_AddRefs(localFile));
+ }
+
+ // Unless otherwise set, the core pieces of the GRE exist
+ // in the current process directory.
+ else if (inAtom == nsDirectoryService::sGRE_Directory ||
+ inAtom == nsDirectoryService::sGRE_BinDirectory) {
+ rv = GetCurrentProcessDirectory(getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sOS_DriveDirectory) {
+ rv = GetSpecialSystemDirectory(OS_DriveDirectory, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sOS_TemporaryDirectory) {
+ rv = GetSpecialSystemDirectory(OS_TemporaryDirectory, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sOS_CurrentProcessDirectory) {
+ rv = GetSpecialSystemDirectory(OS_CurrentProcessDirectory, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sOS_CurrentWorkingDirectory) {
+ rv = GetSpecialSystemDirectory(OS_CurrentWorkingDirectory, getter_AddRefs(localFile));
+ }
+
+#if defined(MOZ_WIDGET_COCOA)
+ else if (inAtom == nsDirectoryService::sDirectory) {
+ rv = GetOSXFolderType(kClassicDomain, kSystemFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sTrashDirectory) {
+ rv = GetOSXFolderType(kClassicDomain, kTrashFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sStartupDirectory) {
+ rv = GetOSXFolderType(kClassicDomain, kStartupFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sShutdownDirectory) {
+ rv = GetOSXFolderType(kClassicDomain, kShutdownFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sAppleMenuDirectory) {
+ rv = GetOSXFolderType(kClassicDomain, kAppleMenuFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sControlPanelDirectory) {
+ rv = GetOSXFolderType(kClassicDomain, kControlPanelFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sExtensionDirectory) {
+ rv = GetOSXFolderType(kClassicDomain, kExtensionFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sFontsDirectory) {
+ rv = GetOSXFolderType(kClassicDomain, kFontsFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sPreferencesDirectory) {
+ rv = GetOSXFolderType(kClassicDomain, kPreferencesFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sDocumentsDirectory) {
+ rv = GetOSXFolderType(kClassicDomain, kDocumentsFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sInternetSearchDirectory) {
+ rv = GetOSXFolderType(kClassicDomain, kInternetSearchSitesFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sUserLibDirectory) {
+ rv = GetOSXFolderType(kUserDomain, kDomainLibraryFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sOS_HomeDirectory) {
+ rv = GetOSXFolderType(kUserDomain, kDomainTopLevelFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sDefaultDownloadDirectory) {
+ // 10.5 and later, we can use kDownloadsFolderType which is defined in
+ // Folders.h as "down". However, in order to support 10.4 still, we
+ // cannot use the named constant. We'll use it's value, and if it
+ // fails, fall back to the desktop.
+#ifndef kDownloadsFolderType
+#define kDownloadsFolderType 'down'
+#endif
+
+ rv = GetOSXFolderType(kUserDomain, kDownloadsFolderType,
+ getter_AddRefs(localFile));
+ if (NS_FAILED(rv)) {
+ rv = GetOSXFolderType(kUserDomain, kDesktopFolderType,
+ getter_AddRefs(localFile));
+ }
+ } else if (inAtom == nsDirectoryService::sUserDesktopDirectory ||
+ inAtom == nsDirectoryService::sOS_DesktopDirectory) {
+ rv = GetOSXFolderType(kUserDomain, kDesktopFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sLocalDesktopDirectory) {
+ rv = GetOSXFolderType(kLocalDomain, kDesktopFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sUserApplicationsDirectory) {
+ rv = GetOSXFolderType(kUserDomain, kApplicationsFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sLocalApplicationsDirectory) {
+ rv = GetOSXFolderType(kLocalDomain, kApplicationsFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sUserDocumentsDirectory) {
+ rv = GetOSXFolderType(kUserDomain, kDocumentsFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sLocalDocumentsDirectory) {
+ rv = GetOSXFolderType(kLocalDomain, kDocumentsFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sUserInternetPlugInDirectory) {
+ rv = GetOSXFolderType(kUserDomain, kInternetPlugInFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sLocalInternetPlugInDirectory) {
+ rv = GetOSXFolderType(kLocalDomain, kInternetPlugInFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sUserFrameworksDirectory) {
+ rv = GetOSXFolderType(kUserDomain, kFrameworksFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sLocalFrameworksDirectory) {
+ rv = GetOSXFolderType(kLocalDomain, kFrameworksFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sUserPreferencesDirectory) {
+ rv = GetOSXFolderType(kUserDomain, kPreferencesFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sLocalPreferencesDirectory) {
+ rv = GetOSXFolderType(kLocalDomain, kPreferencesFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sPictureDocumentsDirectory) {
+ rv = GetOSXFolderType(kUserDomain, kPictureDocumentsFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sMovieDocumentsDirectory) {
+ rv = GetOSXFolderType(kUserDomain, kMovieDocumentsFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sMusicDocumentsDirectory) {
+ rv = GetOSXFolderType(kUserDomain, kMusicDocumentsFolderType, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sInternetSitesDirectory) {
+ rv = GetOSXFolderType(kUserDomain, kInternetSitesFolderType, getter_AddRefs(localFile));
+ }
+#elif defined (XP_WIN)
+ else if (inAtom == nsDirectoryService::sSystemDirectory) {
+ rv = GetSpecialSystemDirectory(Win_SystemDirectory, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sWindowsDirectory) {
+ rv = GetSpecialSystemDirectory(Win_WindowsDirectory, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sWindowsProgramFiles) {
+ rv = GetSpecialSystemDirectory(Win_ProgramFiles, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sOS_HomeDirectory) {
+ rv = GetSpecialSystemDirectory(Win_HomeDirectory, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sDesktop) {
+ rv = GetSpecialSystemDirectory(Win_Desktop, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sPrograms) {
+ rv = GetSpecialSystemDirectory(Win_Programs, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sControls) {
+ rv = GetSpecialSystemDirectory(Win_Controls, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sPrinters) {
+ rv = GetSpecialSystemDirectory(Win_Printers, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sPersonal) {
+ rv = GetSpecialSystemDirectory(Win_Personal, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sFavorites) {
+ rv = GetSpecialSystemDirectory(Win_Favorites, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sStartup) {
+ rv = GetSpecialSystemDirectory(Win_Startup, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sRecent) {
+ rv = GetSpecialSystemDirectory(Win_Recent, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sSendto) {
+ rv = GetSpecialSystemDirectory(Win_Sendto, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sBitbucket) {
+ rv = GetSpecialSystemDirectory(Win_Bitbucket, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sStartmenu) {
+ rv = GetSpecialSystemDirectory(Win_Startmenu, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sDesktopdirectory ||
+ inAtom == nsDirectoryService::sOS_DesktopDirectory) {
+ rv = GetSpecialSystemDirectory(Win_Desktopdirectory, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sDrives) {
+ rv = GetSpecialSystemDirectory(Win_Drives, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sNetwork) {
+ rv = GetSpecialSystemDirectory(Win_Network, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sNethood) {
+ rv = GetSpecialSystemDirectory(Win_Nethood, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sFonts) {
+ rv = GetSpecialSystemDirectory(Win_Fonts, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sTemplates) {
+ rv = GetSpecialSystemDirectory(Win_Templates, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sCommon_Startmenu) {
+ rv = GetSpecialSystemDirectory(Win_Common_Startmenu, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sCommon_Programs) {
+ rv = GetSpecialSystemDirectory(Win_Common_Programs, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sCommon_Startup) {
+ rv = GetSpecialSystemDirectory(Win_Common_Startup, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sCommon_Desktopdirectory) {
+ rv = GetSpecialSystemDirectory(Win_Common_Desktopdirectory, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sCommon_AppData) {
+ rv = GetSpecialSystemDirectory(Win_Common_AppData, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sAppdata) {
+ rv = GetSpecialSystemDirectory(Win_Appdata, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sLocalAppdata) {
+ rv = GetSpecialSystemDirectory(Win_LocalAppdata, getter_AddRefs(localFile));
+#if defined(MOZ_CONTENT_SANDBOX)
+ } else if (inAtom == nsDirectoryService::sLocalAppdataLow) {
+ rv = GetSpecialSystemDirectory(Win_LocalAppdataLow, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sLowIntegrityTempBase) {
+ rv = GetLowIntegrityTempBase(getter_AddRefs(localFile));
+#endif
+ } else if (inAtom == nsDirectoryService::sPrinthood) {
+ rv = GetSpecialSystemDirectory(Win_Printhood, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sWinCookiesDirectory) {
+ rv = GetSpecialSystemDirectory(Win_Cookies, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sDefaultDownloadDirectory) {
+ rv = GetSpecialSystemDirectory(Win_Downloads, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sDocs) {
+ rv = GetSpecialSystemDirectory(Win_Documents, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sPictures) {
+ rv = GetSpecialSystemDirectory(Win_Pictures, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sMusic) {
+ rv = GetSpecialSystemDirectory(Win_Music, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sVideos) {
+ rv = GetSpecialSystemDirectory(Win_Videos, getter_AddRefs(localFile));
+ }
+#elif defined (XP_UNIX)
+
+ else if (inAtom == nsDirectoryService::sLocalDirectory) {
+ rv = GetSpecialSystemDirectory(Unix_LocalDirectory, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sLibDirectory) {
+ rv = GetSpecialSystemDirectory(Unix_LibDirectory, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sOS_HomeDirectory) {
+ rv = GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(localFile));
+ } else if (inAtom == nsDirectoryService::sXDGDesktop ||
+ inAtom == nsDirectoryService::sOS_DesktopDirectory) {
+ rv = GetSpecialSystemDirectory(Unix_XDG_Desktop, getter_AddRefs(localFile));
+ *aPersistent = false;
+ } else if (inAtom == nsDirectoryService::sXDGDocuments) {
+ rv = GetSpecialSystemDirectory(Unix_XDG_Documents, getter_AddRefs(localFile));
+ *aPersistent = false;
+ } else if (inAtom == nsDirectoryService::sXDGDownload ||
+ inAtom == nsDirectoryService::sDefaultDownloadDirectory) {
+ rv = GetSpecialSystemDirectory(Unix_XDG_Download, getter_AddRefs(localFile));
+ *aPersistent = false;
+ } else if (inAtom == nsDirectoryService::sXDGMusic) {
+ rv = GetSpecialSystemDirectory(Unix_XDG_Music, getter_AddRefs(localFile));
+ *aPersistent = false;
+ } else if (inAtom == nsDirectoryService::sXDGPictures) {
+ rv = GetSpecialSystemDirectory(Unix_XDG_Pictures, getter_AddRefs(localFile));
+ *aPersistent = false;
+ } else if (inAtom == nsDirectoryService::sXDGPublicShare) {
+ rv = GetSpecialSystemDirectory(Unix_XDG_PublicShare, getter_AddRefs(localFile));
+ *aPersistent = false;
+ } else if (inAtom == nsDirectoryService::sXDGTemplates) {
+ rv = GetSpecialSystemDirectory(Unix_XDG_Templates, getter_AddRefs(localFile));
+ *aPersistent = false;
+ } else if (inAtom == nsDirectoryService::sXDGVideos) {
+ rv = GetSpecialSystemDirectory(Unix_XDG_Videos, getter_AddRefs(localFile));
+ *aPersistent = false;
+ }
+#endif
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!localFile) {
+ return NS_ERROR_FAILURE;
+ }
+
+ localFile.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirectoryService::GetFiles(const char* aProp, nsISimpleEnumerator** aResult)
+{
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aResult = nullptr;
+
+ return NS_ERROR_FAILURE;
+}
diff --git a/xpcom/io/nsDirectoryService.h b/xpcom/io/nsDirectoryService.h
new file mode 100644
index 000000000..d0f92b75a
--- /dev/null
+++ b/xpcom/io/nsDirectoryService.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDirectoryService_h___
+#define nsDirectoryService_h___
+
+#include "nsIDirectoryService.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIFile.h"
+#include "nsIAtom.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPtr.h"
+
+#define NS_XPCOM_INIT_CURRENT_PROCESS_DIR "MozBinD" // Can be used to set NS_XPCOM_CURRENT_PROCESS_DIR
+ // CANNOT be used to GET a location
+#define NS_DIRECTORY_SERVICE_CID {0xf00152d0,0xb40b,0x11d3,{0x8c, 0x9c, 0x00, 0x00, 0x64, 0x65, 0x73, 0x74}}
+
+class nsDirectoryService final
+ : public nsIDirectoryService
+ , public nsIProperties
+ , public nsIDirectoryServiceProvider2
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIPROPERTIES
+
+ NS_DECL_NSIDIRECTORYSERVICE
+
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER
+
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER2
+
+ nsDirectoryService();
+
+ static void RealInit();
+ void RegisterCategoryProviders();
+
+ static nsresult
+ Create(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+ static mozilla::StaticRefPtr<nsDirectoryService> gService;
+
+private:
+ ~nsDirectoryService();
+
+ nsresult GetCurrentProcessDirectory(nsIFile** aFile);
+
+ nsInterfaceHashtable<nsCStringHashKey, nsIFile> mHashtable;
+ nsTArray<nsCOMPtr<nsIDirectoryServiceProvider>> mProviders;
+
+public:
+
+#define DIR_ATOM(name_, value_) static nsIAtom* name_;
+#include "nsDirectoryServiceAtomList.h"
+#undef DIR_ATOM
+
+};
+
+
+#endif
+
diff --git a/xpcom/io/nsDirectoryServiceAtomList.h b/xpcom/io/nsDirectoryServiceAtomList.h
new file mode 100644
index 000000000..38a2f0e9d
--- /dev/null
+++ b/xpcom/io/nsDirectoryServiceAtomList.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+DIR_ATOM(sCurrentProcess, NS_XPCOM_CURRENT_PROCESS_DIR)
+DIR_ATOM(sGRE_Directory, NS_GRE_DIR)
+DIR_ATOM(sGRE_BinDirectory, NS_GRE_BIN_DIR)
+DIR_ATOM(sOS_DriveDirectory, NS_OS_DRIVE_DIR)
+DIR_ATOM(sOS_TemporaryDirectory, NS_OS_TEMP_DIR)
+DIR_ATOM(sOS_CurrentProcessDirectory, NS_OS_CURRENT_PROCESS_DIR)
+DIR_ATOM(sOS_CurrentWorkingDirectory, NS_OS_CURRENT_WORKING_DIR)
+DIR_ATOM(sOS_HomeDirectory, NS_OS_HOME_DIR)
+DIR_ATOM(sOS_DesktopDirectory, NS_OS_DESKTOP_DIR)
+DIR_ATOM(sInitCurrentProcess_dummy, NS_XPCOM_INIT_CURRENT_PROCESS_DIR)
+#if defined (MOZ_WIDGET_COCOA)
+DIR_ATOM(sDirectory, NS_OS_SYSTEM_DIR)
+DIR_ATOM(sTrashDirectory, NS_MAC_TRASH_DIR)
+DIR_ATOM(sStartupDirectory, NS_MAC_STARTUP_DIR)
+DIR_ATOM(sShutdownDirectory, NS_MAC_SHUTDOWN_DIR)
+DIR_ATOM(sAppleMenuDirectory, NS_MAC_APPLE_MENU_DIR)
+DIR_ATOM(sControlPanelDirectory, NS_MAC_CONTROL_PANELS_DIR)
+DIR_ATOM(sExtensionDirectory, NS_MAC_EXTENSIONS_DIR)
+DIR_ATOM(sFontsDirectory, NS_MAC_FONTS_DIR)
+DIR_ATOM(sPreferencesDirectory, NS_MAC_PREFS_DIR)
+DIR_ATOM(sDocumentsDirectory, NS_MAC_DOCUMENTS_DIR)
+DIR_ATOM(sInternetSearchDirectory, NS_MAC_INTERNET_SEARCH_DIR)
+DIR_ATOM(sUserLibDirectory, NS_MAC_USER_LIB_DIR)
+DIR_ATOM(sDefaultDownloadDirectory, NS_OSX_DEFAULT_DOWNLOAD_DIR)
+DIR_ATOM(sUserDesktopDirectory, NS_OSX_USER_DESKTOP_DIR)
+DIR_ATOM(sLocalDesktopDirectory, NS_OSX_LOCAL_DESKTOP_DIR)
+DIR_ATOM(sUserApplicationsDirectory, NS_OSX_USER_APPLICATIONS_DIR)
+DIR_ATOM(sLocalApplicationsDirectory, NS_OSX_LOCAL_APPLICATIONS_DIR)
+DIR_ATOM(sUserDocumentsDirectory, NS_OSX_USER_DOCUMENTS_DIR)
+DIR_ATOM(sLocalDocumentsDirectory, NS_OSX_LOCAL_DOCUMENTS_DIR)
+DIR_ATOM(sUserInternetPlugInDirectory, NS_OSX_USER_INTERNET_PLUGIN_DIR)
+DIR_ATOM(sLocalInternetPlugInDirectory, NS_OSX_LOCAL_INTERNET_PLUGIN_DIR)
+DIR_ATOM(sUserFrameworksDirectory, NS_OSX_USER_FRAMEWORKS_DIR)
+DIR_ATOM(sLocalFrameworksDirectory, NS_OSX_LOCAL_FRAMEWORKS_DIR)
+DIR_ATOM(sUserPreferencesDirectory, NS_OSX_USER_PREFERENCES_DIR)
+DIR_ATOM(sLocalPreferencesDirectory, NS_OSX_LOCAL_PREFERENCES_DIR)
+DIR_ATOM(sPictureDocumentsDirectory, NS_OSX_PICTURE_DOCUMENTS_DIR)
+DIR_ATOM(sMovieDocumentsDirectory, NS_OSX_MOVIE_DOCUMENTS_DIR)
+DIR_ATOM(sMusicDocumentsDirectory, NS_OSX_MUSIC_DOCUMENTS_DIR)
+DIR_ATOM(sInternetSitesDirectory, NS_OSX_INTERNET_SITES_DIR)
+#elif defined (XP_WIN)
+DIR_ATOM(sSystemDirectory, NS_OS_SYSTEM_DIR)
+DIR_ATOM(sWindowsDirectory, NS_WIN_WINDOWS_DIR)
+DIR_ATOM(sWindowsProgramFiles, NS_WIN_PROGRAM_FILES_DIR)
+DIR_ATOM(sDesktop, NS_WIN_DESKTOP_DIR)
+DIR_ATOM(sPrograms, NS_WIN_PROGRAMS_DIR)
+DIR_ATOM(sControls, NS_WIN_CONTROLS_DIR)
+DIR_ATOM(sPrinters, NS_WIN_PRINTERS_DIR)
+DIR_ATOM(sPersonal, NS_WIN_PERSONAL_DIR)
+DIR_ATOM(sFavorites, NS_WIN_FAVORITES_DIR)
+DIR_ATOM(sStartup, NS_WIN_STARTUP_DIR)
+DIR_ATOM(sRecent, NS_WIN_RECENT_DIR)
+DIR_ATOM(sSendto, NS_WIN_SEND_TO_DIR)
+DIR_ATOM(sBitbucket, NS_WIN_BITBUCKET_DIR)
+DIR_ATOM(sStartmenu, NS_WIN_STARTMENU_DIR)
+DIR_ATOM(sDesktopdirectory, NS_WIN_DESKTOP_DIRECTORY)
+DIR_ATOM(sDrives, NS_WIN_DRIVES_DIR)
+DIR_ATOM(sNetwork, NS_WIN_NETWORK_DIR)
+DIR_ATOM(sNethood, NS_WIN_NETHOOD_DIR)
+DIR_ATOM(sFonts, NS_WIN_FONTS_DIR)
+DIR_ATOM(sTemplates, NS_WIN_TEMPLATES_DIR)
+DIR_ATOM(sCommon_Startmenu, NS_WIN_COMMON_STARTMENU_DIR)
+DIR_ATOM(sCommon_Programs, NS_WIN_COMMON_PROGRAMS_DIR)
+DIR_ATOM(sCommon_Startup, NS_WIN_COMMON_STARTUP_DIR)
+DIR_ATOM(sCommon_Desktopdirectory, NS_WIN_COMMON_DESKTOP_DIRECTORY)
+DIR_ATOM(sCommon_AppData, NS_WIN_COMMON_APPDATA_DIR)
+DIR_ATOM(sAppdata, NS_WIN_APPDATA_DIR)
+DIR_ATOM(sLocalAppdata, NS_WIN_LOCAL_APPDATA_DIR)
+#if defined(MOZ_CONTENT_SANDBOX)
+DIR_ATOM(sLocalAppdataLow, NS_WIN_LOCAL_APPDATA_LOW_DIR)
+DIR_ATOM(sLowIntegrityTempBase, NS_WIN_LOW_INTEGRITY_TEMP_BASE)
+#endif
+DIR_ATOM(sPrinthood, NS_WIN_PRINTHOOD)
+DIR_ATOM(sWinCookiesDirectory, NS_WIN_COOKIES_DIR)
+DIR_ATOM(sDefaultDownloadDirectory, NS_WIN_DEFAULT_DOWNLOAD_DIR)
+DIR_ATOM(sDocs, NS_WIN_DOCUMENTS_DIR)
+DIR_ATOM(sPictures, NS_WIN_PICTURES_DIR)
+DIR_ATOM(sMusic, NS_WIN_MUSIC_DIR)
+DIR_ATOM(sVideos, NS_WIN_VIDEOS_DIR)
+#elif defined (XP_UNIX)
+DIR_ATOM(sLocalDirectory, NS_UNIX_LOCAL_DIR)
+DIR_ATOM(sLibDirectory, NS_UNIX_LIB_DIR)
+DIR_ATOM(sDefaultDownloadDirectory, NS_UNIX_DEFAULT_DOWNLOAD_DIR)
+DIR_ATOM(sXDGDesktop, NS_UNIX_XDG_DESKTOP_DIR)
+DIR_ATOM(sXDGDocuments, NS_UNIX_XDG_DOCUMENTS_DIR)
+DIR_ATOM(sXDGDownload, NS_UNIX_XDG_DOWNLOAD_DIR)
+DIR_ATOM(sXDGMusic, NS_UNIX_XDG_MUSIC_DIR)
+DIR_ATOM(sXDGPictures, NS_UNIX_XDG_PICTURES_DIR)
+DIR_ATOM(sXDGPublicShare, NS_UNIX_XDG_PUBLIC_SHARE_DIR)
+DIR_ATOM(sXDGTemplates, NS_UNIX_XDG_TEMPLATES_DIR)
+DIR_ATOM(sXDGVideos, NS_UNIX_XDG_VIDEOS_DIR)
+#endif
diff --git a/xpcom/io/nsDirectoryServiceDefs.h b/xpcom/io/nsDirectoryServiceDefs.h
new file mode 100644
index 000000000..0bdc5e390
--- /dev/null
+++ b/xpcom/io/nsDirectoryServiceDefs.h
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Defines the property names for directories available from
+ * nsIDirectoryService. These dirs are always available even if no
+ * nsIDirectoryServiceProviders have been registered with the service.
+ * Application level keys are defined in nsAppDirectoryServiceDefs.h.
+ *
+ * Keys whose definition ends in "DIR" or "FILE" return a single nsIFile (or
+ * subclass). Keys whose definition ends in "LIST" return an nsISimpleEnumerator
+ * which enumerates a list of file objects.
+ *
+ * Defines listed in this file are FROZEN. This list may grow.
+ */
+
+#ifndef nsDirectoryServiceDefs_h___
+#define nsDirectoryServiceDefs_h___
+
+/* General OS specific locations */
+
+#define NS_OS_HOME_DIR "Home"
+#define NS_OS_TEMP_DIR "TmpD"
+#define NS_OS_CURRENT_WORKING_DIR "CurWorkD"
+/* Files stored in this directory will appear on the user's desktop,
+ * if there is one, otherwise it's just the same as "Home"
+ */
+#define NS_OS_DESKTOP_DIR "Desk"
+
+/* Property returns the directory in which the procces was started from.
+ * On Unix this will be the path in the MOZILLA_FIVE_HOME env var and if
+ * unset will be the current working directory.
+ */
+#define NS_OS_CURRENT_PROCESS_DIR "CurProcD"
+
+/* This location is similar to NS_OS_CURRENT_PROCESS_DIR, however,
+ * NS_XPCOM_CURRENT_PROCESS_DIR can be overriden by passing a "bin
+ * directory" to NS_InitXPCOM2().
+ */
+#define NS_XPCOM_CURRENT_PROCESS_DIR "XCurProcD"
+
+/* Property will return the location of the the XPCOM Shared Library.
+ */
+#define NS_XPCOM_LIBRARY_FILE "XpcomLib"
+
+/* Property will return the current location of the GRE directory.
+ * On OSX, this typically points to Contents/Resources in the app bundle.
+ * If no GRE is used, this propery will behave like
+ * NS_XPCOM_CURRENT_PROCESS_DIR.
+ */
+#define NS_GRE_DIR "GreD"
+
+/* Property will return the current location of the GRE-binaries directory.
+ * On OSX, this typically points to Contents/MacOS in the app bundle. On
+ * all other platforms, this will be identical to NS_GRE_DIR.
+ * Since this property is based on the NS_GRE_DIR, if no GRE is used, this
+ * propery will behave like NS_XPCOM_CURRENT_PROCESS_DIR.
+ */
+#define NS_GRE_BIN_DIR "GreBinD"
+
+/* Platform Specific Locations */
+
+#if !defined (XP_UNIX) || defined(MOZ_WIDGET_COCOA)
+ #define NS_OS_SYSTEM_DIR "SysD"
+#endif
+
+#if defined (MOZ_WIDGET_COCOA)
+ #define NS_MAC_DESKTOP_DIR NS_OS_DESKTOP_DIR
+ #define NS_MAC_TRASH_DIR "Trsh"
+ #define NS_MAC_STARTUP_DIR "Strt"
+ #define NS_MAC_SHUTDOWN_DIR "Shdwn"
+ #define NS_MAC_APPLE_MENU_DIR "ApplMenu"
+ #define NS_MAC_CONTROL_PANELS_DIR "CntlPnl"
+ #define NS_MAC_EXTENSIONS_DIR "Exts"
+ #define NS_MAC_FONTS_DIR "Fnts"
+ #define NS_MAC_PREFS_DIR "Prfs"
+ #define NS_MAC_DOCUMENTS_DIR "Docs"
+ #define NS_MAC_INTERNET_SEARCH_DIR "ISrch"
+ #define NS_OSX_HOME_DIR NS_OS_HOME_DIR
+ #define NS_MAC_HOME_DIR NS_OS_HOME_DIR
+ #define NS_MAC_DEFAULT_DOWNLOAD_DIR "DfltDwnld"
+ #define NS_MAC_USER_LIB_DIR "ULibDir" // Only available under OS X
+ #define NS_OSX_DEFAULT_DOWNLOAD_DIR NS_MAC_DEFAULT_DOWNLOAD_DIR
+ #define NS_OSX_USER_DESKTOP_DIR "UsrDsk"
+ #define NS_OSX_LOCAL_DESKTOP_DIR "LocDsk"
+ #define NS_OSX_USER_APPLICATIONS_DIR "UsrApp"
+ #define NS_OSX_LOCAL_APPLICATIONS_DIR "LocApp"
+ #define NS_OSX_USER_DOCUMENTS_DIR "UsrDocs"
+ #define NS_OSX_LOCAL_DOCUMENTS_DIR "LocDocs"
+ #define NS_OSX_USER_INTERNET_PLUGIN_DIR "UsrIntrntPlgn"
+ #define NS_OSX_LOCAL_INTERNET_PLUGIN_DIR "LoclIntrntPlgn"
+ #define NS_OSX_USER_FRAMEWORKS_DIR "UsrFrmwrks"
+ #define NS_OSX_LOCAL_FRAMEWORKS_DIR "LocFrmwrks"
+ #define NS_OSX_USER_PREFERENCES_DIR "UsrPrfs"
+ #define NS_OSX_LOCAL_PREFERENCES_DIR "LocPrfs"
+ #define NS_OSX_PICTURE_DOCUMENTS_DIR "Pct"
+ #define NS_OSX_MOVIE_DOCUMENTS_DIR "Mov"
+ #define NS_OSX_MUSIC_DOCUMENTS_DIR "Music"
+ #define NS_OSX_INTERNET_SITES_DIR "IntrntSts"
+#elif defined (XP_WIN)
+ #define NS_WIN_WINDOWS_DIR "WinD"
+ #define NS_WIN_PROGRAM_FILES_DIR "ProgF"
+ #define NS_WIN_HOME_DIR NS_OS_HOME_DIR
+ #define NS_WIN_DESKTOP_DIR "DeskV" // virtual folder at the root of the namespace
+ #define NS_WIN_PROGRAMS_DIR "Progs" // User start menu programs directory!
+ #define NS_WIN_CONTROLS_DIR "Cntls"
+ #define NS_WIN_PRINTERS_DIR "Prnts"
+ #define NS_WIN_PERSONAL_DIR "Pers"
+ #define NS_WIN_FAVORITES_DIR "Favs"
+ #define NS_WIN_STARTUP_DIR "Strt"
+ #define NS_WIN_RECENT_DIR "Rcnt"
+ #define NS_WIN_SEND_TO_DIR "SndTo"
+ #define NS_WIN_BITBUCKET_DIR "Buckt"
+ #define NS_WIN_STARTMENU_DIR "Strt"
+// This gives the same thing as NS_OS_DESKTOP_DIR
+ #define NS_WIN_DESKTOP_DIRECTORY "DeskP" // file sys dir which physically stores objects on desktop
+ #define NS_WIN_DRIVES_DIR "Drivs"
+ #define NS_WIN_NETWORK_DIR "NetW"
+ #define NS_WIN_NETHOOD_DIR "netH"
+ #define NS_WIN_FONTS_DIR "Fnts"
+ #define NS_WIN_TEMPLATES_DIR "Tmpls"
+ #define NS_WIN_COMMON_STARTMENU_DIR "CmStrt"
+ #define NS_WIN_COMMON_PROGRAMS_DIR "CmPrgs"
+ #define NS_WIN_COMMON_STARTUP_DIR "CmStrt"
+ #define NS_WIN_COMMON_DESKTOP_DIRECTORY "CmDeskP"
+ #define NS_WIN_COMMON_APPDATA_DIR "CmAppData"
+ #define NS_WIN_APPDATA_DIR "AppData"
+ #define NS_WIN_LOCAL_APPDATA_DIR "LocalAppData"
+#if defined(MOZ_CONTENT_SANDBOX)
+ #define NS_WIN_LOCAL_APPDATA_LOW_DIR "LocalAppDataLow"
+ #define NS_WIN_LOW_INTEGRITY_TEMP_BASE "LowTmpDBase"
+#endif
+ #define NS_WIN_PRINTHOOD "PrntHd"
+ #define NS_WIN_COOKIES_DIR "CookD"
+ #define NS_WIN_DEFAULT_DOWNLOAD_DIR "DfltDwnld"
+ // On Win7 and up these ids will return the default save-to location for
+ // Windows Libraries associated with the specific content type. For other
+ // os they return the local user folder. Note these can return network file
+ // paths which can jank the ui thread so be careful how you access them.
+ #define NS_WIN_DOCUMENTS_DIR "Docs"
+ #define NS_WIN_PICTURES_DIR "Pict"
+ #define NS_WIN_MUSIC_DIR "Music"
+ #define NS_WIN_VIDEOS_DIR "Vids"
+#elif defined (XP_UNIX)
+ #define NS_UNIX_LOCAL_DIR "Locl"
+ #define NS_UNIX_LIB_DIR "LibD"
+ #define NS_UNIX_HOME_DIR NS_OS_HOME_DIR
+ #define NS_UNIX_XDG_DESKTOP_DIR "XDGDesk"
+ #define NS_UNIX_XDG_DOCUMENTS_DIR "XDGDocs"
+ #define NS_UNIX_XDG_DOWNLOAD_DIR "XDGDwnld"
+ #define NS_UNIX_XDG_MUSIC_DIR "XDGMusic"
+ #define NS_UNIX_XDG_PICTURES_DIR "XDGPict"
+ #define NS_UNIX_XDG_PUBLIC_SHARE_DIR "XDGPubSh"
+ #define NS_UNIX_XDG_TEMPLATES_DIR "XDGTempl"
+ #define NS_UNIX_XDG_VIDEOS_DIR "XDGVids"
+ #define NS_UNIX_DEFAULT_DOWNLOAD_DIR "DfltDwnld"
+#endif
+
+/* Deprecated */
+
+#define NS_OS_DRIVE_DIR "DrvD"
+
+
+
+#endif
diff --git a/xpcom/io/nsDirectoryServiceUtils.h b/xpcom/io/nsDirectoryServiceUtils.h
new file mode 100644
index 000000000..6100e7565
--- /dev/null
+++ b/xpcom/io/nsDirectoryServiceUtils.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDirectoryServiceUtils_h___
+#define nsDirectoryServiceUtils_h___
+
+#include "nsIServiceManager.h"
+#include "nsIProperties.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsXPCOMCID.h"
+#include "nsIFile.h"
+
+inline nsresult
+NS_GetSpecialDirectory(const char* aSpecialDirName, nsIFile** aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProperties> serv(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID,
+ &rv));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return serv->Get(aSpecialDirName, NS_GET_IID(nsIFile),
+ reinterpret_cast<void**>(aResult));
+}
+
+#endif
diff --git a/xpcom/io/nsEscape.cpp b/xpcom/io/nsEscape.cpp
new file mode 100644
index 000000000..f16edc4ce
--- /dev/null
+++ b/xpcom/io/nsEscape.cpp
@@ -0,0 +1,633 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsEscape.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/BinarySearch.h"
+#include "nsTArray.h"
+#include "nsCRT.h"
+#include "plstr.h"
+
+static const char hexCharsUpper[] = "0123456789ABCDEF";
+static const char hexCharsUpperLower[] = "0123456789ABCDEFabcdef";
+
+static const int netCharType[256] =
+/* Bit 0 xalpha -- the alphas
+** Bit 1 xpalpha -- as xalpha but
+** converts spaces to plus and plus to %2B
+** Bit 3 ... path -- as xalphas but doesn't escape '/'
+*/
+ /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
+ { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x */
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1x */
+ 0,0,0,0,0,0,0,0,0,0,7,4,0,7,7,4, /* 2x !"#$%&'()*+,-./ */
+ 7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0, /* 3x 0123456789:;<=>? */
+ 0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 4x @ABCDEFGHIJKLMNO */
+ /* bits for '@' changed from 7 to 0 so '@' can be escaped */
+ /* in usernames and passwords in publishing. */
+ 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7, /* 5X PQRSTUVWXYZ[\]^_ */
+ 0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 6x `abcdefghijklmno */
+ 7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0, /* 7X pqrstuvwxyz{\}~ DEL */
+ 0, };
+
+/* decode % escaped hex codes into character values
+ */
+#define UNHEX(C) \
+ ((C >= '0' && C <= '9') ? C - '0' : \
+ ((C >= 'A' && C <= 'F') ? C - 'A' + 10 : \
+ ((C >= 'a' && C <= 'f') ? C - 'a' + 10 : 0)))
+
+
+#define IS_OK(C) (netCharType[((unsigned int)(C))] & (aFlags))
+#define HEX_ESCAPE '%'
+
+static const uint32_t ENCODE_MAX_LEN = 6; // %uABCD
+
+static uint32_t
+AppendPercentHex(char* aBuffer, unsigned char aChar)
+{
+ uint32_t i = 0;
+ aBuffer[i++] = '%';
+ aBuffer[i++] = hexCharsUpper[aChar >> 4]; // high nibble
+ aBuffer[i++] = hexCharsUpper[aChar & 0xF]; // low nibble
+ return i;
+}
+
+static uint32_t
+AppendPercentHex(char16_t* aBuffer, char16_t aChar)
+{
+ uint32_t i = 0;
+ aBuffer[i++] = '%';
+ if (aChar & 0xff00) {
+ aBuffer[i++] = 'u';
+ aBuffer[i++] = hexCharsUpper[aChar >> 12]; // high-byte high nibble
+ aBuffer[i++] = hexCharsUpper[(aChar >> 8) & 0xF]; // high-byte low nibble
+ }
+ aBuffer[i++] = hexCharsUpper[(aChar >> 4) & 0xF]; // low-byte high nibble
+ aBuffer[i++] = hexCharsUpper[aChar & 0xF]; // low-byte low nibble
+ return i;
+}
+
+//----------------------------------------------------------------------------------------
+char*
+nsEscape(const char* aStr, size_t aLength, size_t* aOutputLength,
+ nsEscapeMask aFlags)
+//----------------------------------------------------------------------------------------
+{
+ if (!aStr) {
+ return nullptr;
+ }
+
+ size_t charsToEscape = 0;
+
+ const unsigned char* src = (const unsigned char*)aStr;
+ for (size_t i = 0; i < aLength; ++i) {
+ if (!IS_OK(src[i])) {
+ charsToEscape++;
+ }
+ }
+
+ // calculate how much memory should be allocated
+ // original length + 2 bytes for each escaped character + terminating '\0'
+ // do the sum in steps to check for overflow
+ size_t dstSize = aLength + 1 + charsToEscape;
+ if (dstSize <= aLength) {
+ return nullptr;
+ }
+ dstSize += charsToEscape;
+ if (dstSize < aLength) {
+ return nullptr;
+ }
+
+ // fail if we need more than 4GB
+ if (dstSize > UINT32_MAX) {
+ return nullptr;
+ }
+
+ char* result = (char*)moz_xmalloc(dstSize);
+ if (!result) {
+ return nullptr;
+ }
+
+ unsigned char* dst = (unsigned char*)result;
+ src = (const unsigned char*)aStr;
+ if (aFlags == url_XPAlphas) {
+ for (size_t i = 0; i < aLength; ++i) {
+ unsigned char c = *src++;
+ if (IS_OK(c)) {
+ *dst++ = c;
+ } else if (c == ' ') {
+ *dst++ = '+'; /* convert spaces to pluses */
+ } else {
+ *dst++ = HEX_ESCAPE;
+ *dst++ = hexCharsUpper[c >> 4]; /* high nibble */
+ *dst++ = hexCharsUpper[c & 0x0f]; /* low nibble */
+ }
+ }
+ } else {
+ for (size_t i = 0; i < aLength; ++i) {
+ unsigned char c = *src++;
+ if (IS_OK(c)) {
+ *dst++ = c;
+ } else {
+ *dst++ = HEX_ESCAPE;
+ *dst++ = hexCharsUpper[c >> 4]; /* high nibble */
+ *dst++ = hexCharsUpper[c & 0x0f]; /* low nibble */
+ }
+ }
+ }
+
+ *dst = '\0'; /* tack on eos */
+ if (aOutputLength) {
+ *aOutputLength = dst - (unsigned char*)result;
+ }
+
+ return result;
+}
+
+//----------------------------------------------------------------------------------------
+char*
+nsUnescape(char* aStr)
+//----------------------------------------------------------------------------------------
+{
+ nsUnescapeCount(aStr);
+ return aStr;
+}
+
+//----------------------------------------------------------------------------------------
+int32_t
+nsUnescapeCount(char* aStr)
+//----------------------------------------------------------------------------------------
+{
+ char* src = aStr;
+ char* dst = aStr;
+
+ char c1[] = " ";
+ char c2[] = " ";
+ char* const pc1 = c1;
+ char* const pc2 = c2;
+
+ if (!*src) {
+ // A null string was passed in. Nothing to escape.
+ // Returns early as the string might not actually be mutable with
+ // length 0.
+ return 0;
+ }
+
+ while (*src) {
+ c1[0] = *(src + 1);
+ if (*(src + 1) == '\0') {
+ c2[0] = '\0';
+ } else {
+ c2[0] = *(src + 2);
+ }
+
+ if (*src != HEX_ESCAPE || PL_strpbrk(pc1, hexCharsUpperLower) == 0 ||
+ PL_strpbrk(pc2, hexCharsUpperLower) == 0) {
+ *dst++ = *src++;
+ } else {
+ src++; /* walk over escape */
+ if (*src) {
+ *dst = UNHEX(*src) << 4;
+ src++;
+ }
+ if (*src) {
+ *dst = (*dst + UNHEX(*src));
+ src++;
+ }
+ dst++;
+ }
+ }
+
+ *dst = 0;
+ return (int)(dst - aStr);
+
+} /* NET_UnEscapeCnt */
+
+
+char*
+nsEscapeHTML(const char* aString)
+{
+ char* rv = nullptr;
+ /* XXX Hardcoded max entity len. The +1 is for the trailing null. */
+ uint32_t len = strlen(aString);
+ if (len >= (UINT32_MAX / 6)) {
+ return nullptr;
+ }
+
+ rv = (char*)moz_xmalloc((6 * len) + 1);
+ char* ptr = rv;
+
+ if (rv) {
+ for (; *aString != '\0'; ++aString) {
+ if (*aString == '<') {
+ *ptr++ = '&';
+ *ptr++ = 'l';
+ *ptr++ = 't';
+ *ptr++ = ';';
+ } else if (*aString == '>') {
+ *ptr++ = '&';
+ *ptr++ = 'g';
+ *ptr++ = 't';
+ *ptr++ = ';';
+ } else if (*aString == '&') {
+ *ptr++ = '&';
+ *ptr++ = 'a';
+ *ptr++ = 'm';
+ *ptr++ = 'p';
+ *ptr++ = ';';
+ } else if (*aString == '"') {
+ *ptr++ = '&';
+ *ptr++ = 'q';
+ *ptr++ = 'u';
+ *ptr++ = 'o';
+ *ptr++ = 't';
+ *ptr++ = ';';
+ } else if (*aString == '\'') {
+ *ptr++ = '&';
+ *ptr++ = '#';
+ *ptr++ = '3';
+ *ptr++ = '9';
+ *ptr++ = ';';
+ } else {
+ *ptr++ = *aString;
+ }
+ }
+ *ptr = '\0';
+ }
+
+ return rv;
+}
+
+char16_t*
+nsEscapeHTML2(const char16_t* aSourceBuffer, int32_t aSourceBufferLen)
+{
+ // Calculate the length, if the caller didn't.
+ if (aSourceBufferLen < 0) {
+ aSourceBufferLen = NS_strlen(aSourceBuffer);
+ }
+
+ /* XXX Hardcoded max entity len. */
+ if (uint32_t(aSourceBufferLen) >=
+ ((UINT32_MAX - sizeof(char16_t)) / (6 * sizeof(char16_t)))) {
+ return nullptr;
+ }
+
+ char16_t* resultBuffer = (char16_t*)moz_xmalloc(
+ aSourceBufferLen * 6 * sizeof(char16_t) + sizeof(char16_t('\0')));
+ char16_t* ptr = resultBuffer;
+
+ if (resultBuffer) {
+ int32_t i;
+
+ for (i = 0; i < aSourceBufferLen; ++i) {
+ if (aSourceBuffer[i] == '<') {
+ *ptr++ = '&';
+ *ptr++ = 'l';
+ *ptr++ = 't';
+ *ptr++ = ';';
+ } else if (aSourceBuffer[i] == '>') {
+ *ptr++ = '&';
+ *ptr++ = 'g';
+ *ptr++ = 't';
+ *ptr++ = ';';
+ } else if (aSourceBuffer[i] == '&') {
+ *ptr++ = '&';
+ *ptr++ = 'a';
+ *ptr++ = 'm';
+ *ptr++ = 'p';
+ *ptr++ = ';';
+ } else if (aSourceBuffer[i] == '"') {
+ *ptr++ = '&';
+ *ptr++ = 'q';
+ *ptr++ = 'u';
+ *ptr++ = 'o';
+ *ptr++ = 't';
+ *ptr++ = ';';
+ } else if (aSourceBuffer[i] == '\'') {
+ *ptr++ = '&';
+ *ptr++ = '#';
+ *ptr++ = '3';
+ *ptr++ = '9';
+ *ptr++ = ';';
+ } else {
+ *ptr++ = aSourceBuffer[i];
+ }
+ }
+ *ptr = 0;
+ }
+
+ return resultBuffer;
+}
+
+//----------------------------------------------------------------------------------------
+//
+// The following table encodes which characters needs to be escaped for which
+// parts of an URL. The bits are the "url components" in the enum EscapeMask,
+// see nsEscape.h.
+//
+// esc_Scheme = 1
+// esc_Username = 2
+// esc_Password = 4
+// esc_Host = 8
+// esc_Directory = 16
+// esc_FileBaseName = 32
+// esc_FileExtension = 64
+// esc_Param = 128
+// esc_Query = 256
+// esc_Ref = 512
+
+static const uint32_t EscapeChars[256] =
+// 0 1 2 3 4 5 6 7 8 9 A B C D E F
+{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
+ 0,1023, 0, 512,1023, 0,1023, 112,1023,1023,1023,1023,1023,1023, 953, 784, // 2x !"#$%&'()*+,-./
+ 1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1008,1008, 0,1008, 0, 768, // 3x 0123456789:;<=>?
+ 1008,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023, // 4x @ABCDEFGHIJKLMNO
+ 1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1008, 896,1008, 896,1023, // 5x PQRSTUVWXYZ[\]^_
+ 384,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023, // 6x `abcdefghijklmno
+ 1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023, 896,1012, 896,1023, 0, // 7x pqrstuvwxyz{|}~ DEL
+ 0 // 80 to FF are zero
+};
+
+static uint16_t dontNeedEscape(unsigned char aChar, uint32_t aFlags)
+{
+ return EscapeChars[(uint32_t)aChar] & aFlags;
+}
+static uint16_t dontNeedEscape(uint16_t aChar, uint32_t aFlags)
+{
+ return aChar < mozilla::ArrayLength(EscapeChars) ?
+ (EscapeChars[(uint32_t)aChar] & aFlags) : 0;
+}
+
+//----------------------------------------------------------------------------------------
+
+/**
+ * Templated helper for URL escaping a portion of a string.
+ *
+ * @param aPart The pointer to the beginning of the portion of the string to
+ * escape.
+ * @param aPartLen The length of the string to escape.
+ * @param aFlags Flags used to configure escaping. @see EscapeMask
+ * @param aResult String that has the URL escaped portion appended to. Only
+ * altered if the string is URL escaped or |esc_AlwaysCopy| is specified.
+ * @param aDidAppend Indicates whether or not data was appended to |aResult|.
+ * @return NS_ERROR_INVALID_ARG, NS_ERROR_OUT_OF_MEMORY on failure.
+ */
+template<class T>
+static nsresult
+T_EscapeURL(const typename T::char_type* aPart, size_t aPartLen,
+ uint32_t aFlags, T& aResult, bool& aDidAppend)
+{
+ typedef nsCharTraits<typename T::char_type> traits;
+ typedef typename traits::unsigned_char_type unsigned_char_type;
+ static_assert(sizeof(*aPart) == 1 || sizeof(*aPart) == 2,
+ "unexpected char type");
+
+ if (!aPart) {
+ NS_NOTREACHED("null pointer");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool forced = !!(aFlags & esc_Forced);
+ bool ignoreNonAscii = !!(aFlags & esc_OnlyASCII);
+ bool ignoreAscii = !!(aFlags & esc_OnlyNonASCII);
+ bool writing = !!(aFlags & esc_AlwaysCopy);
+ bool colon = !!(aFlags & esc_Colon);
+
+ auto src = reinterpret_cast<const unsigned_char_type*>(aPart);
+
+ typename T::char_type tempBuffer[100];
+ unsigned int tempBufferPos = 0;
+
+ bool previousIsNonASCII = false;
+ for (size_t i = 0; i < aPartLen; ++i) {
+ unsigned_char_type c = *src++;
+
+ // if the char has not to be escaped or whatever follows % is
+ // a valid escaped string, just copy the char.
+ //
+ // Also the % will not be escaped until forced
+ // See bugzilla bug 61269 for details why we changed this
+ //
+ // And, we will not escape non-ascii characters if requested.
+ // On special request we will also escape the colon even when
+ // not covered by the matrix.
+ // ignoreAscii is not honored for control characters (C0 and DEL)
+ //
+ // And, we should escape the '|' character when it occurs after any
+ // non-ASCII character as it may be aPart of a multi-byte character.
+ //
+ // 0x20..0x7e are the valid ASCII characters. We also escape spaces
+ // (0x20) since they are not legal in URLs.
+ if ((dontNeedEscape(c, aFlags) || (c == HEX_ESCAPE && !forced)
+ || (c > 0x7f && ignoreNonAscii)
+ || (c > 0x20 && c < 0x7f && ignoreAscii))
+ && !(c == ':' && colon)
+ && !(previousIsNonASCII && c == '|' && !ignoreNonAscii)) {
+ if (writing) {
+ tempBuffer[tempBufferPos++] = c;
+ }
+ } else { /* do the escape magic */
+ if (!writing) {
+ if (!aResult.Append(aPart, i, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ writing = true;
+ }
+ uint32_t len = ::AppendPercentHex(tempBuffer + tempBufferPos, c);
+ tempBufferPos += len;
+ MOZ_ASSERT(len <= ENCODE_MAX_LEN, "potential buffer overflow");
+ }
+
+ // Flush the temp buffer if it doesnt't have room for another encoded char.
+ if (tempBufferPos >= mozilla::ArrayLength(tempBuffer) - ENCODE_MAX_LEN) {
+ NS_ASSERTION(writing, "should be writing");
+ if (!aResult.Append(tempBuffer, tempBufferPos, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ tempBufferPos = 0;
+ }
+
+ previousIsNonASCII = (c > 0x7f);
+ }
+ if (writing) {
+ if (!aResult.Append(tempBuffer, tempBufferPos, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ aDidAppend = writing;
+ return NS_OK;
+}
+
+bool
+NS_EscapeURL(const char* aPart, int32_t aPartLen, uint32_t aFlags,
+ nsACString& aResult)
+{
+ if (aPartLen < 0) {
+ aPartLen = strlen(aPart);
+ }
+
+ bool result = false;
+ nsresult rv = T_EscapeURL(aPart, aPartLen, aFlags, aResult, result);
+ if (NS_FAILED(rv)) {
+ ::NS_ABORT_OOM(aResult.Length() * sizeof(nsACString::char_type));
+ }
+
+ return result;
+}
+
+nsresult
+NS_EscapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult,
+ const mozilla::fallible_t&)
+{
+ bool appended = false;
+ nsresult rv = T_EscapeURL(aStr.Data(), aStr.Length(), aFlags, aResult, appended);
+ if (NS_FAILED(rv)) {
+ aResult.Truncate();
+ return rv;
+ }
+
+ if (!appended) {
+ aResult = aStr;
+ }
+
+ return rv;
+}
+
+const nsSubstring&
+NS_EscapeURL(const nsSubstring& aStr, uint32_t aFlags, nsSubstring& aResult)
+{
+ bool result = false;
+ nsresult rv = T_EscapeURL<nsSubstring>(aStr.Data(), aStr.Length(), aFlags, aResult, result);
+
+ if (NS_FAILED(rv)) {
+ ::NS_ABORT_OOM(aResult.Length() * sizeof(nsSubstring::char_type));
+ }
+
+ if (result) {
+ return aResult;
+ }
+ return aStr;
+}
+
+// Starting at aStr[aStart] find the first index in aStr that matches any
+// character in aForbidden. Return false if not found.
+static bool
+FindFirstMatchFrom(const nsAFlatString& aStr, size_t aStart,
+ const nsTArray<char16_t>& aForbidden, size_t* aIndex)
+{
+ const size_t len = aForbidden.Length();
+ for (size_t j = aStart, l = aStr.Length(); j < l; ++j) {
+ size_t unused;
+ if (mozilla::BinarySearch(aForbidden, 0, len, aStr[j], &unused)) {
+ *aIndex = j;
+ return true;
+ }
+ }
+ return false;
+}
+
+const nsSubstring&
+NS_EscapeURL(const nsAFlatString& aStr, const nsTArray<char16_t>& aForbidden,
+ nsSubstring& aResult)
+{
+ bool didEscape = false;
+ for (size_t i = 0, strLen = aStr.Length(); i < strLen; ) {
+ size_t j;
+ if (MOZ_UNLIKELY(FindFirstMatchFrom(aStr, i, aForbidden, &j))) {
+ if (i == 0) {
+ didEscape = true;
+ aResult.Truncate();
+ aResult.SetCapacity(aStr.Length());
+ }
+ if (j != i) {
+ // The substring from 'i' up to 'j' that needs no escaping.
+ aResult.Append(nsDependentSubstring(aStr, i, j - i));
+ }
+ char16_t buffer[ENCODE_MAX_LEN];
+ uint32_t bufferLen = ::AppendPercentHex(buffer, aStr[j]);
+ MOZ_ASSERT(bufferLen <= ENCODE_MAX_LEN, "buffer overflow");
+ aResult.Append(buffer, bufferLen);
+ i = j + 1;
+ } else {
+ if (MOZ_UNLIKELY(didEscape)) {
+ // The tail of the string that needs no escaping.
+ aResult.Append(nsDependentSubstring(aStr, i, strLen - i));
+ }
+ break;
+ }
+ }
+ if (MOZ_UNLIKELY(didEscape)) {
+ return aResult;
+ }
+ return aStr;
+}
+
+#define ISHEX(c) memchr(hexCharsUpperLower, c, sizeof(hexCharsUpperLower)-1)
+
+bool
+NS_UnescapeURL(const char* aStr, int32_t aLen, uint32_t aFlags,
+ nsACString& aResult)
+{
+ if (!aStr) {
+ NS_NOTREACHED("null pointer");
+ return false;
+ }
+
+ MOZ_ASSERT(aResult.IsEmpty(),
+ "Passing a non-empty string as an out parameter!");
+
+ if (aLen < 0) {
+ aLen = strlen(aStr);
+ }
+
+ bool ignoreNonAscii = !!(aFlags & esc_OnlyASCII);
+ bool ignoreAscii = !!(aFlags & esc_OnlyNonASCII);
+ bool writing = !!(aFlags & esc_AlwaysCopy);
+ bool skipControl = !!(aFlags & esc_SkipControl);
+ bool skipInvalidHostChar = !!(aFlags & esc_Host);
+
+ if (writing) {
+ aResult.SetCapacity(aLen);
+ }
+
+ const char* last = aStr;
+ const char* p = aStr;
+
+ for (int i = 0; i < aLen; ++i, ++p) {
+ if (*p == HEX_ESCAPE && i < aLen - 2) {
+ unsigned char c1 = *((unsigned char*)p + 1);
+ unsigned char c2 = *((unsigned char*)p + 2);
+ unsigned char u = (UNHEX(c1) << 4) + UNHEX(c2);
+ if (ISHEX(c1) && ISHEX(c2) &&
+ (!skipInvalidHostChar || dontNeedEscape(u, aFlags) || c1 >= '8') &&
+ ((c1 < '8' && !ignoreAscii) || (c1 >= '8' && !ignoreNonAscii)) &&
+ !(skipControl &&
+ (c1 < '2' || (c1 == '7' && (c2 == 'f' || c2 == 'F'))))) {
+ if (!writing) {
+ writing = true;
+ aResult.SetCapacity(aLen);
+ }
+ if (p > last) {
+ aResult.Append(last, p - last);
+ last = p;
+ }
+ aResult.Append(u);
+ i += 2;
+ p += 2;
+ last += 3;
+ }
+ }
+ }
+ if (writing && last < aStr + aLen) {
+ aResult.Append(last, aStr + aLen - last);
+ }
+
+ return writing;
+}
diff --git a/xpcom/io/nsEscape.h b/xpcom/io/nsEscape.h
new file mode 100644
index 000000000..bf89b737a
--- /dev/null
+++ b/xpcom/io/nsEscape.h
@@ -0,0 +1,224 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* First checked in on 98/12/03 by John R. McMullen, derived from net.h/mkparse.c. */
+
+#ifndef _ESCAPE_H_
+#define _ESCAPE_H_
+
+#include "nscore.h"
+#include "nsError.h"
+#include "nsString.h"
+
+/**
+ * Valid mask values for nsEscape
+ * Note: these values are copied in nsINetUtil.idl. Any changes should be kept
+ * in sync.
+ */
+typedef enum {
+ url_All = 0, // %-escape every byte unconditionally
+ url_XAlphas = 1u << 0, // Normal escape - leave alphas intact, escape the rest
+ url_XPAlphas = 1u << 1, // As url_XAlphas, but convert spaces (0x20) to '+' and plus to %2B
+ url_Path = 1u << 2 // As url_XAlphas, but don't escape slash ('/')
+} nsEscapeMask;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Escape the given string according to mask
+ * @param aSstr The string to escape
+ * @param aLength The length of the string to escape
+ * @param aOutputLen A pointer that will be used to store the length of the
+ * output string, if not null
+ * @param aMask How to escape the string
+ * @return A newly allocated escaped string that must be free'd with
+ * nsCRT::free, or null on failure
+ * @note: Please, don't use this function. Use NS_Escape instead!
+ */
+char* nsEscape(const char* aStr, size_t aLength, size_t* aOutputLen,
+ nsEscapeMask aMask);
+
+char* nsUnescape(char* aStr);
+/* decode % escaped hex codes into character values,
+ * modifies the parameter, returns the same buffer
+ */
+
+int32_t nsUnescapeCount(char* aStr);
+/* decode % escaped hex codes into character values,
+ * modifies the parameter buffer, returns the length of the result
+ * (result may contain \0's).
+ */
+
+char*
+nsEscapeHTML(const char* aString);
+
+char16_t*
+nsEscapeHTML2(const char16_t* aSourceBuffer,
+ int32_t aSourceBufferLen = -1);
+/*
+ * Escape problem char's for HTML display
+ */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+/**
+ * NS_EscapeURL/NS_UnescapeURL constants for |flags| parameter:
+ *
+ * Note: These values are copied to nsINetUtil.idl
+ * Any changes should be kept in sync
+ */
+enum EscapeMask {
+ /** url components **/
+ esc_Scheme = 1u << 0,
+ esc_Username = 1u << 1,
+ esc_Password = 1u << 2,
+ esc_Host = 1u << 3,
+ esc_Directory = 1u << 4,
+ esc_FileBaseName = 1u << 5,
+ esc_FileExtension = 1u << 6,
+ esc_FilePath = esc_Directory | esc_FileBaseName | esc_FileExtension,
+ esc_Param = 1u << 7,
+ esc_Query = 1u << 8,
+ esc_Ref = 1u << 9,
+ /** special flags **/
+ esc_Minimal = esc_Scheme | esc_Username | esc_Password | esc_Host | esc_FilePath | esc_Param | esc_Query | esc_Ref,
+ esc_Forced = 1u << 10, /* forces escaping of existing escape sequences */
+ esc_OnlyASCII = 1u << 11, /* causes non-ascii octets to be skipped */
+ esc_OnlyNonASCII = 1u << 12, /* causes _graphic_ ascii octets (0x20-0x7E)
+ * to be skipped when escaping. causes all
+ * ascii octets (<= 0x7F) to be skipped when unescaping */
+ esc_AlwaysCopy = 1u << 13, /* copy input to result buf even if escaping is unnecessary */
+ esc_Colon = 1u << 14, /* forces escape of colon */
+ esc_SkipControl = 1u << 15 /* skips C0 and DEL from unescaping */
+};
+
+/**
+ * NS_EscapeURL
+ *
+ * Escapes invalid char's in an URL segment. Has no side-effect if the URL
+ * segment is already escaped, unless aFlags has the esc_Forced bit in which
+ * case % will also be escaped. Iff some part of aStr is escaped is the
+ * final result appended to aResult. You can also request that aStr is
+ * always appended to aResult with esc_AlwaysCopy.
+ *
+ * @param aStr url segment string
+ * @param aLen url segment string length (-1 if unknown)
+ * @param aFlags url segment type flag (see EscapeMask above)
+ * @param aResult result buffer, untouched if aStr is already escaped unless
+ * aFlags has esc_AlwaysCopy
+ *
+ * @return true if aResult was written to (i.e. at least one character was
+ * escaped or esc_AlwaysCopy was requested), false otherwise.
+ */
+bool NS_EscapeURL(const char* aStr,
+ int32_t aLen,
+ uint32_t aFlags,
+ nsACString& aResult);
+
+/**
+ * Expands URL escape sequences... beware embedded null bytes!
+ *
+ * @param aStr url string to unescape
+ * @param aLen length of aStr
+ * @param aFlags only esc_OnlyNonASCII, esc_SkipControl and esc_AlwaysCopy
+ * are recognized
+ * @param aResult result buffer, untouched if aStr is already unescaped unless
+ * aFlags has esc_AlwaysCopy
+ *
+ * @return true if aResult was written to (i.e. at least one character was
+ * unescaped or esc_AlwaysCopy was requested), false otherwise.
+ */
+bool NS_UnescapeURL(const char* aStr,
+ int32_t aLen,
+ uint32_t aFlags,
+ nsACString& aResult);
+
+/** returns resultant string length **/
+inline int32_t
+NS_UnescapeURL(char* aStr)
+{
+ return nsUnescapeCount(aStr);
+}
+
+/**
+ * String friendly versions...
+ */
+inline const nsCSubstring&
+NS_EscapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult)
+{
+ if (NS_EscapeURL(aStr.Data(), aStr.Length(), aFlags, aResult)) {
+ return aResult;
+ }
+ return aStr;
+}
+
+/**
+ * Fallible version of NS_EscapeURL. On success aResult will point to either
+ * the original string or an escaped copy.
+ */
+nsresult
+NS_EscapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult,
+ const mozilla::fallible_t&);
+
+inline const nsCSubstring&
+NS_UnescapeURL(const nsCSubstring& aStr, uint32_t aFlags, nsCSubstring& aResult)
+{
+ if (NS_UnescapeURL(aStr.Data(), aStr.Length(), aFlags, aResult)) {
+ return aResult;
+ }
+ return aStr;
+}
+const nsSubstring&
+NS_EscapeURL(const nsSubstring& aStr, uint32_t aFlags, nsSubstring& aResult);
+
+/**
+ * Percent-escapes all characters in aStr that occurs in aForbidden.
+ * @param aStr the input URL string
+ * @param aForbidden the characters that should be escaped if found in aStr
+ * @note that aForbidden MUST be sorted (low to high)
+ * @param aResult the result if some characters were escaped
+ * @return aResult if some characters were escaped, or aStr otherwise (aResult
+ * is unmodified in that case)
+ */
+const nsSubstring&
+NS_EscapeURL(const nsAFlatString& aStr, const nsTArray<char16_t>& aForbidden,
+ nsSubstring& aResult);
+
+/**
+ * CString version of nsEscape. Returns true on success, false
+ * on out of memory. To reverse this function, use NS_UnescapeURL.
+ */
+inline bool
+NS_Escape(const nsACString& aOriginal, nsACString& aEscaped,
+ nsEscapeMask aMask)
+{
+ size_t escLen = 0;
+ char* esc = nsEscape(aOriginal.BeginReading(), aOriginal.Length(), &escLen,
+ aMask);
+ if (! esc) {
+ return false;
+ }
+ aEscaped.Adopt(esc, escLen);
+ return true;
+}
+
+/**
+ * Inline unescape of mutable string object.
+ */
+inline nsACString&
+NS_UnescapeURL(nsACString& aStr)
+{
+ aStr.SetLength(nsUnescapeCount(aStr.BeginWriting()));
+ return aStr;
+}
+
+#endif // _ESCAPE_H_
diff --git a/xpcom/io/nsIAsyncInputStream.idl b/xpcom/io/nsIAsyncInputStream.idl
new file mode 100644
index 000000000..5570817dd
--- /dev/null
+++ b/xpcom/io/nsIAsyncInputStream.idl
@@ -0,0 +1,104 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+
+interface nsIInputStreamCallback;
+interface nsIEventTarget;
+
+/**
+ * If an input stream is non-blocking, it may return NS_BASE_STREAM_WOULD_BLOCK
+ * when read. The caller must then wait for the stream to have some data to
+ * read. If the stream implements nsIAsyncInputStream, then the caller can use
+ * this interface to request an asynchronous notification when the stream
+ * becomes readable or closed (via the AsyncWait method).
+ *
+ * While this interface is almost exclusively used with non-blocking streams, it
+ * is not necessary that nsIInputStream::isNonBlocking return true. Nor is it
+ * necessary that a non-blocking nsIInputStream implementation also implement
+ * nsIAsyncInputStream.
+ */
+[scriptable, uuid(a5f255ab-4801-4161-8816-277ac92f6ad1)]
+interface nsIAsyncInputStream : nsIInputStream
+{
+ /**
+ * This method closes the stream and sets its internal status. If the
+ * stream is already closed, then this method is ignored. Once the stream
+ * is closed, the stream's status cannot be changed. Any successful status
+ * code passed to this method is treated as NS_BASE_STREAM_CLOSED, which
+ * has an effect equivalent to nsIInputStream::close.
+ *
+ * NOTE: this method exists in part to support pipes, which have both an
+ * input end and an output end. If the input end of a pipe is closed, then
+ * writes to the output end of the pipe will fail. The error code returned
+ * when an attempt is made to write to a "broken" pipe corresponds to the
+ * status code passed in when the input end of the pipe was closed, which
+ * greatly simplifies working with pipes in some cases.
+ *
+ * @param aStatus
+ * The error that will be reported if this stream is accessed after
+ * it has been closed.
+ */
+ void closeWithStatus(in nsresult aStatus);
+
+ /**
+ * Asynchronously wait for the stream to be readable or closed. The
+ * notification is one-shot, meaning that each asyncWait call will result
+ * in exactly one notification callback. After the OnInputStreamReady event
+ * is dispatched, the stream releases its reference to the
+ * nsIInputStreamCallback object. It is safe to call asyncWait again from the
+ * notification handler.
+ *
+ * This method may be called at any time (even if read has not been called).
+ * In other words, this method may be called when the stream already has
+ * data to read. It may also be called when the stream is closed. If the
+ * stream is already readable or closed when AsyncWait is called, then the
+ * OnInputStreamReady event will be dispatched immediately. Otherwise, the
+ * event will be dispatched when the stream becomes readable or closed.
+ *
+ * @param aCallback
+ * This object is notified when the stream becomes ready. This
+ * parameter may be null to clear an existing callback.
+ * @param aFlags
+ * This parameter specifies optional flags passed in to configure
+ * the behavior of this method. Pass zero to specify no flags.
+ * @param aRequestedCount
+ * Wait until at least this many bytes can be read. This is only
+ * a suggestion to the underlying stream; it may be ignored. The
+ * caller may pass zero to indicate no preference.
+ * @param aEventTarget
+ * Specify NULL to receive notification on ANY thread (possibly even
+ * recursively on the calling thread -- i.e., synchronously), or
+ * specify that the notification be delivered to a specific event
+ * target.
+ */
+ void asyncWait(in nsIInputStreamCallback aCallback,
+ in unsigned long aFlags,
+ in unsigned long aRequestedCount,
+ in nsIEventTarget aEventTarget);
+
+ /**
+ * If passed to asyncWait, this flag overrides the default behavior,
+ * causing the OnInputStreamReady notification to be suppressed until the
+ * stream becomes closed (either as a result of closeWithStatus/close being
+ * called on the stream or possibly due to some error in the underlying
+ * stream).
+ */
+ const unsigned long WAIT_CLOSURE_ONLY = (1<<0);
+};
+
+/**
+ * This is a companion interface for nsIAsyncInputStream::asyncWait.
+ */
+[function, scriptable, uuid(d1f28e94-3a6e-4050-a5f5-2e81b1fc2a43)]
+interface nsIInputStreamCallback : nsISupports
+{
+ /**
+ * Called to indicate that the stream is either readable or closed.
+ *
+ * @param aStream
+ * The stream whose asyncWait method was called.
+ */
+ void onInputStreamReady(in nsIAsyncInputStream aStream);
+};
diff --git a/xpcom/io/nsIAsyncOutputStream.idl b/xpcom/io/nsIAsyncOutputStream.idl
new file mode 100644
index 000000000..7e74579c6
--- /dev/null
+++ b/xpcom/io/nsIAsyncOutputStream.idl
@@ -0,0 +1,104 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIOutputStream.idl"
+
+interface nsIOutputStreamCallback;
+interface nsIEventTarget;
+
+/**
+ * If an output stream is non-blocking, it may return NS_BASE_STREAM_WOULD_BLOCK
+ * when written to. The caller must then wait for the stream to become
+ * writable. If the stream implements nsIAsyncOutputStream, then the caller can
+ * use this interface to request an asynchronous notification when the stream
+ * becomes writable or closed (via the AsyncWait method).
+ *
+ * While this interface is almost exclusively used with non-blocking streams, it
+ * is not necessary that nsIOutputStream::isNonBlocking return true. Nor is it
+ * necessary that a non-blocking nsIOutputStream implementation also implement
+ * nsIAsyncOutputStream.
+ */
+[scriptable, uuid(beb632d3-d77a-4e90-9134-f9ece69e8200)]
+interface nsIAsyncOutputStream : nsIOutputStream
+{
+ /**
+ * This method closes the stream and sets its internal status. If the
+ * stream is already closed, then this method is ignored. Once the stream
+ * is closed, the stream's status cannot be changed. Any successful status
+ * code passed to this method is treated as NS_BASE_STREAM_CLOSED, which
+ * is equivalent to nsIInputStream::close.
+ *
+ * NOTE: this method exists in part to support pipes, which have both an
+ * input end and an output end. If the output end of a pipe is closed, then
+ * reads from the input end of the pipe will fail. The error code returned
+ * when an attempt is made to read from a "closed" pipe corresponds to the
+ * status code passed in when the output end of the pipe is closed, which
+ * greatly simplifies working with pipes in some cases.
+ *
+ * @param aStatus
+ * The error that will be reported if this stream is accessed after
+ * it has been closed.
+ */
+ void closeWithStatus(in nsresult reason);
+
+ /**
+ * Asynchronously wait for the stream to be writable or closed. The
+ * notification is one-shot, meaning that each asyncWait call will result
+ * in exactly one notification callback. After the OnOutputStreamReady event
+ * is dispatched, the stream releases its reference to the
+ * nsIOutputStreamCallback object. It is safe to call asyncWait again from the
+ * notification handler.
+ *
+ * This method may be called at any time (even if write has not been called).
+ * In other words, this method may be called when the stream already has
+ * room for more data. It may also be called when the stream is closed. If
+ * the stream is already writable or closed when AsyncWait is called, then the
+ * OnOutputStreamReady event will be dispatched immediately. Otherwise, the
+ * event will be dispatched when the stream becomes writable or closed.
+ *
+ * @param aCallback
+ * This object is notified when the stream becomes ready. This
+ * parameter may be null to clear an existing callback.
+ * @param aFlags
+ * This parameter specifies optional flags passed in to configure
+ * the behavior of this method. Pass zero to specify no flags.
+ * @param aRequestedCount
+ * Wait until at least this many bytes can be written. This is only
+ * a suggestion to the underlying stream; it may be ignored. The
+ * caller may pass zero to indicate no preference.
+ * @param aEventTarget
+ * Specify NULL to receive notification on ANY thread (possibly even
+ * recursively on the calling thread -- i.e., synchronously), or
+ * specify that the notification be delivered to a specific event
+ * target.
+ */
+ void asyncWait(in nsIOutputStreamCallback aCallback,
+ in unsigned long aFlags,
+ in unsigned long aRequestedCount,
+ in nsIEventTarget aEventTarget);
+
+ /**
+ * If passed to asyncWait, this flag overrides the default behavior,
+ * causing the OnOutputStreamReady notification to be suppressed until the
+ * stream becomes closed (either as a result of closeWithStatus/close being
+ * called on the stream or possibly due to some error in the underlying
+ * stream).
+ */
+ const unsigned long WAIT_CLOSURE_ONLY = (1<<0);
+};
+
+/**
+ * This is a companion interface for nsIAsyncOutputStream::asyncWait.
+ */
+[function, scriptable, uuid(40dbcdff-9053-42c5-a57c-3ec910d0f148)]
+interface nsIOutputStreamCallback : nsISupports
+{
+ /**
+ * Called to indicate that the stream is either writable or closed.
+ *
+ * @param aStream
+ * The stream whose asyncWait method was called.
+ */
+ void onOutputStreamReady(in nsIAsyncOutputStream aStream);
+};
diff --git a/xpcom/io/nsIBinaryInputStream.idl b/xpcom/io/nsIBinaryInputStream.idl
new file mode 100644
index 000000000..c3a27e203
--- /dev/null
+++ b/xpcom/io/nsIBinaryInputStream.idl
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+
+/**
+ * This interface allows consumption of primitive data types from a "binary
+ * stream" containing untagged, big-endian binary data, i.e. as produced by an
+ * implementation of nsIBinaryOutputStream. This might be used, for example,
+ * to implement network protocols or to read from architecture-neutral disk
+ * files, i.e. ones that can be read and written by both big-endian and
+ * little-endian platforms.
+ *
+ * @See nsIBinaryOutputStream
+ */
+
+[scriptable, uuid(899b826b-2eb3-469c-8b31-4c29f5d341a6)]
+interface nsIBinaryInputStream : nsIInputStream {
+ void setInputStream(in nsIInputStream aInputStream);
+
+ /**
+ * Read 8-bits from the stream.
+ *
+ * @return that byte to be treated as a boolean.
+ */
+ boolean readBoolean();
+
+ uint8_t read8();
+ uint16_t read16();
+ uint32_t read32();
+ uint64_t read64();
+
+ float readFloat();
+ double readDouble();
+
+ /**
+ * Read an 8-bit pascal style string from the stream.
+ * 32-bit length field, followed by length 8-bit chars.
+ */
+ ACString readCString();
+
+ /**
+ * Read an 16-bit pascal style string from the stream.
+ * 32-bit length field, followed by length PRUnichars.
+ */
+ AString readString();
+
+ /**
+ * Read an opaque byte array from the stream.
+ *
+ * @param aLength the number of bytes that must be read.
+ *
+ * @throws NS_ERROR_FAILURE if it can't read aLength bytes
+ */
+ void readBytes(in uint32_t aLength,
+ [size_is(aLength), retval] out string aString);
+
+ /**
+ * Read an opaque byte array from the stream, storing the results
+ * as an array of PRUint8s.
+ *
+ * @param aLength the number of bytes that must be read.
+ *
+ * @throws NS_ERROR_FAILURE if it can't read aLength bytes
+ */
+ void readByteArray(in uint32_t aLength,
+ [array, size_is(aLength), retval] out uint8_t aBytes);
+
+ /**
+ * Read opaque bytes from the stream, storing the results in an ArrayBuffer.
+ *
+ * @param aLength the number of bytes that must be read
+ * @param aArrayBuffer the arraybuffer in which to store the results
+ * Note: passing view.buffer, where view is an ArrayBufferView of an
+ * ArrayBuffer, is not valid unless view.byteOffset == 0.
+ *
+ * @return The number of bytes actually read into aArrayBuffer.
+ */
+ [implicit_jscontext]
+ unsigned long readArrayBuffer(in uint32_t aLength, in jsval aArrayBuffer);
+};
+
+%{C++
+
+#ifdef MOZILLA_INTERNAL_API
+#include "nsString.h"
+
+inline nsresult
+NS_ReadOptionalCString(nsIBinaryInputStream* aStream, nsACString& aResult)
+{
+ bool nonnull;
+ nsresult rv = aStream->ReadBoolean(&nonnull);
+ if (NS_SUCCEEDED(rv)) {
+ if (nonnull)
+ rv = aStream->ReadCString(aResult);
+ else
+ aResult.Truncate();
+ }
+ return rv;
+}
+
+inline nsresult
+NS_ReadOptionalString(nsIBinaryInputStream* aStream, nsAString& aResult)
+{
+ bool nonnull;
+ nsresult rv = aStream->ReadBoolean(&nonnull);
+ if (NS_SUCCEEDED(rv)) {
+ if (nonnull)
+ rv = aStream->ReadString(aResult);
+ else
+ aResult.Truncate();
+ }
+ return rv;
+}
+#endif
+
+%}
diff --git a/xpcom/io/nsIBinaryOutputStream.idl b/xpcom/io/nsIBinaryOutputStream.idl
new file mode 100644
index 000000000..4d426d580
--- /dev/null
+++ b/xpcom/io/nsIBinaryOutputStream.idl
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIOutputStream.idl"
+
+/**
+ * This interface allows writing of primitive data types (integers,
+ * floating-point values, booleans, etc.) to a stream in a binary, untagged,
+ * fixed-endianness format. This might be used, for example, to implement
+ * network protocols or to produce architecture-neutral binary disk files,
+ * i.e. ones that can be read and written by both big-endian and little-endian
+ * platforms. Output is written in big-endian order (high-order byte first),
+ * as this is traditional network order.
+ *
+ * @See nsIBinaryInputStream
+ */
+
+[scriptable, uuid(204ee610-8765-11d3-90cf-0040056a906e)]
+interface nsIBinaryOutputStream : nsIOutputStream {
+ void setOutputStream(in nsIOutputStream aOutputStream);
+
+ /**
+ * Write a boolean as an 8-bit char to the stream.
+ */
+ void writeBoolean(in boolean aBoolean);
+
+ void write8(in uint8_t aByte);
+ void write16(in uint16_t a16);
+ void write32(in uint32_t a32);
+ void write64(in uint64_t a64);
+
+ void writeFloat(in float aFloat);
+ void writeDouble(in double aDouble);
+
+ /**
+ * Write an 8-bit pascal style string to the stream.
+ * 32-bit length field, followed by length 8-bit chars.
+ */
+ void writeStringZ(in string aString);
+
+ /**
+ * Write a 16-bit pascal style string to the stream.
+ * 32-bit length field, followed by length PRUnichars.
+ */
+ void writeWStringZ(in wstring aString);
+
+ /**
+ * Write an 8-bit pascal style string (UTF8-encoded) to the stream.
+ * 32-bit length field, followed by length 8-bit chars.
+ */
+ void writeUtf8Z(in wstring aString);
+
+ /**
+ * Write an opaque byte array to the stream.
+ */
+ void writeBytes([size_is(aLength)] in string aString, in uint32_t aLength);
+
+ /**
+ * Write an opaque byte array to the stream.
+ */
+ void writeByteArray([array, size_is(aLength)] in uint8_t aBytes,
+ in uint32_t aLength);
+
+};
+
+%{C++
+
+inline nsresult
+NS_WriteOptionalStringZ(nsIBinaryOutputStream* aStream, const char* aString)
+{
+ bool nonnull = (aString != nullptr);
+ nsresult rv = aStream->WriteBoolean(nonnull);
+ if (NS_SUCCEEDED(rv) && nonnull)
+ rv = aStream->WriteStringZ(aString);
+ return rv;
+}
+
+inline nsresult
+NS_WriteOptionalWStringZ(nsIBinaryOutputStream* aStream, const char16_t* aString)
+{
+ bool nonnull = (aString != nullptr);
+ nsresult rv = aStream->WriteBoolean(nonnull);
+ if (NS_SUCCEEDED(rv) && nonnull)
+ rv = aStream->WriteWStringZ(aString);
+ return rv;
+}
+
+%}
diff --git a/xpcom/io/nsICloneableInputStream.idl b/xpcom/io/nsICloneableInputStream.idl
new file mode 100644
index 000000000..4fd9a74f7
--- /dev/null
+++ b/xpcom/io/nsICloneableInputStream.idl
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+
+[scriptable, builtinclass, uuid(8149be1f-44d3-4f14-8b65-a57a5fbbeb97)]
+interface nsICloneableInputStream : nsISupports
+{
+ // Allow streams that implement the interface to determine if cloning
+ // possible at runtime. For example, this allows wrappers to check if
+ // their base stream supports cloning.
+ [infallible] readonly attribute boolean cloneable;
+
+ // Produce a copy of the current stream in the most efficient way possible.
+ // In this case "copy" means that both the original and cloned streams
+ // should produce the same bytes for all future reads. Bytes that have
+ // already been consumed from the original stream are not copied to the
+ // clone. Operations on the two streams should be completely independent
+ // after the clone() occurs.
+ nsIInputStream clone();
+};
diff --git a/xpcom/io/nsIConverterInputStream.idl b/xpcom/io/nsIConverterInputStream.idl
new file mode 100644
index 000000000..7f35d8c5c
--- /dev/null
+++ b/xpcom/io/nsIConverterInputStream.idl
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIUnicharInputStream.idl"
+
+interface nsIInputStream;
+
+/**
+ * A unichar input stream that wraps an input stream.
+ * This allows reading unicode strings from a stream, automatically converting
+ * the bytes from a selected character encoding.
+ */
+[scriptable, uuid(FC66FFB6-5404-4908-A4A3-27F92FA0579D)]
+interface nsIConverterInputStream : nsIUnicharInputStream {
+ /**
+ * Default replacement char value, U+FFFD REPLACEMENT CHARACTER.
+ */
+ const char16_t DEFAULT_REPLACEMENT_CHARACTER = 0xFFFD;
+
+ /**
+ * Initialize this stream.
+ * @param aStream
+ * The underlying stream to read from.
+ * @param aCharset
+ * The character encoding to use for converting the bytes of the
+ * stream. A null charset will be interpreted as UTF-8.
+ * @param aBufferSize
+ * How many bytes to buffer.
+ * @param aReplacementChar
+ * The character to replace unknown byte sequences in the stream
+ * with. The standard replacement character is U+FFFD.
+ * A value of 0x0000 will cause an exception to be thrown if unknown
+ * byte sequences are encountered in the stream.
+ */
+ void init (in nsIInputStream aStream, in string aCharset,
+ in long aBufferSize, in char16_t aReplacementChar);
+};
+
diff --git a/xpcom/io/nsIConverterOutputStream.idl b/xpcom/io/nsIConverterOutputStream.idl
new file mode 100644
index 000000000..a93d3cfa6
--- /dev/null
+++ b/xpcom/io/nsIConverterOutputStream.idl
@@ -0,0 +1,44 @@
+/* vim:set expandtab ts=4 sw=4 sts=4 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIUnicharOutputStream.idl"
+
+interface nsIOutputStream;
+
+/**
+ * This interface allows writing strings to a stream, doing automatic
+ * character encoding conversion.
+ */
+[scriptable, uuid(4b71113a-cb0d-479f-8ed5-01daeba2e8d4)]
+interface nsIConverterOutputStream : nsIUnicharOutputStream
+{
+ /**
+ * Initialize this stream. Must be called before any other method on this
+ * interface, or you will crash. The output stream passed to this method
+ * must not be null, or you will crash.
+ *
+ * @param aOutStream
+ * The underlying output stream to which the converted strings will
+ * be written.
+ * @param aCharset
+ * The character set to use for encoding the characters. A null
+ * charset will be interpreted as UTF-8.
+ * @param aBufferSize
+ * How many bytes to buffer. A value of 0 means that no bytes will be
+ * buffered. Implementations not supporting buffering may ignore
+ * this parameter.
+ * @param aReplacementCharacter
+ * The replacement character to use when an unsupported character is found.
+ * The character must be encodable in the selected character
+ * encoding; otherwise, attempts to write an unsupported character
+ * will throw NS_ERROR_LOSS_OF_SIGNIFICANT_DATA.
+ *
+ * A value of 0x0000 will cause an exception to be thrown upon
+ * attempts to write unsupported characters.
+ */
+ void init(in nsIOutputStream aOutStream, in string aCharset,
+ in unsigned long aBufferSize,
+ in char16_t aReplacementCharacter);
+};
diff --git a/xpcom/io/nsIDirectoryEnumerator.idl b/xpcom/io/nsIDirectoryEnumerator.idl
new file mode 100644
index 000000000..7a1135fda
--- /dev/null
+++ b/xpcom/io/nsIDirectoryEnumerator.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+/**
+ * This interface provides a means for enumerating the contents of a directory.
+ * It is similar to nsISimpleEnumerator except the retrieved entries are QI'ed
+ * to nsIFile, and there is a mechanism for closing the directory when the
+ * enumeration is complete.
+ */
+[scriptable, uuid(31f7f4ae-6916-4f2d-a81e-926a4e3022ee)]
+interface nsIDirectoryEnumerator : nsISupports
+{
+ /**
+ * Retrieves the next file in the sequence. The "nextFile" element is the
+ * first element upon the first call. This attribute is null if there is no
+ * next element.
+ */
+ readonly attribute nsIFile nextFile;
+
+ /**
+ * Closes the directory being enumerated, releasing the system resource.
+ * @throws NS_OK if the call succeeded and the directory was closed.
+ * NS_ERROR_FAILURE if the directory close failed.
+ * It is safe to call this function many times.
+ */
+ void close();
+};
+
diff --git a/xpcom/io/nsIDirectoryService.idl b/xpcom/io/nsIDirectoryService.idl
new file mode 100644
index 000000000..6f58e37b9
--- /dev/null
+++ b/xpcom/io/nsIDirectoryService.idl
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsISimpleEnumerator;
+
+/**
+ * nsIDirectoryServiceProvider
+ *
+ * Used by Directory Service to get file locations.
+ */
+
+[scriptable, uuid(bbf8cab0-d43a-11d3-8cc2-00609792278c)]
+interface nsIDirectoryServiceProvider: nsISupports
+{
+ /**
+ * getFile
+ *
+ * Directory Service calls this when it gets the first request for
+ * a prop or on every request if the prop is not persistent.
+ *
+ * @param prop The symbolic name of the file.
+ * @param persistent TRUE - The returned file will be cached by Directory
+ * Service. Subsequent requests for this prop will
+ * bypass the provider and use the cache.
+ * FALSE - The provider will be asked for this prop
+ * each time it is requested.
+ *
+ * @return The file represented by the property.
+ *
+ */
+ nsIFile getFile(in string prop, out boolean persistent);
+};
+
+/**
+ * nsIDirectoryServiceProvider2
+ *
+ * An extension of nsIDirectoryServiceProvider which allows
+ * multiple files to be returned for the given key.
+ */
+
+[scriptable, uuid(2f977d4b-5485-11d4-87e2-0010a4e75ef2)]
+interface nsIDirectoryServiceProvider2: nsIDirectoryServiceProvider
+{
+ /**
+ * getFiles
+ *
+ * Directory Service calls this when it gets a request for
+ * a prop and the requested type is nsISimpleEnumerator.
+ *
+ * @param prop The symbolic name of the file list.
+ *
+ * @return An enumerator for a list of file locations.
+ * The elements in the enumeration are nsIFile
+ * @returnCode NS_SUCCESS_AGGREGATE_RESULT if this result should be
+ * aggregated with other "lower" providers.
+ */
+ nsISimpleEnumerator getFiles(in string prop);
+};
+
+/**
+ * nsIDirectoryService
+ */
+
+[scriptable, uuid(57a66a60-d43a-11d3-8cc2-00609792278c)]
+interface nsIDirectoryService: nsISupports
+{
+ /**
+ * init
+ *
+ * Must be called. Used internally by XPCOM initialization.
+ *
+ */
+ void init();
+
+ /**
+ * registerProvider
+ *
+ * Register a provider with the service.
+ *
+ * @param prov The service will keep a strong reference
+ * to this object. It will be released when
+ * the service is released.
+ *
+ */
+ void registerProvider(in nsIDirectoryServiceProvider prov);
+
+ /**
+ * unregisterProvider
+ *
+ * Unregister a provider with the service.
+ *
+ * @param prov
+ *
+ */
+ void unregisterProvider(in nsIDirectoryServiceProvider prov);
+};
+
+
diff --git a/xpcom/io/nsIFile.idl b/xpcom/io/nsIFile.idl
new file mode 100644
index 000000000..fc07106b9
--- /dev/null
+++ b/xpcom/io/nsIFile.idl
@@ -0,0 +1,521 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{C++
+struct PRFileDesc;
+struct PRLibrary;
+#include <stdio.h>
+%}
+
+[ptr] native PRFileDescStar(PRFileDesc);
+[ptr] native PRLibraryStar(PRLibrary);
+[ptr] native FILE(FILE);
+
+interface nsISimpleEnumerator;
+
+/**
+ * An nsIFile is an abstract representation of a filename. It manages
+ * filename encoding issues, pathname component separators ('/' vs. '\\'
+ * vs. ':') and weird stuff like differing volumes with identical names, as
+ * on pre-Darwin Macintoshes.
+ *
+ * This file has long introduced itself to new hackers with this opening
+ * paragraph:
+ *
+ * This is the only correct cross-platform way to specify a file.
+ * Strings are not such a way. If you grew up on windows or unix, you
+ * may think they are. Welcome to reality.
+ *
+ * While taking the pose struck here to heart would be uncalled for, one
+ * may safely conclude that writing cross-platform code is an embittering
+ * experience.
+ *
+ * All methods with string parameters have two forms. The preferred
+ * form operates on UCS-2 encoded characters strings. An alternate
+ * form operates on characters strings encoded in the "native" charset.
+ *
+ * A string containing characters encoded in the native charset cannot
+ * be safely passed to javascript via xpconnect. Therefore, the "native
+ * methods" are not scriptable.
+ */
+[scriptable, main_process_scriptable_only, uuid(2fa6884a-ae65-412a-9d4c-ce6e34544ba1), builtinclass]
+interface nsIFile : nsISupports
+{
+ /**
+ * Create Types
+ *
+ * NORMAL_FILE_TYPE - A normal file.
+ * DIRECTORY_TYPE - A directory/folder.
+ */
+ const unsigned long NORMAL_FILE_TYPE = 0;
+ const unsigned long DIRECTORY_TYPE = 1;
+
+ /**
+ * append[Native]
+ *
+ * This function is used for constructing a descendent of the
+ * current nsIFile.
+ *
+ * @param node
+ * A string which is intended to be a child node of the nsIFile.
+ * For the |appendNative| method, the node must be in the native
+ * filesystem charset.
+ */
+ void append(in AString node);
+ [noscript] void appendNative(in ACString node);
+
+ /**
+ * Normalize the pathName (e.g. removing .. and . components on Unix).
+ */
+ void normalize();
+
+ /**
+ * create
+ *
+ * This function will create a new file or directory in the
+ * file system. Any nodes that have not been created or
+ * resolved, will be. If the file or directory already
+ * exists create() will return NS_ERROR_FILE_ALREADY_EXISTS.
+ *
+ * @param type
+ * This specifies the type of file system object
+ * to be made. The only two types at this time
+ * are file and directory which are defined above.
+ * If the type is unrecongnized, we will return an
+ * error (NS_ERROR_FILE_UNKNOWN_TYPE).
+ *
+ * @param permissions
+ * The unix style octal permissions. This may
+ * be ignored on systems that do not need to do
+ * permissions.
+ */
+ [must_use] void create(in unsigned long type, in unsigned long permissions);
+
+ /**
+ * Accessor to the leaf name of the file itself.
+ * For the |nativeLeafName| method, the nativeLeafName must
+ * be in the native filesystem charset.
+ */
+ attribute AString leafName;
+ [noscript] attribute ACString nativeLeafName;
+
+ /**
+ * copyTo[Native]
+ *
+ * This will copy this file to the specified newParentDir.
+ * If a newName is specified, the file will be renamed.
+ * If 'this' is not created we will return an error
+ * (NS_ERROR_FILE_TARGET_DOES_NOT_EXIST).
+ *
+ * copyTo may fail if the file already exists in the destination
+ * directory.
+ *
+ * copyTo will NOT resolve aliases/shortcuts during the copy.
+ *
+ * @param newParentDir
+ * This param is the destination directory. If the
+ * newParentDir is null, copyTo() will use the parent
+ * directory of this file. If the newParentDir is not
+ * empty and is not a directory, an error will be
+ * returned (NS_ERROR_FILE_DESTINATION_NOT_DIR). For the
+ * |CopyToNative| method, the newName must be in the
+ * native filesystem charset.
+ *
+ * @param newName
+ * This param allows you to specify a new name for
+ * the file to be copied. This param may be empty, in
+ * which case the current leaf name will be used.
+ */
+ void copyTo(in nsIFile newParentDir, in AString newName);
+ [noscript] void CopyToNative(in nsIFile newParentDir, in ACString newName);
+
+ /**
+ * copyToFollowingLinks[Native]
+ *
+ * This function is identical to copyTo with the exception that,
+ * as the name implies, it follows symbolic links. The XP_UNIX
+ * implementation always follow symbolic links when copying. For
+ * the |CopyToFollowingLinks| method, the newName must be in the
+ * native filesystem charset.
+ */
+ void copyToFollowingLinks(in nsIFile newParentDir, in AString newName);
+ [noscript] void copyToFollowingLinksNative(in nsIFile newParentDir, in ACString newName);
+
+ /**
+ * moveTo[Native]
+ *
+ * A method to move this file or directory to newParentDir.
+ * If a newName is specified, the file or directory will be renamed.
+ * If 'this' is not created we will return an error
+ * (NS_ERROR_FILE_TARGET_DOES_NOT_EXIST).
+ * If 'this' is a file, and the destination file already exists, moveTo
+ * will replace the old file.
+ * This object is updated to refer to the new file.
+ *
+ * moveTo will NOT resolve aliases/shortcuts during the copy.
+ * moveTo will do the right thing and allow copies across volumes.
+ * moveTo will return an error (NS_ERROR_FILE_DIR_NOT_EMPTY) if 'this' is
+ * a directory and the destination directory is not empty.
+ * moveTo will return an error (NS_ERROR_FILE_ACCESS_DENIED) if 'this' is
+ * a directory and the destination directory is not writable.
+ *
+ * @param newParentDir
+ * This param is the destination directory. If the
+ * newParentDir is empty, moveTo() will rename the file
+ * within its current directory. If the newParentDir is
+ * not empty and does not name a directory, an error will
+ * be returned (NS_ERROR_FILE_DESTINATION_NOT_DIR). For
+ * the |moveToNative| method, the newName must be in the
+ * native filesystem charset.
+ *
+ * @param newName
+ * This param allows you to specify a new name for
+ * the file to be moved. This param may be empty, in
+ * which case the current leaf name will be used.
+ */
+ void moveTo(in nsIFile newParentDir, in AString newName);
+ [noscript] void moveToNative(in nsIFile newParentDir, in ACString newName);
+
+ /**
+ * renameTo
+ *
+ * This method is identical to moveTo except that if this file or directory
+ * is moved to a a different volume, it fails and returns an error
+ * (NS_ERROR_FILE_ACCESS_DENIED).
+ * This object will still point to the old location after renaming.
+ */
+ void renameTo(in nsIFile newParentDir, in AString newName);
+ [noscript] void renameToNative(in nsIFile newParentDir, in ACString newName);
+
+ /**
+ * This will try to delete this file. The 'recursive' flag
+ * must be PR_TRUE to delete directories which are not empty.
+ *
+ * This will not resolve any symlinks.
+ */
+ void remove(in boolean recursive);
+
+ /**
+ * Attributes of nsIFile.
+ */
+
+ attribute unsigned long permissions;
+ attribute unsigned long permissionsOfLink;
+
+ /**
+ * File Times are to be in milliseconds from
+ * midnight (00:00:00), January 1, 1970 Greenwich Mean
+ * Time (GMT).
+ */
+ attribute PRTime lastModifiedTime;
+ attribute PRTime lastModifiedTimeOfLink;
+
+ /**
+ * WARNING! On the Mac, getting/setting the file size with nsIFile
+ * only deals with the size of the data fork. If you need to
+ * know the size of the combined data and resource forks use the
+ * GetFileSizeWithResFork() method defined on nsILocalFileMac.
+ */
+ attribute int64_t fileSize;
+ readonly attribute int64_t fileSizeOfLink;
+
+ /**
+ * target & path
+ *
+ * Accessor to the string path. The native version of these
+ * strings are not guaranteed to be a usable path to pass to
+ * NSPR or the C stdlib. There are problems that affect
+ * platforms on which a path does not fully specify a file
+ * because two volumes can have the same name (e.g., mac).
+ * This is solved by holding "private", native data in the
+ * nsIFile implementation. This native data is lost when
+ * you convert to a string.
+ *
+ * DO NOT PASS TO USE WITH NSPR OR STDLIB!
+ *
+ * target
+ * Find out what the symlink points at. Will give error
+ * (NS_ERROR_FILE_INVALID_PATH) if not a symlink.
+ *
+ * path
+ * Find out what the nsIFile points at.
+ *
+ * Note that the ACString attributes are returned in the
+ * native filesystem charset.
+ *
+ */
+ readonly attribute AString target;
+ [noscript] readonly attribute ACString nativeTarget;
+ readonly attribute AString path;
+ [noscript] readonly attribute ACString nativePath;
+
+ boolean exists();
+ boolean isWritable();
+ boolean isReadable();
+ boolean isExecutable();
+ boolean isHidden();
+ boolean isDirectory();
+ boolean isFile();
+ boolean isSymlink();
+ /**
+ * Not a regular file, not a directory, not a symlink.
+ */
+ boolean isSpecial();
+
+ /**
+ * createUnique
+ *
+ * This function will create a new file or directory in the
+ * file system. Any nodes that have not been created or
+ * resolved, will be. If this file already exists, we try
+ * variations on the leaf name "suggestedName" until we find
+ * one that did not already exist.
+ *
+ * If the search for nonexistent files takes too long
+ * (thousands of the variants already exist), we give up and
+ * return NS_ERROR_FILE_TOO_BIG.
+ *
+ * @param type
+ * This specifies the type of file system object
+ * to be made. The only two types at this time
+ * are file and directory which are defined above.
+ * If the type is unrecongnized, we will return an
+ * error (NS_ERROR_FILE_UNKNOWN_TYPE).
+ *
+ * @param permissions
+ * The unix style octal permissions. This may
+ * be ignored on systems that do not need to do
+ * permissions.
+ */
+ [must_use]
+ void createUnique(in unsigned long type, in unsigned long permissions);
+
+ /**
+ * clone()
+ *
+ * This function will allocate and initialize a nsIFile object to the
+ * exact location of the |this| nsIFile.
+ *
+ * @param file
+ * A nsIFile which this object will be initialize
+ * with.
+ *
+ */
+ nsIFile clone();
+
+ /**
+ * Will determine if the inFile equals this.
+ */
+ boolean equals(in nsIFile inFile);
+
+ /**
+ * Will determine if inFile is a descendant of this file.
+ * This routine looks in subdirectories too.
+ */
+ boolean contains(in nsIFile inFile);
+
+ /**
+ * Parent will be null when this is at the top of the volume.
+ */
+ readonly attribute nsIFile parent;
+
+ /**
+ * Returns an enumeration of the elements in a directory. Each
+ * element in the enumeration is an nsIFile.
+ *
+ * @throws NS_ERROR_FILE_NOT_DIRECTORY if the current nsIFile does
+ * not specify a directory.
+ */
+ readonly attribute nsISimpleEnumerator directoryEntries;
+
+ /**
+ * initWith[Native]Path
+ *
+ * This function will initialize the nsIFile object. Any
+ * internal state information will be reset.
+ *
+ * @param filePath
+ * A string which specifies a full file path to a
+ * location. Relative paths will be treated as an
+ * error (NS_ERROR_FILE_UNRECOGNIZED_PATH). For
+ * initWithNativePath, the filePath must be in the native
+ * filesystem charset.
+ */
+ void initWithPath(in AString filePath);
+ [noscript] void initWithNativePath(in ACString filePath);
+
+ /**
+ * initWithFile
+ *
+ * Initialize this object with another file
+ *
+ * @param aFile
+ * the file this becomes equivalent to
+ */
+ void initWithFile(in nsIFile aFile);
+
+ /**
+ * followLinks
+ *
+ * This attribute will determine if the nsLocalFile will auto
+ * resolve symbolic links. By default, this value will be false
+ * on all non unix systems. On unix, this attribute is effectively
+ * a noop.
+ */
+ attribute boolean followLinks;
+
+ /**
+ * Flag for openNSPRFileDesc(), to hint to the OS that the file will be
+ * read sequentially with agressive readahead.
+ */
+ const unsigned long OS_READAHEAD = 0x40000000;
+
+ /**
+ * Flag for openNSPRFileDesc(). Deprecated and unreliable!
+ * Instead use NS_OpenAnonymousTemporaryFile() to create a temporary
+ * file which will be deleted upon close!
+ */
+ const unsigned long DELETE_ON_CLOSE = 0x80000000;
+
+ /**
+ * Return the result of PR_Open on the file. The caller is
+ * responsible for calling PR_Close on the result. On success, the
+ * returned PRFileDescr must be non-null.
+ *
+ * @param flags the PR_Open flags from prio.h, plus optionally
+ * OS_READAHEAD or DELETE_ON_CLOSE. OS_READAHEAD is a hint to the
+ * OS that the file will be read sequentially with agressive
+ * readahead. DELETE_ON_CLOSE is unreliable on Windows and is deprecated.
+ * Instead use NS_OpenAnonymousTemporaryFile() to create a temporary
+ * file which will be deleted upon close.
+ */
+ [noscript, must_use] PRFileDescStar openNSPRFileDesc(in long flags,
+ in long mode);
+
+ /**
+ * Return the result of fopen on the file. The caller is
+ * responsible for calling fclose on the result. On success, the
+ * returned FILE pointer must be non-null.
+ */
+ [noscript, must_use] FILE openANSIFileDesc(in string mode);
+
+ /**
+ * Return the result of PR_LoadLibrary on the file. The caller is
+ * responsible for calling PR_UnloadLibrary on the result.
+ */
+ [noscript, must_use] PRLibraryStar load();
+
+ // number of bytes available on disk to non-superuser
+ [must_use] readonly attribute int64_t diskSpaceAvailable;
+
+ /**
+ * appendRelative[Native]Path
+ *
+ * Append a relative path to the current path of the nsIFile object.
+ *
+ * @param relativeFilePath
+ * relativeFilePath is a native relative path. For security reasons,
+ * this cannot contain .. or cannot start with a directory separator.
+ * For the |appendRelativeNativePath| method, the relativeFilePath
+ * must be in the native filesystem charset.
+ */
+ void appendRelativePath(in AString relativeFilePath);
+ [noscript] void appendRelativeNativePath(in ACString relativeFilePath);
+
+ /**
+ * Accessor to a null terminated string which will specify
+ * the file in a persistent manner for disk storage.
+ *
+ * The character set of this attribute is undefined. DO NOT TRY TO
+ * INTERPRET IT AS HUMAN READABLE TEXT!
+ */
+ [must_use] attribute ACString persistentDescriptor;
+
+ /**
+ * reveal
+ *
+ * Ask the operating system to open the folder which contains
+ * this file or folder. This routine only works on platforms which
+ * support the ability to open a folder and is run async on Windows.
+ * This routine must be called on the main.
+ */
+ [must_use] void reveal();
+
+ /**
+ * launch
+ *
+ * Ask the operating system to attempt to open the file.
+ * this really just simulates "double clicking" the file on your platform.
+ * This routine only works on platforms which support this functionality
+ * and is run async on Windows. This routine must be called on the
+ * main thread.
+ */
+ [must_use] void launch();
+
+ /**
+ * getRelativeDescriptor
+ *
+ * Returns a relative file path in an opaque, XP format. It is therefore
+ * not a native path.
+ *
+ * The character set of the string returned from this function is
+ * undefined. DO NOT TRY TO INTERPRET IT AS HUMAN READABLE TEXT!
+ *
+ * @param fromFile
+ * the file from which the descriptor is relative.
+ * Throws if fromFile is null.
+ */
+ [must_use] ACString getRelativeDescriptor(in nsIFile fromFile);
+
+ /**
+ * setRelativeDescriptor
+ *
+ * Initializes the file to the location relative to fromFile using
+ * a string returned by getRelativeDescriptor.
+ *
+ * @param fromFile
+ * the file to which the descriptor is relative
+ * @param relative
+ * the relative descriptor obtained from getRelativeDescriptor
+ */
+ [must_use]
+ void setRelativeDescriptor(in nsIFile fromFile, in ACString relativeDesc);
+
+ /**
+ * getRelativePath
+ *
+ * Returns a relative file from 'fromFile' to this file as a UTF-8 string.
+ * Going up the directory tree is represented via "../". '/' is used as
+ * the path segment separator. This is not a native path, since it's UTF-8
+ * encoded.
+ *
+ * @param fromFile
+ * the file from which the path is relative.
+ * Throws if fromFile is null.
+ */
+ [must_use] AUTF8String getRelativePath(in nsIFile fromFile);
+
+ /**
+ * setRelativePath
+ *
+ * Initializes the file to the location relative to fromFile using
+ * a string returned by getRelativePath.
+ *
+ * @param fromFile
+ * the file from which the path is relative
+ * @param relative
+ * the relative path obtained from getRelativePath
+ */
+ [must_use]
+ void setRelativePath(in nsIFile fromFile, in AUTF8String relativeDesc);
+};
+
+%{C++
+#ifdef MOZILLA_INTERNAL_API
+#include "nsDirectoryServiceUtils.h"
+#endif
+%}
diff --git a/xpcom/io/nsIIOUtil.idl b/xpcom/io/nsIIOUtil.idl
new file mode 100644
index 000000000..577ba4046
--- /dev/null
+++ b/xpcom/io/nsIIOUtil.idl
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsIOutputStream;
+
+/**
+ * nsIIOUtil provdes various xpcom/io-related utility methods.
+ */
+[scriptable, uuid(e8152f7f-4209-4c63-ad23-c3d2aa0c5a49)]
+interface nsIIOUtil : nsISupports
+{
+ /**
+ * Test whether an input stream is buffered. See nsStreamUtils.h
+ * documentation for NS_InputStreamIsBuffered for the definition of
+ * "buffered" used here and for edge-case behavior.
+ *
+ * @throws NS_ERROR_INVALID_POINTER if null is passed in.
+ */
+ boolean inputStreamIsBuffered(in nsIInputStream aStream);
+
+ /**
+ * Test whether an output stream is buffered. See nsStreamUtils.h
+ * documentation for NS_OutputStreamIsBuffered for the definition of
+ * "buffered" used here and for edge-case behavior.
+ *
+ * @throws NS_ERROR_INVALID_POINTER if null is passed in.
+ */
+ boolean outputStreamIsBuffered(in nsIOutputStream aStream);
+};
diff --git a/xpcom/io/nsIInputStream.idl b/xpcom/io/nsIInputStream.idl
new file mode 100644
index 000000000..b2f165274
--- /dev/null
+++ b/xpcom/io/nsIInputStream.idl
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+
+%{C++
+/**
+ * The signature of the writer function passed to ReadSegments. This
+ * is the "consumer" of data that gets read from the stream's buffer.
+ *
+ * @param aInStream stream being read
+ * @param aClosure opaque parameter passed to ReadSegments
+ * @param aFromSegment pointer to memory owned by the input stream. This is
+ * where the writer function should start consuming data.
+ * @param aToOffset amount of data already consumed by this writer during this
+ * ReadSegments call. This is also the sum of the aWriteCount
+ * returns from this writer over the previous invocations of
+ * the writer by this ReadSegments call.
+ * @param aCount Number of bytes available to be read starting at aFromSegment
+ * @param [out] aWriteCount number of bytes read by this writer function call
+ *
+ * Implementers should return the following:
+ *
+ * @return NS_OK and (*aWriteCount > 0) if consumed some data
+ * @return <any-error> if not interested in consuming any data
+ *
+ * Errors are never passed to the caller of ReadSegments.
+ *
+ * NOTE: returning NS_OK and (*aWriteCount = 0) has undefined behavior.
+ */
+typedef nsresult (*nsWriteSegmentFun)(nsIInputStream *aInStream,
+ void *aClosure,
+ const char *aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t *aWriteCount);
+%}
+
+native nsWriteSegmentFun(nsWriteSegmentFun);
+
+/**
+ * nsIInputStream
+ *
+ * An interface describing a readable stream of data. An input stream may be
+ * "blocking" or "non-blocking" (see the IsNonBlocking method). A blocking
+ * input stream may suspend the calling thread in order to satisfy a call to
+ * Close, Available, Read, or ReadSegments. A non-blocking input stream, on
+ * the other hand, must not block the calling thread of execution.
+ *
+ * NOTE: blocking input streams are often read on a background thread to avoid
+ * locking up the main application thread. For this reason, it is generally
+ * the case that a blocking input stream should be implemented using thread-
+ * safe AddRef and Release.
+ */
+[scriptable, uuid(53cdbc97-c2d7-4e30-b2c3-45b2ee79db18)]
+interface nsIInputStream : nsISupports
+{
+ /**
+ * Close the stream. This method causes subsequent calls to Read and
+ * ReadSegments to return 0 bytes read to indicate end-of-file. Any
+ * subsequent calls to Available should throw NS_BASE_STREAM_CLOSED.
+ */
+ void close();
+
+ /**
+ * Determine number of bytes available in the stream. A non-blocking
+ * stream that does not yet have any data to read should return 0 bytes
+ * from this method (i.e., it must not throw the NS_BASE_STREAM_WOULD_BLOCK
+ * exception).
+ *
+ * In addition to the number of bytes available in the stream, this method
+ * also informs the caller of the current status of the stream. A stream
+ * that is closed will throw an exception when this method is called. That
+ * enables the caller to know the condition of the stream before attempting
+ * to read from it. If a stream is at end-of-file, but not closed, then
+ * this method returns 0 bytes available. (Note: some nsIInputStream
+ * implementations automatically close when eof is reached; some do not).
+ *
+ * @return number of bytes currently available in the stream.
+ *
+ * @throws NS_BASE_STREAM_CLOSED if the stream is closed normally.
+ * @throws <other-error> if the stream is closed due to some error
+ * condition
+ */
+ unsigned long long available();
+
+ /**
+ * Read data from the stream.
+ *
+ * @param aBuf the buffer into which the data is to be read
+ * @param aCount the maximum number of bytes to be read
+ *
+ * @return number of bytes read (may be less than aCount).
+ * @return 0 if reached end-of-file
+ *
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream would
+ * block the calling thread (non-blocking mode only)
+ * @throws <other-error> on failure
+ *
+ * NOTE: this method should not throw NS_BASE_STREAM_CLOSED.
+ */
+ [noscript] unsigned long read(in charPtr aBuf, in unsigned long aCount);
+
+ /**
+ * Low-level read method that provides access to the stream's underlying
+ * buffer. The writer function may be called multiple times for segmented
+ * buffers. ReadSegments is expected to keep calling the writer until
+ * either there is nothing left to read or the writer returns an error.
+ * ReadSegments should not call the writer with zero bytes to consume.
+ *
+ * @param aWriter the "consumer" of the data to be read
+ * @param aClosure opaque parameter passed to writer
+ * @param aCount the maximum number of bytes to be read
+ *
+ * @return number of bytes read (may be less than aCount)
+ * @return 0 if reached end-of-file (or if aWriter refused to consume data)
+ *
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream would
+ * block the calling thread (non-blocking mode only)
+ * @throws NS_ERROR_NOT_IMPLEMENTED if the stream has no underlying buffer
+ * @throws <other-error> on failure
+ *
+ * NOTE: this function may be unimplemented if a stream has no underlying
+ * buffer (e.g., socket input stream).
+ *
+ * NOTE: this method should not throw NS_BASE_STREAM_CLOSED.
+ */
+ [noscript] unsigned long readSegments(in nsWriteSegmentFun aWriter,
+ in voidPtr aClosure,
+ in unsigned long aCount);
+
+ /**
+ * @return true if stream is non-blocking
+ *
+ * NOTE: reading from a blocking input stream will block the calling thread
+ * until at least one byte of data can be extracted from the stream.
+ *
+ * NOTE: a non-blocking input stream may implement nsIAsyncInputStream to
+ * provide consumers with a way to wait for the stream to have more data
+ * once its read method is unable to return any data without blocking.
+ */
+ boolean isNonBlocking();
+};
diff --git a/xpcom/io/nsIInputStreamTee.idl b/xpcom/io/nsIInputStreamTee.idl
new file mode 100644
index 000000000..953be7a7c
--- /dev/null
+++ b/xpcom/io/nsIInputStreamTee.idl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+
+interface nsIOutputStream;
+interface nsIEventTarget;
+
+/**
+ * A nsIInputStreamTee is a wrapper for an input stream, that when read
+ * reads the specified amount of data from its |source| and copies that
+ * data to its |sink|. |sink| must be a blocking output stream.
+ */
+[scriptable, uuid(90a9d790-3bca-421e-a73b-98f68e13c917)]
+interface nsIInputStreamTee : nsIInputStream
+{
+ attribute nsIInputStream source;
+ attribute nsIOutputStream sink;
+
+ /**
+ * If |eventTarget| is set, copying to sink is done asynchronously using
+ * the event-target (e.g. a thread). If |eventTarget| is not set, copying
+ * to sink happens synchronously while reading from the source.
+ */
+ attribute nsIEventTarget eventTarget;
+};
+
+%{C++
+// factory methods
+extern nsresult
+NS_NewInputStreamTee(nsIInputStream **tee, // read from this input stream
+ nsIInputStream *source,
+ nsIOutputStream *sink);
+
+extern nsresult
+NS_NewInputStreamTeeAsync(nsIInputStream **tee, // read from this input stream
+ nsIInputStream *source,
+ nsIOutputStream *sink,
+ nsIEventTarget *eventTarget);
+%}
diff --git a/xpcom/io/nsILineInputStream.idl b/xpcom/io/nsILineInputStream.idl
new file mode 100644
index 000000000..4a8eff42b
--- /dev/null
+++ b/xpcom/io/nsILineInputStream.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(c97b466c-1e6e-4773-a4ab-2b2b3190a7a6)]
+interface nsILineInputStream : nsISupports
+{
+ /**
+ * Read a single line from the stream, where a line is a
+ * possibly zero length sequence of 8bit chars terminated by a
+ * CR, LF, CRLF, LFCR, or eof.
+ * The line terminator is not returned.
+ * @retval false
+ * End of file. This line is the last line of the file
+ * (aLine is valid).
+ * @retval true
+ * The file contains further lines.
+ * @note Do not mix readLine with other read functions.
+ * Doing so can cause various problems and is not supported.
+ */
+ boolean readLine(out ACString aLine);
+};
diff --git a/xpcom/io/nsILocalFile.idl b/xpcom/io/nsILocalFile.idl
new file mode 100644
index 000000000..cead3e3f4
--- /dev/null
+++ b/xpcom/io/nsILocalFile.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIFile.idl"
+
+/**
+ * An empty interface to provide backwards compatibility for existing code.
+ *
+ * @see nsIFile
+ */
+[scriptable, builtinclass, uuid(7ba8c6ba-2ce2-48b1-bd60-4c32aac35f9c)]
+interface nsILocalFile : nsIFile
+{
+};
+
diff --git a/xpcom/io/nsILocalFileMac.idl b/xpcom/io/nsILocalFileMac.idl
new file mode 100644
index 000000000..d8655449b
--- /dev/null
+++ b/xpcom/io/nsILocalFileMac.idl
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsILocalFile.idl"
+
+%{C++
+#include <Carbon/Carbon.h>
+#include <CoreFoundation/CoreFoundation.h>
+%}
+
+ native OSType(OSType);
+ native FSSpec(FSSpec);
+ native FSRef(FSRef);
+[ptr] native FSRefPtr(FSRef);
+ native CFURLRef(CFURLRef);
+
+[scriptable, builtinclass, uuid(623eca5b-c25d-4e27-be5a-789a66c4b2f7)]
+interface nsILocalFileMac : nsILocalFile
+{
+ /**
+ * initWithCFURL
+ *
+ * Init this object with a CFURLRef
+ *
+ * NOTE: Supported only for XP_MACOSX
+ * NOTE: If the path of the CFURL is /a/b/c, at least a/b must exist beforehand.
+ *
+ * @param aCFURL the CoreFoundation URL
+ *
+ */
+ [noscript] void initWithCFURL(in CFURLRef aCFURL);
+
+ /**
+ * initWithFSRef
+ *
+ * Init this object with an FSRef
+ *
+ * NOTE: Supported only for XP_MACOSX
+ *
+ * @param aFSRef the native FSRef
+ *
+ */
+ [noscript] void initWithFSRef([const] in FSRefPtr aFSRef);
+
+ /**
+ * getCFURL
+ *
+ * Returns the CFURLRef of the file object. The caller is
+ * responsible for calling CFRelease() on it.
+ *
+ * NOTE: Observes the state of the followLinks attribute.
+ * If the file object is an alias and followLinks is TRUE, returns
+ * the target of the alias. If followLinks is FALSE, returns
+ * the unresolved alias file.
+ *
+ * NOTE: Supported only for XP_MACOSX
+ *
+ * @return
+ *
+ */
+ [noscript] CFURLRef getCFURL();
+
+ /**
+ * getFSRef
+ *
+ * Returns the FSRef of the file object.
+ *
+ * NOTE: Observes the state of the followLinks attribute.
+ * If the file object is an alias and followLinks is TRUE, returns
+ * the target of the alias. If followLinks is FALSE, returns
+ * the unresolved alias file.
+ *
+ * NOTE: Supported only for XP_MACOSX
+ *
+ * @return
+ *
+ */
+ [noscript] FSRef getFSRef();
+
+ /**
+ * getFSSpec
+ *
+ * Returns the FSSpec of the file object.
+ *
+ * NOTE: Observes the state of the followLinks attribute.
+ * If the file object is an alias and followLinks is TRUE, returns
+ * the target of the alias. If followLinks is FALSE, returns
+ * the unresolved alias file.
+ *
+ * @return
+ *
+ */
+ [noscript] FSSpec getFSSpec();
+
+ /**
+ * fileSizeWithResFork
+ *
+ * Returns the combined size of both the data fork and the resource
+ * fork (if present) rather than just the size of the data fork
+ * as returned by GetFileSize()
+ *
+ */
+ readonly attribute int64_t fileSizeWithResFork;
+
+ /**
+ * fileType, creator
+ *
+ * File type and creator attributes
+ *
+ */
+ [noscript] attribute OSType fileType;
+ [noscript] attribute OSType fileCreator;
+
+ /**
+ * launchWithDoc
+ *
+ * Launch the application that this file points to with a document.
+ *
+ * @param aDocToLoad Must not be NULL. If no document, use nsIFile::launch
+ * @param aLaunchInBackground TRUE if the application should not come to the front.
+ *
+ */
+ void launchWithDoc(in nsIFile aDocToLoad, in boolean aLaunchInBackground);
+
+ /**
+ * openDocWithApp
+ *
+ * Open the document that this file points to with the given application.
+ *
+ * @param aAppToOpenWith The application with which to open the document.
+ * If NULL, the creator code of the document is used
+ * to determine the application.
+ * @param aLaunchInBackground TRUE if the application should not come to the front.
+ *
+ */
+ void openDocWithApp(in nsIFile aAppToOpenWith, in boolean aLaunchInBackground);
+
+ /**
+ * isPackage
+ *
+ * returns true if a directory is determined to be a package under Mac OS 9/X
+ *
+ */
+ boolean isPackage();
+
+ /**
+ * bundleDisplayName
+ *
+ * returns the display name of the application bundle (usually the human
+ * readable name of the application)
+ */
+ readonly attribute AString bundleDisplayName;
+
+ /**
+ * bundleIdentifier
+ *
+ * returns the identifier of the bundle
+ */
+ readonly attribute AUTF8String bundleIdentifier;
+
+ /**
+ * Last modified time of a bundle's contents (as opposed to its package
+ * directory). Our convention is to make the bundle's Info.plist file
+ * stand in for the rest of its contents -- since this file contains the
+ * bundle's version information and other identifiers. For non-bundles
+ * this is the same as lastModifiedTime.
+ */
+ readonly attribute int64_t bundleContentsLastModifiedTime;
+};
+
+%{C++
+extern "C"
+{
+NS_EXPORT nsresult NS_NewLocalFileWithFSRef(const FSRef* aFSRef, bool aFollowSymlinks, nsILocalFileMac** result);
+NS_EXPORT nsresult NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowSymlinks, nsILocalFileMac** result);
+}
+%}
diff --git a/xpcom/io/nsILocalFileWin.idl b/xpcom/io/nsILocalFileWin.idl
new file mode 100644
index 000000000..c036cb96b
--- /dev/null
+++ b/xpcom/io/nsILocalFileWin.idl
@@ -0,0 +1,121 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include "nsILocalFile.idl"
+
+%{C++
+struct PRFileDesc;
+%}
+
+[ptr] native PRFileDescStar(PRFileDesc);
+
+[scriptable, builtinclass, uuid(e7a3a954-384b-4aeb-a5f7-55626b0de9be)]
+interface nsILocalFileWin : nsILocalFile
+{
+ /**
+ * initWithCommandLine
+ *
+ * Initialize this object based on the main app path of a commandline
+ * handler.
+ *
+ * @param aCommandLine
+ * the commandline to parse an app path out of.
+ */
+ void initWithCommandLine(in AString aCommandLine);
+ /**
+ * getVersionInfoValue
+ *
+ * Retrieve a metadata field from the file's VERSIONINFO block.
+ * Throws NS_ERROR_FAILURE if no value is found, or the value is empty.
+ *
+ * @param aField The field to look up.
+ *
+ */
+ AString getVersionInfoField(in string aField);
+
+ /**
+ * The canonical path of the file, which avoids short/long
+ * pathname inconsistencies. The nsIFile persistent
+ * descriptor is not guaranteed to be canonicalized (it may
+ * persist either the long or the short path name). The format of
+ * the canonical path will vary with the underlying file system:
+ * it will typically be the short pathname on filesystems that
+ * support both short and long path forms.
+ */
+ readonly attribute AString canonicalPath;
+ [noscript] readonly attribute ACString nativeCanonicalPath;
+
+ /**
+ * Windows specific file attributes.
+ */
+
+ /*
+ * WFA_SEARCH_INDEXED: Generally the default on files in Windows except
+ * those created in temp locations. Valid on XP and up. When set the
+ * file or directory is marked to be indexed by desktop search services.
+ */
+ const unsigned long WFA_SEARCH_INDEXED = 1;
+
+ /*
+ * WFA_READONLY: Whether the file is readonly or not.
+ */
+ const unsigned long WFA_READONLY = 2;
+
+ /*
+ * WFA_READWRITE: Used to clear the readonly attribute.
+ */
+ const unsigned long WFA_READWRITE = 4;
+
+ /**
+ * fileAttributesWin
+ *
+ * Set or get windows specific file attributes.
+ *
+ * Throws NS_ERROR_FILE_INVALID_PATH for an invalid file.
+ * Throws NS_ERROR_FAILURE if the set or get fails.
+ */
+ attribute unsigned long fileAttributesWin;
+
+ /**
+ * setShortcut
+ *
+ * Creates the specified shortcut, or updates it if it already exists.
+ *
+ * If the shortcut is being updated (i.e. the shortcut already exists),
+ * any excluded parameters will remain unchanged in the shortcut file.
+ * For example, if you want to change the description of a specific
+ * shortcut but keep the target, working dir, args, and icon the same,
+ * pass null for those parameters and only pass in a value for the
+ * description.
+ *
+ * If the shortcut does not already exist and targetFile is not specified,
+ * setShortcut will throw NS_ERROR_FILE_TARGET_DOES_NOT_EXIST.
+ *
+ * @param targetFile the path that the shortcut should target
+ * @param workingDir the working dir that should be set for the shortcut
+ * @param args the args string that should be set for the shortcut
+ * @param description the description that should be set for the shortcut
+ * @param iconFile the file containing an icon to be used for this
+ shortcut
+ * @param iconIndex this value selects a specific icon from within
+ iconFile. If iconFile contains only one icon, this
+ value should be 0.
+ */
+ void setShortcut([optional] in nsIFile targetFile,
+ [optional] in nsIFile workingDir,
+ [optional] in wstring args,
+ [optional] in wstring description,
+ [optional] in nsIFile iconFile,
+ [optional] in long iconIndex);
+
+ /**
+ * Identical to nsIFile::openNSPRFileDesc except it also uses the
+ * FILE_SHARE_DELETE flag.
+ */
+ [noscript] PRFileDescStar openNSPRFileDescShareDelete(in long flags,
+ in long mode);
+};
+
diff --git a/xpcom/io/nsIMultiplexInputStream.idl b/xpcom/io/nsIMultiplexInputStream.idl
new file mode 100644
index 000000000..d42adcbd4
--- /dev/null
+++ b/xpcom/io/nsIMultiplexInputStream.idl
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+
+/**
+ * The multiplex stream concatenates a list of input streams into a single
+ * stream.
+ */
+
+[scriptable, uuid(a076fd12-1dd1-11b2-b19a-d53b5dffaade)]
+interface nsIMultiplexInputStream : nsIInputStream
+{
+ /**
+ * Number of streams in this multiplex-stream
+ */
+ readonly attribute unsigned long count;
+
+ /**
+ * Appends a stream to the end of the streams. The cursor of the stream
+ * should be located at the beginning of the stream if the implementation
+ * of this nsIMultiplexInputStream also is used as an nsISeekableStream.
+ * @param stream stream to append
+ */
+ void appendStream(in nsIInputStream stream);
+
+ /**
+ * Insert a stream at specified index. If the cursor of this stream is at
+ * the beginning of the stream at index, the cursor will be placed at the
+ * beginning of the inserted stream instead.
+ * The cursor of the new stream should be located at the beginning of the
+ * stream if the implementation of this nsIMultiplexInputStream also is
+ * used as an nsISeekableStream.
+ * @param stream stream to insert
+ * @param index index to insert stream at, must be <= count
+ */
+ void insertStream(in nsIInputStream stream, in unsigned long index);
+
+ /**
+ * Remove stream at specified index. If this stream is the one currently
+ * being read the readcursor is moved to the beginning of the next
+ * stream
+ * @param index remove stream at this index, must be < count
+ */
+ void removeStream(in unsigned long index);
+
+ /**
+ * Get stream at specified index.
+ * @param index return stream at this index, must be < count
+ * @return stream at specified index
+ */
+ nsIInputStream getStream(in unsigned long index);
+};
diff --git a/xpcom/io/nsIOUtil.cpp b/xpcom/io/nsIOUtil.cpp
new file mode 100644
index 000000000..d583dd75b
--- /dev/null
+++ b/xpcom/io/nsIOUtil.cpp
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIOUtil.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsStreamUtils.h"
+
+NS_IMPL_ISUPPORTS(nsIOUtil, nsIIOUtil)
+
+NS_IMETHODIMP
+nsIOUtil::InputStreamIsBuffered(nsIInputStream* aStream, bool* aResult)
+{
+ if (NS_WARN_IF(!aStream)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aResult = NS_InputStreamIsBuffered(aStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsIOUtil::OutputStreamIsBuffered(nsIOutputStream* aStream, bool* aResult)
+{
+ if (NS_WARN_IF(!aStream)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aResult = NS_OutputStreamIsBuffered(aStream);
+ return NS_OK;
+}
diff --git a/xpcom/io/nsIOUtil.h b/xpcom/io/nsIOUtil.h
new file mode 100644
index 000000000..47b02a02e
--- /dev/null
+++ b/xpcom/io/nsIOUtil.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIOUtil_h__
+#define nsIOUtil_h__
+
+#define NS_IOUTIL_CID \
+{ 0xeb833911, 0x4f49, 0x4623, \
+ { 0x84, 0x5f, 0xe5, 0x8a, 0x8e, 0x6d, 0xe4, 0xc2 } }
+
+
+#include "nsIIOUtil.h"
+#include "mozilla/Attributes.h"
+
+class nsIOUtil final : public nsIIOUtil
+{
+ ~nsIOUtil() {}
+
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIOUTIL
+};
+
+#endif /* nsIOUtil_h__ */
diff --git a/xpcom/io/nsIObjectInputStream.idl b/xpcom/io/nsIObjectInputStream.idl
new file mode 100644
index 000000000..c482d3b89
--- /dev/null
+++ b/xpcom/io/nsIObjectInputStream.idl
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIBinaryInputStream.idl"
+
+/**
+ * @see nsIObjectOutputStream
+ * @see nsIBinaryInputStream
+ */
+
+[scriptable, uuid(6c248606-4eae-46fa-9df0-ba58502368eb)]
+interface nsIObjectInputStream : nsIBinaryInputStream
+{
+ /**
+ * Read an object from this stream to satisfy a strong or weak reference
+ * to one of its interfaces. If the interface was not along the primary
+ * inheritance chain ending in the "root" or XPCOM-identity nsISupports,
+ * readObject will QueryInterface from the deserialized object root to the
+ * correct interface, which was specified when the object was serialized.
+ *
+ * @see nsIObjectOutputStream
+ */
+ nsISupports readObject(in boolean aIsStrongRef);
+
+ [notxpcom] nsresult readID(out nsID aID);
+
+ /**
+ * Optimized deserialization support -- see nsIStreamBufferAccess.idl.
+ */
+ [notxpcom] charPtr getBuffer(in uint32_t aLength, in uint32_t aAlignMask);
+ [notxpcom] void putBuffer(in charPtr aBuffer, in uint32_t aLength);
+};
+
+%{C++
+
+inline nsresult
+NS_ReadOptionalObject(nsIObjectInputStream* aStream, bool aIsStrongRef,
+ nsISupports* *aResult)
+{
+ bool nonnull;
+ nsresult rv = aStream->ReadBoolean(&nonnull);
+ if (NS_SUCCEEDED(rv)) {
+ if (nonnull)
+ rv = aStream->ReadObject(aIsStrongRef, aResult);
+ else
+ *aResult = nullptr;
+ }
+ return rv;
+}
+
+%}
diff --git a/xpcom/io/nsIObjectOutputStream.idl b/xpcom/io/nsIObjectOutputStream.idl
new file mode 100644
index 000000000..3ef6711d4
--- /dev/null
+++ b/xpcom/io/nsIObjectOutputStream.idl
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIBinaryOutputStream.idl"
+
+/**
+ * @See nsIObjectInputStream
+ * @See nsIBinaryOutputStream
+ */
+
+[scriptable, uuid(92c898ac-5fde-4b99-87b3-5d486422094b)]
+interface nsIObjectOutputStream : nsIBinaryOutputStream
+{
+ /**
+ * Write the object whose "root" or XPCOM-identity nsISupports is aObject.
+ * The cause for writing this object is a strong or weak reference, so the
+ * aIsStrongRef argument must tell which kind of pointer is being followed
+ * here during serialization.
+ *
+ * If the object has only one strong reference in the serialization and no
+ * weak refs, use writeSingleRefObject. This is a valuable optimization:
+ * it saves space in the stream, and cycles on both ends of the process.
+ *
+ * If the reference being serialized is a pointer to an interface not on
+ * the primary inheritance chain ending in the root nsISupports, you must
+ * call writeCompoundObject instead of this method.
+ */
+ void writeObject(in nsISupports aObject, in boolean aIsStrongRef);
+
+ /**
+ * Write an object referenced singly and strongly via its root nsISupports
+ * or a subclass of its root nsISupports. There must not be other refs to
+ * aObject in memory, or in the serialization.
+ */
+ void writeSingleRefObject(in nsISupports aObject);
+
+ /**
+ * Write the object referenced by an interface pointer at aObject that
+ * inherits from a non-primary nsISupports, i.e., a reference to one of
+ * the multiply inherited interfaces derived from an nsISupports other
+ * than the root or XPCOM-identity nsISupports; or a reference to an
+ * inner object in the case of true XPCOM aggregation. aIID identifies
+ * this interface.
+ */
+ void writeCompoundObject(in nsISupports aObject,
+ in nsIIDRef aIID,
+ in boolean aIsStrongRef);
+
+ void writeID(in nsIDRef aID);
+
+ /**
+ * Optimized serialization support -- see nsIStreamBufferAccess.idl.
+ */
+ [notxpcom] charPtr getBuffer(in uint32_t aLength, in uint32_t aAlignMask);
+ [notxpcom] void putBuffer(in charPtr aBuffer, in uint32_t aLength);
+};
+
+%{C++
+
+inline nsresult
+NS_WriteOptionalObject(nsIObjectOutputStream* aStream, nsISupports* aObject,
+ bool aIsStrongRef)
+{
+ bool nonnull = (aObject != nullptr);
+ nsresult rv = aStream->WriteBoolean(nonnull);
+ if (NS_SUCCEEDED(rv) && nonnull)
+ rv = aStream->WriteObject(aObject, aIsStrongRef);
+ return rv;
+}
+
+inline nsresult
+NS_WriteOptionalSingleRefObject(nsIObjectOutputStream* aStream,
+ nsISupports* aObject)
+{
+ bool nonnull = (aObject != nullptr);
+ nsresult rv = aStream->WriteBoolean(nonnull);
+ if (NS_SUCCEEDED(rv) && nonnull)
+ rv = aStream->WriteSingleRefObject(aObject);
+ return rv;
+}
+
+inline nsresult
+NS_WriteOptionalCompoundObject(nsIObjectOutputStream* aStream,
+ nsISupports* aObject,
+ const nsIID& aIID,
+ bool aIsStrongRef)
+{
+ bool nonnull = (aObject != nullptr);
+ nsresult rv = aStream->WriteBoolean(nonnull);
+ if (NS_SUCCEEDED(rv) && nonnull)
+ rv = aStream->WriteCompoundObject(aObject, aIID, aIsStrongRef);
+ return rv;
+}
+
+%}
diff --git a/xpcom/io/nsIOutputStream.idl b/xpcom/io/nsIOutputStream.idl
new file mode 100644
index 000000000..0e04a3910
--- /dev/null
+++ b/xpcom/io/nsIOutputStream.idl
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIOutputStream;
+interface nsIInputStream;
+
+%{C++
+/**
+ * The signature for the reader function passed to WriteSegments. This
+ * is the "provider" of data that gets written into the stream's buffer.
+ *
+ * @param aOutStream stream being written to
+ * @param aClosure opaque parameter passed to WriteSegments
+ * @param aToSegment pointer to memory owned by the output stream
+ * @param aFromOffset amount already written (since WriteSegments was called)
+ * @param aCount length of toSegment
+ * @param aReadCount number of bytes written
+ *
+ * Implementers should return the following:
+ *
+ * @throws <any-error> if not interested in providing any data
+ *
+ * Errors are never passed to the caller of WriteSegments.
+ */
+typedef nsresult (*nsReadSegmentFun)(nsIOutputStream *aOutStream,
+ void *aClosure,
+ char *aToSegment,
+ uint32_t aFromOffset,
+ uint32_t aCount,
+ uint32_t *aReadCount);
+%}
+
+native nsReadSegmentFun(nsReadSegmentFun);
+
+/**
+ * nsIOutputStream
+ *
+ * An interface describing a writable stream of data. An output stream may be
+ * "blocking" or "non-blocking" (see the IsNonBlocking method). A blocking
+ * output stream may suspend the calling thread in order to satisfy a call to
+ * Close, Flush, Write, WriteFrom, or WriteSegments. A non-blocking output
+ * stream, on the other hand, must not block the calling thread of execution.
+ *
+ * NOTE: blocking output streams are often written to on a background thread to
+ * avoid locking up the main application thread. For this reason, it is
+ * generally the case that a blocking output stream should be implemented using
+ * thread- safe AddRef and Release.
+ */
+[scriptable, uuid(0d0acd2a-61b4-11d4-9877-00c04fa0cf4a)]
+interface nsIOutputStream : nsISupports
+{
+ /**
+ * Close the stream. Forces the output stream to flush any buffered data.
+ *
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if unable to flush without blocking
+ * the calling thread (non-blocking mode only)
+ */
+ void close();
+
+ /**
+ * Flush the stream.
+ *
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if unable to flush without blocking
+ * the calling thread (non-blocking mode only)
+ */
+ void flush();
+
+ /**
+ * Write data into the stream.
+ *
+ * @param aBuf the buffer containing the data to be written
+ * @param aCount the maximum number of bytes to be written
+ *
+ * @return number of bytes written (may be less than aCount)
+ *
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if writing to the output stream would
+ * block the calling thread (non-blocking mode only)
+ * @throws <other-error> on failure
+ */
+ unsigned long write(in string aBuf, in unsigned long aCount);
+
+ /**
+ * Writes data into the stream from an input stream.
+ *
+ * @param aFromStream the stream containing the data to be written
+ * @param aCount the maximum number of bytes to be written
+ *
+ * @return number of bytes written (may be less than aCount)
+ *
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if writing to the output stream would
+ * block the calling thread (non-blocking mode only). This failure
+ * means no bytes were transferred.
+ * @throws <other-error> on failure
+ *
+ * NOTE: This method is defined by this interface in order to allow the
+ * output stream to efficiently copy the data from the input stream into
+ * its internal buffer (if any). If this method was provided as an external
+ * facility, a separate char* buffer would need to be used in order to call
+ * the output stream's other Write method.
+ */
+ unsigned long writeFrom(in nsIInputStream aFromStream,
+ in unsigned long aCount);
+
+ /**
+ * Low-level write method that has access to the stream's underlying buffer.
+ * The reader function may be called multiple times for segmented buffers.
+ * WriteSegments is expected to keep calling the reader until either there
+ * is nothing left to write or the reader returns an error. WriteSegments
+ * should not call the reader with zero bytes to provide.
+ *
+ * @param aReader the "provider" of the data to be written
+ * @param aClosure opaque parameter passed to reader
+ * @param aCount the maximum number of bytes to be written
+ *
+ * @return number of bytes written (may be less than aCount)
+ *
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if writing to the output stream would
+ * block the calling thread (non-blocking mode only). This failure
+ * means no bytes were transferred.
+ * @throws NS_ERROR_NOT_IMPLEMENTED if the stream has no underlying buffer
+ * @throws <other-error> on failure
+ *
+ * NOTE: this function may be unimplemented if a stream has no underlying
+ * buffer (e.g., socket output stream).
+ */
+ [noscript] unsigned long writeSegments(in nsReadSegmentFun aReader,
+ in voidPtr aClosure,
+ in unsigned long aCount);
+
+ /**
+ * @return true if stream is non-blocking
+ *
+ * NOTE: writing to a blocking output stream will block the calling thread
+ * until all given data can be consumed by the stream.
+ *
+ * NOTE: a non-blocking output stream may implement nsIAsyncOutputStream to
+ * provide consumers with a way to wait for the stream to accept more data
+ * once its write method is unable to accept any data without blocking.
+ */
+ boolean isNonBlocking();
+};
diff --git a/xpcom/io/nsIPipe.idl b/xpcom/io/nsIPipe.idl
new file mode 100644
index 000000000..596be92e9
--- /dev/null
+++ b/xpcom/io/nsIPipe.idl
@@ -0,0 +1,171 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAsyncInputStream;
+interface nsIAsyncOutputStream;
+
+/**
+ * nsIPipe represents an in-process buffer that can be read using nsIInputStream
+ * and written using nsIOutputStream. The reader and writer of a pipe do not
+ * have to be on the same thread. As a result, the pipe is an ideal mechanism
+ * to bridge data exchange between two threads. For example, a worker thread
+ * might write data to a pipe from which the main thread will read.
+ *
+ * Each end of the pipe can be either blocking or non-blocking. Recall that a
+ * non-blocking stream will return NS_BASE_STREAM_WOULD_BLOCK if it cannot be
+ * read or written to without blocking the calling thread. For example, if you
+ * try to read from an empty pipe that has not yet been closed, then if that
+ * pipe's input end is non-blocking, then the read call will fail immediately
+ * with NS_BASE_STREAM_WOULD_BLOCK as the error condition. However, if that
+ * pipe's input end is blocking, then the read call will not return until the
+ * pipe has data or until the pipe is closed. This example presumes that the
+ * pipe is being filled asynchronously on some background thread.
+ *
+ * The pipe supports nsIAsyncInputStream and nsIAsyncOutputStream, which give
+ * the user of a non-blocking pipe the ability to wait for the pipe to become
+ * ready again. For example, in the case of an empty non-blocking pipe, the
+ * user can call AsyncWait on the input end of the pipe to be notified when
+ * the pipe has data to read (or when the pipe becomes closed).
+ *
+ * NS_NewPipe2 and NS_NewPipe provide convenient pipe constructors. In most
+ * cases nsIPipe is not actually used. It is usually enough to just get
+ * references to the pipe's input and output end. In which case, the pipe is
+ * automatically closed when the respective pipe ends are released.
+ */
+[scriptable, uuid(25d0de93-685e-4ea4-95d3-d884e31df63c)]
+interface nsIPipe : nsISupports
+{
+ /**
+ * initialize this pipe
+ *
+ * @param nonBlockingInput
+ * true specifies non-blocking input stream behavior
+ * @param nonBlockingOutput
+ * true specifies non-blocking output stream behavior
+ * @param segmentSize
+ * specifies the segment size in bytes (pass 0 to use default value)
+ * @param segmentCount
+ * specifies the max number of segments (pass 0 to use default
+ * value). Passing UINT32_MAX here causes the pipe to have
+ * "infinite" space. This mode can be useful in some cases, but
+ * should always be used with caution. The default value for this
+ * parameter is a finite value.
+ */
+ [must_use] void init(in boolean nonBlockingInput,
+ in boolean nonBlockingOutput,
+ in unsigned long segmentSize,
+ in unsigned long segmentCount);
+
+ /**
+ * The pipe's input end, which also implements nsISearchableInputStream.
+ * Getting fails if the pipe hasn't been initialized.
+ */
+ [must_use] readonly attribute nsIAsyncInputStream inputStream;
+
+ /**
+ * The pipe's output end. Getting fails if the pipe hasn't been
+ * initialized.
+ */
+ [must_use] readonly attribute nsIAsyncOutputStream outputStream;
+};
+
+/**
+ * XXX this interface doesn't really belong in here. It is here because
+ * currently nsPipeInputStream is the only implementation of this interface.
+ */
+[scriptable, uuid(8C39EF62-F7C9-11d4-98F5-001083010E9B)]
+interface nsISearchableInputStream : nsISupports
+{
+ /**
+ * Searches for a string in the input stream. Since the stream has a notion
+ * of EOF, it is possible that the string may at some time be in the
+ * buffer, but is is not currently found up to some offset. Consequently,
+ * both the found and not found cases return an offset:
+ * if found, return offset where it was found
+ * if not found, return offset of the first byte not searched
+ * In the case the stream is at EOF and the string is not found, the first
+ * byte not searched will correspond to the length of the buffer.
+ */
+ void search(in string forString,
+ in boolean ignoreCase,
+ out boolean found,
+ out unsigned long offsetSearchedTo);
+};
+
+%{C++
+
+class nsIInputStream;
+class nsIOutputStream;
+
+/**
+ * NS_NewPipe2
+ *
+ * This function supersedes NS_NewPipe. It differs from NS_NewPipe in two
+ * major ways:
+ * (1) returns nsIAsyncInputStream and nsIAsyncOutputStream, so it is
+ * not necessary to QI in order to access these interfaces.
+ * (2) the size of the pipe is determined by the number of segments
+ * times the size of each segment.
+ *
+ * @param pipeIn
+ * resulting input end of the pipe
+ * @param pipeOut
+ * resulting output end of the pipe
+ * @param nonBlockingInput
+ * true specifies non-blocking input stream behavior
+ * @param nonBlockingOutput
+ * true specifies non-blocking output stream behavior
+ * @param segmentSize
+ * specifies the segment size in bytes (pass 0 to use default value)
+ * @param segmentCount
+ * specifies the max number of segments (pass 0 to use default value)
+ * passing UINT32_MAX here causes the pipe to have "infinite" space.
+ * this mode can be useful in some cases, but should always be used with
+ * caution. the default value for this parameter is a finite value.
+ */
+extern MOZ_MUST_USE nsresult
+NS_NewPipe2(nsIAsyncInputStream **pipeIn,
+ nsIAsyncOutputStream **pipeOut,
+ bool nonBlockingInput = false,
+ bool nonBlockingOutput = false,
+ uint32_t segmentSize = 0,
+ uint32_t segmentCount = 0);
+
+/**
+ * NS_NewPipe
+ *
+ * Preserved for backwards compatibility. Plus, this interface is more
+ * amiable in certain contexts (e.g., when you don't need the pipe's async
+ * capabilities).
+ *
+ * @param pipeIn
+ * resulting input end of the pipe
+ * @param pipeOut
+ * resulting output end of the pipe
+ * @param segmentSize
+ * specifies the segment size in bytes (pass 0 to use default value)
+ * @param maxSize
+ * specifies the max size of the pipe (pass 0 to use default value)
+ * number of segments is maxSize / segmentSize, and maxSize must be a
+ * multiple of segmentSize. passing UINT32_MAX here causes the
+ * pipe to have "infinite" space. this mode can be useful in some
+ * cases, but should always be used with caution. the default value
+ * for this parameter is a finite value.
+ * @param nonBlockingInput
+ * true specifies non-blocking input stream behavior
+ * @param nonBlockingOutput
+ * true specifies non-blocking output stream behavior
+ */
+extern MOZ_MUST_USE nsresult
+NS_NewPipe(nsIInputStream **pipeIn,
+ nsIOutputStream **pipeOut,
+ uint32_t segmentSize = 0,
+ uint32_t maxSize = 0,
+ bool nonBlockingInput = false,
+ bool nonBlockingOutput = false);
+
+%}
diff --git a/xpcom/io/nsISafeOutputStream.idl b/xpcom/io/nsISafeOutputStream.idl
new file mode 100644
index 000000000..c62d85f07
--- /dev/null
+++ b/xpcom/io/nsISafeOutputStream.idl
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface provides a mechanism to control an output stream
+ * that takes care not to overwrite an existing target until it is known
+ * that all writes to the destination succeeded.
+ *
+ * An object that supports this interface is intended to also support
+ * nsIOutputStream.
+ *
+ * For example, a file output stream that supports this interface writes to
+ * a temporary file, and moves it over the original file when |finish| is
+ * called only if the stream can be successfully closed and all writes
+ * succeeded. If |finish| is called but something went wrong during
+ * writing, it will delete the temporary file and not touch the original.
+ * If the stream is closed by calling |close| directly, or the stream
+ * goes away, the original file will not be overwritten, and the temporary
+ * file will be deleted.
+ *
+ * Currently, this interface is implemented only for file output streams.
+ */
+[scriptable, uuid(5f914307-5c34-4e1f-8e32-ec749d25b27a)]
+interface nsISafeOutputStream : nsISupports
+{
+ /**
+ * Call this method to close the stream and cause the original target
+ * to be overwritten. Note: if any call to |write| failed to write out
+ * all of the data given to it, then calling this method will |close| the
+ * stream and return failure. Further, if closing the stream fails, this
+ * method will return failure. The original target will be overwritten only
+ * if all calls to |write| succeeded and the stream was successfully closed.
+ */
+ void finish();
+};
diff --git a/xpcom/io/nsIScriptableBase64Encoder.idl b/xpcom/io/nsIScriptableBase64Encoder.idl
new file mode 100644
index 000000000..641451805
--- /dev/null
+++ b/xpcom/io/nsIScriptableBase64Encoder.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+
+/**
+ * nsIScriptableBase64Encoder efficiently encodes the contents
+ * of a nsIInputStream to a Base64 string. This avoids the need
+ * to read the entire stream into a buffer, and only then do the
+ * Base64 encoding.
+ *
+ * If you already have a buffer full of data, you should use
+ * btoa instead!
+ */
+[scriptable, uuid(9479c864-d1f9-45ab-b7b9-28b907bd2ba9)]
+interface nsIScriptableBase64Encoder : nsISupports
+{
+ /**
+ * These methods take an nsIInputStream and return a narrow or wide
+ * string with the contents of the nsIInputStream base64 encoded.
+ *
+ * The stream passed in must support ReadSegments and must not be
+ * a non-blocking stream that will return NS_BASE_STREAM_WOULD_BLOCK.
+ * If either of these restrictions are violated we will abort.
+ */
+ ACString encodeToCString(in nsIInputStream stream, in unsigned long length);
+ AString encodeToString(in nsIInputStream stream, in unsigned long length);
+};
diff --git a/xpcom/io/nsIScriptableInputStream.idl b/xpcom/io/nsIScriptableInputStream.idl
new file mode 100644
index 000000000..d9248f425
--- /dev/null
+++ b/xpcom/io/nsIScriptableInputStream.idl
@@ -0,0 +1,67 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+
+/**
+ * nsIScriptableInputStream provides scriptable access to an nsIInputStream
+ * instance.
+ */
+[scriptable, uuid(3fce9015-472a-4080-ac3e-cd875dbe361e)]
+interface nsIScriptableInputStream : nsISupports
+{
+ /**
+ * Closes the stream.
+ */
+ void close();
+
+ /**
+ * Wrap the given nsIInputStream with this nsIScriptableInputStream.
+ *
+ * @param aInputStream parameter providing the stream to wrap
+ */
+ void init(in nsIInputStream aInputStream);
+
+ /**
+ * Return the number of bytes currently available in the stream
+ *
+ * @return the number of bytes
+ *
+ * @throws NS_BASE_STREAM_CLOSED if called after the stream has been closed
+ */
+ unsigned long long available();
+
+ /**
+ * Read data from the stream.
+ *
+ * WARNING: If the data contains a null byte, then this method will return
+ * a truncated string.
+ *
+ * @param aCount the maximum number of bytes to read
+ *
+ * @return the data, which will be an empty string if the stream is at EOF.
+ *
+ * @throws NS_BASE_STREAM_CLOSED if called after the stream has been closed
+ * @throws NS_ERROR_NOT_INITIALIZED if init was not called
+ */
+ string read(in unsigned long aCount);
+
+ /**
+ * Read data from the stream, including NULL bytes.
+ *
+ * @param aCount the maximum number of bytes to read.
+ *
+ * @return the data from the stream, which will be an empty string if EOF
+ * has been reached.
+ *
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream
+ * would block the calling thread (non-blocking mode only).
+ * @throws NS_ERROR_FAILURE if there are not enough bytes available to read
+ * aCount amount of data.
+ */
+ ACString readBytes(in unsigned long aCount);
+};
diff --git a/xpcom/io/nsISeekableStream.idl b/xpcom/io/nsISeekableStream.idl
new file mode 100644
index 000000000..1be7ea60c
--- /dev/null
+++ b/xpcom/io/nsISeekableStream.idl
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/*
+ * nsISeekableStream
+ *
+ * Note that a stream might not implement all methods (e.g., a readonly stream
+ * won't implement setEOF)
+ */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(8429d350-1040-4661-8b71-f2a6ba455980)]
+interface nsISeekableStream : nsISupports
+{
+ /*
+ * Sets the stream pointer to the value of the 'offset' parameter
+ */
+ const int32_t NS_SEEK_SET = 0;
+
+ /*
+ * Sets the stream pointer to its current location plus the value
+ * of the offset parameter.
+ */
+ const int32_t NS_SEEK_CUR = 1;
+
+ /*
+ * Sets the stream pointer to the size of the stream plus the value
+ * of the offset parameter.
+ */
+ const int32_t NS_SEEK_END = 2;
+
+ /**
+ * seek
+ *
+ * This method moves the stream offset of the steam implementing this
+ * interface.
+ *
+ * @param whence specifies how to interpret the 'offset' parameter in
+ * setting the stream offset associated with the implementing
+ * stream.
+ *
+ * @param offset specifies a value, in bytes, that is used in conjunction
+ * with the 'whence' parameter to set the stream offset of the
+ * implementing stream. A negative value causes seeking in
+ * the reverse direction.
+ *
+ * @throws NS_BASE_STREAM_CLOSED if called on a closed stream.
+ */
+ void seek(in long whence, in long long offset);
+
+ /**
+ * tell
+ *
+ * This method reports the current offset, in bytes, from the start of the
+ * stream.
+ *
+ * @throws NS_BASE_STREAM_CLOSED if called on a closed stream.
+ */
+ long long tell();
+
+
+ /**
+ * setEOF
+ *
+ * This method truncates the stream at the current offset.
+ *
+ * @throws NS_BASE_STREAM_CLOSED if called on a closed stream.
+ */
+ void setEOF();
+};
diff --git a/xpcom/io/nsIStorageStream.idl b/xpcom/io/nsIStorageStream.idl
new file mode 100644
index 000000000..7d5061e23
--- /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 000000000..c37afd8dc
--- /dev/null
+++ b/xpcom/io/nsIStreamBufferAccess.idl
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * An interface for access to a buffering stream implementation's underlying
+ * memory buffer.
+ *
+ * Stream implementations that QueryInterface to nsIStreamBufferAccess must
+ * ensure that all buffers are aligned on the most restrictive type size for
+ * the current architecture (e.g., sizeof(double) for RISCy CPUs). malloc(3)
+ * satisfies this requirement.
+ */
+[scriptable, uuid(ac923b72-ac87-4892-ac7a-ca385d429435)]
+interface nsIStreamBufferAccess : nsISupports
+{
+ /**
+ * Get access to a contiguous, aligned run of bytes in the stream's buffer.
+ * Exactly one successful getBuffer call must occur before a putBuffer call
+ * taking the non-null pointer returned by the successful getBuffer.
+ *
+ * The run of bytes are the next bytes (modulo alignment padding) to read
+ * for an input stream, and the next bytes (modulo alignment padding) to
+ * store before (eventually) writing buffered data to an output stream.
+ * There can be space beyond this run of bytes in the buffer for further
+ * accesses before the fill or flush point is reached.
+ *
+ * @param aLength
+ * Count of contiguous bytes requested at the address A that satisfies
+ * (A & aAlignMask) == 0 in the buffer, starting from the current stream
+ * position, mapped to a buffer address B. The stream implementation
+ * must pad from B to A by skipping bytes (if input stream) or storing
+ * zero bytes (if output stream).
+ *
+ * @param aAlignMask
+ * Bit-mask computed by subtracting 1 from the power-of-two alignment
+ * modulus (e.g., 3 or sizeof(uint32_t)-1 for uint32_t alignment).
+ *
+ * @return
+ * The aligned pointer to aLength bytes in the buffer, or null if the
+ * buffer has no room for aLength bytes starting at the next address A
+ * after the current position that satisfies (A & aAlignMask) == 0.
+ */
+ [notxpcom,noscript] charPtr getBuffer(in uint32_t aLength, in uint32_t aAlignMask);
+
+ /**
+ * Relinquish access to the stream's buffer, filling if at end of an input
+ * buffer, flushing if completing an output buffer. After a getBuffer call
+ * that returns non-null, putBuffer must be called.
+ *
+ * @param aBuffer
+ * A non-null pointer returned by getBuffer on the same stream buffer
+ * access object.
+ *
+ * @param aLength
+ * The same count of contiguous bytes passed to the getBuffer call that
+ * returned aBuffer.
+ */
+ [notxpcom,noscript] void putBuffer(in charPtr aBuffer, in uint32_t aLength);
+
+ /**
+ * Disable and enable buffering on the stream implementing this interface.
+ * DisableBuffering flushes an output stream's buffer, and invalidates an
+ * input stream's buffer.
+ */
+ void disableBuffering();
+ void enableBuffering();
+
+ /**
+ * The underlying, unbuffered input or output stream.
+ */
+ readonly attribute nsISupports unbufferedStream;
+};
+
+%{C++
+
+/**
+ * These macros get and put a buffer given either an sba parameter that may
+ * point to an object implementing nsIStreamBufferAccess, nsIObjectInputStream,
+ * or nsIObjectOutputStream.
+ */
+#define NS_GET_BUFFER(sba,n,a) ((sba)->GetBuffer(n, a))
+#define NS_PUT_BUFFER(sba,p,n) ((sba)->PutBuffer(p, n))
+
+%}
diff --git a/xpcom/io/nsIStringStream.idl b/xpcom/io/nsIStringStream.idl
new file mode 100644
index 000000000..d43200382
--- /dev/null
+++ b/xpcom/io/nsIStringStream.idl
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+
+%{C++
+#include "mozilla/MemoryReporting.h"
+%}
+
+native MallocSizeOf(mozilla::MallocSizeOf);
+
+/**
+ * nsIStringInputStream
+ *
+ * Provides scriptable and specialized C++-only methods for initializing a
+ * nsIInputStream implementation with a simple character array.
+ */
+[scriptable, builtinclass, uuid(450cd2d4-f0fd-424d-b365-b1251f80fd53)]
+interface nsIStringInputStream : nsIInputStream
+{
+ /**
+ * SetData - assign data to the input stream (copied on assignment).
+ *
+ * @param data - stream data
+ * @param dataLen - stream data length (-1 if length should be computed)
+ *
+ * NOTE: C++ code should consider using AdoptData or ShareData to avoid
+ * making an extra copy of the stream data.
+ *
+ * NOTE: For JS callers, the given data must not contain null characters
+ * (other than a null terminator) because a null character in the middle of
+ * the data string will be seen as a terminator when the data is converted
+ * from a JS string to a C++ character array.
+ */
+ void setData(in string data, in long dataLen);
+
+ /**
+ * NOTE: the following methods are designed to give C++ code added control
+ * over the ownership and lifetime of the stream data. Use with care :-)
+ */
+
+ /**
+ * AdoptData - assign data to the input stream. the input stream takes
+ * ownership of the given data buffer and will free it when
+ * the input stream is destroyed.
+ *
+ * @param data - stream data
+ * @param dataLen - stream data length (-1 if length should be computed)
+ */
+ [noscript] void adoptData(in charPtr data, in long dataLen);
+
+ /**
+ * ShareData - assign data to the input stream. the input stream references
+ * the given data buffer until the input stream is destroyed. the given
+ * data buffer must outlive the input stream.
+ *
+ * @param data - stream data
+ * @param dataLen - stream data length (-1 if length should be computed)
+ */
+ [noscript] void shareData(in string data, in long dataLen);
+
+ [noscript, notxpcom]
+ size_t SizeOfIncludingThis(in MallocSizeOf aMallocSizeOf);
+};
diff --git a/xpcom/io/nsIUnicharInputStream.idl b/xpcom/io/nsIUnicharInputStream.idl
new file mode 100644
index 000000000..8d0459fed
--- /dev/null
+++ b/xpcom/io/nsIUnicharInputStream.idl
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIUnicharInputStream;
+interface nsIInputStream;
+
+%{C++
+/**
+ * The signature of the writer function passed to ReadSegments. This
+ * is the "consumer" of data that gets read from the stream's buffer.
+ *
+ * @param aInStream stream being read
+ * @param aClosure opaque parameter passed to ReadSegments
+ * @param aFromSegment pointer to memory owned by the input stream
+ * @param aToOffset amount already read (since ReadSegments was called)
+ * @param aCount length of fromSegment
+ * @param aWriteCount number of bytes read
+ *
+ * Implementers should return the following:
+ *
+ * @throws <any-error> if not interested in consuming any data
+ *
+ * Errors are never passed to the caller of ReadSegments.
+ *
+ * NOTE: returning NS_OK and (*aWriteCount = 0) has undefined behavior.
+ */
+typedef nsresult (*nsWriteUnicharSegmentFun)(nsIUnicharInputStream *aInStream,
+ void *aClosure,
+ const char16_t *aFromSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t *aWriteCount);
+%}
+native nsWriteUnicharSegmentFun(nsWriteUnicharSegmentFun);
+
+/**
+ * Abstract unicode character input stream
+ * @see nsIInputStream
+ */
+[scriptable, uuid(d5e3bd80-6723-4b92-b0c9-22f6162fd94f)]
+interface nsIUnicharInputStream : nsISupports {
+ /**
+ * Reads into a caller-provided character array.
+ *
+ * @return The number of characters that were successfully read. May be less
+ * than aCount, even if there is more data in the input stream.
+ * A return value of 0 means EOF.
+ *
+ * @note To read more than 2^32 characters, call this method multiple times.
+ */
+ [noscript] unsigned long read([array, size_is(aCount)] in char16_t aBuf,
+ in unsigned long aCount);
+
+ /**
+ * Low-level read method that has access to the stream's underlying buffer.
+ * The writer function may be called multiple times for segmented buffers.
+ * ReadSegments is expected to keep calling the writer until either there is
+ * nothing left to read or the writer returns an error. ReadSegments should
+ * not call the writer with zero characters to consume.
+ *
+ * @param aWriter the "consumer" of the data to be read
+ * @param aClosure opaque parameter passed to writer
+ * @param aCount the maximum number of characters to be read
+ *
+ * @return number of characters read (may be less than aCount)
+ * @return 0 if reached end of file (or if aWriter refused to consume data)
+ *
+ * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from the input stream would
+ * block the calling thread (non-blocking mode only)
+ * @throws <other-error> on failure
+ *
+ * NOTE: this function may be unimplemented if a stream has no underlying
+ * buffer
+ */
+ [noscript] unsigned long readSegments(in nsWriteUnicharSegmentFun aWriter,
+ in voidPtr aClosure,
+ in unsigned long aCount);
+
+ /**
+ * Read into a string object.
+ * @param aCount The number of characters that should be read
+ * @return The number of characters that were read.
+ */
+ unsigned long readString(in unsigned long aCount, out AString aString);
+
+ /**
+ * Close the stream and free associated resources. This also closes the
+ * underlying stream, if any.
+ */
+ void close();
+};
diff --git a/xpcom/io/nsIUnicharLineInputStream.idl b/xpcom/io/nsIUnicharLineInputStream.idl
new file mode 100644
index 000000000..34a67f099
--- /dev/null
+++ b/xpcom/io/nsIUnicharLineInputStream.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(67f42475-ba80-40f8-ac0b-649c89230184)]
+interface nsIUnicharLineInputStream : nsISupports
+{
+ /**
+ * Read a single line from the stream, where a line is a
+ * possibly zero length sequence of characters terminated by a
+ * CR, LF, CRLF, LFCR, or eof.
+ * The line terminator is not returned.
+ * @retval false
+ * End of file. This line is the last line of the file
+ * (aLine is valid).
+ * @retval true
+ * The file contains further lines.
+ * @note Do not mix readLine with other read functions.
+ * Doing so can cause various problems and is not supported.
+ */
+ boolean readLine(out AString aLine);
+};
diff --git a/xpcom/io/nsIUnicharOutputStream.idl b/xpcom/io/nsIUnicharOutputStream.idl
new file mode 100644
index 000000000..599224dd0
--- /dev/null
+++ b/xpcom/io/nsIUnicharOutputStream.idl
@@ -0,0 +1,47 @@
+/* vim:set expandtab ts=4 sw=4 sts=4 cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * An interface that allows writing unicode data.
+ */
+[scriptable, uuid(2d00b1bb-8b21-4a63-bcc6-7213f513ac2e)]
+interface nsIUnicharOutputStream : nsISupports
+{
+ /**
+ * Write a single character to the stream. When writing many characters,
+ * prefer the string-taking write method.
+ *
+ * @retval true The character was written successfully
+ * @retval false Not all bytes of the character could be written.
+ */
+ boolean write(in unsigned long aCount,
+ [const, array, size_is(aCount)] in char16_t c);
+
+ /**
+ * Write a string to the stream.
+ *
+ * @retval true The string was written successfully
+ * @retval false Not all bytes of the string could be written.
+ */
+ boolean writeString(in AString str);
+
+ /**
+ * Flush the stream. This finishes the conversion and writes any bytes that
+ * finish the current byte sequence.
+ *
+ * It does NOT flush the underlying stream.
+ *
+ * @see nsIUnicodeEncoder::Finish
+ */
+ void flush();
+
+ /**
+ * Close the stream and free associated resources. This also closes the
+ * underlying stream.
+ */
+ void close();
+};
diff --git a/xpcom/io/nsInputStreamTee.cpp b/xpcom/io/nsInputStreamTee.cpp
new file mode 100644
index 000000000..8f01cb87e
--- /dev/null
+++ b/xpcom/io/nsInputStreamTee.cpp
@@ -0,0 +1,366 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdlib.h>
+#include "mozilla/Logging.h"
+
+#include "mozilla/Mutex.h"
+#include "mozilla/Attributes.h"
+#include "nsIInputStreamTee.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsIEventTarget.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+#ifdef LOG
+#undef LOG
+#endif
+
+static LazyLogModule sTeeLog("nsInputStreamTee");
+#define LOG(args) MOZ_LOG(sTeeLog, mozilla::LogLevel::Debug, args)
+
+class nsInputStreamTee final : public nsIInputStreamTee
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAMTEE
+
+ nsInputStreamTee();
+ bool SinkIsValid();
+ void InvalidateSink();
+
+private:
+ ~nsInputStreamTee()
+ {
+ }
+
+ nsresult TeeSegment(const char* aBuf, uint32_t aCount);
+
+ static nsresult WriteSegmentFun(nsIInputStream*, void*, const char*,
+ uint32_t, uint32_t, uint32_t*);
+
+private:
+ nsCOMPtr<nsIInputStream> mSource;
+ nsCOMPtr<nsIOutputStream> mSink;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+ nsWriteSegmentFun mWriter; // for implementing ReadSegments
+ void* mClosure; // for implementing ReadSegments
+ nsAutoPtr<Mutex> mLock; // synchronize access to mSinkIsValid
+ bool mSinkIsValid; // False if TeeWriteEvent fails
+};
+
+class nsInputStreamTeeWriteEvent : public Runnable
+{
+public:
+ // aTee's lock is held across construction of this object
+ nsInputStreamTeeWriteEvent(const char* aBuf, uint32_t aCount,
+ nsIOutputStream* aSink, nsInputStreamTee* aTee)
+ {
+ // copy the buffer - will be free'd by dtor
+ mBuf = (char*)malloc(aCount);
+ if (mBuf) {
+ memcpy(mBuf, (char*)aBuf, aCount);
+ }
+ mCount = aCount;
+ mSink = aSink;
+ bool isNonBlocking;
+ mSink->IsNonBlocking(&isNonBlocking);
+ NS_ASSERTION(isNonBlocking == false, "mSink is nonblocking");
+ mTee = aTee;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (!mBuf) {
+ NS_WARNING("nsInputStreamTeeWriteEvent::Run() "
+ "memory not allocated\n");
+ return NS_OK;
+ }
+ MOZ_ASSERT(mSink, "mSink is null!");
+
+ // The output stream could have been invalidated between when
+ // this event was dispatched and now, so check before writing.
+ if (!mTee->SinkIsValid()) {
+ return NS_OK;
+ }
+
+ LOG(("nsInputStreamTeeWriteEvent::Run() [%p]"
+ "will write %u bytes to %p\n",
+ this, mCount, mSink.get()));
+
+ uint32_t totalBytesWritten = 0;
+ while (mCount) {
+ nsresult rv;
+ uint32_t bytesWritten = 0;
+ rv = mSink->Write(mBuf + totalBytesWritten, mCount, &bytesWritten);
+ if (NS_FAILED(rv)) {
+ LOG(("nsInputStreamTeeWriteEvent::Run[%p] error %x in writing",
+ this, rv));
+ mTee->InvalidateSink();
+ break;
+ }
+ totalBytesWritten += bytesWritten;
+ NS_ASSERTION(bytesWritten <= mCount, "wrote too much");
+ mCount -= bytesWritten;
+ }
+ return NS_OK;
+ }
+
+protected:
+ virtual ~nsInputStreamTeeWriteEvent()
+ {
+ if (mBuf) {
+ free(mBuf);
+ }
+ mBuf = nullptr;
+ }
+
+private:
+ char* mBuf;
+ uint32_t mCount;
+ nsCOMPtr<nsIOutputStream> mSink;
+ // back pointer to the tee that created this runnable
+ RefPtr<nsInputStreamTee> mTee;
+};
+
+nsInputStreamTee::nsInputStreamTee(): mLock(nullptr)
+ , mSinkIsValid(true)
+{
+}
+
+bool
+nsInputStreamTee::SinkIsValid()
+{
+ MutexAutoLock lock(*mLock);
+ return mSinkIsValid;
+}
+
+void
+nsInputStreamTee::InvalidateSink()
+{
+ MutexAutoLock lock(*mLock);
+ mSinkIsValid = false;
+}
+
+nsresult
+nsInputStreamTee::TeeSegment(const char* aBuf, uint32_t aCount)
+{
+ if (!mSink) {
+ return NS_OK; // nothing to do
+ }
+ if (mLock) { // asynchronous case
+ NS_ASSERTION(mEventTarget, "mEventTarget is null, mLock is not null.");
+ if (!SinkIsValid()) {
+ return NS_OK; // nothing to do
+ }
+ nsCOMPtr<nsIRunnable> event =
+ new nsInputStreamTeeWriteEvent(aBuf, aCount, mSink, this);
+ LOG(("nsInputStreamTee::TeeSegment [%p] dispatching write %u bytes\n",
+ this, aCount));
+ return mEventTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ } else { // synchronous case
+ NS_ASSERTION(!mEventTarget, "mEventTarget is not null, mLock is null.");
+ nsresult rv;
+ uint32_t totalBytesWritten = 0;
+ while (aCount) {
+ uint32_t bytesWritten = 0;
+ rv = mSink->Write(aBuf + totalBytesWritten, aCount, &bytesWritten);
+ if (NS_FAILED(rv)) {
+ // ok, this is not a fatal error... just drop our reference to mSink
+ // and continue on as if nothing happened.
+ NS_WARNING("Write failed (non-fatal)");
+ // catch possible misuse of the input stream tee
+ NS_ASSERTION(rv != NS_BASE_STREAM_WOULD_BLOCK, "sink must be a blocking stream");
+ mSink = nullptr;
+ break;
+ }
+ totalBytesWritten += bytesWritten;
+ NS_ASSERTION(bytesWritten <= aCount, "wrote too much");
+ aCount -= bytesWritten;
+ }
+ return NS_OK;
+ }
+}
+
+nsresult
+nsInputStreamTee::WriteSegmentFun(nsIInputStream* aIn, void* aClosure,
+ const char* aFromSegment, uint32_t aOffset,
+ uint32_t aCount, uint32_t* aWriteCount)
+{
+ nsInputStreamTee* tee = reinterpret_cast<nsInputStreamTee*>(aClosure);
+ nsresult rv = tee->mWriter(aIn, tee->mClosure, aFromSegment, aOffset,
+ aCount, aWriteCount);
+ if (NS_FAILED(rv) || (*aWriteCount == 0)) {
+ NS_ASSERTION((NS_FAILED(rv) ? (*aWriteCount == 0) : true),
+ "writer returned an error with non-zero writeCount");
+ return rv;
+ }
+
+ return tee->TeeSegment(aFromSegment, *aWriteCount);
+}
+
+NS_IMPL_ISUPPORTS(nsInputStreamTee,
+ nsIInputStreamTee,
+ nsIInputStream)
+NS_IMETHODIMP
+nsInputStreamTee::Close()
+{
+ if (NS_WARN_IF(!mSource)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ nsresult rv = mSource->Close();
+ mSource = nullptr;
+ mSink = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsInputStreamTee::Available(uint64_t* aAvail)
+{
+ if (NS_WARN_IF(!mSource)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ return mSource->Available(aAvail);
+}
+
+NS_IMETHODIMP
+nsInputStreamTee::Read(char* aBuf, uint32_t aCount, uint32_t* aBytesRead)
+{
+ if (NS_WARN_IF(!mSource)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ nsresult rv = mSource->Read(aBuf, aCount, aBytesRead);
+ if (NS_FAILED(rv) || (*aBytesRead == 0)) {
+ return rv;
+ }
+
+ return TeeSegment(aBuf, *aBytesRead);
+}
+
+NS_IMETHODIMP
+nsInputStreamTee::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure,
+ uint32_t aCount,
+ uint32_t* aBytesRead)
+{
+ if (NS_WARN_IF(!mSource)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ mWriter = aWriter;
+ mClosure = aClosure;
+
+ return mSource->ReadSegments(WriteSegmentFun, this, aCount, aBytesRead);
+}
+
+NS_IMETHODIMP
+nsInputStreamTee::IsNonBlocking(bool* aResult)
+{
+ if (NS_WARN_IF(!mSource)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ return mSource->IsNonBlocking(aResult);
+}
+
+NS_IMETHODIMP
+nsInputStreamTee::SetSource(nsIInputStream* aSource)
+{
+ mSource = aSource;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamTee::GetSource(nsIInputStream** aSource)
+{
+ NS_IF_ADDREF(*aSource = mSource);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamTee::SetSink(nsIOutputStream* aSink)
+{
+#ifdef DEBUG
+ if (aSink) {
+ bool nonBlocking;
+ nsresult rv = aSink->IsNonBlocking(&nonBlocking);
+ if (NS_FAILED(rv) || nonBlocking) {
+ NS_ERROR("aSink should be a blocking stream");
+ }
+ }
+#endif
+ mSink = aSink;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamTee::GetSink(nsIOutputStream** aSink)
+{
+ NS_IF_ADDREF(*aSink = mSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamTee::SetEventTarget(nsIEventTarget* aEventTarget)
+{
+ mEventTarget = aEventTarget;
+ if (mEventTarget) {
+ // Only need synchronization if this is an async tee
+ mLock = new Mutex("nsInputStreamTee.mLock");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsInputStreamTee::GetEventTarget(nsIEventTarget** aEventTarget)
+{
+ NS_IF_ADDREF(*aEventTarget = mEventTarget);
+ return NS_OK;
+}
+
+
+nsresult
+NS_NewInputStreamTeeAsync(nsIInputStream** aResult,
+ nsIInputStream* aSource,
+ nsIOutputStream* aSink,
+ nsIEventTarget* aEventTarget)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIInputStreamTee> tee = new nsInputStreamTee();
+ rv = tee->SetSource(aSource);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = tee->SetSink(aSink);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = tee->SetEventTarget(aEventTarget);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ tee.forget(aResult);
+ return rv;
+}
+
+nsresult
+NS_NewInputStreamTee(nsIInputStream** aResult,
+ nsIInputStream* aSource,
+ nsIOutputStream* aSink)
+{
+ return NS_NewInputStreamTeeAsync(aResult, aSource, aSink, nullptr);
+}
+
+#undef LOG
diff --git a/xpcom/io/nsLinebreakConverter.cpp b/xpcom/io/nsLinebreakConverter.cpp
new file mode 100644
index 000000000..007685a6a
--- /dev/null
+++ b/xpcom/io/nsLinebreakConverter.cpp
@@ -0,0 +1,488 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsLinebreakConverter.h"
+
+#include "nsMemory.h"
+#include "nsCRT.h"
+
+
+/*----------------------------------------------------------------------------
+ GetLinebreakString
+
+ Could make this inline
+----------------------------------------------------------------------------*/
+static const char*
+GetLinebreakString(nsLinebreakConverter::ELinebreakType aBreakType)
+{
+ static const char* const sLinebreaks[] = {
+ "", // any
+ NS_LINEBREAK, // platform
+ LFSTR, // content
+ CRLF, // net
+ CRSTR, // Mac
+ LFSTR, // Unix
+ CRLF, // Windows
+ " ", // space
+ nullptr
+ };
+
+ return sLinebreaks[aBreakType];
+}
+
+
+/*----------------------------------------------------------------------------
+ AppendLinebreak
+
+ Wee inline method to append a line break. Modifies ioDest.
+----------------------------------------------------------------------------*/
+template<class T>
+void
+AppendLinebreak(T*& aIoDest, const char* aLineBreakStr)
+{
+ *aIoDest++ = *aLineBreakStr;
+
+ if (aLineBreakStr[1]) {
+ *aIoDest++ = aLineBreakStr[1];
+ }
+}
+
+/*----------------------------------------------------------------------------
+ CountChars
+
+ Counts occurrences of breakStr in aSrc
+----------------------------------------------------------------------------*/
+template<class T>
+int32_t
+CountLinebreaks(const T* aSrc, int32_t aInLen, const char* aBreakStr)
+{
+ const T* src = aSrc;
+ const T* srcEnd = aSrc + aInLen;
+ int32_t theCount = 0;
+
+ while (src < srcEnd) {
+ if (*src == *aBreakStr) {
+ src++;
+
+ if (aBreakStr[1]) {
+ if (src < srcEnd && *src == aBreakStr[1]) {
+ src++;
+ theCount++;
+ }
+ } else {
+ theCount++;
+ }
+ } else {
+ src++;
+ }
+ }
+
+ return theCount;
+}
+
+
+/*----------------------------------------------------------------------------
+ ConvertBreaks
+
+ ioLen *includes* a terminating null, if any
+----------------------------------------------------------------------------*/
+template<class T>
+static T*
+ConvertBreaks(const T* aInSrc, int32_t& aIoLen, const char* aSrcBreak,
+ const char* aDestBreak)
+{
+ NS_ASSERTION(aInSrc && aSrcBreak && aDestBreak, "Got a null string");
+
+ T* resultString = nullptr;
+
+ // handle the no conversion case
+ if (nsCRT::strcmp(aSrcBreak, aDestBreak) == 0) {
+ resultString = (T*)malloc(sizeof(T) * aIoLen);
+ if (!resultString) {
+ return nullptr;
+ }
+ memcpy(resultString, aInSrc, sizeof(T) * aIoLen); // includes the null, if any
+ return resultString;
+ }
+
+ int32_t srcBreakLen = strlen(aSrcBreak);
+ int32_t destBreakLen = strlen(aDestBreak);
+
+ // handle the easy case, where the string length does not change, and the
+ // breaks are only 1 char long, i.e. CR <-> LF
+ if (srcBreakLen == destBreakLen && srcBreakLen == 1) {
+ resultString = (T*)malloc(sizeof(T) * aIoLen);
+ if (!resultString) {
+ return nullptr;
+ }
+
+ const T* src = aInSrc;
+ const T* srcEnd = aInSrc + aIoLen; // includes null, if any
+ T* dst = resultString;
+
+ char srcBreakChar = *aSrcBreak; // we know it's one char long already
+ char dstBreakChar = *aDestBreak;
+
+ while (src < srcEnd) {
+ if (*src == srcBreakChar) {
+ *dst++ = dstBreakChar;
+ src++;
+ } else {
+ *dst++ = *src++;
+ }
+ }
+
+ // aIoLen does not change
+ } else {
+ // src and dest termination is different length. Do it a slower way.
+
+ // count linebreaks in src. Assumes that chars in 2-char linebreaks are unique.
+ int32_t numLinebreaks = CountLinebreaks(aInSrc, aIoLen, aSrcBreak);
+
+ int32_t newBufLen =
+ aIoLen - (numLinebreaks * srcBreakLen) + (numLinebreaks * destBreakLen);
+ resultString = (T*)malloc(sizeof(T) * newBufLen);
+ if (!resultString) {
+ return nullptr;
+ }
+
+ const T* src = aInSrc;
+ const T* srcEnd = aInSrc + aIoLen; // includes null, if any
+ T* dst = resultString;
+
+ while (src < srcEnd) {
+ if (*src == *aSrcBreak) {
+ *dst++ = *aDestBreak;
+ if (aDestBreak[1]) {
+ *dst++ = aDestBreak[1];
+ }
+
+ src++;
+ if (src < srcEnd && aSrcBreak[1] && *src == aSrcBreak[1]) {
+ src++;
+ }
+ } else {
+ *dst++ = *src++;
+ }
+ }
+
+ aIoLen = newBufLen;
+ }
+
+ return resultString;
+}
+
+
+/*----------------------------------------------------------------------------
+ ConvertBreaksInSitu
+
+ Convert breaks in situ. Can only do this if the linebreak length
+ does not change.
+----------------------------------------------------------------------------*/
+template<class T>
+static void
+ConvertBreaksInSitu(T* aInSrc, int32_t aInLen, char aSrcBreak, char aDestBreak)
+{
+ T* src = aInSrc;
+ T* srcEnd = aInSrc + aInLen;
+
+ while (src < srcEnd) {
+ if (*src == aSrcBreak) {
+ *src = aDestBreak;
+ }
+
+ src++;
+ }
+}
+
+
+/*----------------------------------------------------------------------------
+ ConvertUnknownBreaks
+
+ Convert unknown line breaks to the specified break.
+
+ This will convert CRLF pairs to one break, and single CR or LF to a break.
+----------------------------------------------------------------------------*/
+template<class T>
+static T*
+ConvertUnknownBreaks(const T* aInSrc, int32_t& aIoLen, const char* aDestBreak)
+{
+ const T* src = aInSrc;
+ const T* srcEnd = aInSrc + aIoLen; // includes null, if any
+
+ int32_t destBreakLen = strlen(aDestBreak);
+ int32_t finalLen = 0;
+
+ while (src < srcEnd) {
+ if (*src == nsCRT::CR) {
+ if (src < srcEnd && src[1] == nsCRT::LF) {
+ // CRLF
+ finalLen += destBreakLen;
+ src++;
+ } else {
+ // Lone CR
+ finalLen += destBreakLen;
+ }
+ } else if (*src == nsCRT::LF) {
+ // Lone LF
+ finalLen += destBreakLen;
+ } else {
+ finalLen++;
+ }
+ src++;
+ }
+
+ T* resultString = (T*)malloc(sizeof(T) * finalLen);
+ if (!resultString) {
+ return nullptr;
+ }
+
+ src = aInSrc;
+ srcEnd = aInSrc + aIoLen; // includes null, if any
+
+ T* dst = resultString;
+
+ while (src < srcEnd) {
+ if (*src == nsCRT::CR) {
+ if (src < srcEnd && src[1] == nsCRT::LF) {
+ // CRLF
+ AppendLinebreak(dst, aDestBreak);
+ src++;
+ } else {
+ // Lone CR
+ AppendLinebreak(dst, aDestBreak);
+ }
+ } else if (*src == nsCRT::LF) {
+ // Lone LF
+ AppendLinebreak(dst, aDestBreak);
+ } else {
+ *dst++ = *src;
+ }
+ src++;
+ }
+
+ aIoLen = finalLen;
+ return resultString;
+}
+
+
+/*----------------------------------------------------------------------------
+ ConvertLineBreaks
+
+----------------------------------------------------------------------------*/
+char*
+nsLinebreakConverter::ConvertLineBreaks(const char* aSrc,
+ ELinebreakType aSrcBreaks,
+ ELinebreakType aDestBreaks,
+ int32_t aSrcLen, int32_t* aOutLen)
+{
+ NS_ASSERTION(aDestBreaks != eLinebreakAny &&
+ aSrcBreaks != eLinebreakSpace, "Invalid parameter");
+ if (!aSrc) {
+ return nullptr;
+ }
+
+ int32_t sourceLen = (aSrcLen == kIgnoreLen) ? strlen(aSrc) + 1 : aSrcLen;
+
+ char* resultString;
+ if (aSrcBreaks == eLinebreakAny) {
+ resultString = ConvertUnknownBreaks(aSrc, sourceLen,
+ GetLinebreakString(aDestBreaks));
+ } else
+ resultString = ConvertBreaks(aSrc, sourceLen,
+ GetLinebreakString(aSrcBreaks),
+ GetLinebreakString(aDestBreaks));
+
+ if (aOutLen) {
+ *aOutLen = sourceLen;
+ }
+ return resultString;
+}
+
+
+/*----------------------------------------------------------------------------
+ ConvertLineBreaksInSitu
+
+----------------------------------------------------------------------------*/
+nsresult
+nsLinebreakConverter::ConvertLineBreaksInSitu(char** aIoBuffer,
+ ELinebreakType aSrcBreaks,
+ ELinebreakType aDestBreaks,
+ int32_t aSrcLen, int32_t* aOutLen)
+{
+ NS_ASSERTION(aIoBuffer && *aIoBuffer, "Null pointer passed");
+ if (!aIoBuffer || !*aIoBuffer) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ NS_ASSERTION(aDestBreaks != eLinebreakAny &&
+ aSrcBreaks != eLinebreakSpace, "Invalid parameter");
+
+ int32_t sourceLen = (aSrcLen == kIgnoreLen) ? strlen(*aIoBuffer) + 1 : aSrcLen;
+
+ // can we convert in-place?
+ const char* srcBreaks = GetLinebreakString(aSrcBreaks);
+ const char* dstBreaks = GetLinebreakString(aDestBreaks);
+
+ if (aSrcBreaks != eLinebreakAny &&
+ strlen(srcBreaks) == 1 &&
+ strlen(dstBreaks) == 1) {
+ ConvertBreaksInSitu(*aIoBuffer, sourceLen, *srcBreaks, *dstBreaks);
+ if (aOutLen) {
+ *aOutLen = sourceLen;
+ }
+ } else {
+ char* destBuffer;
+
+ if (aSrcBreaks == eLinebreakAny) {
+ destBuffer = ConvertUnknownBreaks(*aIoBuffer, sourceLen, dstBreaks);
+ } else {
+ destBuffer = ConvertBreaks(*aIoBuffer, sourceLen, srcBreaks, dstBreaks);
+ }
+
+ if (!destBuffer) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ *aIoBuffer = destBuffer;
+ if (aOutLen) {
+ *aOutLen = sourceLen;
+ }
+ }
+
+ return NS_OK;
+}
+
+
+/*----------------------------------------------------------------------------
+ ConvertUnicharLineBreaks
+
+----------------------------------------------------------------------------*/
+char16_t*
+nsLinebreakConverter::ConvertUnicharLineBreaks(const char16_t* aSrc,
+ ELinebreakType aSrcBreaks,
+ ELinebreakType aDestBreaks,
+ int32_t aSrcLen,
+ int32_t* aOutLen)
+{
+ NS_ASSERTION(aDestBreaks != eLinebreakAny &&
+ aSrcBreaks != eLinebreakSpace, "Invalid parameter");
+ if (!aSrc) {
+ return nullptr;
+ }
+
+ int32_t bufLen = (aSrcLen == kIgnoreLen) ? NS_strlen(aSrc) + 1 : aSrcLen;
+
+ char16_t* resultString;
+ if (aSrcBreaks == eLinebreakAny) {
+ resultString = ConvertUnknownBreaks(aSrc, bufLen,
+ GetLinebreakString(aDestBreaks));
+ } else
+ resultString = ConvertBreaks(aSrc, bufLen, GetLinebreakString(aSrcBreaks),
+ GetLinebreakString(aDestBreaks));
+
+ if (aOutLen) {
+ *aOutLen = bufLen;
+ }
+ return resultString;
+}
+
+
+/*----------------------------------------------------------------------------
+ ConvertStringLineBreaks
+
+----------------------------------------------------------------------------*/
+nsresult
+nsLinebreakConverter::ConvertUnicharLineBreaksInSitu(
+ char16_t** aIoBuffer, ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks,
+ int32_t aSrcLen, int32_t* aOutLen)
+{
+ NS_ASSERTION(aIoBuffer && *aIoBuffer, "Null pointer passed");
+ if (!aIoBuffer || !*aIoBuffer) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ NS_ASSERTION(aDestBreaks != eLinebreakAny &&
+ aSrcBreaks != eLinebreakSpace, "Invalid parameter");
+
+ int32_t sourceLen =
+ (aSrcLen == kIgnoreLen) ? NS_strlen(*aIoBuffer) + 1 : aSrcLen;
+
+ // can we convert in-place?
+ const char* srcBreaks = GetLinebreakString(aSrcBreaks);
+ const char* dstBreaks = GetLinebreakString(aDestBreaks);
+
+ if ((aSrcBreaks != eLinebreakAny) &&
+ (strlen(srcBreaks) == 1) &&
+ (strlen(dstBreaks) == 1)) {
+ ConvertBreaksInSitu(*aIoBuffer, sourceLen, *srcBreaks, *dstBreaks);
+ if (aOutLen) {
+ *aOutLen = sourceLen;
+ }
+ } else {
+ char16_t* destBuffer;
+
+ if (aSrcBreaks == eLinebreakAny) {
+ destBuffer = ConvertUnknownBreaks(*aIoBuffer, sourceLen, dstBreaks);
+ } else {
+ destBuffer = ConvertBreaks(*aIoBuffer, sourceLen, srcBreaks, dstBreaks);
+ }
+
+ if (!destBuffer) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ *aIoBuffer = destBuffer;
+ if (aOutLen) {
+ *aOutLen = sourceLen;
+ }
+ }
+
+ return NS_OK;
+}
+
+/*----------------------------------------------------------------------------
+ ConvertStringLineBreaks
+
+----------------------------------------------------------------------------*/
+nsresult
+nsLinebreakConverter::ConvertStringLineBreaks(nsString& aIoString,
+ ELinebreakType aSrcBreaks,
+ ELinebreakType aDestBreaks)
+{
+
+ NS_ASSERTION(aDestBreaks != eLinebreakAny &&
+ aSrcBreaks != eLinebreakSpace, "Invalid parameter");
+
+ // nothing to do
+ if (aIoString.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ // remember the old buffer in case
+ // we blow it away later
+ nsString::char_iterator stringBuf;
+ if (!aIoString.BeginWriting(stringBuf, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ int32_t newLen;
+
+ rv = ConvertUnicharLineBreaksInSitu(&stringBuf,
+ aSrcBreaks, aDestBreaks,
+ aIoString.Length() + 1, &newLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (stringBuf != aIoString.get()) {
+ aIoString.Adopt(stringBuf, newLen - 1);
+ }
+
+ return NS_OK;
+}
+
+
+
diff --git a/xpcom/io/nsLinebreakConverter.h b/xpcom/io/nsLinebreakConverter.h
new file mode 100644
index 000000000..a1678ef2d
--- /dev/null
+++ b/xpcom/io/nsLinebreakConverter.h
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsLinebreakConverter_h_
+#define nsLinebreakConverter_h_
+
+#include "nscore.h"
+#include "nsString.h"
+
+// utility class for converting between different line breaks.
+
+class nsLinebreakConverter
+{
+public:
+
+ // Note: enum must match char* array in GetLinebreakString
+ typedef enum {
+ eLinebreakAny, // any kind of linebreak (i.e. "don't care" source)
+
+ eLinebreakPlatform, // platform linebreak
+ eLinebreakContent, // Content model linebreak (LF)
+ eLinebreakNet, // Form submission linebreak (CRLF)
+
+ eLinebreakMac, // CR
+ eLinebreakUnix, // LF
+ eLinebreakWindows, // CRLF
+
+ eLinebreakSpace // space characters. Only valid as destination type
+
+ } ELinebreakType;
+
+ enum {
+ kIgnoreLen = -1
+ };
+
+ /* ConvertLineBreaks
+ * Convert line breaks in the supplied string, allocating and returning
+ * a new buffer. Returns nullptr on failure.
+ * @param aSrc: the source string. if aSrcLen == kIgnoreLen this string is assumed
+ * to be null terminated, otherwise it must be at least aSrcLen long.
+ * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny.
+ * If known, pass the known value, as this may be more efficient.
+ * @param aDestBreaks: the line breaks you want in the output.
+ * @param aSrcLen: length of the source. If -1, the source is assumed to be a null-
+ * terminated string.
+ * @param aOutLen: used to return character length of returned buffer, if not null.
+ */
+ static char* ConvertLineBreaks(const char* aSrc,
+ ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks,
+ int32_t aSrcLen = kIgnoreLen, int32_t* aOutLen = nullptr);
+
+
+ /* ConvertUnicharLineBreaks
+ * Convert line breaks in the supplied string, allocating and returning
+ * a new buffer. Returns nullptr on failure.
+ * @param aSrc: the source string. if aSrcLen == kIgnoreLen this string is assumed
+ * to be null terminated, otherwise it must be at least aSrcLen long.
+ * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny.
+ * If known, pass the known value, as this may be more efficient.
+ * @param aDestBreaks: the line breaks you want in the output.
+ * @param aSrcLen: length of the source, in characters. If -1, the source is assumed to be a null-
+ * terminated string.
+ * @param aOutLen: used to return character length of returned buffer, if not null.
+ */
+ static char16_t* ConvertUnicharLineBreaks(const char16_t* aSrc,
+ ELinebreakType aSrcBreaks, ELinebreakType aDestBreaks,
+ int32_t aSrcLen = kIgnoreLen, int32_t* aOutLen = nullptr);
+
+
+ /* ConvertStringLineBreaks
+ * Convert line breaks in the supplied string, changing the string buffer (i.e. in-place conversion)
+ * @param ioString: the string to be converted.
+ * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny.
+ * If known, pass the known value, as this may be more efficient.
+ * @param aDestBreaks: the line breaks you want in the output.
+ * @param aSrcLen: length of the source, in characters. If -1, the source is assumed to be a null-
+ * terminated string.
+ */
+ static nsresult ConvertStringLineBreaks(nsString& aIoString,
+ ELinebreakType aSrcBreaks,
+ ELinebreakType aDestBreaks);
+
+
+ /* ConvertLineBreaksInSitu
+ * Convert line breaks in place if possible. NOTE: THIS MAY REALLOCATE THE BUFFER,
+ * BUT IT WON'T FREE THE OLD BUFFER (because it doesn't know how). So be prepared
+ * to keep a copy of the old pointer, and free it if this passes back a new pointer.
+ * ALSO NOTE: DON'T PASS A STATIC STRING POINTER TO THIS FUNCTION.
+ *
+ * @param ioBuffer: the source buffer. if aSrcLen == kIgnoreLen this string is assumed
+ * to be null terminated, otherwise it must be at least aSrcLen long.
+ * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny.
+ * If known, pass the known value, as this may be more efficient.
+ * @param aDestBreaks: the line breaks you want in the output.
+ * @param aSrcLen: length of the source. If -1, the source is assumed to be a null-
+ * terminated string.
+ * @param aOutLen: used to return character length of returned buffer, if not null.
+ */
+ static nsresult ConvertLineBreaksInSitu(char** aIoBuffer,
+ ELinebreakType aSrcBreaks,
+ ELinebreakType aDestBreaks,
+ int32_t aSrcLen = kIgnoreLen,
+ int32_t* aOutLen = nullptr);
+
+
+ /* ConvertUnicharLineBreaksInSitu
+ * Convert line breaks in place if possible. NOTE: THIS MAY REALLOCATE THE BUFFER,
+ * BUT IT WON'T FREE THE OLD BUFFER (because it doesn't know how). So be prepared
+ * to keep a copy of the old pointer, and free it if this passes back a new pointer.
+ *
+ * @param ioBuffer: the source buffer. if aSrcLen == kIgnoreLen this string is assumed
+ * to be null terminated, otherwise it must be at least aSrcLen long.
+ * @param aSrcBreaks: the line breaks in the source. If unknown, pass eLinebreakAny.
+ * If known, pass the known value, as this may be more efficient.
+ * @param aDestBreaks: the line breaks you want in the output.
+ * @param aSrcLen: length of the source in characters. If -1, the source is assumed to be a null-
+ * terminated string.
+ * @param aOutLen: used to return character length of returned buffer, if not null.
+ */
+ static nsresult ConvertUnicharLineBreaksInSitu(char16_t** aIoBuffer,
+ ELinebreakType aSrcBreaks,
+ ELinebreakType aDestBreaks,
+ int32_t aSrcLen = kIgnoreLen,
+ int32_t* aOutLen = nullptr);
+
+};
+
+#endif // nsLinebreakConverter_h_
diff --git a/xpcom/io/nsLocalFile.h b/xpcom/io/nsLocalFile.h
new file mode 100644
index 000000000..f7bdb86f7
--- /dev/null
+++ b/xpcom/io/nsLocalFile.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Original Code has been modified by IBM Corporation. Modifications made by IBM
+ * described herein are Copyright (c) International Business Machines Corporation, 2000.
+ * Modifications to Mozilla code or documentation identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 build.
+ */
+
+#ifndef _NS_LOCAL_FILE_H_
+#define _NS_LOCAL_FILE_H_
+
+#include "nscore.h"
+
+#define NS_LOCAL_FILE_CID {0x2e23e220, 0x60be, 0x11d3, {0x8c, 0x4a, 0x00, 0x00, 0x64, 0x65, 0x73, 0x74}}
+
+#define NS_DECL_NSLOCALFILE_UNICODE_METHODS \
+ nsresult AppendUnicode(const char16_t *aNode); \
+ nsresult GetUnicodeLeafName(char16_t **aLeafName); \
+ nsresult SetUnicodeLeafName(const char16_t *aLeafName); \
+ nsresult CopyToUnicode(nsIFile *aNewParentDir, const char16_t *aNewLeafName); \
+ nsresult CopyToFollowingLinksUnicode(nsIFile *aNewParentDir, const char16_t *aNewLeafName); \
+ nsresult MoveToUnicode(nsIFile *aNewParentDir, const char16_t *aNewLeafName); \
+ nsresult GetUnicodeTarget(char16_t **aTarget); \
+ nsresult GetUnicodePath(char16_t **aPath); \
+ nsresult InitWithUnicodePath(const char16_t *aPath); \
+ nsresult AppendRelativeUnicodePath(const char16_t *aRelativePath);
+
+// XPCOMInit needs to know about how we are implemented,
+// so here we will export it. Other users should not depend
+// on this.
+
+#include <errno.h>
+#include "nsILocalFile.h"
+
+#ifdef XP_WIN
+#include "nsLocalFileWin.h"
+#elif defined(XP_UNIX)
+#include "nsLocalFileUnix.h"
+#else
+#error NOT_IMPLEMENTED
+#endif
+
+#define NSRESULT_FOR_RETURN(ret) (((ret) < 0) ? NSRESULT_FOR_ERRNO() : NS_OK)
+
+inline nsresult
+nsresultForErrno(int aErr)
+{
+ switch (aErr) {
+ case 0:
+ return NS_OK;
+#ifdef EDQUOT
+ case EDQUOT: /* Quota exceeded */
+ // FALLTHROUGH to return NS_ERROR_FILE_DISK_FULL
+#endif
+ case ENOSPC:
+ return NS_ERROR_FILE_DISK_FULL;
+#ifdef EISDIR
+ case EISDIR: /* Is a directory. */
+ return NS_ERROR_FILE_IS_DIRECTORY;
+#endif
+ case ENAMETOOLONG:
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ case ENOEXEC: /* Executable file format error. */
+ return NS_ERROR_FILE_EXECUTION_FAILED;
+ case ENOENT:
+ return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
+ case ENOTDIR:
+ return NS_ERROR_FILE_DESTINATION_NOT_DIR;
+#ifdef ELOOP
+ case ELOOP:
+ return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
+#endif /* ELOOP */
+#ifdef ENOLINK
+ case ENOLINK:
+ return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
+#endif /* ENOLINK */
+ case EEXIST:
+ return NS_ERROR_FILE_ALREADY_EXISTS;
+#ifdef EPERM
+ case EPERM:
+#endif /* EPERM */
+ case EACCES:
+ return NS_ERROR_FILE_ACCESS_DENIED;
+#ifdef EROFS
+ case EROFS: /* Read-only file system. */
+ return NS_ERROR_FILE_READ_ONLY;
+#endif
+ /*
+ * On AIX 4.3, ENOTEMPTY is defined as EEXIST,
+ * so there can't be cases for both without
+ * preprocessing.
+ */
+#if ENOTEMPTY != EEXIST
+ case ENOTEMPTY:
+ return NS_ERROR_FILE_DIR_NOT_EMPTY;
+#endif /* ENOTEMPTY != EEXIST */
+ /* Note that nsIFile.createUnique() returns
+ NS_ERROR_FILE_TOO_BIG when it cannot create a temporary
+ file with a unique filename.
+ See https://developer.mozilla.org/en-US/docs/Table_Of_Errors
+ Other usages of NS_ERROR_FILE_TOO_BIG in the source tree
+ are in line with the POSIX semantics of EFBIG.
+ So this is a reasonably good approximation.
+ */
+ case EFBIG: /* File too large. */
+ return NS_ERROR_FILE_TOO_BIG;
+
+ default:
+ return NS_ERROR_FAILURE;
+ }
+}
+
+#define NSRESULT_FOR_ERRNO() nsresultForErrno(errno)
+
+void NS_StartupLocalFile();
+void NS_ShutdownLocalFile();
+
+#endif
diff --git a/xpcom/io/nsLocalFileCommon.cpp b/xpcom/io/nsLocalFileCommon.cpp
new file mode 100644
index 000000000..8fbb7227d
--- /dev/null
+++ b/xpcom/io/nsLocalFileCommon.cpp
@@ -0,0 +1,328 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIServiceManager.h"
+
+#include "nsLocalFile.h" // includes platform-specific headers
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsReadableUtils.h"
+#include "nsPrintfCString.h"
+#include "nsCRT.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsUTF8Utils.h"
+
+#ifdef XP_WIN
+#include <string.h>
+#endif
+
+
+void
+NS_StartupLocalFile()
+{
+ nsLocalFile::GlobalInit();
+}
+
+void
+NS_ShutdownLocalFile()
+{
+ nsLocalFile::GlobalShutdown();
+}
+
+#if !defined(MOZ_WIDGET_COCOA) && !defined(XP_WIN)
+NS_IMETHODIMP
+nsLocalFile::InitWithFile(nsIFile* aFile)
+{
+ if (NS_WARN_IF(!aFile)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoCString path;
+ aFile->GetNativePath(path);
+ if (path.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return InitWithNativePath(path);
+}
+#endif
+
+#define kMaxFilenameLength 255
+#define kMaxExtensionLength 100
+#define kMaxSequenceNumberLength 5 // "-9999"
+// requirement: kMaxExtensionLength < kMaxFilenameLength - kMaxSequenceNumberLength
+
+NS_IMETHODIMP
+nsLocalFile::CreateUnique(uint32_t aType, uint32_t aAttributes)
+{
+ nsresult rv;
+ bool longName;
+
+#ifdef XP_WIN
+ nsAutoString pathName, leafName, rootName, suffix;
+ rv = GetPath(pathName);
+#else
+ nsAutoCString pathName, leafName, rootName, suffix;
+ rv = GetNativePath(pathName);
+#endif
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ longName = (pathName.Length() + kMaxSequenceNumberLength >
+ kMaxFilenameLength);
+ if (!longName) {
+ rv = Create(aType, aAttributes);
+ if (rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+ return rv;
+ }
+ }
+
+#ifdef XP_WIN
+ rv = GetLeafName(leafName);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ const int32_t lastDot = leafName.RFindChar(char16_t('.'));
+#else
+ rv = GetNativeLeafName(leafName);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ const int32_t lastDot = leafName.RFindChar('.');
+#endif
+
+ if (lastDot == kNotFound) {
+ rootName = leafName;
+ } else {
+ suffix = Substring(leafName, lastDot); // include '.'
+ rootName = Substring(leafName, 0, lastDot); // strip suffix and dot
+ }
+
+ if (longName) {
+ int32_t maxRootLength = (kMaxFilenameLength -
+ (pathName.Length() - leafName.Length()) -
+ suffix.Length() - kMaxSequenceNumberLength);
+
+ // We cannot create an item inside a directory whose name is too long.
+ // Also, ensure that at least one character remains after we truncate
+ // the root name, as we don't want to end up with an empty leaf name.
+ if (maxRootLength < 2) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+#ifdef XP_WIN
+ // ensure that we don't cut the name in mid-UTF16-character
+ rootName.SetLength(NS_IS_LOW_SURROGATE(rootName[maxRootLength]) ?
+ maxRootLength - 1 : maxRootLength);
+ SetLeafName(rootName + suffix);
+#else
+ if (NS_IsNativeUTF8()) {
+ // ensure that we don't cut the name in mid-UTF8-character
+ // (assume the name is valid UTF8 to begin with)
+ while (UTF8traits::isInSeq(rootName[maxRootLength])) {
+ --maxRootLength;
+ }
+
+ // Another check to avoid ending up with an empty leaf name.
+ if (maxRootLength == 0 && suffix.IsEmpty()) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+ }
+
+ rootName.SetLength(maxRootLength);
+ SetNativeLeafName(rootName + suffix);
+#endif
+ nsresult rvCreate = Create(aType, aAttributes);
+ if (rvCreate != NS_ERROR_FILE_ALREADY_EXISTS) {
+ return rvCreate;
+ }
+ }
+
+ for (int indx = 1; indx < 10000; ++indx) {
+ // start with "Picture-1.jpg" after "Picture.jpg" exists
+#ifdef XP_WIN
+ SetLeafName(rootName +
+ NS_ConvertASCIItoUTF16(nsPrintfCString("-%d", indx)) +
+ suffix);
+#else
+ SetNativeLeafName(rootName + nsPrintfCString("-%d", indx) + suffix);
+#endif
+ rv = Create(aType, aAttributes);
+ if (NS_SUCCEEDED(rv) || rv != NS_ERROR_FILE_ALREADY_EXISTS) {
+ return rv;
+ }
+ }
+
+ // The disk is full, sort of
+ return NS_ERROR_FILE_TOO_BIG;
+}
+
+#if defined(XP_WIN)
+static const char16_t kPathSeparatorChar = '\\';
+#elif defined(XP_UNIX)
+static const char16_t kPathSeparatorChar = '/';
+#else
+#error Need to define file path separator for your platform
+#endif
+
+static void
+SplitPath(char16_t* aPath, nsTArray<char16_t*>& aNodeArray)
+{
+ if (*aPath == 0) {
+ return;
+ }
+
+ if (*aPath == kPathSeparatorChar) {
+ aPath++;
+ }
+ aNodeArray.AppendElement(aPath);
+
+ for (char16_t* cp = aPath; *cp != 0; ++cp) {
+ if (*cp == kPathSeparatorChar) {
+ *cp++ = 0;
+ if (*cp == 0) {
+ break;
+ }
+ aNodeArray.AppendElement(cp);
+ }
+ }
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::GetRelativeDescriptor(nsIFile* aFromFile, nsACString& aResult)
+{
+ if (NS_WARN_IF(!aFromFile)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ //
+ // aResult will be UTF-8 encoded
+ //
+
+ nsresult rv;
+ aResult.Truncate(0);
+
+ nsAutoString thisPath, fromPath;
+ AutoTArray<char16_t*, 32> thisNodes;
+ AutoTArray<char16_t*, 32> fromNodes;
+
+ rv = GetPath(thisPath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = aFromFile->GetPath(fromPath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // get raw pointer to mutable string buffer
+ char16_t* thisPathPtr;
+ thisPath.BeginWriting(thisPathPtr);
+ char16_t* fromPathPtr;
+ fromPath.BeginWriting(fromPathPtr);
+
+ SplitPath(thisPathPtr, thisNodes);
+ SplitPath(fromPathPtr, fromNodes);
+
+ size_t nodeIndex;
+ for (nodeIndex = 0;
+ nodeIndex < thisNodes.Length() && nodeIndex < fromNodes.Length();
+ ++nodeIndex) {
+#ifdef XP_WIN
+ if (_wcsicmp(char16ptr_t(thisNodes[nodeIndex]),
+ char16ptr_t(fromNodes[nodeIndex]))) {
+ break;
+ }
+#else
+ if (nsCRT::strcmp(thisNodes[nodeIndex], fromNodes[nodeIndex])) {
+ break;
+ }
+#endif
+ }
+
+ size_t branchIndex = nodeIndex;
+ for (nodeIndex = branchIndex; nodeIndex < fromNodes.Length(); ++nodeIndex) {
+ aResult.AppendLiteral("../");
+ }
+ for (nodeIndex = branchIndex; nodeIndex < thisNodes.Length(); ++nodeIndex) {
+ NS_ConvertUTF16toUTF8 nodeStr(thisNodes[nodeIndex]);
+ aResult.Append(nodeStr);
+ if (nodeIndex + 1 < thisNodes.Length()) {
+ aResult.Append('/');
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetRelativeDescriptor(nsIFile* aFromFile,
+ const nsACString& aRelativeDesc)
+{
+ NS_NAMED_LITERAL_CSTRING(kParentDirStr, "../");
+
+ nsCOMPtr<nsIFile> targetFile;
+ nsresult rv = aFromFile->Clone(getter_AddRefs(targetFile));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ //
+ // aRelativeDesc is UTF-8 encoded
+ //
+
+ nsCString::const_iterator strBegin, strEnd;
+ aRelativeDesc.BeginReading(strBegin);
+ aRelativeDesc.EndReading(strEnd);
+
+ nsCString::const_iterator nodeBegin(strBegin), nodeEnd(strEnd);
+ nsCString::const_iterator pos(strBegin);
+
+ nsCOMPtr<nsIFile> parentDir;
+ while (FindInReadable(kParentDirStr, nodeBegin, nodeEnd)) {
+ rv = targetFile->GetParent(getter_AddRefs(parentDir));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!parentDir) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+ targetFile = parentDir;
+
+ nodeBegin = nodeEnd;
+ pos = nodeEnd;
+ nodeEnd = strEnd;
+ }
+
+ nodeBegin = nodeEnd = pos;
+ while (nodeEnd != strEnd) {
+ FindCharInReadable('/', nodeEnd, strEnd);
+ targetFile->Append(NS_ConvertUTF8toUTF16(Substring(nodeBegin, nodeEnd)));
+ if (nodeEnd != strEnd) { // If there's more left in the string, inc over the '/' nodeEnd is on.
+ ++nodeEnd;
+ }
+ nodeBegin = nodeEnd;
+ }
+
+ return InitWithFile(targetFile);
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetRelativePath(nsIFile* aFromFile, nsACString& aResult)
+{
+ return GetRelativeDescriptor(aFromFile, aResult);
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetRelativePath(nsIFile* aFromFile,
+ const nsACString& aRelativePath)
+{
+ return SetRelativeDescriptor(aFromFile, aRelativePath);
+}
diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp
new file mode 100644
index 000000000..194e5835e
--- /dev/null
+++ b/xpcom/io/nsLocalFileUnix.cpp
@@ -0,0 +1,2715 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Implementation of nsIFile for "unixy" systems.
+ */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Sprintf.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <utime.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <locale.h>
+
+#if defined(HAVE_SYS_QUOTA_H) && defined(HAVE_LINUX_QUOTA_H)
+#define USE_LINUX_QUOTACTL
+#include <sys/mount.h>
+#include <sys/quota.h>
+#include <sys/sysmacros.h>
+#ifndef BLOCK_SIZE
+#define BLOCK_SIZE 1024 /* kernel block size */
+#endif
+#endif
+
+#include "xpcom-private.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsCRT.h"
+#include "nsCOMPtr.h"
+#include "nsMemory.h"
+#include "nsIFile.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsLocalFile.h"
+#include "nsIComponentManager.h"
+#include "nsXPIDLString.h"
+#include "prproces.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsISimpleEnumerator.h"
+#include "private/pprio.h"
+#include "prlink.h"
+
+#ifdef MOZ_WIDGET_GTK
+#include "nsIGIOService.h"
+#endif
+
+#ifdef MOZ_WIDGET_COCOA
+#include <Carbon/Carbon.h>
+#include "CocoaFileUtils.h"
+#include "prmem.h"
+#include "plbase64.h"
+
+static nsresult MacErrorMapper(OSErr inErr);
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+#include "GeneratedJNIWrappers.h"
+#include "nsIMIMEService.h"
+#include <linux/magic.h>
+#endif
+
+#ifdef MOZ_ENABLE_CONTENTACTION
+#include <contentaction/contentaction.h>
+#endif
+
+#include "nsNativeCharsetUtils.h"
+#include "nsTraceRefcnt.h"
+#include "nsHashKeys.h"
+
+using namespace mozilla;
+
+#define ENSURE_STAT_CACHE() \
+ PR_BEGIN_MACRO \
+ if (!FillStatCache()) \
+ return NSRESULT_FOR_ERRNO(); \
+ PR_END_MACRO
+
+#define CHECK_mPath() \
+ PR_BEGIN_MACRO \
+ if (mPath.IsEmpty()) \
+ return NS_ERROR_NOT_INITIALIZED; \
+ PR_END_MACRO
+
+/* directory enumerator */
+class nsDirEnumeratorUnix final
+ : public nsISimpleEnumerator
+ , public nsIDirectoryEnumerator
+{
+public:
+ nsDirEnumeratorUnix();
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+
+ // nsISimpleEnumerator interface
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // nsIDirectoryEnumerator interface
+ NS_DECL_NSIDIRECTORYENUMERATOR
+
+ NS_IMETHOD Init(nsLocalFile* aParent, bool aIgnored);
+
+private:
+ ~nsDirEnumeratorUnix();
+
+protected:
+ NS_IMETHOD GetNextEntry();
+
+ DIR* mDir;
+ struct dirent* mEntry;
+ nsCString mParentPath;
+};
+
+nsDirEnumeratorUnix::nsDirEnumeratorUnix() :
+ mDir(nullptr),
+ mEntry(nullptr)
+{
+}
+
+nsDirEnumeratorUnix::~nsDirEnumeratorUnix()
+{
+ Close();
+}
+
+NS_IMPL_ISUPPORTS(nsDirEnumeratorUnix, nsISimpleEnumerator,
+ nsIDirectoryEnumerator)
+
+NS_IMETHODIMP
+nsDirEnumeratorUnix::Init(nsLocalFile* aParent,
+ bool aResolveSymlinks /*ignored*/)
+{
+ nsAutoCString dirPath;
+ if (NS_FAILED(aParent->GetNativePath(dirPath)) ||
+ dirPath.IsEmpty()) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ if (NS_FAILED(aParent->GetNativePath(mParentPath))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mDir = opendir(dirPath.get());
+ if (!mDir) {
+ return NSRESULT_FOR_ERRNO();
+ }
+ return GetNextEntry();
+}
+
+NS_IMETHODIMP
+nsDirEnumeratorUnix::HasMoreElements(bool* aResult)
+{
+ *aResult = mDir && mEntry;
+ if (!*aResult) {
+ Close();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirEnumeratorUnix::GetNext(nsISupports** aResult)
+{
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetNextFile(getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ NS_IF_ADDREF(*aResult = file);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirEnumeratorUnix::GetNextEntry()
+{
+ do {
+ errno = 0;
+ mEntry = readdir(mDir);
+
+ // end of dir or error
+ if (!mEntry) {
+ return NSRESULT_FOR_ERRNO();
+ }
+
+ // keep going past "." and ".."
+ } while (mEntry->d_name[0] == '.' &&
+ (mEntry->d_name[1] == '\0' || // .\0
+ (mEntry->d_name[1] == '.' &&
+ mEntry->d_name[2] == '\0'))); // ..\0
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDirEnumeratorUnix::GetNextFile(nsIFile** aResult)
+{
+ nsresult rv;
+ if (!mDir || !mEntry) {
+ *aResult = nullptr;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> file = new nsLocalFile();
+
+ if (NS_FAILED(rv = file->InitWithNativePath(mParentPath)) ||
+ NS_FAILED(rv = file->AppendNative(nsDependentCString(mEntry->d_name)))) {
+ return rv;
+ }
+
+ file.forget(aResult);
+ return GetNextEntry();
+}
+
+NS_IMETHODIMP
+nsDirEnumeratorUnix::Close()
+{
+ if (mDir) {
+ closedir(mDir);
+ mDir = nullptr;
+ }
+ return NS_OK;
+}
+
+nsLocalFile::nsLocalFile()
+{
+}
+
+nsLocalFile::nsLocalFile(const nsLocalFile& aOther)
+ : mPath(aOther.mPath)
+{
+}
+
+#ifdef MOZ_WIDGET_COCOA
+NS_IMPL_ISUPPORTS(nsLocalFile,
+ nsILocalFileMac,
+ nsILocalFile,
+ nsIFile,
+ nsIHashable)
+#else
+NS_IMPL_ISUPPORTS(nsLocalFile,
+ nsILocalFile,
+ nsIFile,
+ nsIHashable)
+#endif
+
+nsresult
+nsLocalFile::nsLocalFileConstructor(nsISupports* aOuter,
+ const nsIID& aIID,
+ void** aInstancePtr)
+{
+ if (NS_WARN_IF(!aInstancePtr)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aOuter)) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ *aInstancePtr = nullptr;
+
+ nsCOMPtr<nsIFile> inst = new nsLocalFile();
+ return inst->QueryInterface(aIID, aInstancePtr);
+}
+
+bool
+nsLocalFile::FillStatCache()
+{
+ if (STAT(mPath.get(), &mCachedStat) == -1) {
+ // try lstat it may be a symlink
+ if (LSTAT(mPath.get(), &mCachedStat) == -1) {
+ return false;
+ }
+ }
+ return true;
+}
+
+NS_IMETHODIMP
+nsLocalFile::Clone(nsIFile** aFile)
+{
+ // Just copy-construct ourselves
+ RefPtr<nsLocalFile> copy = new nsLocalFile(*this);
+ copy.forget(aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::InitWithNativePath(const nsACString& aFilePath)
+{
+ if (aFilePath.EqualsLiteral("~") ||
+ Substring(aFilePath, 0, 2).EqualsLiteral("~/")) {
+ nsCOMPtr<nsIFile> homeDir;
+ nsAutoCString homePath;
+ if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_HOME_DIR,
+ getter_AddRefs(homeDir))) ||
+ NS_FAILED(homeDir->GetNativePath(homePath))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mPath = homePath;
+ if (aFilePath.Length() > 2) {
+ mPath.Append(Substring(aFilePath, 1, aFilePath.Length() - 1));
+ }
+ } else {
+ if (aFilePath.IsEmpty() || aFilePath.First() != '/') {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+ mPath = aFilePath;
+ }
+
+ // trim off trailing slashes
+ ssize_t len = mPath.Length();
+ while ((len > 1) && (mPath[len - 1] == '/')) {
+ --len;
+ }
+ mPath.SetLength(len);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::CreateAllAncestors(uint32_t aPermissions)
+{
+ // <jband> I promise to play nice
+ char* buffer = mPath.BeginWriting();
+ char* slashp = buffer;
+
+#ifdef DEBUG_NSIFILE
+ fprintf(stderr, "nsIFile: before: %s\n", buffer);
+#endif
+
+ while ((slashp = strchr(slashp + 1, '/'))) {
+ /*
+ * Sequences of '/' are equivalent to a single '/'.
+ */
+ if (slashp[1] == '/') {
+ continue;
+ }
+
+ /*
+ * If the path has a trailing slash, don't make the last component,
+ * because we'll get EEXIST in Create when we try to build the final
+ * component again, and it's easier to condition the logic here than
+ * there.
+ */
+ if (slashp[1] == '\0') {
+ break;
+ }
+
+ /* Temporarily NUL-terminate here */
+ *slashp = '\0';
+#ifdef DEBUG_NSIFILE
+ fprintf(stderr, "nsIFile: mkdir(\"%s\")\n", buffer);
+#endif
+ int mkdir_result = mkdir(buffer, aPermissions);
+ int mkdir_errno = errno;
+ if (mkdir_result == -1) {
+ /*
+ * Always set |errno| to EEXIST if the dir already exists
+ * (we have to do this here since the errno value is not consistent
+ * in all cases - various reasons like different platform,
+ * automounter-controlled dir, etc. can affect it (see bug 125489
+ * for details)).
+ */
+ if (access(buffer, F_OK) == 0) {
+ mkdir_errno = EEXIST;
+ }
+ }
+
+ /* Put the / back before we (maybe) return */
+ *slashp = '/';
+
+ /*
+ * We could get EEXIST for an existing file -- not directory --
+ * with the name of one of our ancestors, but that's OK: we'll get
+ * ENOTDIR when we try to make the next component in the path,
+ * either here on back in Create, and error out appropriately.
+ */
+ if (mkdir_result == -1 && mkdir_errno != EEXIST) {
+ return nsresultForErrno(mkdir_errno);
+ }
+ }
+
+#ifdef DEBUG_NSIFILE
+ fprintf(stderr, "nsIFile: after: %s\n", buffer);
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode,
+ PRFileDesc** aResult)
+{
+ *aResult = PR_Open(mPath.get(), aFlags, aMode);
+ if (!*aResult) {
+ return NS_ErrorAccordingToNSPR();
+ }
+
+ if (aFlags & DELETE_ON_CLOSE) {
+ PR_Delete(mPath.get());
+ }
+
+#if defined(HAVE_POSIX_FADVISE)
+ if (aFlags & OS_READAHEAD) {
+ posix_fadvise(PR_FileDesc2NativeHandle(*aResult), 0, 0,
+ POSIX_FADV_SEQUENTIAL);
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult)
+{
+ *aResult = fopen(mPath.get(), aMode);
+ if (!*aResult) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static int
+do_create(const char* aPath, int aFlags, mode_t aMode, PRFileDesc** aResult)
+{
+ *aResult = PR_Open(aPath, aFlags, aMode);
+ return *aResult ? 0 : -1;
+}
+
+static int
+do_mkdir(const char* aPath, int aFlags, mode_t aMode, PRFileDesc** aResult)
+{
+ *aResult = nullptr;
+ return mkdir(aPath, aMode);
+}
+
+nsresult
+nsLocalFile::CreateAndKeepOpen(uint32_t aType, int aFlags,
+ uint32_t aPermissions, PRFileDesc** aResult)
+{
+ if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) {
+ return NS_ERROR_FILE_UNKNOWN_TYPE;
+ }
+
+ int (*createFunc)(const char*, int, mode_t, PRFileDesc**) =
+ (aType == NORMAL_FILE_TYPE) ? do_create : do_mkdir;
+
+ int result = createFunc(mPath.get(), aFlags, aPermissions, aResult);
+ if (result == -1 && errno == ENOENT) {
+ /*
+ * If we failed because of missing ancestor components, try to create
+ * them and then retry the original creation.
+ *
+ * Ancestor directories get the same permissions as the file we're
+ * creating, with the X bit set for each of (user,group,other) with
+ * an R bit in the original permissions. If you want to do anything
+ * fancy like setgid or sticky bits, do it by hand.
+ */
+ int dirperm = aPermissions;
+ if (aPermissions & S_IRUSR) {
+ dirperm |= S_IXUSR;
+ }
+ if (aPermissions & S_IRGRP) {
+ dirperm |= S_IXGRP;
+ }
+ if (aPermissions & S_IROTH) {
+ dirperm |= S_IXOTH;
+ }
+
+#ifdef DEBUG_NSIFILE
+ fprintf(stderr, "nsIFile: perm = %o, dirperm = %o\n", aPermissions,
+ dirperm);
+#endif
+
+ if (NS_FAILED(CreateAllAncestors(dirperm))) {
+ return NS_ERROR_FAILURE;
+ }
+
+#ifdef DEBUG_NSIFILE
+ fprintf(stderr, "nsIFile: Create(\"%s\") again\n", mPath.get());
+#endif
+ result = createFunc(mPath.get(), aFlags, aPermissions, aResult);
+ }
+ return NSRESULT_FOR_RETURN(result);
+}
+
+NS_IMETHODIMP
+nsLocalFile::Create(uint32_t aType, uint32_t aPermissions)
+{
+ PRFileDesc* junk = nullptr;
+ nsresult rv = CreateAndKeepOpen(aType,
+ PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE |
+ PR_EXCL,
+ aPermissions,
+ &junk);
+ if (junk) {
+ PR_Close(junk);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::AppendNative(const nsACString& aFragment)
+{
+ if (aFragment.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // only one component of path can be appended
+ nsACString::const_iterator begin, end;
+ if (FindCharInReadable('/', aFragment.BeginReading(begin),
+ aFragment.EndReading(end))) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ return AppendRelativeNativePath(aFragment);
+}
+
+NS_IMETHODIMP
+nsLocalFile::AppendRelativeNativePath(const nsACString& aFragment)
+{
+ if (aFragment.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // No leading '/'
+ if (aFragment.First() == '/') {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ if (!mPath.EqualsLiteral("/")) {
+ mPath.Append('/');
+ }
+ mPath.Append(aFragment);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::Normalize()
+{
+ char resolved_path[PATH_MAX] = "";
+ char* resolved_path_ptr = nullptr;
+
+ resolved_path_ptr = realpath(mPath.get(), resolved_path);
+
+ // if there is an error, the return is null.
+ if (!resolved_path_ptr) {
+ return NSRESULT_FOR_ERRNO();
+ }
+
+ mPath = resolved_path;
+ return NS_OK;
+}
+
+void
+nsLocalFile::LocateNativeLeafName(nsACString::const_iterator& aBegin,
+ nsACString::const_iterator& aEnd)
+{
+ // XXX perhaps we should cache this??
+
+ mPath.BeginReading(aBegin);
+ mPath.EndReading(aEnd);
+
+ nsACString::const_iterator it = aEnd;
+ nsACString::const_iterator stop = aBegin;
+ --stop;
+ while (--it != stop) {
+ if (*it == '/') {
+ aBegin = ++it;
+ return;
+ }
+ }
+ // else, the entire path is the leaf name (which means this
+ // isn't an absolute path... unexpected??)
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetNativeLeafName(nsACString& aLeafName)
+{
+ nsACString::const_iterator begin, end;
+ LocateNativeLeafName(begin, end);
+ aLeafName = Substring(begin, end);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetNativeLeafName(const nsACString& aLeafName)
+{
+ nsACString::const_iterator begin, end;
+ LocateNativeLeafName(begin, end);
+ mPath.Replace(begin.get() - mPath.get(), Distance(begin, end), aLeafName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetNativePath(nsACString& aResult)
+{
+ aResult = mPath;
+ return NS_OK;
+}
+
+nsresult
+nsLocalFile::GetNativeTargetPathName(nsIFile* aNewParent,
+ const nsACString& aNewName,
+ nsACString& aResult)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> oldParent;
+
+ if (!aNewParent) {
+ if (NS_FAILED(rv = GetParent(getter_AddRefs(oldParent)))) {
+ return rv;
+ }
+ aNewParent = oldParent.get();
+ } else {
+ // check to see if our target directory exists
+ bool targetExists;
+ if (NS_FAILED(rv = aNewParent->Exists(&targetExists))) {
+ return rv;
+ }
+
+ if (!targetExists) {
+ // XXX create the new directory with some permissions
+ rv = aNewParent->Create(DIRECTORY_TYPE, 0755);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ // make sure that the target is actually a directory
+ bool targetIsDirectory;
+ if (NS_FAILED(rv = aNewParent->IsDirectory(&targetIsDirectory))) {
+ return rv;
+ }
+ if (!targetIsDirectory) {
+ return NS_ERROR_FILE_DESTINATION_NOT_DIR;
+ }
+ }
+ }
+
+ nsACString::const_iterator nameBegin, nameEnd;
+ if (!aNewName.IsEmpty()) {
+ aNewName.BeginReading(nameBegin);
+ aNewName.EndReading(nameEnd);
+ } else {
+ LocateNativeLeafName(nameBegin, nameEnd);
+ }
+
+ nsAutoCString dirName;
+ if (NS_FAILED(rv = aNewParent->GetNativePath(dirName))) {
+ return rv;
+ }
+
+ aResult = dirName + NS_LITERAL_CSTRING("/") + Substring(nameBegin, nameEnd);
+ return NS_OK;
+}
+
+nsresult
+nsLocalFile::CopyDirectoryTo(nsIFile* aNewParent)
+{
+ nsresult rv;
+ /*
+ * dirCheck is used for various boolean test results such as from Equals,
+ * Exists, isDir, etc.
+ */
+ bool dirCheck, isSymlink;
+ uint32_t oldPerms;
+
+ if (NS_FAILED(rv = IsDirectory(&dirCheck))) {
+ return rv;
+ }
+ if (!dirCheck) {
+ return CopyToNative(aNewParent, EmptyCString());
+ }
+
+ if (NS_FAILED(rv = Equals(aNewParent, &dirCheck))) {
+ return rv;
+ }
+ if (dirCheck) {
+ // can't copy dir to itself
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) {
+ return rv;
+ }
+ // get the dirs old permissions
+ if (NS_FAILED(rv = GetPermissions(&oldPerms))) {
+ return rv;
+ }
+ if (!dirCheck) {
+ if (NS_FAILED(rv = aNewParent->Create(DIRECTORY_TYPE, oldPerms))) {
+ return rv;
+ }
+ } else { // dir exists lets try to use leaf
+ nsAutoCString leafName;
+ if (NS_FAILED(rv = GetNativeLeafName(leafName))) {
+ return rv;
+ }
+ if (NS_FAILED(rv = aNewParent->AppendNative(leafName))) {
+ return rv;
+ }
+ if (NS_FAILED(rv = aNewParent->Exists(&dirCheck))) {
+ return rv;
+ }
+ if (dirCheck) {
+ return NS_ERROR_FILE_ALREADY_EXISTS; // dest exists
+ }
+ if (NS_FAILED(rv = aNewParent->Create(DIRECTORY_TYPE, oldPerms))) {
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> dirIterator;
+ if (NS_FAILED(rv = GetDirectoryEntries(getter_AddRefs(dirIterator)))) {
+ return rv;
+ }
+
+ bool hasMore = false;
+ while (dirIterator->HasMoreElements(&hasMore), hasMore) {
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIFile> entry;
+ rv = dirIterator->GetNext(getter_AddRefs(supports));
+ entry = do_QueryInterface(supports);
+ if (NS_FAILED(rv) || !entry) {
+ continue;
+ }
+ if (NS_FAILED(rv = entry->IsSymlink(&isSymlink))) {
+ return rv;
+ }
+ if (NS_FAILED(rv = entry->IsDirectory(&dirCheck))) {
+ return rv;
+ }
+ if (dirCheck && !isSymlink) {
+ nsCOMPtr<nsIFile> destClone;
+ rv = aNewParent->Clone(getter_AddRefs(destClone));
+ if (NS_SUCCEEDED(rv)) {
+ if (NS_FAILED(rv = entry->CopyToNative(destClone, EmptyCString()))) {
+#ifdef DEBUG
+ nsresult rv2;
+ nsAutoCString pathName;
+ if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) {
+ return rv2;
+ }
+ printf("Operation not supported: %s\n", pathName.get());
+#endif
+ if (rv == NS_ERROR_OUT_OF_MEMORY) {
+ return rv;
+ }
+ continue;
+ }
+ }
+ } else {
+ if (NS_FAILED(rv = entry->CopyToNative(aNewParent, EmptyCString()))) {
+#ifdef DEBUG
+ nsresult rv2;
+ nsAutoCString pathName;
+ if (NS_FAILED(rv2 = entry->GetNativePath(pathName))) {
+ return rv2;
+ }
+ printf("Operation not supported: %s\n", pathName.get());
+#endif
+ if (rv == NS_ERROR_OUT_OF_MEMORY) {
+ return rv;
+ }
+ continue;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::CopyToNative(nsIFile* aNewParent, const nsACString& aNewName)
+{
+ nsresult rv;
+ // check to make sure that this has been initialized properly
+ CHECK_mPath();
+
+ // we copy the parent here so 'aNewParent' remains immutable
+ nsCOMPtr <nsIFile> workParent;
+ if (aNewParent) {
+ if (NS_FAILED(rv = aNewParent->Clone(getter_AddRefs(workParent)))) {
+ return rv;
+ }
+ } else {
+ if (NS_FAILED(rv = GetParent(getter_AddRefs(workParent)))) {
+ return rv;
+ }
+ }
+
+ // check to see if we are a directory or if we are a file
+ bool isDirectory;
+ if (NS_FAILED(rv = IsDirectory(&isDirectory))) {
+ return rv;
+ }
+
+ nsAutoCString newPathName;
+ if (isDirectory) {
+ if (!aNewName.IsEmpty()) {
+ if (NS_FAILED(rv = workParent->AppendNative(aNewName))) {
+ return rv;
+ }
+ } else {
+ if (NS_FAILED(rv = GetNativeLeafName(newPathName))) {
+ return rv;
+ }
+ if (NS_FAILED(rv = workParent->AppendNative(newPathName))) {
+ return rv;
+ }
+ }
+ if (NS_FAILED(rv = CopyDirectoryTo(workParent))) {
+ return rv;
+ }
+ } else {
+ rv = GetNativeTargetPathName(workParent, aNewName, newPathName);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+#ifdef DEBUG_blizzard
+ printf("nsLocalFile::CopyTo() %s -> %s\n", mPath.get(), newPathName.get());
+#endif
+
+ // actually create the file.
+ nsLocalFile* newFile = new nsLocalFile();
+ nsCOMPtr<nsIFile> fileRef(newFile); // release on exit
+
+ rv = newFile->InitWithNativePath(newPathName);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // get the old permissions
+ uint32_t myPerms;
+ GetPermissions(&myPerms);
+
+ // Create the new file with the old file's permissions, even if write
+ // permission is missing. We can't create with write permission and
+ // then change back to myPerm on all filesystems (FAT on Linux, e.g.).
+ // But we can write to a read-only file on all Unix filesystems if we
+ // open it successfully for writing.
+
+ PRFileDesc* newFD;
+ rv = newFile->CreateAndKeepOpen(NORMAL_FILE_TYPE,
+ PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
+ myPerms,
+ &newFD);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // open the old file, too
+ bool specialFile;
+ if (NS_FAILED(rv = IsSpecial(&specialFile))) {
+ PR_Close(newFD);
+ return rv;
+ }
+ if (specialFile) {
+#ifdef DEBUG
+ printf("Operation not supported: %s\n", mPath.get());
+#endif
+ // make sure to clean up properly
+ PR_Close(newFD);
+ return NS_OK;
+ }
+
+ PRFileDesc* oldFD;
+ rv = OpenNSPRFileDesc(PR_RDONLY, myPerms, &oldFD);
+ if (NS_FAILED(rv)) {
+ // make sure to clean up properly
+ PR_Close(newFD);
+ return rv;
+ }
+
+#ifdef DEBUG_blizzard
+ int32_t totalRead = 0;
+ int32_t totalWritten = 0;
+#endif
+ char buf[BUFSIZ];
+ int32_t bytesRead;
+
+ // record PR_Write() error for better error message later.
+ nsresult saved_write_error = NS_OK;
+ nsresult saved_read_error = NS_OK;
+ nsresult saved_read_close_error = NS_OK;
+ nsresult saved_write_close_error = NS_OK;
+
+ // DONE: Does PR_Read() return bytesRead < 0 for error?
+ // Yes., The errors from PR_Read are not so common and
+ // the value may not have correspondence in NS_ERROR_*, but
+ // we do catch it still, immediately after while() loop.
+ // We can differentiate errors pf PR_Read and PR_Write by
+ // looking at saved_write_error value. If PR_Write error occurs (and not
+ // PR_Read() error), save_write_error is not NS_OK.
+
+ while ((bytesRead = PR_Read(oldFD, buf, BUFSIZ)) > 0) {
+#ifdef DEBUG_blizzard
+ totalRead += bytesRead;
+#endif
+
+ // PR_Write promises never to do a short write
+ int32_t bytesWritten = PR_Write(newFD, buf, bytesRead);
+ if (bytesWritten < 0) {
+ saved_write_error = NSRESULT_FOR_ERRNO();
+ bytesRead = -1;
+ break;
+ }
+ NS_ASSERTION(bytesWritten == bytesRead, "short PR_Write?");
+
+#ifdef DEBUG_blizzard
+ totalWritten += bytesWritten;
+#endif
+ }
+
+ // TODO/FIXME: If CIFS (and NFS?) may force read/write to return EINTR,
+ // we are better off to prepare for retrying. But we need confirmation if
+ // EINTR is returned.
+
+ // Record error if PR_Read() failed.
+ // Must be done before any other I/O which may reset errno.
+ if (bytesRead < 0 && saved_write_error == NS_OK) {
+ saved_read_error = NSRESULT_FOR_ERRNO();
+ }
+
+#ifdef DEBUG_blizzard
+ printf("read %d bytes, wrote %d bytes\n",
+ totalRead, totalWritten);
+#endif
+
+ // DONE: Errors of close can occur. Read man page of
+ // close(2);
+ // This is likely to happen if the file system is remote file
+ // system (NFS, CIFS, etc.) and network outage occurs.
+ // At least, we should tell the user that filesystem/disk is
+ // hosed (possibly due to network error, hard disk failure,
+ // etc.) so that users can take remedial action.
+
+ // close the files
+ if (PR_Close(newFD) < 0) {
+ saved_write_close_error = NSRESULT_FOR_ERRNO();
+#if DEBUG
+ // This error merits printing.
+ fprintf(stderr, "ERROR: PR_Close(newFD) returned error. errno = %d\n", errno);
+#endif
+ }
+
+ if (PR_Close(oldFD) < 0) {
+ saved_read_close_error = NSRESULT_FOR_ERRNO();
+#if DEBUG
+ fprintf(stderr, "ERROR: PR_Close(oldFD) returned error. errno = %d\n", errno);
+#endif
+ }
+
+ // Let us report the failure to write and read.
+ // check for write/read error after cleaning up
+ if (bytesRead < 0) {
+ if (saved_write_error != NS_OK) {
+ return saved_write_error;
+ } else if (saved_read_error != NS_OK) {
+ return saved_read_error;
+ }
+#if DEBUG
+ else { // sanity check. Die and debug.
+ MOZ_ASSERT(0);
+ }
+#endif
+ }
+
+ if (saved_write_close_error != NS_OK) {
+ return saved_write_close_error;
+ }
+ if (saved_read_close_error != NS_OK) {
+ return saved_read_close_error;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParent,
+ const nsACString& aNewName)
+{
+ return CopyToNative(aNewParent, aNewName);
+}
+
+NS_IMETHODIMP
+nsLocalFile::MoveToNative(nsIFile* aNewParent, const nsACString& aNewName)
+{
+ nsresult rv;
+
+ // check to make sure that this has been initialized properly
+ CHECK_mPath();
+
+ // check to make sure that we have a new parent
+ nsAutoCString newPathName;
+ rv = GetNativeTargetPathName(aNewParent, aNewName, newPathName);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // try for atomic rename, falling back to copy/delete
+ if (rename(mPath.get(), newPathName.get()) < 0) {
+ if (errno == EXDEV) {
+ rv = CopyToNative(aNewParent, aNewName);
+ if (NS_SUCCEEDED(rv)) {
+ rv = Remove(true);
+ }
+ } else {
+ rv = NSRESULT_FOR_ERRNO();
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ // Adjust this
+ mPath = newPathName;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::Remove(bool aRecursive)
+{
+ CHECK_mPath();
+ ENSURE_STAT_CACHE();
+
+ bool isSymLink;
+
+ nsresult rv = IsSymlink(&isSymLink);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (isSymLink || !S_ISDIR(mCachedStat.st_mode)) {
+ return NSRESULT_FOR_RETURN(unlink(mPath.get()));
+ }
+
+ if (aRecursive) {
+ nsDirEnumeratorUnix* dir = new nsDirEnumeratorUnix();
+
+ nsCOMPtr<nsISimpleEnumerator> dirRef(dir); // release on exit
+
+ rv = dir->Init(this, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool more;
+ while (dir->HasMoreElements(&more), more) {
+ nsCOMPtr<nsISupports> item;
+ rv = dir->GetNext(getter_AddRefs(item));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(item, &rv);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ rv = file->Remove(aRecursive);
+
+#ifdef ANDROID
+ // See bug 580434 - Bionic gives us just deleted files
+ if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
+ continue;
+ }
+#endif
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+ return NSRESULT_FOR_RETURN(rmdir(mPath.get()));
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetLastModifiedTime(PRTime* aLastModTime)
+{
+ CHECK_mPath();
+ if (NS_WARN_IF(!aLastModTime)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PRFileInfo64 info;
+ if (PR_GetFileInfo64(mPath.get(), &info) != PR_SUCCESS) {
+ return NSRESULT_FOR_ERRNO();
+ }
+ PRTime modTime = info.modifyTime;
+ if (modTime == 0) {
+ *aLastModTime = 0;
+ } else {
+ *aLastModTime = modTime / PR_USEC_PER_MSEC;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetLastModifiedTime(PRTime aLastModTime)
+{
+ CHECK_mPath();
+
+ int result;
+ if (aLastModTime != 0) {
+ ENSURE_STAT_CACHE();
+ struct utimbuf ut;
+ ut.actime = mCachedStat.st_atime;
+
+ // convert milliseconds to seconds since the unix epoch
+ ut.modtime = (time_t)(aLastModTime / PR_MSEC_PER_SEC);
+ result = utime(mPath.get(), &ut);
+ } else {
+ result = utime(mPath.get(), nullptr);
+ }
+ return NSRESULT_FOR_RETURN(result);
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModTimeOfLink)
+{
+ CHECK_mPath();
+ if (NS_WARN_IF(!aLastModTimeOfLink)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ struct STAT sbuf;
+ if (LSTAT(mPath.get(), &sbuf) == -1) {
+ return NSRESULT_FOR_ERRNO();
+ }
+ *aLastModTimeOfLink = PRTime(sbuf.st_mtime) * PR_MSEC_PER_SEC;
+
+ return NS_OK;
+}
+
+/*
+ * utime(2) may or may not dereference symlinks, joy.
+ */
+NS_IMETHODIMP
+nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink)
+{
+ return SetLastModifiedTime(aLastModTimeOfLink);
+}
+
+/*
+ * Only send back permissions bits: maybe we want to send back the whole
+ * mode_t to permit checks against other file types?
+ */
+
+#define NORMALIZE_PERMS(mode) ((mode)& (S_IRWXU | S_IRWXG | S_IRWXO))
+
+NS_IMETHODIMP
+nsLocalFile::GetPermissions(uint32_t* aPermissions)
+{
+ if (NS_WARN_IF(!aPermissions)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ ENSURE_STAT_CACHE();
+ *aPermissions = NORMALIZE_PERMS(mCachedStat.st_mode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissionsOfLink)
+{
+ CHECK_mPath();
+ if (NS_WARN_IF(!aPermissionsOfLink)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ struct STAT sbuf;
+ if (LSTAT(mPath.get(), &sbuf) == -1) {
+ return NSRESULT_FOR_ERRNO();
+ }
+ *aPermissionsOfLink = NORMALIZE_PERMS(sbuf.st_mode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetPermissions(uint32_t aPermissions)
+{
+ CHECK_mPath();
+
+ /*
+ * Race condition here: we should use fchmod instead, there's no way to
+ * guarantee the name still refers to the same file.
+ */
+ if (chmod(mPath.get(), aPermissions) >= 0) {
+ return NS_OK;
+ }
+#if defined(ANDROID) && defined(STATFS)
+ // For the time being, this is restricted for use by Android, but we
+ // will figure out what to do for all platforms in bug 638503
+ struct STATFS sfs;
+ if (STATFS(mPath.get(), &sfs) < 0) {
+ return NSRESULT_FOR_ERRNO();
+ }
+
+ // if this is a FAT file system we can't set file permissions
+ if (sfs.f_type == MSDOS_SUPER_MAGIC) {
+ return NS_OK;
+ }
+#endif
+ return NSRESULT_FOR_ERRNO();
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions)
+{
+ // There isn't a consistent mechanism for doing this on UNIX platforms. We
+ // might want to carefully implement this in the future though.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetFileSize(int64_t* aFileSize)
+{
+ if (NS_WARN_IF(!aFileSize)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aFileSize = 0;
+ ENSURE_STAT_CACHE();
+
+ if (!S_ISDIR(mCachedStat.st_mode)) {
+ *aFileSize = (int64_t)mCachedStat.st_size;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetFileSize(int64_t aFileSize)
+{
+ CHECK_mPath();
+
+#if defined(ANDROID)
+ /* no truncate on bionic */
+ int fd = open(mPath.get(), O_WRONLY);
+ if (fd == -1) {
+ return NSRESULT_FOR_ERRNO();
+ }
+
+ int ret = ftruncate(fd, (off_t)aFileSize);
+ close(fd);
+
+ if (ret == -1) {
+ return NSRESULT_FOR_ERRNO();
+ }
+#elif defined(HAVE_TRUNCATE64)
+ if (truncate64(mPath.get(), (off64_t)aFileSize) == -1) {
+ return NSRESULT_FOR_ERRNO();
+ }
+#else
+ off_t size = (off_t)aFileSize;
+ if (truncate(mPath.get(), size) == -1) {
+ return NSRESULT_FOR_ERRNO();
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize)
+{
+ CHECK_mPath();
+ if (NS_WARN_IF(!aFileSize)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ struct STAT sbuf;
+ if (LSTAT(mPath.get(), &sbuf) == -1) {
+ return NSRESULT_FOR_ERRNO();
+ }
+
+ *aFileSize = (int64_t)sbuf.st_size;
+ return NS_OK;
+}
+
+#if defined(USE_LINUX_QUOTACTL)
+/*
+ * Searches /proc/self/mountinfo for given device (Major:Minor),
+ * returns exported name from /dev
+ *
+ * Fails when /proc/self/mountinfo or diven device don't exist.
+ */
+static bool
+GetDeviceName(int aDeviceMajor, int aDeviceMinor, nsACString& aDeviceName)
+{
+ bool ret = false;
+
+ const int kMountInfoLineLength = 200;
+ const int kMountInfoDevPosition = 6;
+
+ char mountinfoLine[kMountInfoLineLength];
+ char deviceNum[kMountInfoLineLength];
+
+ SprintfLiteral(deviceNum, "%d:%d", aDeviceMajor, aDeviceMinor);
+
+ FILE* f = fopen("/proc/self/mountinfo", "rt");
+ if (!f) {
+ return ret;
+ }
+
+ // Expects /proc/self/mountinfo in format:
+ // 'ID ID major:minor root mountpoint flags - type devicename flags'
+ while (fgets(mountinfoLine, kMountInfoLineLength, f)) {
+ char* p_dev = strstr(mountinfoLine, deviceNum);
+
+ for (int i = 0; i < kMountInfoDevPosition && p_dev; ++i) {
+ p_dev = strchr(p_dev, ' ');
+ if (p_dev) {
+ p_dev++;
+ }
+ }
+
+ if (p_dev) {
+ char* p_dev_end = strchr(p_dev, ' ');
+ if (p_dev_end) {
+ *p_dev_end = '\0';
+ aDeviceName.Assign(p_dev);
+ ret = true;
+ break;
+ }
+ }
+ }
+
+ fclose(f);
+ return ret;
+}
+#endif
+
+NS_IMETHODIMP
+nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable)
+{
+ if (NS_WARN_IF(!aDiskSpaceAvailable)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // These systems have the operations necessary to check disk space.
+
+#ifdef STATFS
+
+ // check to make sure that mPath is properly initialized
+ CHECK_mPath();
+
+ struct STATFS fs_buf;
+
+ /*
+ * Members of the STATFS struct that you should know about:
+ * F_BSIZE = block size on disk.
+ * f_bavail = number of free blocks available to a non-superuser.
+ * f_bfree = number of total free blocks in file system.
+ */
+
+ if (STATFS(mPath.get(), &fs_buf) < 0) {
+ // The call to STATFS failed.
+#ifdef DEBUG
+ printf("ERROR: GetDiskSpaceAvailable: STATFS call FAILED. \n");
+#endif
+ return NS_ERROR_FAILURE;
+ }
+
+ *aDiskSpaceAvailable = (int64_t)fs_buf.F_BSIZE * fs_buf.f_bavail;
+
+#ifdef DEBUG_DISK_SPACE
+ printf("DiskSpaceAvailable: %lu bytes\n",
+ *aDiskSpaceAvailable);
+#endif
+
+#if defined(USE_LINUX_QUOTACTL)
+
+ if (!FillStatCache()) {
+ // Return available size from statfs
+ return NS_OK;
+ }
+
+ nsCString deviceName;
+ if (!GetDeviceName(major(mCachedStat.st_dev),
+ minor(mCachedStat.st_dev),
+ deviceName)) {
+ return NS_OK;
+ }
+
+ struct dqblk dq;
+ if (!quotactl(QCMD(Q_GETQUOTA, USRQUOTA), deviceName.get(),
+ getuid(), (caddr_t)&dq)
+#ifdef QIF_BLIMITS
+ && dq.dqb_valid & QIF_BLIMITS
+#endif
+ && dq.dqb_bhardlimit) {
+ int64_t QuotaSpaceAvailable = 0;
+ // dqb_bhardlimit is count of BLOCK_SIZE blocks, dqb_curspace is bytes
+ if ((BLOCK_SIZE * dq.dqb_bhardlimit) > dq.dqb_curspace)
+ QuotaSpaceAvailable = int64_t(BLOCK_SIZE * dq.dqb_bhardlimit - dq.dqb_curspace);
+ if (QuotaSpaceAvailable < *aDiskSpaceAvailable) {
+ *aDiskSpaceAvailable = QuotaSpaceAvailable;
+ }
+ }
+#endif
+
+ return NS_OK;
+
+#else
+ /*
+ * This platform doesn't have statfs or statvfs. I'm sure that there's
+ * a way to check for free disk space on platforms that don't have statfs
+ * (I'm SURE they have df, for example).
+ *
+ * Until we figure out how to do that, lets be honest and say that this
+ * command isn't implemented properly for these platforms yet.
+ */
+#ifdef DEBUG
+ printf("ERROR: GetDiskSpaceAvailable: Not implemented for plaforms without statfs.\n");
+#endif
+ return NS_ERROR_NOT_IMPLEMENTED;
+
+#endif /* STATFS */
+
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetParent(nsIFile** aParent)
+{
+ CHECK_mPath();
+ if (NS_WARN_IF(!aParent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aParent = nullptr;
+
+ // if '/' we are at the top of the volume, return null
+ if (mPath.EqualsLiteral("/")) {
+ return NS_OK;
+ }
+
+ // <brendan, after jband> I promise to play nice
+ char* buffer = mPath.BeginWriting();
+ // find the last significant slash in buffer
+ char* slashp = strrchr(buffer, '/');
+ NS_ASSERTION(slashp, "non-canonical path?");
+ if (!slashp) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ // for the case where we are at '/'
+ if (slashp == buffer) {
+ slashp++;
+ }
+
+ // temporarily terminate buffer at the last significant slash
+ char c = *slashp;
+ *slashp = '\0';
+
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_NewNativeLocalFile(nsDependentCString(buffer), true,
+ getter_AddRefs(localFile));
+
+ // make buffer whole again
+ *slashp = c;
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ localFile.forget(aParent);
+ return NS_OK;
+}
+
+/*
+ * The results of Exists, isWritable and isReadable are not cached.
+ */
+
+
+NS_IMETHODIMP
+nsLocalFile::Exists(bool* aResult)
+{
+ CHECK_mPath();
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = (access(mPath.get(), F_OK) == 0);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::IsWritable(bool* aResult)
+{
+ CHECK_mPath();
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = (access(mPath.get(), W_OK) == 0);
+ if (*aResult || errno == EACCES) {
+ return NS_OK;
+ }
+ return NSRESULT_FOR_ERRNO();
+}
+
+NS_IMETHODIMP
+nsLocalFile::IsReadable(bool* aResult)
+{
+ CHECK_mPath();
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aResult = (access(mPath.get(), R_OK) == 0);
+ if (*aResult || errno == EACCES) {
+ return NS_OK;
+ }
+ return NSRESULT_FOR_ERRNO();
+}
+
+NS_IMETHODIMP
+nsLocalFile::IsExecutable(bool* aResult)
+{
+ CHECK_mPath();
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Check extension (bug 663899). On certain platforms, the file
+ // extension may cause the OS to treat it as executable regardless of
+ // the execute bit, such as .jar on Mac OS X. We borrow the code from
+ // nsLocalFileWin, slightly modified.
+
+ // Don't be fooled by symlinks.
+ bool symLink;
+ nsresult rv = IsSymlink(&symLink);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoString path;
+ if (symLink) {
+ GetTarget(path);
+ } else {
+ GetPath(path);
+ }
+
+ int32_t dotIdx = path.RFindChar(char16_t('.'));
+ if (dotIdx != kNotFound) {
+ // Convert extension to lower case.
+ char16_t* p = path.BeginWriting();
+ for (p += dotIdx + 1; *p; ++p) {
+ *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0;
+ }
+
+ // Search for any of the set of executable extensions.
+ static const char* const executableExts[] = {
+ "air", // Adobe AIR installer
+ "jar" // java application bundle
+ };
+ nsDependentSubstring ext = Substring(path, dotIdx + 1);
+ for (size_t i = 0; i < ArrayLength(executableExts); i++) {
+ if (ext.EqualsASCII(executableExts[i])) {
+ // Found a match. Set result and quit.
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+ }
+
+ // On OS X, then query Launch Services.
+#ifdef MOZ_WIDGET_COCOA
+ // Certain Mac applications, such as Classic applications, which
+ // run under Rosetta, might not have the +x mode bit but are still
+ // considered to be executable by Launch Services (bug 646748).
+ CFURLRef url;
+ if (NS_FAILED(GetCFURL(&url))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LSRequestedInfo theInfoRequest = kLSRequestAllInfo;
+ LSItemInfoRecord theInfo;
+ OSStatus result = ::LSCopyItemInfoForURL(url, theInfoRequest, &theInfo);
+ ::CFRelease(url);
+ if (result == noErr) {
+ if ((theInfo.flags & kLSItemInfoIsApplication) != 0) {
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+#endif
+
+ // Then check the execute bit.
+ *aResult = (access(mPath.get(), X_OK) == 0);
+#ifdef SOLARIS
+ // On Solaris, access will always return 0 for root user, however
+ // the file is only executable if S_IXUSR | S_IXGRP | S_IXOTH is set.
+ // See bug 351950, https://bugzilla.mozilla.org/show_bug.cgi?id=351950
+ if (*aResult) {
+ struct STAT buf;
+
+ *aResult = (STAT(mPath.get(), &buf) == 0);
+ if (*aResult || errno == EACCES) {
+ *aResult = *aResult && (buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH));
+ return NS_OK;
+ }
+
+ return NSRESULT_FOR_ERRNO();
+ }
+#endif
+ if (*aResult || errno == EACCES) {
+ return NS_OK;
+ }
+ return NSRESULT_FOR_ERRNO();
+}
+
+NS_IMETHODIMP
+nsLocalFile::IsDirectory(bool* aResult)
+{
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aResult = false;
+ ENSURE_STAT_CACHE();
+ *aResult = S_ISDIR(mCachedStat.st_mode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::IsFile(bool* aResult)
+{
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aResult = false;
+ ENSURE_STAT_CACHE();
+ *aResult = S_ISREG(mCachedStat.st_mode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::IsHidden(bool* aResult)
+{
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsACString::const_iterator begin, end;
+ LocateNativeLeafName(begin, end);
+ *aResult = (*begin == '.');
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::IsSymlink(bool* aResult)
+{
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ CHECK_mPath();
+
+ struct STAT symStat;
+ if (LSTAT(mPath.get(), &symStat) == -1) {
+ return NSRESULT_FOR_ERRNO();
+ }
+ *aResult = S_ISLNK(symStat.st_mode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::IsSpecial(bool* aResult)
+{
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ ENSURE_STAT_CACHE();
+ *aResult = S_ISCHR(mCachedStat.st_mode) ||
+ S_ISBLK(mCachedStat.st_mode) ||
+#ifdef S_ISSOCK
+ S_ISSOCK(mCachedStat.st_mode) ||
+#endif
+ S_ISFIFO(mCachedStat.st_mode);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::Equals(nsIFile* aInFile, bool* aResult)
+{
+ if (NS_WARN_IF(!aInFile)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aResult = false;
+
+ nsAutoCString inPath;
+ nsresult rv = aInFile->GetNativePath(inPath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // We don't need to worry about "/foo/" vs. "/foo" here
+ // because trailing slashes are stripped on init.
+ *aResult = !strcmp(inPath.get(), mPath.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::Contains(nsIFile* aInFile, bool* aResult)
+{
+ CHECK_mPath();
+ if (NS_WARN_IF(!aInFile)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoCString inPath;
+ nsresult rv;
+
+ if (NS_FAILED(rv = aInFile->GetNativePath(inPath))) {
+ return rv;
+ }
+
+ *aResult = false;
+
+ ssize_t len = mPath.Length();
+ if (strncmp(mPath.get(), inPath.get(), len) == 0) {
+ // Now make sure that the |aInFile|'s path has a separator at len,
+ // which implies that it has more components after len.
+ if (inPath[len] == '/') {
+ *aResult = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetNativeTarget(nsACString& aResult)
+{
+ CHECK_mPath();
+ aResult.Truncate();
+
+ struct STAT symStat;
+ if (LSTAT(mPath.get(), &symStat) == -1) {
+ return NSRESULT_FOR_ERRNO();
+ }
+
+ if (!S_ISLNK(symStat.st_mode)) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ int32_t size = (int32_t)symStat.st_size;
+ char* target = (char*)moz_xmalloc(size + 1);
+ if (!target) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (readlink(mPath.get(), target, (size_t)size) < 0) {
+ free(target);
+ return NSRESULT_FOR_ERRNO();
+ }
+ target[size] = '\0';
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFile> self(this);
+ int32_t maxLinks = 40;
+ while (true) {
+ if (maxLinks-- == 0) {
+ rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
+ break;
+ }
+
+ if (target[0] != '/') {
+ nsCOMPtr<nsIFile> parent;
+ if (NS_FAILED(rv = self->GetParent(getter_AddRefs(parent)))) {
+ break;
+ }
+ if (NS_FAILED(rv = parent->AppendRelativeNativePath(nsDependentCString(target)))) {
+ break;
+ }
+ if (NS_FAILED(rv = parent->GetNativePath(aResult))) {
+ break;
+ }
+ self = parent;
+ } else {
+ aResult = target;
+ }
+
+ const nsPromiseFlatCString& flatRetval = PromiseFlatCString(aResult);
+
+ // Any failure in testing the current target we'll just interpret
+ // as having reached our destiny.
+ if (LSTAT(flatRetval.get(), &symStat) == -1) {
+ break;
+ }
+
+ // And of course we're done if it isn't a symlink.
+ if (!S_ISLNK(symStat.st_mode)) {
+ break;
+ }
+
+ int32_t newSize = (int32_t)symStat.st_size;
+ if (newSize > size) {
+ char* newTarget = (char*)moz_xrealloc(target, newSize + 1);
+ if (!newTarget) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ }
+ target = newTarget;
+ size = newSize;
+ }
+
+ int32_t linkLen = readlink(flatRetval.get(), target, size);
+ if (linkLen == -1) {
+ rv = NSRESULT_FOR_ERRNO();
+ break;
+ }
+ target[linkLen] = '\0';
+ }
+
+ free(target);
+
+ if (NS_FAILED(rv)) {
+ aResult.Truncate();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetFollowLinks(bool* aFollowLinks)
+{
+ *aFollowLinks = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetFollowLinks(bool aFollowLinks)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator** aEntries)
+{
+ RefPtr<nsDirEnumeratorUnix> dir = new nsDirEnumeratorUnix();
+
+ nsresult rv = dir->Init(this, false);
+ if (NS_FAILED(rv)) {
+ *aEntries = nullptr;
+ } else {
+ dir.forget(aEntries);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::Load(PRLibrary** aResult)
+{
+ CHECK_mPath();
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+ nsTraceRefcnt::SetActivityIsLegal(false);
+#endif
+
+ *aResult = PR_LoadLibrary(mPath.get());
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+ nsTraceRefcnt::SetActivityIsLegal(true);
+#endif
+
+ if (!*aResult) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor)
+{
+ return GetNativePath(aPersistentDescriptor);
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor)
+{
+#ifdef MOZ_WIDGET_COCOA
+ if (aPersistentDescriptor.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Support pathnames as user-supplied descriptors if they begin with '/'
+ // or '~'. These characters do not collide with the base64 set used for
+ // encoding alias records.
+ char first = aPersistentDescriptor.First();
+ if (first == '/' || first == '~') {
+ return InitWithNativePath(aPersistentDescriptor);
+ }
+
+ uint32_t dataSize = aPersistentDescriptor.Length();
+ char* decodedData = PL_Base64Decode(
+ PromiseFlatCString(aPersistentDescriptor).get(), dataSize, nullptr);
+ if (!decodedData) {
+ NS_ERROR("SetPersistentDescriptor was given bad data");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Cast to an alias record and resolve.
+ AliasRecord aliasHeader = *(AliasPtr)decodedData;
+ int32_t aliasSize = ::GetAliasSizeFromPtr(&aliasHeader);
+ if (aliasSize > ((int32_t)dataSize * 3) / 4) { // be paranoid about having too few data
+ PR_Free(decodedData);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = NS_OK;
+
+ // Move the now-decoded data into the Handle.
+ // The size of the decoded data is 3/4 the size of the encoded data. See plbase64.h
+ Handle newHandle = nullptr;
+ if (::PtrToHand(decodedData, &newHandle, aliasSize) != noErr) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ PR_Free(decodedData);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ Boolean changed;
+ FSRef resolvedFSRef;
+ OSErr err = ::FSResolveAlias(nullptr, (AliasHandle)newHandle, &resolvedFSRef,
+ &changed);
+
+ rv = MacErrorMapper(err);
+ DisposeHandle(newHandle);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return InitWithFSRef(&resolvedFSRef);
+#else
+ return InitWithNativePath(aPersistentDescriptor);
+#endif
+}
+
+NS_IMETHODIMP
+nsLocalFile::Reveal()
+{
+#ifdef MOZ_WIDGET_GTK
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isDirectory;
+ if (NS_FAILED(IsDirectory(&isDirectory))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (isDirectory) {
+ return giovfs->ShowURIForInput(mPath);
+ } else if (NS_SUCCEEDED(giovfs->OrgFreedesktopFileManager1ShowItems(mPath))) {
+ return NS_OK;
+ } else {
+ nsCOMPtr<nsIFile> parentDir;
+ nsAutoCString dirPath;
+ if (NS_FAILED(GetParent(getter_AddRefs(parentDir)))) {
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(parentDir->GetNativePath(dirPath))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return giovfs->ShowURIForInput(dirPath);
+ }
+#elif defined(MOZ_WIDGET_COCOA)
+ CFURLRef url;
+ if (NS_SUCCEEDED(GetCFURL(&url))) {
+ nsresult rv = CocoaFileUtils::RevealFileInFinder(url);
+ ::CFRelease(url);
+ return rv;
+ }
+ return NS_ERROR_FAILURE;
+#else
+ return NS_ERROR_FAILURE;
+#endif
+}
+
+NS_IMETHODIMP
+nsLocalFile::Launch()
+{
+#ifdef MOZ_WIDGET_GTK
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return giovfs->ShowURIForInput(mPath);
+#elif defined(MOZ_ENABLE_CONTENTACTION)
+ QUrl uri = QUrl::fromLocalFile(QString::fromUtf8(mPath.get()));
+ ContentAction::Action action =
+ ContentAction::Action::defaultActionForFile(uri);
+
+ if (action.isValid()) {
+ action.trigger();
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+#elif defined(MOZ_WIDGET_ANDROID)
+ // Try to get a mimetype, if this fails just use the file uri alone
+ nsresult rv;
+ nsAutoCString type;
+ nsCOMPtr<nsIMIMEService> mimeService(do_GetService("@mozilla.org/mime;1", &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = mimeService->GetTypeFromFile(this, type);
+ }
+
+ nsAutoCString fileUri = NS_LITERAL_CSTRING("file://") + mPath;
+ return java::GeckoAppShell::OpenUriExternal(
+ NS_ConvertUTF8toUTF16(fileUri),
+ NS_ConvertUTF8toUTF16(type),
+ EmptyString(),
+ EmptyString(),
+ EmptyString(),
+ EmptyString()) ? NS_OK : NS_ERROR_FAILURE;
+#elif defined(MOZ_WIDGET_COCOA)
+ CFURLRef url;
+ if (NS_SUCCEEDED(GetCFURL(&url))) {
+ nsresult rv = CocoaFileUtils::OpenURL(url);
+ ::CFRelease(url);
+ return rv;
+ }
+ return NS_ERROR_FAILURE;
+#else
+ return NS_ERROR_FAILURE;
+#endif
+}
+
+nsresult
+NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowSymlinks,
+ nsIFile** aResult)
+{
+ RefPtr<nsLocalFile> file = new nsLocalFile();
+
+ file->SetFollowLinks(aFollowSymlinks);
+
+ if (!aPath.IsEmpty()) {
+ nsresult rv = file->InitWithNativePath(aPath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ file.forget(aResult);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// unicode support
+//-----------------------------------------------------------------------------
+
+#define SET_UCS(func, ucsArg) \
+ { \
+ nsAutoCString buf; \
+ nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \
+ if (NS_FAILED(rv)) \
+ return rv; \
+ return (func)(buf); \
+ }
+
+#define GET_UCS(func, ucsArg) \
+ { \
+ nsAutoCString buf; \
+ nsresult rv = (func)(buf); \
+ if (NS_FAILED(rv)) return rv; \
+ return NS_CopyNativeToUnicode(buf, ucsArg); \
+ }
+
+#define SET_UCS_2ARGS_2(func, opaqueArg, ucsArg) \
+ { \
+ nsAutoCString buf; \
+ nsresult rv = NS_CopyUnicodeToNative(ucsArg, buf); \
+ if (NS_FAILED(rv)) \
+ return rv; \
+ return (func)(opaqueArg, buf); \
+ }
+
+// Unicode interface Wrapper
+nsresult
+nsLocalFile::InitWithPath(const nsAString& aFilePath)
+{
+ SET_UCS(InitWithNativePath, aFilePath);
+}
+nsresult
+nsLocalFile::Append(const nsAString& aNode)
+{
+ SET_UCS(AppendNative, aNode);
+}
+nsresult
+nsLocalFile::AppendRelativePath(const nsAString& aNode)
+{
+ SET_UCS(AppendRelativeNativePath, aNode);
+}
+nsresult
+nsLocalFile::GetLeafName(nsAString& aLeafName)
+{
+ GET_UCS(GetNativeLeafName, aLeafName);
+}
+nsresult
+nsLocalFile::SetLeafName(const nsAString& aLeafName)
+{
+ SET_UCS(SetNativeLeafName, aLeafName);
+}
+nsresult
+nsLocalFile::GetPath(nsAString& aResult)
+{
+ return NS_CopyNativeToUnicode(mPath, aResult);
+}
+nsresult
+nsLocalFile::CopyTo(nsIFile* aNewParentDir, const nsAString& aNewName)
+{
+ SET_UCS_2ARGS_2(CopyToNative , aNewParentDir, aNewName);
+}
+nsresult
+nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir,
+ const nsAString& aNewName)
+{
+ SET_UCS_2ARGS_2(CopyToFollowingLinksNative , aNewParentDir, aNewName);
+}
+nsresult
+nsLocalFile::MoveTo(nsIFile* aNewParentDir, const nsAString& aNewName)
+{
+ SET_UCS_2ARGS_2(MoveToNative, aNewParentDir, aNewName);
+}
+
+NS_IMETHODIMP
+nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName)
+{
+ SET_UCS_2ARGS_2(RenameToNative, aNewParentDir, aNewName);
+}
+
+NS_IMETHODIMP
+nsLocalFile::RenameToNative(nsIFile* aNewParentDir, const nsACString& aNewName)
+{
+ nsresult rv;
+
+ // check to make sure that this has been initialized properly
+ CHECK_mPath();
+
+ // check to make sure that we have a new parent
+ nsAutoCString newPathName;
+ rv = GetNativeTargetPathName(aNewParentDir, aNewName, newPathName);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // try for atomic rename
+ if (rename(mPath.get(), newPathName.get()) < 0) {
+ if (errno == EXDEV) {
+ rv = NS_ERROR_FILE_ACCESS_DENIED;
+ } else {
+ rv = NSRESULT_FOR_ERRNO();
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsLocalFile::GetTarget(nsAString& aResult)
+{
+ GET_UCS(GetNativeTarget, aResult);
+}
+
+// nsIHashable
+
+NS_IMETHODIMP
+nsLocalFile::Equals(nsIHashable* aOther, bool* aResult)
+{
+ nsCOMPtr<nsIFile> otherFile(do_QueryInterface(aOther));
+ if (!otherFile) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ return Equals(otherFile, aResult);
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetHashCode(uint32_t* aResult)
+{
+ *aResult = HashString(mPath);
+ return NS_OK;
+}
+
+nsresult
+NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks, nsIFile** aResult)
+{
+ nsAutoCString buf;
+ nsresult rv = NS_CopyUnicodeToNative(aPath, buf);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return NS_NewNativeLocalFile(buf, aFollowLinks, aResult);
+}
+
+//-----------------------------------------------------------------------------
+// global init/shutdown
+//-----------------------------------------------------------------------------
+
+void
+nsLocalFile::GlobalInit()
+{
+}
+
+void
+nsLocalFile::GlobalShutdown()
+{
+}
+
+// nsILocalFileMac
+
+#ifdef MOZ_WIDGET_COCOA
+
+static nsresult MacErrorMapper(OSErr inErr)
+{
+ nsresult outErr;
+
+ switch (inErr) {
+ case noErr:
+ outErr = NS_OK;
+ break;
+
+ case fnfErr:
+ case afpObjectNotFound:
+ case afpDirNotFound:
+ outErr = NS_ERROR_FILE_NOT_FOUND;
+ break;
+
+ case dupFNErr:
+ case afpObjectExists:
+ outErr = NS_ERROR_FILE_ALREADY_EXISTS;
+ break;
+
+ case dskFulErr:
+ case afpDiskFull:
+ outErr = NS_ERROR_FILE_DISK_FULL;
+ break;
+
+ case fLckdErr:
+ case afpVolLocked:
+ outErr = NS_ERROR_FILE_IS_LOCKED;
+ break;
+
+ case afpAccessDenied:
+ outErr = NS_ERROR_FILE_ACCESS_DENIED;
+ break;
+
+ case afpDirNotEmpty:
+ outErr = NS_ERROR_FILE_DIR_NOT_EMPTY;
+ break;
+
+ // Can't find good map for some
+ case bdNamErr:
+ outErr = NS_ERROR_FAILURE;
+ break;
+
+ default:
+ outErr = NS_ERROR_FAILURE;
+ break;
+ }
+
+ return outErr;
+}
+
+static nsresult CFStringReftoUTF8(CFStringRef aInStrRef, nsACString& aOutStr)
+{
+ // first see if the conversion would succeed and find the length of the result
+ CFIndex usedBufLen, inStrLen = ::CFStringGetLength(aInStrRef);
+ CFIndex charsConverted = ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen),
+ kCFStringEncodingUTF8, 0, false,
+ nullptr, 0, &usedBufLen);
+ if (charsConverted == inStrLen) {
+ // all characters converted, do the actual conversion
+ aOutStr.SetLength(usedBufLen);
+ if (aOutStr.Length() != (unsigned int)usedBufLen) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ UInt8* buffer = (UInt8*)aOutStr.BeginWriting();
+ ::CFStringGetBytes(aInStrRef, CFRangeMake(0, inStrLen), kCFStringEncodingUTF8,
+ 0, false, buffer, usedBufLen, &usedBufLen);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsLocalFile::InitWithCFURL(CFURLRef aCFURL)
+{
+ UInt8 path[PATH_MAX];
+ if (::CFURLGetFileSystemRepresentation(aCFURL, true, path, PATH_MAX)) {
+ nsDependentCString nativePath((char*)path);
+ return InitWithNativePath(nativePath);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsLocalFile::InitWithFSRef(const FSRef* aFSRef)
+{
+ if (NS_WARN_IF(!aFSRef)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ CFURLRef newURLRef = ::CFURLCreateFromFSRef(kCFAllocatorDefault, aFSRef);
+ if (newURLRef) {
+ nsresult rv = InitWithCFURL(newURLRef);
+ ::CFRelease(newURLRef);
+ return rv;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetCFURL(CFURLRef* aResult)
+{
+ CHECK_mPath();
+
+ bool isDir;
+ IsDirectory(&isDir);
+ *aResult = ::CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
+ (UInt8*)mPath.get(),
+ mPath.Length(),
+ isDir);
+
+ return (*aResult ? NS_OK : NS_ERROR_FAILURE);
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetFSRef(FSRef* aResult)
+{
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ CFURLRef url = nullptr;
+ if (NS_SUCCEEDED(GetCFURL(&url))) {
+ if (::CFURLGetFSRef(url, aResult)) {
+ rv = NS_OK;
+ }
+ ::CFRelease(url);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetFSSpec(FSSpec* aResult)
+{
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ FSRef fsRef;
+ nsresult rv = GetFSRef(&fsRef);
+ if (NS_SUCCEEDED(rv)) {
+ OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoNone, nullptr, nullptr,
+ aResult, nullptr);
+ return MacErrorMapper(err);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetFileSizeWithResFork(int64_t* aFileSizeWithResFork)
+{
+ if (NS_WARN_IF(!aFileSizeWithResFork)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ FSRef fsRef;
+ nsresult rv = GetFSRef(&fsRef);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ FSCatalogInfo catalogInfo;
+ OSErr err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoDataSizes + kFSCatInfoRsrcSizes,
+ &catalogInfo, nullptr, nullptr, nullptr);
+ if (err != noErr) {
+ return MacErrorMapper(err);
+ }
+
+ *aFileSizeWithResFork =
+ catalogInfo.dataLogicalSize + catalogInfo.rsrcLogicalSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetFileType(OSType* aFileType)
+{
+ CFURLRef url;
+ if (NS_SUCCEEDED(GetCFURL(&url))) {
+ nsresult rv = CocoaFileUtils::GetFileTypeCode(url, aFileType);
+ ::CFRelease(url);
+ return rv;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetFileType(OSType aFileType)
+{
+ CFURLRef url;
+ if (NS_SUCCEEDED(GetCFURL(&url))) {
+ nsresult rv = CocoaFileUtils::SetFileTypeCode(url, aFileType);
+ ::CFRelease(url);
+ return rv;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetFileCreator(OSType* aFileCreator)
+{
+ CFURLRef url;
+ if (NS_SUCCEEDED(GetCFURL(&url))) {
+ nsresult rv = CocoaFileUtils::GetFileCreatorCode(url, aFileCreator);
+ ::CFRelease(url);
+ return rv;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetFileCreator(OSType aFileCreator)
+{
+ CFURLRef url;
+ if (NS_SUCCEEDED(GetCFURL(&url))) {
+ nsresult rv = CocoaFileUtils::SetFileCreatorCode(url, aFileCreator);
+ ::CFRelease(url);
+ return rv;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsLocalFile::LaunchWithDoc(nsIFile* aDocToLoad, bool aLaunchInBackground)
+{
+ bool isExecutable;
+ nsresult rv = IsExecutable(&isExecutable);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!isExecutable) {
+ return NS_ERROR_FILE_EXECUTION_FAILED;
+ }
+
+ FSRef appFSRef, docFSRef;
+ rv = GetFSRef(&appFSRef);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (aDocToLoad) {
+ nsCOMPtr<nsILocalFileMac> macDoc = do_QueryInterface(aDocToLoad);
+ rv = macDoc->GetFSRef(&docFSRef);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ LSLaunchFlags theLaunchFlags = kLSLaunchDefaults;
+ LSLaunchFSRefSpec thelaunchSpec;
+
+ if (aLaunchInBackground) {
+ theLaunchFlags |= kLSLaunchDontSwitch;
+ }
+ memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec));
+
+ thelaunchSpec.appRef = &appFSRef;
+ if (aDocToLoad) {
+ thelaunchSpec.numDocs = 1;
+ thelaunchSpec.itemRefs = &docFSRef;
+ }
+ thelaunchSpec.launchFlags = theLaunchFlags;
+
+ OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr);
+ if (err != noErr) {
+ return MacErrorMapper(err);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::OpenDocWithApp(nsIFile* aAppToOpenWith, bool aLaunchInBackground)
+{
+ FSRef docFSRef;
+ nsresult rv = GetFSRef(&docFSRef);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!aAppToOpenWith) {
+ OSErr err = ::LSOpenFSRef(&docFSRef, nullptr);
+ return MacErrorMapper(err);
+ }
+
+ nsCOMPtr<nsILocalFileMac> appFileMac = do_QueryInterface(aAppToOpenWith, &rv);
+ if (!appFileMac) {
+ return rv;
+ }
+
+ bool isExecutable;
+ rv = appFileMac->IsExecutable(&isExecutable);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!isExecutable) {
+ return NS_ERROR_FILE_EXECUTION_FAILED;
+ }
+
+ FSRef appFSRef;
+ rv = appFileMac->GetFSRef(&appFSRef);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ LSLaunchFlags theLaunchFlags = kLSLaunchDefaults;
+ LSLaunchFSRefSpec thelaunchSpec;
+
+ if (aLaunchInBackground) {
+ theLaunchFlags |= kLSLaunchDontSwitch;
+ }
+ memset(&thelaunchSpec, 0, sizeof(LSLaunchFSRefSpec));
+
+ thelaunchSpec.appRef = &appFSRef;
+ thelaunchSpec.numDocs = 1;
+ thelaunchSpec.itemRefs = &docFSRef;
+ thelaunchSpec.launchFlags = theLaunchFlags;
+
+ OSErr err = ::LSOpenFromRefSpec(&thelaunchSpec, nullptr);
+ if (err != noErr) {
+ return MacErrorMapper(err);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::IsPackage(bool* aResult)
+{
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aResult = false;
+
+ CFURLRef url;
+ nsresult rv = GetCFURL(&url);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ LSItemInfoRecord info;
+ OSStatus status = ::LSCopyItemInfoForURL(url, kLSRequestBasicFlagsOnly, &info);
+
+ ::CFRelease(url);
+
+ if (status != noErr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = !!(info.flags & kLSItemInfoIsPackage);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetBundleDisplayName(nsAString& aOutBundleName)
+{
+ bool isPackage = false;
+ nsresult rv = IsPackage(&isPackage);
+ if (NS_FAILED(rv) || !isPackage) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString name;
+ rv = GetLeafName(name);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ int32_t length = name.Length();
+ if (Substring(name, length - 4, length).EqualsLiteral(".app")) {
+ // 4 characters in ".app"
+ aOutBundleName = Substring(name, 0, length - 4);
+ } else {
+ aOutBundleName = name;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetBundleIdentifier(nsACString& aOutBundleIdentifier)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ CFURLRef urlRef;
+ if (NS_SUCCEEDED(GetCFURL(&urlRef))) {
+ CFBundleRef bundle = ::CFBundleCreate(nullptr, urlRef);
+ if (bundle) {
+ CFStringRef bundleIdentifier = ::CFBundleGetIdentifier(bundle);
+ if (bundleIdentifier) {
+ rv = CFStringReftoUTF8(bundleIdentifier, aOutBundleIdentifier);
+ }
+ ::CFRelease(bundle);
+ }
+ ::CFRelease(urlRef);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetBundleContentsLastModifiedTime(int64_t* aLastModTime)
+{
+ CHECK_mPath();
+ if (NS_WARN_IF(!aLastModTime)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool isPackage = false;
+ nsresult rv = IsPackage(&isPackage);
+ if (NS_FAILED(rv) || !isPackage) {
+ return GetLastModifiedTime(aLastModTime);
+ }
+
+ nsAutoCString infoPlistPath(mPath);
+ infoPlistPath.AppendLiteral("/Contents/Info.plist");
+ PRFileInfo64 info;
+ if (PR_GetFileInfo64(infoPlistPath.get(), &info) != PR_SUCCESS) {
+ return GetLastModifiedTime(aLastModTime);
+ }
+ int64_t modTime = int64_t(info.modifyTime);
+ if (modTime == 0) {
+ *aLastModTime = 0;
+ } else {
+ *aLastModTime = modTime / int64_t(PR_USEC_PER_MSEC);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsLocalFile::InitWithFile(nsIFile* aFile)
+{
+ if (NS_WARN_IF(!aFile)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoCString nativePath;
+ nsresult rv = aFile->GetNativePath(nativePath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return InitWithNativePath(nativePath);
+}
+
+nsresult
+NS_NewLocalFileWithFSRef(const FSRef* aFSRef, bool aFollowLinks,
+ nsILocalFileMac** aResult)
+{
+ RefPtr<nsLocalFile> file = new nsLocalFile();
+
+ file->SetFollowLinks(aFollowLinks);
+
+ nsresult rv = file->InitWithFSRef(aFSRef);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ file.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+NS_NewLocalFileWithCFURL(const CFURLRef aURL, bool aFollowLinks,
+ nsILocalFileMac** aResult)
+{
+ RefPtr<nsLocalFile> file = new nsLocalFile();
+
+ file->SetFollowLinks(aFollowLinks);
+
+ nsresult rv = file->InitWithCFURL(aURL);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ file.forget(aResult);
+ return NS_OK;
+}
+
+#endif
diff --git a/xpcom/io/nsLocalFileUnix.h b/xpcom/io/nsLocalFileUnix.h
new file mode 100644
index 000000000..9a3e7d6af
--- /dev/null
+++ b/xpcom/io/nsLocalFileUnix.h
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation of nsIFile for ``Unixy'' systems.
+ */
+
+#ifndef _nsLocalFileUNIX_H_
+#define _nsLocalFileUNIX_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "nscore.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIHashable.h"
+#include "nsIClassInfoImpl.h"
+#include "mozilla/Attributes.h"
+#ifdef MOZ_WIDGET_COCOA
+#include "nsILocalFileMac.h"
+#endif
+
+/**
+ * we need these for statfs()
+ */
+#ifdef HAVE_SYS_STATVFS_H
+ #if defined(__osf__) && defined(__DECCXX)
+ extern "C" int statvfs(const char *, struct statvfs *);
+ #endif
+ #include <sys/statvfs.h>
+#endif
+
+#ifdef HAVE_SYS_STATFS_H
+ #include <sys/statfs.h>
+#endif
+
+#ifdef HAVE_SYS_VFS_H
+ #include <sys/vfs.h>
+#endif
+
+#ifdef HAVE_SYS_MOUNT_H
+ #include <sys/param.h>
+ #include <sys/mount.h>
+#endif
+
+#if defined(HAVE_STATVFS64) && (!defined(LINUX) && !defined(__osf__))
+ #define STATFS statvfs64
+ #define F_BSIZE f_frsize
+#elif defined(HAVE_STATVFS) && (!defined(LINUX) && !defined(__osf__))
+ #define STATFS statvfs
+ #define F_BSIZE f_frsize
+#elif defined(HAVE_STATFS64)
+ #define STATFS statfs64
+ #define F_BSIZE f_bsize
+#elif defined(HAVE_STATFS)
+ #define STATFS statfs
+ #define F_BSIZE f_bsize
+#endif
+
+// stat64 and lstat64 are deprecated on OS X. Normal stat and lstat are
+// 64-bit by default on OS X 10.6+.
+#if defined(HAVE_STAT64) && defined(HAVE_LSTAT64) && !defined(XP_DARWIN)
+ #if defined (AIX)
+ #if defined STAT
+ #undef STAT
+ #endif
+ #endif
+ #define STAT stat64
+ #define LSTAT lstat64
+ #define HAVE_STATS64 1
+#else
+ #define STAT stat
+ #define LSTAT lstat
+#endif
+
+
+class nsLocalFile final
+#ifdef MOZ_WIDGET_COCOA
+ : public nsILocalFileMac
+#else
+ : public nsILocalFile
+#endif
+ , public nsIHashable
+{
+public:
+ NS_DEFINE_STATIC_CID_ACCESSOR(NS_LOCAL_FILE_CID)
+
+ nsLocalFile();
+
+ static nsresult nsLocalFileConstructor(nsISupports* aOuter,
+ const nsIID& aIID,
+ void** aInstancePtr);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIFILE
+ NS_DECL_NSILOCALFILE
+#ifdef MOZ_WIDGET_COCOA
+ NS_DECL_NSILOCALFILEMAC
+#endif
+ NS_DECL_NSIHASHABLE
+
+public:
+ static void GlobalInit();
+ static void GlobalShutdown();
+
+private:
+ nsLocalFile(const nsLocalFile& aOther);
+ ~nsLocalFile()
+ {
+ }
+
+protected:
+ // This stat cache holds the *last stat* - it does not invalidate.
+ // Call "FillStatCache" whenever you want to stat our file.
+ struct STAT mCachedStat;
+ nsCString mPath;
+
+ void LocateNativeLeafName(nsACString::const_iterator&,
+ nsACString::const_iterator&);
+
+ nsresult CopyDirectoryTo(nsIFile* aNewParent);
+ nsresult CreateAllAncestors(uint32_t aPermissions);
+ nsresult GetNativeTargetPathName(nsIFile* aNewParent,
+ const nsACString& aNewName,
+ nsACString& aResult);
+
+ bool FillStatCache();
+
+ nsresult CreateAndKeepOpen(uint32_t aType, int aFlags,
+ uint32_t aPermissions, PRFileDesc** aResult);
+};
+
+#endif /* _nsLocalFileUNIX_H_ */
diff --git a/xpcom/io/nsLocalFileWin.cpp b/xpcom/io/nsLocalFileWin.cpp
new file mode 100644
index 000000000..3a7e570f5
--- /dev/null
+++ b/xpcom/io/nsLocalFileWin.cpp
@@ -0,0 +1,3787 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/WindowsVersion.h"
+
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsMemory.h"
+#include "GeckoProfiler.h"
+
+#include "nsLocalFile.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsNativeCharsetUtils.h"
+
+#include "nsISimpleEnumerator.h"
+#include "nsIComponentManager.h"
+#include "prio.h"
+#include "private/pprio.h" // To get PR_ImportFile
+#include "prprf.h"
+#include "prmem.h"
+#include "nsHashKeys.h"
+
+#include "nsXPIDLString.h"
+#include "nsReadableUtils.h"
+
+#include <direct.h>
+#include <windows.h>
+#include <shlwapi.h>
+#include <aclapi.h>
+
+#include "shellapi.h"
+#include "shlguid.h"
+
+#include <io.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <mbstring.h>
+
+#include "nsXPIDLString.h"
+#include "prproces.h"
+#include "prlink.h"
+
+#include "mozilla/Mutex.h"
+#include "SpecialSystemDirectory.h"
+
+#include "nsTraceRefcnt.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+
+#include "nsIWindowMediator.h"
+#include "mozIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWidget.h"
+#include "mozilla/WidgetUtils.h"
+
+using namespace mozilla;
+
+#define CHECK_mWorkingPath() \
+ PR_BEGIN_MACRO \
+ if (mWorkingPath.IsEmpty()) \
+ return NS_ERROR_NOT_INITIALIZED; \
+ PR_END_MACRO
+
+// CopyFileEx only supports unbuffered I/O in Windows Vista and above
+#ifndef COPY_FILE_NO_BUFFERING
+#define COPY_FILE_NO_BUFFERING 0x00001000
+#endif
+
+#ifndef FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
+#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000
+#endif
+
+#ifndef DRIVE_REMOTE
+#define DRIVE_REMOTE 4
+#endif
+
+static HWND
+GetMostRecentNavigatorHWND()
+{
+ nsresult rv;
+ nsCOMPtr<nsIWindowMediator> winMediator(
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> navWin;
+ rv = winMediator->GetMostRecentWindow(u"navigator:browser",
+ getter_AddRefs(navWin));
+ if (NS_FAILED(rv) || !navWin) {
+ return nullptr;
+ }
+
+ nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(navWin);
+ nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(win);
+ if (!widget) {
+ return nullptr;
+ }
+
+ return reinterpret_cast<HWND>(widget->GetNativeData(NS_NATIVE_WINDOW));
+}
+
+
+/**
+ * A runnable to dispatch back to the main thread when
+ * AsyncRevealOperation completes.
+*/
+class AsyncLocalFileWinDone : public Runnable
+{
+public:
+ AsyncLocalFileWinDone() :
+ mWorkerThread(do_GetCurrentThread())
+ {
+ // Objects of this type must only be created on worker threads
+ MOZ_ASSERT(!NS_IsMainThread());
+ }
+
+ NS_IMETHOD Run() override
+ {
+ // This event shuts down the worker thread and so must be main thread.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If we don't destroy the thread when we're done with it, it will hang
+ // around forever... and that is bad!
+ mWorkerThread->Shutdown();
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIThread> mWorkerThread;
+};
+
+/**
+ * A runnable to dispatch from the main thread when an async operation should
+ * be performed.
+*/
+class AsyncRevealOperation : public Runnable
+{
+public:
+ explicit AsyncRevealOperation(const nsAString& aResolvedPath)
+ : mResolvedPath(aResolvedPath)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread(),
+ "AsyncRevealOperation should not be run on the main thread!");
+
+ bool doCoUninitialize = SUCCEEDED(
+ CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));
+ Reveal();
+ if (doCoUninitialize) {
+ CoUninitialize();
+ }
+
+ // Send the result back to the main thread so that this thread can be
+ // cleanly shut down
+ nsCOMPtr<nsIRunnable> resultrunnable = new AsyncLocalFileWinDone();
+ NS_DispatchToMainThread(resultrunnable);
+ return NS_OK;
+ }
+
+private:
+ // Reveals the path in explorer.
+ nsresult Reveal()
+ {
+ DWORD attributes = GetFileAttributesW(mResolvedPath.get());
+ if (INVALID_FILE_ATTRIBUTES == attributes) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ HRESULT hr;
+ if (attributes & FILE_ATTRIBUTE_DIRECTORY) {
+ // We have a directory so we should open the directory itself.
+ LPITEMIDLIST dir = ILCreateFromPathW(mResolvedPath.get());
+ if (!dir) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LPCITEMIDLIST selection[] = { dir };
+ UINT count = ArrayLength(selection);
+
+ //Perform the open of the directory.
+ hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
+ CoTaskMemFree(dir);
+ } else {
+ int32_t len = mResolvedPath.Length();
+ // We don't currently handle UNC long paths of the form \\?\ anywhere so
+ // this should be fine.
+ if (len > MAX_PATH) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+ WCHAR parentDirectoryPath[MAX_PATH + 1] = { 0 };
+ wcsncpy(parentDirectoryPath, mResolvedPath.get(), MAX_PATH);
+ PathRemoveFileSpecW(parentDirectoryPath);
+
+ // We have a file so we should open the parent directory.
+ LPITEMIDLIST dir = ILCreateFromPathW(parentDirectoryPath);
+ if (!dir) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Set the item in the directory to select to the file we want to reveal.
+ LPITEMIDLIST item = ILCreateFromPathW(mResolvedPath.get());
+ if (!item) {
+ CoTaskMemFree(dir);
+ return NS_ERROR_FAILURE;
+ }
+
+ LPCITEMIDLIST selection[] = { item };
+ UINT count = ArrayLength(selection);
+
+ //Perform the selection of the file.
+ hr = SHOpenFolderAndSelectItems(dir, count, selection, 0);
+
+ CoTaskMemFree(dir);
+ CoTaskMemFree(item);
+ }
+
+ return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+ // Stores the path to perform the operation on
+ nsString mResolvedPath;
+};
+
+class nsDriveEnumerator : public nsISimpleEnumerator
+{
+public:
+ nsDriveEnumerator();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISIMPLEENUMERATOR
+ nsresult Init();
+private:
+ virtual ~nsDriveEnumerator();
+
+ /* mDrives stores the null-separated drive names.
+ * Init sets them.
+ * HasMoreElements checks mStartOfCurrentDrive.
+ * GetNext advances mStartOfCurrentDrive.
+ */
+ nsString mDrives;
+ nsAString::const_iterator mStartOfCurrentDrive;
+ nsAString::const_iterator mEndOfDrivesString;
+};
+
+//----------------------------------------------------------------------------
+// short cut resolver
+//----------------------------------------------------------------------------
+class ShortcutResolver
+{
+public:
+ ShortcutResolver();
+ // nonvirtual since we're not subclassed
+ ~ShortcutResolver();
+
+ nsresult Init();
+ nsresult Resolve(const WCHAR* aIn, WCHAR* aOut);
+ nsresult SetShortcut(bool aUpdateExisting,
+ const WCHAR* aShortcutPath,
+ const WCHAR* aTargetPath,
+ const WCHAR* aWorkingDir,
+ const WCHAR* aArgs,
+ const WCHAR* aDescription,
+ const WCHAR* aIconFile,
+ int32_t aIconIndex);
+
+private:
+ Mutex mLock;
+ RefPtr<IPersistFile> mPersistFile;
+ RefPtr<IShellLinkW> mShellLink;
+};
+
+ShortcutResolver::ShortcutResolver() :
+ mLock("ShortcutResolver.mLock")
+{
+ CoInitialize(nullptr);
+}
+
+ShortcutResolver::~ShortcutResolver()
+{
+ CoUninitialize();
+}
+
+nsresult
+ShortcutResolver::Init()
+{
+ // Get a pointer to the IPersistFile interface.
+ if (FAILED(CoCreateInstance(CLSID_ShellLink,
+ nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_IShellLinkW,
+ getter_AddRefs(mShellLink))) ||
+ FAILED(mShellLink->QueryInterface(IID_IPersistFile,
+ getter_AddRefs(mPersistFile)))) {
+ mShellLink = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+// |out| must be an allocated buffer of size MAX_PATH
+nsresult
+ShortcutResolver::Resolve(const WCHAR* aIn, WCHAR* aOut)
+{
+ if (!mShellLink) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MutexAutoLock lock(mLock);
+
+ if (FAILED(mPersistFile->Load(aIn, STGM_READ)) ||
+ FAILED(mShellLink->Resolve(nullptr, SLR_NO_UI)) ||
+ FAILED(mShellLink->GetPath(aOut, MAX_PATH, nullptr, SLGP_UNCPRIORITY))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult
+ShortcutResolver::SetShortcut(bool aUpdateExisting,
+ const WCHAR* aShortcutPath,
+ const WCHAR* aTargetPath,
+ const WCHAR* aWorkingDir,
+ const WCHAR* aArgs,
+ const WCHAR* aDescription,
+ const WCHAR* aIconPath,
+ int32_t aIconIndex)
+{
+ if (!mShellLink) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aShortcutPath) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MutexAutoLock lock(mLock);
+
+ if (aUpdateExisting) {
+ if (FAILED(mPersistFile->Load(aShortcutPath, STGM_READWRITE))) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ if (!aTargetPath) {
+ return NS_ERROR_FILE_TARGET_DOES_NOT_EXIST;
+ }
+
+ // Since we reuse our IPersistFile, we have to clear out any values that
+ // may be left over from previous calls to SetShortcut.
+ if (FAILED(mShellLink->SetWorkingDirectory(L"")) ||
+ FAILED(mShellLink->SetArguments(L"")) ||
+ FAILED(mShellLink->SetDescription(L"")) ||
+ FAILED(mShellLink->SetIconLocation(L"", 0))) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (aTargetPath && FAILED(mShellLink->SetPath(aTargetPath))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aWorkingDir && FAILED(mShellLink->SetWorkingDirectory(aWorkingDir))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aArgs && FAILED(mShellLink->SetArguments(aArgs))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aDescription && FAILED(mShellLink->SetDescription(aDescription))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aIconPath && FAILED(mShellLink->SetIconLocation(aIconPath, aIconIndex))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (FAILED(mPersistFile->Save(aShortcutPath,
+ TRUE))) {
+ // Second argument indicates whether the file path specified in the
+ // first argument should become the "current working file" for this
+ // IPersistFile
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static ShortcutResolver* gResolver = nullptr;
+
+static nsresult
+NS_CreateShortcutResolver()
+{
+ gResolver = new ShortcutResolver();
+ return gResolver->Init();
+}
+
+static void
+NS_DestroyShortcutResolver()
+{
+ delete gResolver;
+ gResolver = nullptr;
+}
+
+
+//-----------------------------------------------------------------------------
+// static helper functions
+//-----------------------------------------------------------------------------
+
+// certainly not all the error that can be
+// encountered, but many of them common ones
+static nsresult
+ConvertWinError(DWORD aWinErr)
+{
+ nsresult rv;
+
+ switch (aWinErr) {
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ case ERROR_INVALID_DRIVE:
+ case ERROR_NOT_READY:
+ rv = NS_ERROR_FILE_NOT_FOUND;
+ break;
+ case ERROR_ACCESS_DENIED:
+ case ERROR_NOT_SAME_DEVICE:
+ rv = NS_ERROR_FILE_ACCESS_DENIED;
+ break;
+ case ERROR_SHARING_VIOLATION: // CreateFile without sharing flags
+ case ERROR_LOCK_VIOLATION: // LockFile, LockFileEx
+ rv = NS_ERROR_FILE_IS_LOCKED;
+ break;
+ case ERROR_NOT_ENOUGH_MEMORY:
+ case ERROR_INVALID_BLOCK:
+ case ERROR_INVALID_HANDLE:
+ case ERROR_ARENA_TRASHED:
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ break;
+ case ERROR_CURRENT_DIRECTORY:
+ rv = NS_ERROR_FILE_DIR_NOT_EMPTY;
+ break;
+ case ERROR_WRITE_PROTECT:
+ rv = NS_ERROR_FILE_READ_ONLY;
+ break;
+ case ERROR_HANDLE_DISK_FULL:
+ rv = NS_ERROR_FILE_TOO_BIG;
+ break;
+ case ERROR_FILE_EXISTS:
+ case ERROR_ALREADY_EXISTS:
+ case ERROR_CANNOT_MAKE:
+ rv = NS_ERROR_FILE_ALREADY_EXISTS;
+ break;
+ case ERROR_FILENAME_EXCED_RANGE:
+ rv = NS_ERROR_FILE_NAME_TOO_LONG;
+ break;
+ case ERROR_DIRECTORY:
+ rv = NS_ERROR_FILE_NOT_DIRECTORY;
+ break;
+ case 0:
+ rv = NS_OK;
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ return rv;
+}
+
+// as suggested in the MSDN documentation on SetFilePointer
+static __int64
+MyFileSeek64(HANDLE aHandle, __int64 aDistance, DWORD aMoveMethod)
+{
+ LARGE_INTEGER li;
+
+ li.QuadPart = aDistance;
+ li.LowPart = SetFilePointer(aHandle, li.LowPart, &li.HighPart, aMoveMethod);
+ if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) {
+ li.QuadPart = -1;
+ }
+
+ return li.QuadPart;
+}
+
+static bool
+IsShortcutPath(const nsAString& aPath)
+{
+ // Under Windows, the shortcuts are just files with a ".lnk" extension.
+ // Note also that we don't resolve links in the middle of paths.
+ // i.e. "c:\foo.lnk\bar.txt" is invalid.
+ MOZ_ASSERT(!aPath.IsEmpty(), "don't pass an empty string");
+ int32_t len = aPath.Length();
+ return len >= 4 && (StringTail(aPath, 4).LowerCaseEqualsASCII(".lnk"));
+}
+
+//-----------------------------------------------------------------------------
+// We need the following three definitions to make |OpenFile| convert a file
+// handle to an NSPR file descriptor correctly when |O_APPEND| flag is
+// specified. It is defined in a private header of NSPR (primpl.h) we can't
+// include. As a temporary workaround until we decide how to extend
+// |PR_ImportFile|, we define it here. Currently, |_PR_HAVE_PEEK_BUFFER|
+// and |PR_STRICT_ADDR_LEN| are not defined for the 'w95'-dependent portion
+// of NSPR so that fields of |PRFilePrivate| #ifdef'd by them are not copied.
+// Similarly, |_MDFileDesc| is taken from nsprpub/pr/include/md/_win95.h.
+// In an unlikely case we switch to 'NT'-dependent NSPR AND this temporary
+// workaround last beyond the switch, |PRFilePrivate| and |_MDFileDesc|
+// need to be changed to match the definitions for WinNT.
+//-----------------------------------------------------------------------------
+typedef enum
+{
+ _PR_TRI_TRUE = 1,
+ _PR_TRI_FALSE = 0,
+ _PR_TRI_UNKNOWN = -1
+} _PRTriStateBool;
+
+struct _MDFileDesc
+{
+ PROsfd osfd;
+};
+
+struct PRFilePrivate
+{
+ int32_t state;
+ bool nonblocking;
+ _PRTriStateBool inheritable;
+ PRFileDesc* next;
+ int lockCount; /* 0: not locked
+ * -1: a native lockfile call is in progress
+ * > 0: # times the file is locked */
+ bool appendMode;
+ _MDFileDesc md;
+};
+
+//-----------------------------------------------------------------------------
+// Six static methods defined below (OpenFile, FileTimeToPRTime, GetFileInfo,
+// OpenDir, CloseDir, ReadDir) should go away once the corresponding
+// UTF-16 APIs are implemented on all the supported platforms (or at least
+// Windows 9x/ME) in NSPR. Currently, they're only implemented on
+// Windows NT4 or later. (bug 330665)
+//-----------------------------------------------------------------------------
+
+// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
+// PR_Open and _PR_MD_OPEN
+nsresult
+OpenFile(const nsAFlatString& aName,
+ int aOsflags,
+ int aMode,
+ bool aShareDelete,
+ PRFileDesc** aFd)
+{
+ int32_t access = 0;
+
+ int32_t shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ int32_t disposition = 0;
+ int32_t attributes = 0;
+
+ if (aShareDelete) {
+ shareMode |= FILE_SHARE_DELETE;
+ }
+
+ if (aOsflags & PR_SYNC) {
+ attributes = FILE_FLAG_WRITE_THROUGH;
+ }
+ if (aOsflags & PR_RDONLY || aOsflags & PR_RDWR) {
+ access |= GENERIC_READ;
+ }
+ if (aOsflags & PR_WRONLY || aOsflags & PR_RDWR) {
+ access |= GENERIC_WRITE;
+ }
+
+ if (aOsflags & PR_CREATE_FILE && aOsflags & PR_EXCL) {
+ disposition = CREATE_NEW;
+ } else if (aOsflags & PR_CREATE_FILE) {
+ if (aOsflags & PR_TRUNCATE) {
+ disposition = CREATE_ALWAYS;
+ } else {
+ disposition = OPEN_ALWAYS;
+ }
+ } else {
+ if (aOsflags & PR_TRUNCATE) {
+ disposition = TRUNCATE_EXISTING;
+ } else {
+ disposition = OPEN_EXISTING;
+ }
+ }
+
+ if (aOsflags & nsIFile::DELETE_ON_CLOSE) {
+ attributes |= FILE_FLAG_DELETE_ON_CLOSE;
+ }
+
+ if (aOsflags & nsIFile::OS_READAHEAD) {
+ attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
+ }
+
+ // If no write permissions are requested, and if we are possibly creating
+ // the file, then set the new file as read only.
+ // The flag has no effect if we happen to open the file.
+ if (!(aMode & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) &&
+ disposition != OPEN_EXISTING) {
+ attributes |= FILE_ATTRIBUTE_READONLY;
+ }
+
+ HANDLE file = ::CreateFileW(aName.get(), access, shareMode,
+ nullptr, disposition, attributes, nullptr);
+
+ if (file == INVALID_HANDLE_VALUE) {
+ *aFd = nullptr;
+ return ConvertWinError(GetLastError());
+ }
+
+ *aFd = PR_ImportFile((PROsfd) file);
+ if (*aFd) {
+ // On Windows, _PR_HAVE_O_APPEND is not defined so that we have to
+ // add it manually. (see |PR_Open| in nsprpub/pr/src/io/prfile.c)
+ (*aFd)->secret->appendMode = (PR_APPEND & aOsflags) ? true : false;
+ return NS_OK;
+ }
+
+ nsresult rv = NS_ErrorAccordingToNSPR();
+
+ CloseHandle(file);
+
+ return rv;
+}
+
+// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} :
+// PR_FileTimeToPRTime and _PR_FileTimeToPRTime
+static void
+FileTimeToPRTime(const FILETIME* aFiletime, PRTime* aPrtm)
+{
+#ifdef __GNUC__
+ const PRTime _pr_filetime_offset = 116444736000000000LL;
+#else
+ const PRTime _pr_filetime_offset = 116444736000000000i64;
+#endif
+
+ MOZ_ASSERT(sizeof(FILETIME) == sizeof(PRTime));
+ ::CopyMemory(aPrtm, aFiletime, sizeof(PRTime));
+#ifdef __GNUC__
+ *aPrtm = (*aPrtm - _pr_filetime_offset) / 10LL;
+#else
+ *aPrtm = (*aPrtm - _pr_filetime_offset) / 10i64;
+#endif
+}
+
+// copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} with some
+// changes : PR_GetFileInfo64, _PR_MD_GETFILEINFO64
+static nsresult
+GetFileInfo(const nsAFlatString& aName, PRFileInfo64* aInfo)
+{
+ WIN32_FILE_ATTRIBUTE_DATA fileData;
+
+ if (aName.IsEmpty() || aName.FindCharInSet(u"?*") != kNotFound) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!::GetFileAttributesExW(aName.get(), GetFileExInfoStandard, &fileData)) {
+ return ConvertWinError(GetLastError());
+ }
+
+ if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ aInfo->type = PR_FILE_DIRECTORY;
+ } else {
+ aInfo->type = PR_FILE_FILE;
+ }
+
+ aInfo->size = fileData.nFileSizeHigh;
+ aInfo->size = (aInfo->size << 32) + fileData.nFileSizeLow;
+
+ FileTimeToPRTime(&fileData.ftLastWriteTime, &aInfo->modifyTime);
+
+ if (0 == fileData.ftCreationTime.dwLowDateTime &&
+ 0 == fileData.ftCreationTime.dwHighDateTime) {
+ aInfo->creationTime = aInfo->modifyTime;
+ } else {
+ FileTimeToPRTime(&fileData.ftCreationTime, &aInfo->creationTime);
+ }
+
+ return NS_OK;
+}
+
+struct nsDir
+{
+ HANDLE handle;
+ WIN32_FIND_DATAW data;
+ bool firstEntry;
+};
+
+static nsresult
+OpenDir(const nsAFlatString& aName, nsDir** aDir)
+{
+ if (NS_WARN_IF(!aDir)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aDir = nullptr;
+ if (aName.Length() + 3 >= MAX_PATH) {
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ }
+
+ nsDir* d = new nsDir();
+ nsAutoString filename(aName);
+
+ // If |aName| ends in a slash or backslash, do not append another backslash.
+ if (filename.Last() == L'/' || filename.Last() == L'\\') {
+ filename.Append('*');
+ } else {
+ filename.AppendLiteral("\\*");
+ }
+
+ filename.ReplaceChar(L'/', L'\\');
+
+ // FindFirstFileW Will have a last error of ERROR_DIRECTORY if
+ // <file_path>\* is passed in. If <unknown_path>\* is passed in then
+ // ERROR_PATH_NOT_FOUND will be the last error.
+ d->handle = ::FindFirstFileW(filename.get(), &(d->data));
+
+ if (d->handle == INVALID_HANDLE_VALUE) {
+ delete d;
+ return ConvertWinError(GetLastError());
+ }
+ d->firstEntry = true;
+
+ *aDir = d;
+ return NS_OK;
+}
+
+static nsresult
+ReadDir(nsDir* aDir, PRDirFlags aFlags, nsString& aName)
+{
+ aName.Truncate();
+ if (NS_WARN_IF(!aDir)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ while (1) {
+ BOOL rv;
+ if (aDir->firstEntry) {
+ aDir->firstEntry = false;
+ rv = 1;
+ } else {
+ rv = ::FindNextFileW(aDir->handle, &(aDir->data));
+ }
+
+ if (rv == 0) {
+ break;
+ }
+
+ const wchar_t* fileName;
+ fileName = (aDir)->data.cFileName;
+
+ if ((aFlags & PR_SKIP_DOT) &&
+ (fileName[0] == L'.') && (fileName[1] == L'\0')) {
+ continue;
+ }
+ if ((aFlags & PR_SKIP_DOT_DOT) &&
+ (fileName[0] == L'.') && (fileName[1] == L'.') &&
+ (fileName[2] == L'\0')) {
+ continue;
+ }
+
+ DWORD attrib = aDir->data.dwFileAttributes;
+ if ((aFlags & PR_SKIP_HIDDEN) && (attrib & FILE_ATTRIBUTE_HIDDEN)) {
+ continue;
+ }
+
+ aName = fileName;
+ return NS_OK;
+ }
+
+ DWORD err = GetLastError();
+ return err == ERROR_NO_MORE_FILES ? NS_OK : ConvertWinError(err);
+}
+
+static nsresult
+CloseDir(nsDir*& aDir)
+{
+ if (NS_WARN_IF(!aDir)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ BOOL isOk = FindClose(aDir->handle);
+ delete aDir;
+ aDir = nullptr;
+ return isOk ? NS_OK : ConvertWinError(GetLastError());
+}
+
+//-----------------------------------------------------------------------------
+// nsDirEnumerator
+//-----------------------------------------------------------------------------
+
+class nsDirEnumerator final
+ : public nsISimpleEnumerator
+ , public nsIDirectoryEnumerator
+{
+private:
+ ~nsDirEnumerator()
+ {
+ Close();
+ }
+
+public:
+ NS_DECL_ISUPPORTS
+
+ nsDirEnumerator() : mDir(nullptr)
+ {
+ }
+
+ nsresult Init(nsIFile* aParent)
+ {
+ nsAutoString filepath;
+ aParent->GetTarget(filepath);
+
+ if (filepath.IsEmpty()) {
+ aParent->GetPath(filepath);
+ }
+
+ if (filepath.IsEmpty()) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // IsDirectory is not needed here because OpenDir will return
+ // NS_ERROR_FILE_NOT_DIRECTORY if the passed in path is a file.
+ nsresult rv = OpenDir(filepath, &mDir);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mParent = aParent;
+ return NS_OK;
+ }
+
+ NS_IMETHOD HasMoreElements(bool* aResult)
+ {
+ nsresult rv;
+ if (!mNext && mDir) {
+ nsString name;
+ rv = ReadDir(mDir, PR_SKIP_BOTH, name);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (name.IsEmpty()) {
+ // end of dir entries
+ if (NS_FAILED(CloseDir(mDir))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = false;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> file;
+ rv = mParent->Clone(getter_AddRefs(file));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = file->Append(name);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mNext = do_QueryInterface(file);
+ }
+ *aResult = mNext != nullptr;
+ if (!*aResult) {
+ Close();
+ }
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetNext(nsISupports** aResult)
+ {
+ nsresult rv;
+ bool hasMore;
+ rv = HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *aResult = mNext; // might return nullptr
+ NS_IF_ADDREF(*aResult);
+
+ mNext = nullptr;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetNextFile(nsIFile** aResult)
+ {
+ *aResult = nullptr;
+ bool hasMore = false;
+ nsresult rv = HasMoreElements(&hasMore);
+ if (NS_FAILED(rv) || !hasMore) {
+ return rv;
+ }
+ *aResult = mNext;
+ NS_IF_ADDREF(*aResult);
+ mNext = nullptr;
+ return NS_OK;
+ }
+
+ NS_IMETHOD Close()
+ {
+ if (mDir) {
+ nsresult rv = CloseDir(mDir);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "close failed");
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+ }
+
+protected:
+ nsDir* mDir;
+ nsCOMPtr<nsIFile> mParent;
+ nsCOMPtr<nsIFile> mNext;
+};
+
+NS_IMPL_ISUPPORTS(nsDirEnumerator, nsISimpleEnumerator, nsIDirectoryEnumerator)
+
+
+//-----------------------------------------------------------------------------
+// nsLocalFile <public>
+//-----------------------------------------------------------------------------
+
+nsLocalFile::nsLocalFile()
+ : mDirty(true)
+ , mResolveDirty(true)
+ , mFollowSymlinks(false)
+{
+}
+
+nsresult
+nsLocalFile::nsLocalFileConstructor(nsISupports* aOuter, const nsIID& aIID,
+ void** aInstancePtr)
+{
+ if (NS_WARN_IF(!aInstancePtr)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(aOuter)) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ nsLocalFile* inst = new nsLocalFile();
+ nsresult rv = inst->QueryInterface(aIID, aInstancePtr);
+ if (NS_FAILED(rv)) {
+ delete inst;
+ return rv;
+ }
+ return NS_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// nsLocalFile::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(nsLocalFile,
+ nsILocalFile,
+ nsIFile,
+ nsILocalFileWin,
+ nsIHashable)
+
+
+//-----------------------------------------------------------------------------
+// nsLocalFile <private>
+//-----------------------------------------------------------------------------
+
+nsLocalFile::nsLocalFile(const nsLocalFile& aOther)
+ : mDirty(true)
+ , mResolveDirty(true)
+ , mFollowSymlinks(aOther.mFollowSymlinks)
+ , mWorkingPath(aOther.mWorkingPath)
+{
+}
+
+// Resolve the shortcut file from mWorkingPath and write the path
+// it points to into mResolvedPath.
+nsresult
+nsLocalFile::ResolveShortcut()
+{
+ // we can't do anything without the resolver
+ if (!gResolver) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mResolvedPath.SetLength(MAX_PATH);
+ if (mResolvedPath.Length() != MAX_PATH) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ wchar_t* resolvedPath = wwc(mResolvedPath.BeginWriting());
+
+ // resolve this shortcut
+ nsresult rv = gResolver->Resolve(mWorkingPath.get(), resolvedPath);
+
+ size_t len = NS_FAILED(rv) ? 0 : wcslen(resolvedPath);
+ mResolvedPath.SetLength(len);
+
+ return rv;
+}
+
+// Resolve any shortcuts and stat the resolved path. After a successful return
+// the path is guaranteed valid and the members of mFileInfo64 can be used.
+nsresult
+nsLocalFile::ResolveAndStat()
+{
+ // if we aren't dirty then we are already done
+ if (!mDirty) {
+ return NS_OK;
+ }
+
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
+ // we can't resolve/stat anything that isn't a valid NSPR addressable path
+ if (mWorkingPath.IsEmpty()) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ // this is usually correct
+ mResolvedPath.Assign(mWorkingPath);
+
+ // slutty hack designed to work around bug 134796 until it is fixed
+ nsAutoString nsprPath(mWorkingPath.get());
+ if (mWorkingPath.Length() == 2 && mWorkingPath.CharAt(1) == L':') {
+ nsprPath.Append('\\');
+ }
+
+ // first we will see if the working path exists. If it doesn't then
+ // there is nothing more that can be done
+ nsresult rv = GetFileInfo(nsprPath, &mFileInfo64);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // if this isn't a shortcut file or we aren't following symlinks then we're done
+ if (!mFollowSymlinks ||
+ mFileInfo64.type != PR_FILE_FILE ||
+ !IsShortcutPath(mWorkingPath)) {
+ mDirty = false;
+ mResolveDirty = false;
+ return NS_OK;
+ }
+
+ // we need to resolve this shortcut to what it points to, this will
+ // set mResolvedPath. Even if it fails we need to have the resolved
+ // path equal to working path for those functions that always use
+ // the resolved path.
+ rv = ResolveShortcut();
+ if (NS_FAILED(rv)) {
+ mResolvedPath.Assign(mWorkingPath);
+ return rv;
+ }
+ mResolveDirty = false;
+
+ // get the details of the resolved path
+ rv = GetFileInfo(mResolvedPath, &mFileInfo64);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mDirty = false;
+ return NS_OK;
+}
+
+/**
+ * Fills the mResolvedPath member variable with the file or symlink target
+ * if follow symlinks is on. This is a copy of the Resolve parts from
+ * ResolveAndStat. ResolveAndStat is much slower though because of the stat.
+ *
+ * @return NS_OK on success.
+*/
+nsresult
+nsLocalFile::Resolve()
+{
+ // if we aren't dirty then we are already done
+ if (!mResolveDirty) {
+ return NS_OK;
+ }
+
+ // we can't resolve/stat anything that isn't a valid NSPR addressable path
+ if (mWorkingPath.IsEmpty()) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ // this is usually correct
+ mResolvedPath.Assign(mWorkingPath);
+
+ // if this isn't a shortcut file or we aren't following symlinks then
+ // we're done.
+ if (!mFollowSymlinks ||
+ !IsShortcutPath(mWorkingPath)) {
+ mResolveDirty = false;
+ return NS_OK;
+ }
+
+ // we need to resolve this shortcut to what it points to, this will
+ // set mResolvedPath. Even if it fails we need to have the resolved
+ // path equal to working path for those functions that always use
+ // the resolved path.
+ nsresult rv = ResolveShortcut();
+ if (NS_FAILED(rv)) {
+ mResolvedPath.Assign(mWorkingPath);
+ return rv;
+ }
+
+ mResolveDirty = false;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsLocalFile::nsIFile,nsILocalFile
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsLocalFile::Clone(nsIFile** aFile)
+{
+ // Just copy-construct ourselves
+ RefPtr<nsLocalFile> file = new nsLocalFile(*this);
+ file.forget(aFile);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::InitWithFile(nsIFile* aFile)
+{
+ if (NS_WARN_IF(!aFile)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoString path;
+ aFile->GetPath(path);
+ if (path.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return InitWithPath(path);
+}
+
+NS_IMETHODIMP
+nsLocalFile::InitWithPath(const nsAString& aFilePath)
+{
+ MakeDirty();
+
+ nsAString::const_iterator begin, end;
+ aFilePath.BeginReading(begin);
+ aFilePath.EndReading(end);
+
+ // input string must not be empty
+ if (begin == end) {
+ return NS_ERROR_FAILURE;
+ }
+
+ char16_t firstChar = *begin;
+ char16_t secondChar = *(++begin);
+
+ // just do a sanity check. if it has any forward slashes, it is not a Native path
+ // on windows. Also, it must have a colon at after the first char.
+ if (FindCharInReadable(L'/', begin, end)) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ if (secondChar != L':' && (secondChar != L'\\' || firstChar != L'\\')) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ if (secondChar == L':') {
+ // Make sure we have a valid drive, later code assumes the drive letter
+ // is a single char a-z or A-Z.
+ if (PathGetDriveNumberW(aFilePath.Data()) == -1) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+ }
+
+ mWorkingPath = aFilePath;
+ // kill any trailing '\'
+ if (mWorkingPath.Last() == L'\\') {
+ mWorkingPath.Truncate(mWorkingPath.Length() - 1);
+ }
+
+ return NS_OK;
+
+}
+
+// Strip a handler command string of its quotes and parameters.
+static void
+CleanupHandlerPath(nsString& aPath)
+{
+ // Example command strings passed into this routine:
+
+ // 1) C:\Program Files\Company\some.exe -foo -bar
+ // 2) C:\Program Files\Company\some.dll
+ // 3) C:\Windows\some.dll,-foo -bar
+ // 4) C:\Windows\some.cpl,-foo -bar
+
+ int32_t lastCommaPos = aPath.RFindChar(',');
+ if (lastCommaPos != kNotFound)
+ aPath.Truncate(lastCommaPos);
+
+ aPath.Append(' ');
+
+ // case insensitive
+ uint32_t index = aPath.Find(".exe ", true);
+ if (index == kNotFound)
+ index = aPath.Find(".dll ", true);
+ if (index == kNotFound)
+ index = aPath.Find(".cpl ", true);
+
+ if (index != kNotFound)
+ aPath.Truncate(index + 4);
+ aPath.Trim(" ", true, true);
+}
+
+// Strip the windows host process bootstrap executable rundll32.exe
+// from a handler's command string if it exists.
+static void
+StripRundll32(nsString& aCommandString)
+{
+ // Example rundll formats:
+ // C:\Windows\System32\rundll32.exe "path to dll"
+ // rundll32.exe "path to dll"
+ // C:\Windows\System32\rundll32.exe "path to dll", var var
+ // rundll32.exe "path to dll", var var
+
+ NS_NAMED_LITERAL_STRING(rundllSegment, "rundll32.exe ");
+ NS_NAMED_LITERAL_STRING(rundllSegmentShort, "rundll32 ");
+
+ // case insensitive
+ int32_t strLen = rundllSegment.Length();
+ int32_t index = aCommandString.Find(rundllSegment, true);
+ if (index == kNotFound) {
+ strLen = rundllSegmentShort.Length();
+ index = aCommandString.Find(rundllSegmentShort, true);
+ }
+
+ if (index != kNotFound) {
+ uint32_t rundllSegmentLength = index + strLen;
+ aCommandString.Cut(0, rundllSegmentLength);
+ }
+}
+
+// Returns the fully qualified path to an application handler based on
+// a parameterized command string. Note this routine should not be used
+// to launch the associated application as it strips parameters and
+// rundll.exe from the string. Designed for retrieving display information
+// on a particular handler.
+/* static */ bool
+nsLocalFile::CleanupCmdHandlerPath(nsAString& aCommandHandler)
+{
+ nsAutoString handlerCommand(aCommandHandler);
+
+ // Straight command path:
+ //
+ // %SystemRoot%\system32\NOTEPAD.EXE var
+ // "C:\Program Files\iTunes\iTunes.exe" var var
+ // C:\Program Files\iTunes\iTunes.exe var var
+ //
+ // Example rundll handlers:
+ //
+ // rundll32.exe "%ProgramFiles%\Win...ery\PhotoViewer.dll", var var
+ // rundll32.exe "%ProgramFiles%\Windows Photo Gallery\PhotoViewer.dll"
+ // C:\Windows\System32\rundll32.exe "path to dll", var var
+ // %SystemRoot%\System32\rundll32.exe "%ProgramFiles%\Win...ery\Photo
+ // Viewer.dll", var var
+
+ // Expand environment variables so we have full path strings.
+ uint32_t bufLength = ::ExpandEnvironmentStringsW(handlerCommand.get(),
+ L"", 0);
+ if (bufLength == 0) // Error
+ return false;
+
+ auto destination = mozilla::MakeUniqueFallible<wchar_t[]>(bufLength);
+ if (!destination)
+ return false;
+ if (!::ExpandEnvironmentStringsW(handlerCommand.get(), destination.get(),
+ bufLength))
+ return false;
+
+ handlerCommand.Assign(destination.get());
+
+ // Remove quotes around paths
+ handlerCommand.StripChars("\"");
+
+ // Strip windows host process bootstrap so we can get to the actual
+ // handler.
+ StripRundll32(handlerCommand);
+
+ // Trim any command parameters so that we have a native path we can
+ // initialize a local file with.
+ CleanupHandlerPath(handlerCommand);
+
+ aCommandHandler.Assign(handlerCommand);
+ return true;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::InitWithCommandLine(const nsAString& aCommandLine)
+{
+ nsAutoString commandLine(aCommandLine);
+ if (!CleanupCmdHandlerPath(commandLine)) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+ return InitWithPath(commandLine);
+}
+
+NS_IMETHODIMP
+nsLocalFile::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode,
+ PRFileDesc** aResult)
+{
+ nsresult rv = OpenNSPRFileDescMaybeShareDelete(aFlags, aMode, false, aResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::OpenANSIFileDesc(const char* aMode, FILE** aResult)
+{
+ nsresult rv = ResolveAndStat();
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
+ return rv;
+ }
+
+ *aResult = _wfopen(mResolvedPath.get(), NS_ConvertASCIItoUTF16(aMode).get());
+ if (*aResult) {
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+
+
+NS_IMETHODIMP
+nsLocalFile::Create(uint32_t aType, uint32_t aAttributes)
+{
+ if (aType != NORMAL_FILE_TYPE && aType != DIRECTORY_TYPE) {
+ return NS_ERROR_FILE_UNKNOWN_TYPE;
+ }
+
+ nsresult rv = ResolveAndStat();
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
+ return rv;
+ }
+
+ // create directories to target
+ //
+ // A given local file can be either one of these forms:
+ //
+ // - normal: X:\some\path\on\this\drive
+ // ^--- start here
+ //
+ // - UNC path: \\machine\volume\some\path\on\this\drive
+ // ^--- start here
+ //
+ // Skip the first 'X:\' for the first form, and skip the first full
+ // '\\machine\volume\' segment for the second form.
+
+ wchar_t* path = wwc(mResolvedPath.BeginWriting());
+
+ if (path[0] == L'\\' && path[1] == L'\\') {
+ // dealing with a UNC path here; skip past '\\machine\'
+ path = wcschr(path + 2, L'\\');
+ if (!path) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+ ++path;
+ }
+
+ // search for first slash after the drive (or volume) name
+ wchar_t* slash = wcschr(path, L'\\');
+
+ nsresult directoryCreateError = NS_OK;
+ if (slash) {
+ // skip the first '\\'
+ ++slash;
+ slash = wcschr(slash, L'\\');
+
+ while (slash) {
+ *slash = L'\0';
+
+ if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) {
+ rv = ConvertWinError(GetLastError());
+ if (NS_ERROR_FILE_NOT_FOUND == rv &&
+ NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) {
+ // If a previous CreateDirectory failed due to access, return that.
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+ // perhaps the base path already exists, or perhaps we don't have
+ // permissions to create the directory. NOTE: access denied could
+ // occur on a parent directory even though it exists.
+ else if (rv != NS_ERROR_FILE_ALREADY_EXISTS &&
+ rv != NS_ERROR_FILE_ACCESS_DENIED) {
+ return rv;
+ }
+
+ directoryCreateError = rv;
+ }
+ *slash = L'\\';
+ ++slash;
+ slash = wcschr(slash, L'\\');
+ }
+ }
+
+ if (aType == NORMAL_FILE_TYPE) {
+ PRFileDesc* file;
+ rv = OpenFile(mResolvedPath,
+ PR_RDONLY | PR_CREATE_FILE | PR_APPEND | PR_EXCL,
+ aAttributes, false, &file);
+ if (file) {
+ PR_Close(file);
+ }
+
+ if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
+ // need to return already-exists for directories (bug 452217)
+ bool isdir;
+ if (NS_SUCCEEDED(IsDirectory(&isdir)) && isdir) {
+ rv = NS_ERROR_FILE_ALREADY_EXISTS;
+ }
+ } else if (NS_ERROR_FILE_NOT_FOUND == rv &&
+ NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) {
+ // If a previous CreateDirectory failed due to access, return that.
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+ return rv;
+ }
+
+ if (aType == DIRECTORY_TYPE) {
+ if (!::CreateDirectoryW(mResolvedPath.get(), nullptr)) {
+ rv = ConvertWinError(GetLastError());
+ if (NS_ERROR_FILE_NOT_FOUND == rv &&
+ NS_ERROR_FILE_ACCESS_DENIED == directoryCreateError) {
+ // If a previous CreateDirectory failed due to access, return that.
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ return NS_ERROR_FILE_UNKNOWN_TYPE;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::Append(const nsAString& aNode)
+{
+ // append this path, multiple components are not permitted
+ return AppendInternal(PromiseFlatString(aNode), false);
+}
+
+NS_IMETHODIMP
+nsLocalFile::AppendRelativePath(const nsAString& aNode)
+{
+ // append this path, multiple components are permitted
+ return AppendInternal(PromiseFlatString(aNode), true);
+}
+
+
+nsresult
+nsLocalFile::AppendInternal(const nsAFlatString& aNode,
+ bool aMultipleComponents)
+{
+ if (aNode.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // check the relative path for validity
+ if (aNode.First() == L'\\' || // can't start with an '\'
+ aNode.Contains(L'/') || // can't contain /
+ aNode.EqualsASCII("..")) { // can't be ..
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ if (aMultipleComponents) {
+ // can't contain .. as a path component. Ensure that the valid components
+ // "foo..foo", "..foo", and "foo.." are not falsely detected,
+ // but the invalid paths "..\", "foo\..", "foo\..\foo",
+ // "..\foo", etc are.
+ NS_NAMED_LITERAL_STRING(doubleDot, "\\..");
+ nsAString::const_iterator start, end, offset;
+ aNode.BeginReading(start);
+ aNode.EndReading(end);
+ offset = end;
+ while (FindInReadable(doubleDot, start, offset)) {
+ if (offset == end || *offset == L'\\') {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+ start = offset;
+ offset = end;
+ }
+
+ // catches the remaining cases of prefixes
+ if (StringBeginsWith(aNode, NS_LITERAL_STRING("..\\"))) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+ }
+ // single components can't contain '\'
+ else if (aNode.Contains(L'\\')) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ MakeDirty();
+
+ mWorkingPath.Append('\\');
+ mWorkingPath.Append(aNode);
+
+ return NS_OK;
+}
+
+nsresult
+nsLocalFile::OpenNSPRFileDescMaybeShareDelete(int32_t aFlags,
+ int32_t aMode,
+ bool aShareDelete,
+ PRFileDesc** aResult)
+{
+ nsresult rv = Resolve();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return OpenFile(mResolvedPath, aFlags, aMode, aShareDelete, aResult);
+}
+
+#define TOUPPER(u) (((u) >= L'a' && (u) <= L'z') ? \
+ (u) - (L'a' - L'A') : (u))
+
+NS_IMETHODIMP
+nsLocalFile::Normalize()
+{
+ // XXX See bug 187957 comment 18 for possible problems with this implementation.
+
+ if (mWorkingPath.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsAutoString path(mWorkingPath);
+
+ // find the index of the root backslash for the path. Everything before
+ // this is considered fully normalized and cannot be ascended beyond
+ // using ".." For a local drive this is the first slash (e.g. "c:\").
+ // For a UNC path it is the slash following the share name
+ // (e.g. "\\server\share\").
+ int32_t rootIdx = 2; // default to local drive
+ if (path.First() == L'\\') { // if a share then calculate the rootIdx
+ rootIdx = path.FindChar(L'\\', 2); // skip \\ in front of the server
+ if (rootIdx == kNotFound) {
+ return NS_OK; // already normalized
+ }
+ rootIdx = path.FindChar(L'\\', rootIdx + 1);
+ if (rootIdx == kNotFound) {
+ return NS_OK; // already normalized
+ }
+ } else if (path.CharAt(rootIdx) != L'\\') {
+ // The path has been specified relative to the current working directory
+ // for that drive. To normalize it, the current working directory for
+ // that drive needs to be inserted before the supplied relative path
+ // which will provide an absolute path (and the rootIdx will still be 2).
+ WCHAR cwd[MAX_PATH];
+ WCHAR* pcwd = cwd;
+ int drive = TOUPPER(path.First()) - 'A' + 1;
+ /* We need to worry about IPH, for details read bug 419326.
+ * _getdrives - http://msdn2.microsoft.com/en-us/library/xdhk0xd2.aspx
+ * uses a bitmask, bit 0 is 'a:'
+ * _chdrive - http://msdn2.microsoft.com/en-us/library/0d1409hb.aspx
+ * _getdcwd - http://msdn2.microsoft.com/en-us/library/7t2zk3s4.aspx
+ * take an int, 1 is 'a:'.
+ *
+ * Because of this, we need to do some math. Subtract 1 to convert from
+ * _chdrive/_getdcwd format to _getdrives drive numbering.
+ * Shift left x bits to convert from integer indexing to bitfield indexing.
+ * And of course, we need to find out if the drive is in the bitmask.
+ *
+ * If we're really unlucky, we can still lose, but only if the user
+ * manages to eject the drive between our call to _getdrives() and
+ * our *calls* to _wgetdcwd.
+ */
+ if (!((1 << (drive - 1)) & _getdrives())) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+ if (!_wgetdcwd(drive, pcwd, MAX_PATH)) {
+ pcwd = _wgetdcwd(drive, 0, 0);
+ }
+ if (!pcwd) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ nsAutoString currentDir(pcwd);
+ if (pcwd != cwd) {
+ free(pcwd);
+ }
+
+ if (currentDir.Last() == '\\') {
+ path.Replace(0, 2, currentDir);
+ } else {
+ path.Replace(0, 2, currentDir + NS_LITERAL_STRING("\\"));
+ }
+ }
+ NS_POSTCONDITION(0 < rootIdx && rootIdx < (int32_t)path.Length(), "rootIdx is invalid");
+ NS_POSTCONDITION(path.CharAt(rootIdx) == '\\', "rootIdx is invalid");
+
+ // if there is nothing following the root path then it is already normalized
+ if (rootIdx + 1 == (int32_t)path.Length()) {
+ return NS_OK;
+ }
+
+ // assign the root
+ const char16_t* pathBuffer = path.get(); // simplify access to the buffer
+ mWorkingPath.SetCapacity(path.Length()); // it won't ever grow longer
+ mWorkingPath.Assign(pathBuffer, rootIdx);
+
+ // Normalize the path components. The actions taken are:
+ //
+ // "\\" condense to single backslash
+ // "." remove from path
+ // ".." up a directory
+ // "..." remove from path (any number of dots > 2)
+ //
+ // The last form is something that Windows 95 and 98 supported and
+ // is a shortcut for changing up multiple directories. Windows XP
+ // and ilk ignore it in a path, as is done here.
+ int32_t len, begin, end = rootIdx;
+ while (end < (int32_t)path.Length()) {
+ // find the current segment (text between the backslashes) to
+ // be examined, this will set the following variables:
+ // begin == index of first char in segment
+ // end == index 1 char after last char in segment
+ // len == length of segment
+ begin = end + 1;
+ end = path.FindChar('\\', begin);
+ if (end == kNotFound) {
+ end = path.Length();
+ }
+ len = end - begin;
+
+ // ignore double backslashes
+ if (len == 0) {
+ continue;
+ }
+
+ // len != 0, and interesting paths always begin with a dot
+ if (pathBuffer[begin] == '.') {
+ // ignore single dots
+ if (len == 1) {
+ continue;
+ }
+
+ // handle multiple dots
+ if (len >= 2 && pathBuffer[begin + 1] == L'.') {
+ // back up a path component on double dot
+ if (len == 2) {
+ int32_t prev = mWorkingPath.RFindChar('\\');
+ if (prev >= rootIdx) {
+ mWorkingPath.Truncate(prev);
+ }
+ continue;
+ }
+
+ // length is > 2 and the first two characters are dots.
+ // if the rest of the string is dots, then ignore it.
+ int idx = len - 1;
+ for (; idx >= 2; --idx) {
+ if (pathBuffer[begin + idx] != L'.') {
+ break;
+ }
+ }
+
+ // this is true if the loop above didn't break
+ // and all characters in this segment are dots.
+ if (idx < 2) {
+ continue;
+ }
+ }
+ }
+
+ // add the current component to the path, including the preceding backslash
+ mWorkingPath.Append(pathBuffer + begin - 1, len + 1);
+ }
+
+ // kill trailing dots and spaces.
+ int32_t filePathLen = mWorkingPath.Length() - 1;
+ while (filePathLen > 0 && (mWorkingPath[filePathLen] == L' ' ||
+ mWorkingPath[filePathLen] == L'.')) {
+ mWorkingPath.Truncate(filePathLen--);
+ }
+
+ MakeDirty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetLeafName(nsAString& aLeafName)
+{
+ aLeafName.Truncate();
+
+ if (mWorkingPath.IsEmpty()) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ int32_t offset = mWorkingPath.RFindChar(L'\\');
+
+ // if the working path is just a node without any lashes.
+ if (offset == kNotFound) {
+ aLeafName = mWorkingPath;
+ } else {
+ aLeafName = Substring(mWorkingPath, offset + 1);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetLeafName(const nsAString& aLeafName)
+{
+ MakeDirty();
+
+ if (mWorkingPath.IsEmpty()) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ // cannot use nsCString::RFindChar() due to 0x5c problem
+ int32_t offset = mWorkingPath.RFindChar(L'\\');
+ if (offset) {
+ mWorkingPath.Truncate(offset + 1);
+ }
+ mWorkingPath.Append(aLeafName);
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::GetPath(nsAString& aResult)
+{
+ aResult = mWorkingPath;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetCanonicalPath(nsAString& aResult)
+{
+ EnsureShortPath();
+ aResult.Assign(mShortWorkingPath);
+ return NS_OK;
+}
+
+typedef struct
+{
+ WORD wLanguage;
+ WORD wCodePage;
+} LANGANDCODEPAGE;
+
+NS_IMETHODIMP
+nsLocalFile::GetVersionInfoField(const char* aField, nsAString& aResult)
+{
+ nsresult rv = ResolveAndStat();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = NS_ERROR_FAILURE;
+
+ const WCHAR* path =
+ mFollowSymlinks ? mResolvedPath.get() : mWorkingPath.get();
+
+ DWORD dummy;
+ DWORD size = ::GetFileVersionInfoSizeW(path, &dummy);
+ if (!size) {
+ return rv;
+ }
+
+ void* ver = moz_xcalloc(size, 1);
+ if (::GetFileVersionInfoW(path, 0, size, ver)) {
+ LANGANDCODEPAGE* translate = nullptr;
+ UINT pageCount;
+ BOOL queryResult = ::VerQueryValueW(ver, L"\\VarFileInfo\\Translation",
+ (void**)&translate, &pageCount);
+ if (queryResult && translate) {
+ for (int32_t i = 0; i < 2; ++i) {
+ wchar_t subBlock[MAX_PATH];
+ _snwprintf(subBlock, MAX_PATH,
+ L"\\StringFileInfo\\%04x%04x\\%s",
+ (i == 0 ? translate[0].wLanguage : ::GetUserDefaultLangID()),
+ translate[0].wCodePage,
+ NS_ConvertASCIItoUTF16(
+ nsDependentCString(aField)).get());
+ subBlock[MAX_PATH - 1] = 0;
+ LPVOID value = nullptr;
+ UINT size;
+ queryResult = ::VerQueryValueW(ver, subBlock, &value, &size);
+ if (queryResult && value) {
+ aResult.Assign(static_cast<char16_t*>(value));
+ if (!aResult.IsEmpty()) {
+ rv = NS_OK;
+ break;
+ }
+ }
+ }
+ }
+ }
+ free(ver);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetShortcut(nsIFile* aTargetFile,
+ nsIFile* aWorkingDir,
+ const char16_t* aArgs,
+ const char16_t* aDescription,
+ nsIFile* aIconFile,
+ int32_t aIconIndex)
+{
+ bool exists;
+ nsresult rv = this->Exists(&exists);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ const WCHAR* targetFilePath = nullptr;
+ const WCHAR* workingDirPath = nullptr;
+ const WCHAR* iconFilePath = nullptr;
+
+ nsAutoString targetFilePathAuto;
+ if (aTargetFile) {
+ rv = aTargetFile->GetPath(targetFilePathAuto);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ targetFilePath = targetFilePathAuto.get();
+ }
+
+ nsAutoString workingDirPathAuto;
+ if (aWorkingDir) {
+ rv = aWorkingDir->GetPath(workingDirPathAuto);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ workingDirPath = workingDirPathAuto.get();
+ }
+
+ nsAutoString iconPathAuto;
+ if (aIconFile) {
+ rv = aIconFile->GetPath(iconPathAuto);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ iconFilePath = iconPathAuto.get();
+ }
+
+ rv = gResolver->SetShortcut(exists,
+ mWorkingPath.get(),
+ targetFilePath,
+ workingDirPath,
+ char16ptr_t(aArgs),
+ char16ptr_t(aDescription),
+ iconFilePath,
+ iconFilePath ? aIconIndex : 0);
+ if (targetFilePath && NS_SUCCEEDED(rv)) {
+ MakeDirty();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::OpenNSPRFileDescShareDelete(int32_t aFlags,
+ int32_t aMode,
+ PRFileDesc** aResult)
+{
+ nsresult rv = OpenNSPRFileDescMaybeShareDelete(aFlags, aMode, true, aResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Determines if the drive type for the specified file is rmeote or local.
+ *
+ * @param path The path of the file to check
+ * @param remote Out parameter, on function success holds true if the specified
+ * file path is remote, or false if the file path is local.
+ * @return true on success. The return value implies absolutely nothing about
+ * wether the file is local or remote.
+*/
+static bool
+IsRemoteFilePath(LPCWSTR aPath, bool& aRemote)
+{
+ // Obtain the parent directory path and make sure it ends with
+ // a trailing backslash.
+ WCHAR dirPath[MAX_PATH + 1] = { 0 };
+ wcsncpy(dirPath, aPath, MAX_PATH);
+ if (!PathRemoveFileSpecW(dirPath)) {
+ return false;
+ }
+ size_t len = wcslen(dirPath);
+ // In case the dirPath holds exaclty MAX_PATH and remains unchanged, we
+ // recheck the required length here since we need to terminate it with
+ // a backslash.
+ if (len >= MAX_PATH) {
+ return false;
+ }
+
+ dirPath[len] = L'\\';
+ dirPath[len + 1] = L'\0';
+ UINT driveType = GetDriveTypeW(dirPath);
+ aRemote = driveType == DRIVE_REMOTE;
+ return true;
+}
+
+nsresult
+nsLocalFile::CopySingleFile(nsIFile* aSourceFile, nsIFile* aDestParent,
+ const nsAString& aNewName, uint32_t aOptions)
+{
+ nsresult rv = NS_OK;
+ nsAutoString filePath;
+
+ bool move = aOptions & (Move | Rename);
+
+ // get the path that we are going to copy to.
+ // Since windows does not know how to auto
+ // resolve shortcuts, we must work with the
+ // target.
+ nsAutoString destPath;
+ aDestParent->GetTarget(destPath);
+
+ destPath.Append('\\');
+
+ if (aNewName.IsEmpty()) {
+ nsAutoString aFileName;
+ aSourceFile->GetLeafName(aFileName);
+ destPath.Append(aFileName);
+ } else {
+ destPath.Append(aNewName);
+ }
+
+
+ if (aOptions & FollowSymlinks) {
+ rv = aSourceFile->GetTarget(filePath);
+ if (filePath.IsEmpty()) {
+ rv = aSourceFile->GetPath(filePath);
+ }
+ } else {
+ rv = aSourceFile->GetPath(filePath);
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Pass the flag COPY_FILE_NO_BUFFERING to CopyFileEx as we may be copying
+ // to a SMBV2 remote drive. Without this parameter subsequent append mode
+ // file writes can cause the resultant file to become corrupt. We only need to do
+ // this if the major version of Windows is > 5(Only Windows Vista and above
+ // can support SMBV2). With a 7200RPM hard drive:
+ // Copying a 1KB file with COPY_FILE_NO_BUFFERING takes about 30-60ms.
+ // Copying a 1KB file without COPY_FILE_NO_BUFFERING takes < 1ms.
+ // So we only use COPY_FILE_NO_BUFFERING when we have a remote drive.
+ int copyOK;
+ DWORD dwCopyFlags = COPY_FILE_ALLOW_DECRYPTED_DESTINATION;
+ if (IsVistaOrLater()) {
+ bool path1Remote, path2Remote;
+ if (!IsRemoteFilePath(filePath.get(), path1Remote) ||
+ !IsRemoteFilePath(destPath.get(), path2Remote) ||
+ path1Remote || path2Remote) {
+ dwCopyFlags |= COPY_FILE_NO_BUFFERING;
+ }
+ }
+
+ if (!move) {
+ copyOK = ::CopyFileExW(filePath.get(), destPath.get(), nullptr,
+ nullptr, nullptr, dwCopyFlags);
+ } else {
+ copyOK = ::MoveFileExW(filePath.get(), destPath.get(),
+ MOVEFILE_REPLACE_EXISTING);
+
+ // Check if copying the source file to a different volume,
+ // as this could be an SMBV2 mapped drive.
+ if (!copyOK && GetLastError() == ERROR_NOT_SAME_DEVICE) {
+ if (aOptions & Rename) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+ copyOK = CopyFileExW(filePath.get(), destPath.get(), nullptr,
+ nullptr, nullptr, dwCopyFlags);
+
+ if (copyOK) {
+ DeleteFileW(filePath.get());
+ }
+ }
+ }
+
+ if (!copyOK) { // CopyFileEx and MoveFileEx return zero at failure.
+ rv = ConvertWinError(GetLastError());
+ } else if (move && !(aOptions & SkipNtfsAclReset)) {
+ // Set security permissions to inherit from parent.
+ // Note: propagates to all children: slow for big file trees
+ PACL pOldDACL = nullptr;
+ PSECURITY_DESCRIPTOR pSD = nullptr;
+ ::GetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT,
+ DACL_SECURITY_INFORMATION,
+ nullptr, nullptr, &pOldDACL, nullptr, &pSD);
+ if (pOldDACL)
+ ::SetNamedSecurityInfoW((LPWSTR)destPath.get(), SE_FILE_OBJECT,
+ DACL_SECURITY_INFORMATION |
+ UNPROTECTED_DACL_SECURITY_INFORMATION,
+ nullptr, nullptr, pOldDACL, nullptr);
+ if (pSD) {
+ LocalFree((HLOCAL)pSD);
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsLocalFile::CopyMove(nsIFile* aParentDir, const nsAString& aNewName,
+ uint32_t aOptions)
+{
+ bool move = aOptions & (Move | Rename);
+ bool followSymlinks = aOptions & FollowSymlinks;
+
+ nsCOMPtr<nsIFile> newParentDir = aParentDir;
+ // check to see if this exists, otherwise return an error.
+ // we will check this by resolving. If the user wants us
+ // to follow links, then we are talking about the target,
+ // hence we can use the |FollowSymlinks| option.
+ nsresult rv = ResolveAndStat();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!newParentDir) {
+ // no parent was specified. We must rename.
+ if (aNewName.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ rv = GetParent(getter_AddRefs(newParentDir));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (!newParentDir) {
+ return NS_ERROR_FILE_DESTINATION_NOT_DIR;
+ }
+
+ // make sure it exists and is a directory. Create it if not there.
+ bool exists;
+ newParentDir->Exists(&exists);
+ if (!exists) {
+ rv = newParentDir->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ bool isDir;
+ newParentDir->IsDirectory(&isDir);
+ if (!isDir) {
+ if (followSymlinks) {
+ bool isLink;
+ newParentDir->IsSymlink(&isLink);
+ if (isLink) {
+ nsAutoString target;
+ newParentDir->GetTarget(target);
+
+ nsCOMPtr<nsIFile> realDest = new nsLocalFile();
+ rv = realDest->InitWithPath(target);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return CopyMove(realDest, aNewName, aOptions);
+ }
+ } else {
+ return NS_ERROR_FILE_DESTINATION_NOT_DIR;
+ }
+ }
+ }
+
+ // Try different ways to move/copy files/directories
+ bool done = false;
+ bool isDir;
+ IsDirectory(&isDir);
+ bool isSymlink;
+ IsSymlink(&isSymlink);
+
+ // Try to move the file or directory, or try to copy a single file (or non-followed symlink)
+ if (move || !isDir || (isSymlink && !followSymlinks)) {
+ // Copy/Move single file, or move a directory
+ if (!aParentDir) {
+ aOptions |= SkipNtfsAclReset;
+ }
+ rv = CopySingleFile(this, newParentDir, aNewName, aOptions);
+ done = NS_SUCCEEDED(rv);
+ // If we are moving a directory and that fails, fallback on directory
+ // enumeration. See bug 231300 for details.
+ if (!done && !(move && isDir)) {
+ return rv;
+ }
+ }
+
+ // Not able to copy or move directly, so enumerate it
+ if (!done) {
+ // create a new target destination in the new parentDir;
+ nsCOMPtr<nsIFile> target;
+ rv = newParentDir->Clone(getter_AddRefs(target));
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoString allocatedNewName;
+ if (aNewName.IsEmpty()) {
+ bool isLink;
+ IsSymlink(&isLink);
+ if (isLink) {
+ nsAutoString temp;
+ GetTarget(temp);
+ int32_t offset = temp.RFindChar(L'\\');
+ if (offset == kNotFound) {
+ allocatedNewName = temp;
+ } else {
+ allocatedNewName = Substring(temp, offset + 1);
+ }
+ } else {
+ GetLeafName(allocatedNewName);// this should be the leaf name of the
+ }
+ } else {
+ allocatedNewName = aNewName;
+ }
+
+ rv = target->Append(allocatedNewName);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ allocatedNewName.Truncate();
+
+ // check if the destination directory already exists
+ target->Exists(&exists);
+ if (!exists) {
+ // if the destination directory cannot be created, return an error
+ rv = target->Create(DIRECTORY_TYPE, 0644); // TODO, what permissions should we use
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ // check if the destination directory is writable and empty
+ bool isWritable;
+
+ target->IsWritable(&isWritable);
+ if (!isWritable) {
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> targetIterator;
+ rv = target->GetDirectoryEntries(getter_AddRefs(targetIterator));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool more;
+ targetIterator->HasMoreElements(&more);
+ // return error if target directory is not empty
+ if (more) {
+ return NS_ERROR_FILE_DIR_NOT_EMPTY;
+ }
+ }
+
+ RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator();
+
+ rv = dirEnum->Init(this);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("dirEnum initialization failed");
+ return rv;
+ }
+
+ bool more = false;
+ while (NS_SUCCEEDED(dirEnum->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsISupports> item;
+ nsCOMPtr<nsIFile> file;
+ dirEnum->GetNext(getter_AddRefs(item));
+ file = do_QueryInterface(item);
+ if (file) {
+ bool isDir, isLink;
+
+ file->IsDirectory(&isDir);
+ file->IsSymlink(&isLink);
+
+ if (move) {
+ if (followSymlinks) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = file->MoveTo(target, EmptyString());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ if (followSymlinks) {
+ rv = file->CopyToFollowingLinks(target, EmptyString());
+ } else {
+ rv = file->CopyTo(target, EmptyString());
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+ }
+ // we've finished moving all the children of this directory
+ // in the new directory. so now delete the directory
+ // note, we don't need to do a recursive delete.
+ // MoveTo() is recursive. At this point,
+ // we've already moved the children of the current folder
+ // to the new location. nothing should be left in the folder.
+ if (move) {
+ rv = Remove(false /* recursive */);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ }
+
+
+ // If we moved, we want to adjust this.
+ if (move) {
+ MakeDirty();
+
+ nsAutoString newParentPath;
+ newParentDir->GetPath(newParentPath);
+
+ if (newParentPath.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aNewName.IsEmpty()) {
+ nsAutoString aFileName;
+ GetLeafName(aFileName);
+
+ InitWithPath(newParentPath);
+ Append(aFileName);
+ } else {
+ InitWithPath(newParentPath);
+ Append(aNewName);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::CopyTo(nsIFile* aNewParentDir, const nsAString& aNewName)
+{
+ return CopyMove(aNewParentDir, aNewName, 0);
+}
+
+NS_IMETHODIMP
+nsLocalFile::CopyToFollowingLinks(nsIFile* aNewParentDir,
+ const nsAString& aNewName)
+{
+ return CopyMove(aNewParentDir, aNewName, FollowSymlinks);
+}
+
+NS_IMETHODIMP
+nsLocalFile::MoveTo(nsIFile* aNewParentDir, const nsAString& aNewName)
+{
+ return CopyMove(aNewParentDir, aNewName, Move);
+}
+
+NS_IMETHODIMP
+nsLocalFile::RenameTo(nsIFile* aNewParentDir, const nsAString& aNewName)
+{
+ nsCOMPtr<nsIFile> targetParentDir = aNewParentDir;
+ // check to see if this exists, otherwise return an error.
+ // we will check this by resolving. If the user wants us
+ // to follow links, then we are talking about the target,
+ // hence we can use the |followSymlinks| parameter.
+ nsresult rv = ResolveAndStat();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!targetParentDir) {
+ // no parent was specified. We must rename.
+ if (aNewName.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ rv = GetParent(getter_AddRefs(targetParentDir));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (!targetParentDir) {
+ return NS_ERROR_FILE_DESTINATION_NOT_DIR;
+ }
+
+ // make sure it exists and is a directory. Create it if not there.
+ bool exists;
+ targetParentDir->Exists(&exists);
+ if (!exists) {
+ rv = targetParentDir->Create(DIRECTORY_TYPE, 0644);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ } else {
+ bool isDir;
+ targetParentDir->IsDirectory(&isDir);
+ if (!isDir) {
+ return NS_ERROR_FILE_DESTINATION_NOT_DIR;
+ }
+ }
+
+ uint32_t options = Rename;
+ if (!aNewParentDir) {
+ options |= SkipNtfsAclReset;
+ }
+ // Move single file, or move a directory
+ return CopySingleFile(this, targetParentDir, aNewName, options);
+}
+
+NS_IMETHODIMP
+nsLocalFile::RenameToNative(nsIFile* aNewParentDir, const nsACString& aNewName)
+{
+ nsAutoString tmp;
+ nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
+ if (NS_SUCCEEDED(rv)) {
+ return RenameTo(aNewParentDir, tmp);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::Load(PRLibrary** aResult)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ bool isFile;
+ nsresult rv = IsFile(&isFile);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!isFile) {
+ return NS_ERROR_FILE_IS_DIRECTORY;
+ }
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+ nsTraceRefcnt::SetActivityIsLegal(false);
+#endif
+
+ PRLibSpec libSpec;
+ libSpec.value.pathname_u = mResolvedPath.get();
+ libSpec.type = PR_LibSpec_PathnameU;
+ *aResult = PR_LoadLibraryWithFlags(libSpec, 0);
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+ nsTraceRefcnt::SetActivityIsLegal(true);
+#endif
+
+ if (*aResult) {
+ return NS_OK;
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsLocalFile::Remove(bool aRecursive)
+{
+ // NOTE:
+ //
+ // if the working path points to a shortcut, then we will only
+ // delete the shortcut itself. even if the shortcut points to
+ // a directory, we will not recurse into that directory or
+ // delete that directory itself. likewise, if the shortcut
+ // points to a normal file, we will not delete the real file.
+ // this is done to be consistent with the other platforms that
+ // behave this way. we do this even if the followLinks attribute
+ // is set to true. this helps protect against misuse that could
+ // lead to security bugs (e.g., bug 210588).
+ //
+ // Since shortcut files are no longer permitted to be used as unix-like
+ // symlinks interspersed in the path (e.g. "c:/file.lnk/foo/bar.txt")
+ // this processing is a lot simpler. Even if the shortcut file is
+ // pointing to a directory, only the mWorkingPath value is used and so
+ // only the shortcut file will be deleted.
+
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ bool isDir, isLink;
+ nsresult rv;
+
+ isDir = false;
+ rv = IsSymlink(&isLink);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // only check to see if we have a directory if it isn't a link
+ if (!isLink) {
+ rv = IsDirectory(&isDir);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (isDir) {
+ if (aRecursive) {
+ RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator();
+
+ rv = dirEnum->Init(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool more = false;
+ while (NS_SUCCEEDED(dirEnum->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsISupports> item;
+ dirEnum->GetNext(getter_AddRefs(item));
+ nsCOMPtr<nsIFile> file = do_QueryInterface(item);
+ if (file) {
+ file->Remove(aRecursive);
+ }
+ }
+ }
+ if (RemoveDirectoryW(mWorkingPath.get()) == 0) {
+ return ConvertWinError(GetLastError());
+ }
+ } else {
+ if (DeleteFileW(mWorkingPath.get()) == 0) {
+ return ConvertWinError(GetLastError());
+ }
+ }
+
+ MakeDirty();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetLastModifiedTime(PRTime* aLastModifiedTime)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ if (NS_WARN_IF(!aLastModifiedTime)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // get the modified time of the target as determined by mFollowSymlinks
+ // If true, then this will be for the target of the shortcut file,
+ // otherwise it will be for the shortcut file itself (i.e. the same
+ // results as GetLastModifiedTimeOfLink)
+
+ nsresult rv = ResolveAndStat();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // microseconds -> milliseconds
+ *aLastModifiedTime = mFileInfo64.modifyTime / PR_USEC_PER_MSEC;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::GetLastModifiedTimeOfLink(PRTime* aLastModifiedTime)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ if (NS_WARN_IF(!aLastModifiedTime)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // The caller is assumed to have already called IsSymlink
+ // and to have found that this file is a link.
+
+ PRFileInfo64 info;
+ nsresult rv = GetFileInfo(mWorkingPath, &info);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // microseconds -> milliseconds
+ *aLastModifiedTime = info.modifyTime / PR_USEC_PER_MSEC;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::SetLastModifiedTime(PRTime aLastModifiedTime)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ nsresult rv = ResolveAndStat();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // set the modified time of the target as determined by mFollowSymlinks
+ // If true, then this will be for the target of the shortcut file,
+ // otherwise it will be for the shortcut file itself (i.e. the same
+ // results as SetLastModifiedTimeOfLink)
+
+ rv = SetModDate(aLastModifiedTime, mResolvedPath.get());
+ if (NS_SUCCEEDED(rv)) {
+ MakeDirty();
+ }
+
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::SetLastModifiedTimeOfLink(PRTime aLastModifiedTime)
+{
+ // The caller is assumed to have already called IsSymlink
+ // and to have found that this file is a link.
+
+ nsresult rv = SetModDate(aLastModifiedTime, mWorkingPath.get());
+ if (NS_SUCCEEDED(rv)) {
+ MakeDirty();
+ }
+
+ return rv;
+}
+
+nsresult
+nsLocalFile::SetModDate(PRTime aLastModifiedTime, const wchar_t* aFilePath)
+{
+ // The FILE_FLAG_BACKUP_SEMANTICS is required in order to change the
+ // modification time for directories.
+ HANDLE file = ::CreateFileW(aFilePath, // pointer to name of the file
+ GENERIC_WRITE, // access (write) mode
+ 0, // share mode
+ nullptr, // pointer to security attributes
+ OPEN_EXISTING, // how to create
+ FILE_FLAG_BACKUP_SEMANTICS, // file attributes
+ nullptr);
+
+ if (file == INVALID_HANDLE_VALUE) {
+ return ConvertWinError(GetLastError());
+ }
+
+ FILETIME ft;
+ SYSTEMTIME st;
+ PRExplodedTime pret;
+
+ // PR_ExplodeTime expects usecs...
+ PR_ExplodeTime(aLastModifiedTime * PR_USEC_PER_MSEC, PR_GMTParameters, &pret);
+ st.wYear = pret.tm_year;
+ st.wMonth = pret.tm_month + 1; // Convert start offset -- Win32: Jan=1; NSPR: Jan=0
+ st.wDayOfWeek = pret.tm_wday;
+ st.wDay = pret.tm_mday;
+ st.wHour = pret.tm_hour;
+ st.wMinute = pret.tm_min;
+ st.wSecond = pret.tm_sec;
+ st.wMilliseconds = pret.tm_usec / 1000;
+
+ nsresult rv = NS_OK;
+ // if at least one of these fails...
+ if (!(SystemTimeToFileTime(&st, &ft) != 0 &&
+ SetFileTime(file, nullptr, &ft, &ft) != 0)) {
+ rv = ConvertWinError(GetLastError());
+ }
+
+ CloseHandle(file);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetPermissions(uint32_t* aPermissions)
+{
+ if (NS_WARN_IF(!aPermissions)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // get the permissions of the target as determined by mFollowSymlinks
+ // If true, then this will be for the target of the shortcut file,
+ // otherwise it will be for the shortcut file itself (i.e. the same
+ // results as GetPermissionsOfLink)
+ nsresult rv = ResolveAndStat();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool isWritable, isExecutable;
+ IsWritable(&isWritable);
+ IsExecutable(&isExecutable);
+
+ *aPermissions = PR_IRUSR | PR_IRGRP | PR_IROTH; // all read
+ if (isWritable) {
+ *aPermissions |= PR_IWUSR | PR_IWGRP | PR_IWOTH; // all write
+ }
+ if (isExecutable) {
+ *aPermissions |= PR_IXUSR | PR_IXGRP | PR_IXOTH; // all execute
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetPermissionsOfLink(uint32_t* aPermissions)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ if (NS_WARN_IF(!aPermissions)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // The caller is assumed to have already called IsSymlink
+ // and to have found that this file is a link. It is not
+ // possible for a link file to be executable.
+
+ DWORD word = ::GetFileAttributesW(mWorkingPath.get());
+ if (word == INVALID_FILE_ATTRIBUTES) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ bool isWritable = !(word & FILE_ATTRIBUTE_READONLY);
+ *aPermissions = PR_IRUSR | PR_IRGRP | PR_IROTH; // all read
+ if (isWritable) {
+ *aPermissions |= PR_IWUSR | PR_IWGRP | PR_IWOTH; // all write
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::SetPermissions(uint32_t aPermissions)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ // set the permissions of the target as determined by mFollowSymlinks
+ // If true, then this will be for the target of the shortcut file,
+ // otherwise it will be for the shortcut file itself (i.e. the same
+ // results as SetPermissionsOfLink)
+ nsresult rv = ResolveAndStat();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // windows only knows about the following permissions
+ int mode = 0;
+ if (aPermissions & (PR_IRUSR | PR_IRGRP | PR_IROTH)) { // any read
+ mode |= _S_IREAD;
+ }
+ if (aPermissions & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) { // any write
+ mode |= _S_IWRITE;
+ }
+
+ if (_wchmod(mResolvedPath.get(), mode) == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetPermissionsOfLink(uint32_t aPermissions)
+{
+ // The caller is assumed to have already called IsSymlink
+ // and to have found that this file is a link.
+
+ // windows only knows about the following permissions
+ int mode = 0;
+ if (aPermissions & (PR_IRUSR | PR_IRGRP | PR_IROTH)) { // any read
+ mode |= _S_IREAD;
+ }
+ if (aPermissions & (PR_IWUSR | PR_IWGRP | PR_IWOTH)) { // any write
+ mode |= _S_IWRITE;
+ }
+
+ if (_wchmod(mWorkingPath.get(), mode) == -1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::GetFileSize(int64_t* aFileSize)
+{
+ if (NS_WARN_IF(!aFileSize)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult rv = ResolveAndStat();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *aFileSize = mFileInfo64.size;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::GetFileSizeOfLink(int64_t* aFileSize)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ if (NS_WARN_IF(!aFileSize)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // The caller is assumed to have already called IsSymlink
+ // and to have found that this file is a link.
+
+ PRFileInfo64 info;
+ if (NS_FAILED(GetFileInfo(mWorkingPath, &info))) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ *aFileSize = info.size;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetFileSize(int64_t aFileSize)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ nsresult rv = ResolveAndStat();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ HANDLE hFile = ::CreateFileW(mResolvedPath.get(),// pointer to name of the file
+ GENERIC_WRITE, // access (write) mode
+ FILE_SHARE_READ, // share mode
+ nullptr, // pointer to security attributes
+ OPEN_EXISTING, // how to create
+ FILE_ATTRIBUTE_NORMAL, // file attributes
+ nullptr);
+ if (hFile == INVALID_HANDLE_VALUE) {
+ return ConvertWinError(GetLastError());
+ }
+
+ // seek the file pointer to the new, desired end of file
+ // and then truncate the file at that position
+ rv = NS_ERROR_FAILURE;
+ aFileSize = MyFileSeek64(hFile, aFileSize, FILE_BEGIN);
+ if (aFileSize != -1 && SetEndOfFile(hFile)) {
+ MakeDirty();
+ rv = NS_OK;
+ }
+
+ CloseHandle(hFile);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetDiskSpaceAvailable(int64_t* aDiskSpaceAvailable)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ if (NS_WARN_IF(!aDiskSpaceAvailable)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ ResolveAndStat();
+
+ if (mFileInfo64.type == PR_FILE_FILE) {
+ // Since GetDiskFreeSpaceExW works only on directories, use the parent.
+ nsCOMPtr<nsIFile> parent;
+ if (NS_SUCCEEDED(GetParent(getter_AddRefs(parent))) && parent) {
+ return parent->GetDiskSpaceAvailable(aDiskSpaceAvailable);
+ }
+ }
+
+ ULARGE_INTEGER liFreeBytesAvailableToCaller, liTotalNumberOfBytes;
+ if (::GetDiskFreeSpaceExW(mResolvedPath.get(), &liFreeBytesAvailableToCaller,
+ &liTotalNumberOfBytes, nullptr)) {
+ *aDiskSpaceAvailable = liFreeBytesAvailableToCaller.QuadPart;
+ return NS_OK;
+ }
+ *aDiskSpaceAvailable = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetParent(nsIFile** aParent)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ if (NS_WARN_IF(!aParent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // A two-character path must be a drive such as C:, so it has no parent
+ if (mWorkingPath.Length() == 2) {
+ *aParent = nullptr;
+ return NS_OK;
+ }
+
+ int32_t offset = mWorkingPath.RFindChar(char16_t('\\'));
+ // adding this offset check that was removed in bug 241708 fixes mail
+ // directories that aren't relative to/underneath the profile dir.
+ // e.g., on a different drive. Before you remove them, please make
+ // sure local mail directories that aren't underneath the profile dir work.
+ if (offset == kNotFound) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ // A path of the form \\NAME is a top-level path and has no parent
+ if (offset == 1 && mWorkingPath[0] == L'\\') {
+ *aParent = nullptr;
+ return NS_OK;
+ }
+
+ nsAutoString parentPath(mWorkingPath);
+
+ if (offset > 0) {
+ parentPath.Truncate(offset);
+ } else {
+ parentPath.AssignLiteral("\\\\.");
+ }
+
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_NewLocalFile(parentPath, mFollowSymlinks,
+ getter_AddRefs(localFile));
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ localFile.forget(aParent);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::Exists(bool* aResult)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aResult = false;
+
+ MakeDirty();
+ nsresult rv = ResolveAndStat();
+ *aResult = NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_IS_LOCKED;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::IsWritable(bool* aIsWritable)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ // The read-only attribute on a FAT directory only means that it can't
+ // be deleted. It is still possible to modify the contents of the directory.
+ nsresult rv = IsDirectory(aIsWritable);
+ if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
+ *aIsWritable = true;
+ return NS_OK;
+ } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
+ // If the file is normally allowed write access
+ // we should still return that the file is writable.
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (*aIsWritable) {
+ return NS_OK;
+ }
+
+ // writable if the file doesn't have the readonly attribute
+ rv = HasFileAttribute(FILE_ATTRIBUTE_READONLY, aIsWritable);
+ if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
+ *aIsWritable = false;
+ return NS_OK;
+ } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
+ // If the file is normally allowed write access
+ // we should still return that the file is writable.
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+ *aIsWritable = !*aIsWritable;
+
+ // If the read only attribute is not set, check to make sure
+ // we can open the file with write access.
+ if (*aIsWritable) {
+ PRFileDesc* file;
+ rv = OpenFile(mResolvedPath, PR_WRONLY, 0, false, &file);
+ if (NS_SUCCEEDED(rv)) {
+ PR_Close(file);
+ } else if (rv == NS_ERROR_FILE_ACCESS_DENIED) {
+ *aIsWritable = false;
+ } else if (rv == NS_ERROR_FILE_IS_LOCKED) {
+ // If it is locked and read only we would have
+ // gotten access denied
+ *aIsWritable = true;
+ } else {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::IsReadable(bool* aResult)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aResult = false;
+
+ nsresult rv = ResolveAndStat();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *aResult = true;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::IsExecutable(bool* aResult)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ *aResult = false;
+
+ nsresult rv;
+
+ // only files can be executables
+ bool isFile;
+ rv = IsFile(&isFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!isFile) {
+ return NS_OK;
+ }
+
+ //TODO: shouldn't we be checking mFollowSymlinks here?
+ bool symLink;
+ rv = IsSymlink(&symLink);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsAutoString path;
+ if (symLink) {
+ GetTarget(path);
+ } else {
+ GetPath(path);
+ }
+
+ // kill trailing dots and spaces.
+ int32_t filePathLen = path.Length() - 1;
+ while (filePathLen > 0 && (path[filePathLen] == L' ' ||
+ path[filePathLen] == L'.')) {
+ path.Truncate(filePathLen--);
+ }
+
+ // Get extension.
+ int32_t dotIdx = path.RFindChar(char16_t('.'));
+ if (dotIdx != kNotFound) {
+ // Convert extension to lower case.
+ char16_t* p = path.BeginWriting();
+ for (p += dotIdx + 1; *p; ++p) {
+ *p += (*p >= L'A' && *p <= L'Z') ? 'a' - 'A' : 0;
+ }
+
+ // Search for any of the set of executable extensions.
+ static const char* const executableExts[] = {
+ "ad",
+ "ade", // access project extension
+ "adp",
+ "air", // Adobe AIR installer
+ "app", // executable application
+ "application", // from bug 348763
+ "asp",
+ "bas",
+ "bat",
+ "chm",
+ "cmd",
+ "com",
+ "cpl",
+ "crt",
+ "exe",
+ "fxp", // FoxPro compiled app
+ "hlp",
+ "hta",
+ "inf",
+ "ins",
+ "isp",
+ "jar", // java application bundle
+ "js",
+ "jse",
+ "lnk",
+ "mad", // Access Module Shortcut
+ "maf", // Access
+ "mag", // Access Diagram Shortcut
+ "mam", // Access Macro Shortcut
+ "maq", // Access Query Shortcut
+ "mar", // Access Report Shortcut
+ "mas", // Access Stored Procedure
+ "mat", // Access Table Shortcut
+ "mau", // Media Attachment Unit
+ "mav", // Access View Shortcut
+ "maw", // Access Data Access Page
+ "mda", // Access Add-in, MDA Access 2 Workgroup
+ "mdb",
+ "mde",
+ "mdt", // Access Add-in Data
+ "mdw", // Access Workgroup Information
+ "mdz", // Access Wizard Template
+ "msc",
+ "msh", // Microsoft Shell
+ "mshxml", // Microsoft Shell
+ "msi",
+ "msp",
+ "mst",
+ "ops", // Office Profile Settings
+ "pcd",
+ "pif",
+ "plg", // Developer Studio Build Log
+ "prf", // windows system file
+ "prg",
+ "pst",
+ "reg",
+ "scf", // Windows explorer command
+ "scr",
+ "sct",
+ "shb",
+ "shs",
+ "url",
+ "vb",
+ "vbe",
+ "vbs",
+ "vsd",
+ "vsmacros", // Visual Studio .NET Binary-based Macro Project
+ "vss",
+ "vst",
+ "vsw",
+ "ws",
+ "wsc",
+ "wsf",
+ "wsh"
+ };
+ nsDependentSubstring ext = Substring(path, dotIdx + 1);
+ for (size_t i = 0; i < ArrayLength(executableExts); ++i) {
+ if (ext.EqualsASCII(executableExts[i])) {
+ // Found a match. Set result and quit.
+ *aResult = true;
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::IsDirectory(bool* aResult)
+{
+ return HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, aResult);
+}
+
+NS_IMETHODIMP
+nsLocalFile::IsFile(bool* aResult)
+{
+ nsresult rv = HasFileAttribute(FILE_ATTRIBUTE_DIRECTORY, aResult);
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = !*aResult;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::IsHidden(bool* aResult)
+{
+ return HasFileAttribute(FILE_ATTRIBUTE_HIDDEN, aResult);
+}
+
+nsresult
+nsLocalFile::HasFileAttribute(DWORD aFileAttrib, bool* aResult)
+{
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult rv = Resolve();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ DWORD attributes = GetFileAttributesW(mResolvedPath.get());
+ if (INVALID_FILE_ATTRIBUTES == attributes) {
+ return ConvertWinError(GetLastError());
+ }
+
+ *aResult = ((attributes & aFileAttrib) != 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::IsSymlink(bool* aResult)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // unless it is a valid shortcut path it's not a symlink
+ if (!IsShortcutPath(mWorkingPath)) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ // we need to know if this is a file or directory
+ nsresult rv = ResolveAndStat();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // We should not check mFileInfo64.type here for PR_FILE_FILE because lnk
+ // files can point to directories or files. Important security checks
+ // depend on correctly identifying lnk files. mFileInfo64 now holds info
+ // about the target of the lnk file, not the actual lnk file!
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::IsSpecial(bool* aResult)
+{
+ return HasFileAttribute(FILE_ATTRIBUTE_SYSTEM, aResult);
+}
+
+NS_IMETHODIMP
+nsLocalFile::Equals(nsIFile* aInFile, bool* aResult)
+{
+ if (NS_WARN_IF(!aInFile)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!aResult)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ EnsureShortPath();
+
+ nsCOMPtr<nsILocalFileWin> lf(do_QueryInterface(aInFile));
+ if (!lf) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ nsAutoString inFilePath;
+ lf->GetCanonicalPath(inFilePath);
+
+ // Ok : Win9x
+ *aResult = _wcsicmp(mShortWorkingPath.get(), inFilePath.get()) == 0;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::Contains(nsIFile* aInFile, bool* aResult)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ *aResult = false;
+
+ nsAutoString myFilePath;
+ if (NS_FAILED(GetTarget(myFilePath))) {
+ GetPath(myFilePath);
+ }
+
+ uint32_t myFilePathLen = myFilePath.Length();
+
+ nsAutoString inFilePath;
+ if (NS_FAILED(aInFile->GetTarget(inFilePath))) {
+ aInFile->GetPath(inFilePath);
+ }
+
+ // make sure that the |aInFile|'s path has a trailing separator.
+ if (inFilePath.Length() >= myFilePathLen &&
+ inFilePath[myFilePathLen] == L'\\') {
+ if (_wcsnicmp(myFilePath.get(), inFilePath.get(), myFilePathLen) == 0) {
+ *aResult = true;
+ }
+
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::GetTarget(nsAString& aResult)
+{
+ aResult.Truncate();
+#if STRICT_FAKE_SYMLINKS
+ bool symLink;
+
+ nsresult rv = IsSymlink(&symLink);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!symLink) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+#endif
+ ResolveAndStat();
+
+ aResult = mResolvedPath;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::GetFollowLinks(bool* aFollowLinks)
+{
+ *aFollowLinks = mFollowSymlinks;
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsLocalFile::SetFollowLinks(bool aFollowLinks)
+{
+ MakeDirty();
+ mFollowSymlinks = aFollowLinks;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::GetDirectoryEntries(nsISimpleEnumerator** aEntries)
+{
+ nsresult rv;
+
+ *aEntries = nullptr;
+ if (mWorkingPath.EqualsLiteral("\\\\.")) {
+ RefPtr<nsDriveEnumerator> drives = new nsDriveEnumerator;
+ rv = drives->Init();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ drives.forget(aEntries);
+ return NS_OK;
+ }
+
+ RefPtr<nsDirEnumerator> dirEnum = new nsDirEnumerator();
+ rv = dirEnum->Init(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ dirEnum.forget(aEntries);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetPersistentDescriptor(nsACString& aPersistentDescriptor)
+{
+ CopyUTF16toUTF8(mWorkingPath, aPersistentDescriptor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetPersistentDescriptor(const nsACString& aPersistentDescriptor)
+{
+ if (IsUTF8(aPersistentDescriptor)) {
+ return InitWithPath(NS_ConvertUTF8toUTF16(aPersistentDescriptor));
+ } else {
+ return InitWithNativePath(aPersistentDescriptor);
+ }
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetFileAttributesWin(uint32_t* aAttribs)
+{
+ *aAttribs = 0;
+ DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get());
+ if (dwAttrs == INVALID_FILE_ATTRIBUTES) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ if (!(dwAttrs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)) {
+ *aAttribs |= WFA_SEARCH_INDEXED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetFileAttributesWin(uint32_t aAttribs)
+{
+ DWORD dwAttrs = GetFileAttributesW(mWorkingPath.get());
+ if (dwAttrs == INVALID_FILE_ATTRIBUTES) {
+ return NS_ERROR_FILE_INVALID_PATH;
+ }
+
+ if (aAttribs & WFA_SEARCH_INDEXED) {
+ dwAttrs &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
+ } else {
+ dwAttrs |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
+ }
+
+ if (aAttribs & WFA_READONLY) {
+ dwAttrs |= FILE_ATTRIBUTE_READONLY;
+ } else if ((aAttribs & WFA_READWRITE) &&
+ (dwAttrs & FILE_ATTRIBUTE_READONLY)) {
+ dwAttrs &= ~FILE_ATTRIBUTE_READONLY;
+ }
+
+ if (SetFileAttributesW(mWorkingPath.get(), dwAttrs) == 0) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::Reveal()
+{
+ // This API should be main thread only
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // make sure mResolvedPath is set
+ nsresult rv = Resolve();
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
+ return rv;
+ }
+
+ // To create a new thread, get the thread manager
+ nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID);
+ nsCOMPtr<nsIThread> mythread;
+ rv = tm->NewThread(0, 0, getter_AddRefs(mythread));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIRunnable> runnable = new AsyncRevealOperation(mResolvedPath);
+
+ // After the dispatch, the result runnable will shut down the worker
+ // thread, so we can let it go.
+ mythread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLocalFile::Launch()
+{
+ // This API should be main thread only
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // make sure mResolvedPath is set
+ nsresult rv = Resolve();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // use the app registry name to launch a shell execute....
+ SHELLEXECUTEINFOW seinfo;
+ memset(&seinfo, 0, sizeof(seinfo));
+ seinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
+ seinfo.fMask = SEE_MASK_ASYNCOK;
+ seinfo.hwnd = GetMostRecentNavigatorHWND();
+ seinfo.lpVerb = nullptr;
+ seinfo.lpFile = mResolvedPath.get();
+ seinfo.lpParameters = nullptr;
+ seinfo.lpDirectory = nullptr;
+ seinfo.nShow = SW_SHOWNORMAL;
+
+ // Use the directory of the file we're launching as the working
+ // directory. That way if we have a self extracting EXE it won't
+ // suggest to extract to the install directory.
+ WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' };
+ wcsncpy(workingDirectory, mResolvedPath.get(), MAX_PATH);
+ if (PathRemoveFileSpecW(workingDirectory)) {
+ seinfo.lpDirectory = workingDirectory;
+ } else {
+ NS_WARNING("Could not set working directory for launched file.");
+ }
+
+ if (ShellExecuteExW(&seinfo)) {
+ return NS_OK;
+ }
+ DWORD r = GetLastError();
+ // if the file has no association, we launch windows'
+ // "what do you want to do" dialog
+ if (r == SE_ERR_NOASSOC) {
+ nsAutoString shellArg;
+ shellArg.AssignLiteral(u"shell32.dll,OpenAs_RunDLL ");
+ shellArg.Append(mResolvedPath);
+ seinfo.lpFile = L"RUNDLL32.EXE";
+ seinfo.lpParameters = shellArg.get();
+ if (ShellExecuteExW(&seinfo)) {
+ return NS_OK;
+ }
+ r = GetLastError();
+ }
+ if (r < 32) {
+ switch (r) {
+ case 0:
+ case SE_ERR_OOM:
+ return NS_ERROR_OUT_OF_MEMORY;
+ case ERROR_FILE_NOT_FOUND:
+ return NS_ERROR_FILE_NOT_FOUND;
+ case ERROR_PATH_NOT_FOUND:
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ case ERROR_BAD_FORMAT:
+ return NS_ERROR_FILE_CORRUPTED;
+ case SE_ERR_ACCESSDENIED:
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ case SE_ERR_ASSOCINCOMPLETE:
+ case SE_ERR_NOASSOC:
+ return NS_ERROR_UNEXPECTED;
+ case SE_ERR_DDEBUSY:
+ case SE_ERR_DDEFAIL:
+ case SE_ERR_DDETIMEOUT:
+ return NS_ERROR_NOT_AVAILABLE;
+ case SE_ERR_DLLNOTFOUND:
+ return NS_ERROR_FAILURE;
+ case SE_ERR_SHARE:
+ return NS_ERROR_FILE_IS_LOCKED;
+ default:
+ return NS_ERROR_FILE_EXECUTION_FAILED;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks, nsIFile** aResult)
+{
+ RefPtr<nsLocalFile> file = new nsLocalFile();
+
+ file->SetFollowLinks(aFollowLinks);
+
+ if (!aPath.IsEmpty()) {
+ nsresult rv = file->InitWithPath(aPath);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ file.forget(aResult);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// Native (lossy) interface
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+nsLocalFile::InitWithNativePath(const nsACString& aFilePath)
+{
+ nsAutoString tmp;
+ nsresult rv = NS_CopyNativeToUnicode(aFilePath, tmp);
+ if (NS_SUCCEEDED(rv)) {
+ return InitWithPath(tmp);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::AppendNative(const nsACString& aNode)
+{
+ nsAutoString tmp;
+ nsresult rv = NS_CopyNativeToUnicode(aNode, tmp);
+ if (NS_SUCCEEDED(rv)) {
+ return Append(tmp);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::AppendRelativeNativePath(const nsACString& aNode)
+{
+ nsAutoString tmp;
+ nsresult rv = NS_CopyNativeToUnicode(aNode, tmp);
+ if (NS_SUCCEEDED(rv)) {
+ return AppendRelativePath(tmp);
+ }
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::GetNativeLeafName(nsACString& aLeafName)
+{
+ //NS_WARNING("This API is lossy. Use GetLeafName !");
+ nsAutoString tmp;
+ nsresult rv = GetLeafName(tmp);
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_CopyUnicodeToNative(tmp, aLeafName);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::SetNativeLeafName(const nsACString& aLeafName)
+{
+ nsAutoString tmp;
+ nsresult rv = NS_CopyNativeToUnicode(aLeafName, tmp);
+ if (NS_SUCCEEDED(rv)) {
+ return SetLeafName(tmp);
+ }
+
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::GetNativePath(nsACString& aResult)
+{
+ //NS_WARNING("This API is lossy. Use GetPath !");
+ nsAutoString tmp;
+ nsresult rv = GetPath(tmp);
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_CopyUnicodeToNative(tmp, aResult);
+ }
+
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::GetNativeCanonicalPath(nsACString& aResult)
+{
+ NS_WARNING("This method is lossy. Use GetCanonicalPath !");
+ EnsureShortPath();
+ NS_CopyUnicodeToNative(mShortWorkingPath, aResult);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsLocalFile::CopyToNative(nsIFile* aNewParentDir, const nsACString& aNewName)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ if (aNewName.IsEmpty()) {
+ return CopyTo(aNewParentDir, EmptyString());
+ }
+
+ nsAutoString tmp;
+ nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
+ if (NS_SUCCEEDED(rv)) {
+ return CopyTo(aNewParentDir, tmp);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::CopyToFollowingLinksNative(nsIFile* aNewParentDir,
+ const nsACString& aNewName)
+{
+ if (aNewName.IsEmpty()) {
+ return CopyToFollowingLinks(aNewParentDir, EmptyString());
+ }
+
+ nsAutoString tmp;
+ nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
+ if (NS_SUCCEEDED(rv)) {
+ return CopyToFollowingLinks(aNewParentDir, tmp);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::MoveToNative(nsIFile* aNewParentDir, const nsACString& aNewName)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ if (aNewName.IsEmpty()) {
+ return MoveTo(aNewParentDir, EmptyString());
+ }
+
+ nsAutoString tmp;
+ nsresult rv = NS_CopyNativeToUnicode(aNewName, tmp);
+ if (NS_SUCCEEDED(rv)) {
+ return MoveTo(aNewParentDir, tmp);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetNativeTarget(nsACString& aResult)
+{
+ // Check we are correctly initialized.
+ CHECK_mWorkingPath();
+
+ NS_WARNING("This API is lossy. Use GetTarget !");
+ nsAutoString tmp;
+ nsresult rv = GetTarget(tmp);
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_CopyUnicodeToNative(tmp, aResult);
+ }
+
+ return rv;
+}
+
+nsresult
+NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowLinks,
+ nsIFile** aResult)
+{
+ nsAutoString buf;
+ nsresult rv = NS_CopyNativeToUnicode(aPath, buf);
+ if (NS_FAILED(rv)) {
+ *aResult = nullptr;
+ return rv;
+ }
+ return NS_NewLocalFile(buf, aFollowLinks, aResult);
+}
+
+void
+nsLocalFile::EnsureShortPath()
+{
+ if (!mShortWorkingPath.IsEmpty()) {
+ return;
+ }
+
+ WCHAR shortPath[MAX_PATH + 1];
+ DWORD lengthNeeded = ::GetShortPathNameW(mWorkingPath.get(), shortPath,
+ ArrayLength(shortPath));
+ // If an error occurred then lengthNeeded is set to 0 or the length of the
+ // needed buffer including null termination. If it succeeds the number of
+ // wide characters not including null termination is returned.
+ if (lengthNeeded != 0 && lengthNeeded < ArrayLength(shortPath)) {
+ mShortWorkingPath.Assign(shortPath);
+ } else {
+ mShortWorkingPath.Assign(mWorkingPath);
+ }
+}
+
+// nsIHashable
+
+NS_IMETHODIMP
+nsLocalFile::Equals(nsIHashable* aOther, bool* aResult)
+{
+ nsCOMPtr<nsIFile> otherfile(do_QueryInterface(aOther));
+ if (!otherfile) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ return Equals(otherfile, aResult);
+}
+
+NS_IMETHODIMP
+nsLocalFile::GetHashCode(uint32_t* aResult)
+{
+ // In order for short and long path names to hash to the same value we
+ // always hash on the short pathname.
+ EnsureShortPath();
+
+ *aResult = HashString(mShortWorkingPath);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// nsLocalFile <static members>
+//-----------------------------------------------------------------------------
+
+void
+nsLocalFile::GlobalInit()
+{
+ DebugOnly<nsresult> rv = NS_CreateShortcutResolver();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Shortcut resolver could not be created");
+}
+
+void
+nsLocalFile::GlobalShutdown()
+{
+ NS_DestroyShortcutResolver();
+}
+
+NS_IMPL_ISUPPORTS(nsDriveEnumerator, nsISimpleEnumerator)
+
+nsDriveEnumerator::nsDriveEnumerator()
+{
+}
+
+nsDriveEnumerator::~nsDriveEnumerator()
+{
+}
+
+nsresult
+nsDriveEnumerator::Init()
+{
+ /* If the length passed to GetLogicalDriveStrings is smaller
+ * than the length of the string it would return, it returns
+ * the length required for the string. */
+ DWORD length = GetLogicalDriveStringsW(0, 0);
+ /* The string is null terminated */
+ if (!mDrives.SetLength(length + 1, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (!GetLogicalDriveStringsW(length, wwc(mDrives.BeginWriting()))) {
+ return NS_ERROR_FAILURE;
+ }
+ mDrives.BeginReading(mStartOfCurrentDrive);
+ mDrives.EndReading(mEndOfDrivesString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDriveEnumerator::HasMoreElements(bool* aHasMore)
+{
+ *aHasMore = *mStartOfCurrentDrive != L'\0';
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDriveEnumerator::GetNext(nsISupports** aNext)
+{
+ /* GetLogicalDrives stored in mDrives is a concatenation
+ * of null terminated strings, followed by a null terminator.
+ * mStartOfCurrentDrive is an iterator pointing at the first
+ * character of the current drive. */
+ if (*mStartOfCurrentDrive == L'\0') {
+ *aNext = nullptr;
+ return NS_OK;
+ }
+
+ nsAString::const_iterator driveEnd = mStartOfCurrentDrive;
+ FindCharInReadable(L'\0', driveEnd, mEndOfDrivesString);
+ nsString drive(Substring(mStartOfCurrentDrive, driveEnd));
+ mStartOfCurrentDrive = ++driveEnd;
+
+ nsIFile* file;
+ nsresult rv = NS_NewLocalFile(drive, false, &file);
+
+ *aNext = file;
+ return rv;
+}
diff --git a/xpcom/io/nsLocalFileWin.h b/xpcom/io/nsLocalFileWin.h
new file mode 100644
index 000000000..abef2c106
--- /dev/null
+++ b/xpcom/io/nsLocalFileWin.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsLocalFileWIN_H_
+#define _nsLocalFileWIN_H_
+
+#include "nscore.h"
+#include "nsError.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "nsIFile.h"
+#include "nsIFactory.h"
+#include "nsILocalFileWin.h"
+#include "nsIHashable.h"
+#include "nsIClassInfoImpl.h"
+#include "prio.h"
+
+#include "mozilla/Attributes.h"
+
+#include "windows.h"
+#include "shlobj.h"
+
+#include <sys/stat.h>
+
+class nsLocalFile final
+ : public nsILocalFileWin
+ , public nsIHashable
+{
+public:
+ NS_DEFINE_STATIC_CID_ACCESSOR(NS_LOCAL_FILE_CID)
+
+ nsLocalFile();
+
+ static nsresult nsLocalFileConstructor(nsISupports* aOuter,
+ const nsIID& aIID,
+ void** aInstancePtr);
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIFile interface
+ NS_DECL_NSIFILE
+
+ // nsILocalFile interface
+ NS_DECL_NSILOCALFILE
+
+ // nsILocalFileWin interface
+ NS_DECL_NSILOCALFILEWIN
+
+ // nsIHashable interface
+ NS_DECL_NSIHASHABLE
+
+public:
+ static void GlobalInit();
+ static void GlobalShutdown();
+
+ // Removes registry command handler parameters, quotes, and expands environment strings.
+ static bool CleanupCmdHandlerPath(nsAString& aCommandHandler);
+
+private:
+ // CopyMove and CopySingleFile constants for |options| parameter:
+ enum CopyFileOption {
+ FollowSymlinks = 1u << 0,
+ Move = 1u << 1,
+ SkipNtfsAclReset = 1u << 2,
+ Rename = 1u << 3
+ };
+
+ nsLocalFile(const nsLocalFile& aOther);
+ ~nsLocalFile()
+ {
+ }
+
+ bool mDirty; // cached information can only be used when this is false
+ bool mResolveDirty;
+ bool mFollowSymlinks; // should we follow symlinks when working on this file
+
+ // this string will always be in native format!
+ nsString mWorkingPath;
+
+ // this will be the resolved path of shortcuts, it will *NEVER*
+ // be returned to the user
+ nsString mResolvedPath;
+
+ // this string, if not empty, is the *short* pathname that represents
+ // mWorkingPath
+ nsString mShortWorkingPath;
+
+ PRFileInfo64 mFileInfo64;
+
+ void MakeDirty()
+ {
+ mDirty = true;
+ mResolveDirty = true;
+ mShortWorkingPath.Truncate();
+ }
+
+ nsresult ResolveAndStat();
+ nsresult Resolve();
+ nsresult ResolveShortcut();
+
+ void EnsureShortPath();
+
+ nsresult CopyMove(nsIFile* aNewParentDir, const nsAString& aNewName,
+ uint32_t aOptions);
+ nsresult CopySingleFile(nsIFile* aSource, nsIFile* aDest,
+ const nsAString& aNewName, uint32_t aOptions);
+
+ nsresult SetModDate(int64_t aLastModifiedTime, const wchar_t* aFilePath);
+ nsresult HasFileAttribute(DWORD aFileAttrib, bool* aResult);
+ nsresult AppendInternal(const nsAFlatString& aNode,
+ bool aMultipleComponents);
+
+ nsresult OpenNSPRFileDescMaybeShareDelete(int32_t aFlags,
+ int32_t aMode,
+ bool aShareDelete,
+ PRFileDesc** aResult);
+};
+
+#endif
diff --git a/xpcom/io/nsMultiplexInputStream.cpp b/xpcom/io/nsMultiplexInputStream.cpp
new file mode 100644
index 000000000..4aa397c37
--- /dev/null
+++ b/xpcom/io/nsMultiplexInputStream.cpp
@@ -0,0 +1,835 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The multiplex stream concatenates a list of input streams into a single
+ * stream.
+ */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Mutex.h"
+
+#include "base/basictypes.h"
+
+#include "nsMultiplexInputStream.h"
+#include "nsICloneableInputStream.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::ipc;
+
+using mozilla::DeprecatedAbs;
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+
+class nsMultiplexInputStream final
+ : public nsIMultiplexInputStream
+ , public nsISeekableStream
+ , public nsIIPCSerializableInputStream
+ , public nsICloneableInputStream
+{
+public:
+ nsMultiplexInputStream();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIMULTIPLEXINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+ NS_DECL_NSICLONEABLEINPUTSTREAM
+
+private:
+ ~nsMultiplexInputStream()
+ {
+ }
+
+ struct MOZ_STACK_CLASS ReadSegmentsState
+ {
+ nsCOMPtr<nsIInputStream> mThisStream;
+ uint32_t mOffset;
+ nsWriteSegmentFun mWriter;
+ void* mClosure;
+ bool mDone;
+ };
+
+ static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment, uint32_t aToOffset,
+ uint32_t aCount, uint32_t* aWriteCount);
+
+ Mutex mLock; // Protects access to all data members.
+ nsTArray<nsCOMPtr<nsIInputStream>> mStreams;
+ uint32_t mCurrentStream;
+ bool mStartedReadingCurrent;
+ nsresult mStatus;
+};
+
+NS_IMPL_ADDREF(nsMultiplexInputStream)
+NS_IMPL_RELEASE(nsMultiplexInputStream)
+
+NS_IMPL_CLASSINFO(nsMultiplexInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_MULTIPLEXINPUTSTREAM_CID)
+
+NS_IMPL_QUERY_INTERFACE_CI(nsMultiplexInputStream,
+ nsIMultiplexInputStream,
+ nsIInputStream,
+ nsISeekableStream,
+ nsIIPCSerializableInputStream,
+ nsICloneableInputStream)
+NS_IMPL_CI_INTERFACE_GETTER(nsMultiplexInputStream,
+ nsIMultiplexInputStream,
+ nsIInputStream,
+ nsISeekableStream)
+
+static nsresult
+AvailableMaybeSeek(nsIInputStream* aStream, uint64_t* aResult)
+{
+ nsresult rv = aStream->Available(aResult);
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ // Blindly seek to the current position if Available() returns
+ // NS_BASE_STREAM_CLOSED.
+ // If nsIFileInputStream is closed in Read() due to CLOSE_ON_EOF flag,
+ // Seek() could reopen the file if REOPEN_ON_REWIND flag is set.
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aStream);
+ if (seekable) {
+ nsresult rvSeek = seekable->Seek(nsISeekableStream::NS_SEEK_CUR, 0);
+ if (NS_SUCCEEDED(rvSeek)) {
+ rv = aStream->Available(aResult);
+ }
+ }
+ }
+ return rv;
+}
+
+static nsresult
+TellMaybeSeek(nsISeekableStream* aSeekable, int64_t* aResult)
+{
+ nsresult rv = aSeekable->Tell(aResult);
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ // Blindly seek to the current position if Tell() returns
+ // NS_BASE_STREAM_CLOSED.
+ // If nsIFileInputStream is closed in Read() due to CLOSE_ON_EOF flag,
+ // Seek() could reopen the file if REOPEN_ON_REWIND flag is set.
+ nsresult rvSeek = aSeekable->Seek(nsISeekableStream::NS_SEEK_CUR, 0);
+ if (NS_SUCCEEDED(rvSeek)) {
+ rv = aSeekable->Tell(aResult);
+ }
+ }
+ return rv;
+}
+
+nsMultiplexInputStream::nsMultiplexInputStream()
+ : mLock("nsMultiplexInputStream lock"),
+ mCurrentStream(0),
+ mStartedReadingCurrent(false),
+ mStatus(NS_OK)
+{
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::GetCount(uint32_t* aCount)
+{
+ MutexAutoLock lock(mLock);
+ *aCount = mStreams.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::AppendStream(nsIInputStream* aStream)
+{
+ MutexAutoLock lock(mLock);
+ return mStreams.AppendElement(aStream) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::InsertStream(nsIInputStream* aStream, uint32_t aIndex)
+{
+ MutexAutoLock lock(mLock);
+ mStreams.InsertElementAt(aIndex, aStream);
+ if (mCurrentStream > aIndex ||
+ (mCurrentStream == aIndex && mStartedReadingCurrent)) {
+ ++mCurrentStream;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::RemoveStream(uint32_t aIndex)
+{
+ MutexAutoLock lock(mLock);
+ mStreams.RemoveElementAt(aIndex);
+ if (mCurrentStream > aIndex) {
+ --mCurrentStream;
+ } else if (mCurrentStream == aIndex) {
+ mStartedReadingCurrent = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::GetStream(uint32_t aIndex, nsIInputStream** aResult)
+{
+ MutexAutoLock lock(mLock);
+ *aResult = mStreams.SafeElementAt(aIndex, nullptr);
+ if (NS_WARN_IF(!*aResult)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::Close()
+{
+ MutexAutoLock lock(mLock);
+ mStatus = NS_BASE_STREAM_CLOSED;
+
+ nsresult rv = NS_OK;
+
+ uint32_t len = mStreams.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ nsresult rv2 = mStreams[i]->Close();
+ // We still want to close all streams, but we should return an error
+ if (NS_FAILED(rv2)) {
+ rv = rv2;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::Available(uint64_t* aResult)
+{
+ MutexAutoLock lock(mLock);
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ uint64_t avail = 0;
+
+ uint32_t len = mStreams.Length();
+ for (uint32_t i = mCurrentStream; i < len; i++) {
+ uint64_t streamAvail;
+ mStatus = AvailableMaybeSeek(mStreams[i], &streamAvail);
+ if (NS_WARN_IF(NS_FAILED(mStatus))) {
+ return mStatus;
+ }
+ avail += streamAvail;
+ }
+ *aResult = avail;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aResult)
+{
+ MutexAutoLock lock(mLock);
+ // It is tempting to implement this method in terms of ReadSegments, but
+ // that would prevent this class from being used with streams that only
+ // implement Read (e.g., file streams).
+
+ *aResult = 0;
+
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ return NS_OK;
+ }
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ nsresult rv = NS_OK;
+
+ uint32_t len = mStreams.Length();
+ while (mCurrentStream < len && aCount) {
+ uint32_t read;
+ rv = mStreams[mCurrentStream]->Read(aBuf, aCount, &read);
+
+ // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
+ // (This is a bug in those stream implementations)
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ NS_NOTREACHED("Input stream's Read method returned NS_BASE_STREAM_CLOSED");
+ rv = NS_OK;
+ read = 0;
+ } else if (NS_FAILED(rv)) {
+ break;
+ }
+
+ if (read == 0) {
+ ++mCurrentStream;
+ mStartedReadingCurrent = false;
+ } else {
+ NS_ASSERTION(aCount >= read, "Read more than requested");
+ *aResult += read;
+ aCount -= read;
+ aBuf += read;
+ mStartedReadingCurrent = true;
+ }
+ }
+ return *aResult ? NS_OK : rv;
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult)
+{
+ MutexAutoLock lock(mLock);
+
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ *aResult = 0;
+ return NS_OK;
+ }
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ NS_ASSERTION(aWriter, "missing aWriter");
+
+ nsresult rv = NS_OK;
+ ReadSegmentsState state;
+ state.mThisStream = this;
+ state.mOffset = 0;
+ state.mWriter = aWriter;
+ state.mClosure = aClosure;
+ state.mDone = false;
+
+ uint32_t len = mStreams.Length();
+ while (mCurrentStream < len && aCount) {
+ uint32_t read;
+ rv = mStreams[mCurrentStream]->ReadSegments(ReadSegCb, &state, aCount, &read);
+
+ // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
+ // (This is a bug in those stream implementations)
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ NS_NOTREACHED("Input stream's Read method returned NS_BASE_STREAM_CLOSED");
+ rv = NS_OK;
+ read = 0;
+ }
+
+ // if |aWriter| decided to stop reading segments...
+ if (state.mDone || NS_FAILED(rv)) {
+ break;
+ }
+
+ // if stream is empty, then advance to the next stream.
+ if (read == 0) {
+ ++mCurrentStream;
+ mStartedReadingCurrent = false;
+ } else {
+ NS_ASSERTION(aCount >= read, "Read more than requested");
+ state.mOffset += read;
+ aCount -= read;
+ mStartedReadingCurrent = true;
+ }
+ }
+
+ // if we successfully read some data, then this call succeeded.
+ *aResult = state.mOffset;
+ return state.mOffset ? NS_OK : rv;
+}
+
+nsresult
+nsMultiplexInputStream::ReadSegCb(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset, uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ nsresult rv;
+ ReadSegmentsState* state = (ReadSegmentsState*)aClosure;
+ rv = (state->mWriter)(state->mThisStream,
+ state->mClosure,
+ aFromRawSegment,
+ aToOffset + state->mOffset,
+ aCount,
+ aWriteCount);
+ if (NS_FAILED(rv)) {
+ state->mDone = true;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::IsNonBlocking(bool* aNonBlocking)
+{
+ MutexAutoLock lock(mLock);
+
+ uint32_t len = mStreams.Length();
+ if (len == 0) {
+ // Claim to be non-blocking, since we won't block the caller.
+ // On the other hand we'll never return NS_BASE_STREAM_WOULD_BLOCK,
+ // so maybe we should claim to be blocking? It probably doesn't
+ // matter in practice.
+ *aNonBlocking = true;
+ return NS_OK;
+ }
+ for (uint32_t i = 0; i < len; ++i) {
+ nsresult rv = mStreams[i]->IsNonBlocking(aNonBlocking);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ // If one is non-blocking the entire stream becomes non-blocking
+ // (except that we don't implement nsIAsyncInputStream, so there's
+ // not much for the caller to do if Read returns "would block")
+ if (*aNonBlocking) {
+ return NS_OK;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::Seek(int32_t aWhence, int64_t aOffset)
+{
+ MutexAutoLock lock(mLock);
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ nsresult rv;
+
+ uint32_t oldCurrentStream = mCurrentStream;
+ bool oldStartedReadingCurrent = mStartedReadingCurrent;
+
+ if (aWhence == NS_SEEK_SET) {
+ int64_t remaining = aOffset;
+ if (aOffset == 0) {
+ mCurrentStream = 0;
+ }
+ for (uint32_t i = 0; i < mStreams.Length(); ++i) {
+ nsCOMPtr<nsISeekableStream> stream =
+ do_QueryInterface(mStreams[i]);
+ if (!stream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // See if all remaining streams should be rewound
+ if (remaining == 0) {
+ if (i < oldCurrentStream ||
+ (i == oldCurrentStream && oldStartedReadingCurrent)) {
+ rv = stream->Seek(NS_SEEK_SET, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ // Get position in current stream
+ int64_t streamPos;
+ if (i > oldCurrentStream ||
+ (i == oldCurrentStream && !oldStartedReadingCurrent)) {
+ streamPos = 0;
+ } else {
+ rv = TellMaybeSeek(stream, &streamPos);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ // See if we need to seek current stream forward or backward
+ if (remaining < streamPos) {
+ rv = stream->Seek(NS_SEEK_SET, remaining);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mCurrentStream = i;
+ mStartedReadingCurrent = remaining != 0;
+
+ remaining = 0;
+ } else if (remaining > streamPos) {
+ if (i < oldCurrentStream) {
+ // We're already at end so no need to seek this stream
+ remaining -= streamPos;
+ NS_ASSERTION(remaining >= 0, "Remaining invalid");
+ } else {
+ uint64_t avail;
+ rv = AvailableMaybeSeek(mStreams[i], &avail);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int64_t newPos = XPCOM_MIN(remaining, streamPos + (int64_t)avail);
+
+ rv = stream->Seek(NS_SEEK_SET, newPos);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mCurrentStream = i;
+ mStartedReadingCurrent = true;
+
+ remaining -= newPos;
+ NS_ASSERTION(remaining >= 0, "Remaining invalid");
+ }
+ } else {
+ NS_ASSERTION(remaining == streamPos, "Huh?");
+ remaining = 0;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ if (aWhence == NS_SEEK_CUR && aOffset > 0) {
+ int64_t remaining = aOffset;
+ for (uint32_t i = mCurrentStream; remaining && i < mStreams.Length(); ++i) {
+ nsCOMPtr<nsISeekableStream> stream =
+ do_QueryInterface(mStreams[i]);
+
+ uint64_t avail;
+ rv = AvailableMaybeSeek(mStreams[i], &avail);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int64_t seek = XPCOM_MIN((int64_t)avail, remaining);
+
+ rv = stream->Seek(NS_SEEK_CUR, seek);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mCurrentStream = i;
+ mStartedReadingCurrent = true;
+
+ remaining -= seek;
+ }
+
+ return NS_OK;
+ }
+
+ if (aWhence == NS_SEEK_CUR && aOffset < 0) {
+ int64_t remaining = -aOffset;
+ for (uint32_t i = mCurrentStream; remaining && i != (uint32_t)-1; --i) {
+ nsCOMPtr<nsISeekableStream> stream =
+ do_QueryInterface(mStreams[i]);
+
+ int64_t pos;
+ rv = TellMaybeSeek(stream, &pos);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int64_t seek = XPCOM_MIN(pos, remaining);
+
+ rv = stream->Seek(NS_SEEK_CUR, -seek);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mCurrentStream = i;
+ mStartedReadingCurrent = seek != -pos;
+
+ remaining -= seek;
+ }
+
+ return NS_OK;
+ }
+
+ if (aWhence == NS_SEEK_CUR) {
+ NS_ASSERTION(aOffset == 0, "Should have handled all non-zero values");
+
+ return NS_OK;
+ }
+
+ if (aWhence == NS_SEEK_END) {
+ if (aOffset > 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ int64_t remaining = aOffset;
+ for (uint32_t i = mStreams.Length() - 1; i != (uint32_t)-1; --i) {
+ nsCOMPtr<nsISeekableStream> stream =
+ do_QueryInterface(mStreams[i]);
+
+ // See if all remaining streams should be seeked to end
+ if (remaining == 0) {
+ if (i >= oldCurrentStream) {
+ rv = stream->Seek(NS_SEEK_END, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ } else {
+ break;
+ }
+ }
+
+ // Get position in current stream
+ int64_t streamPos;
+ if (i < oldCurrentStream) {
+ streamPos = 0;
+ } else {
+ uint64_t avail;
+ rv = AvailableMaybeSeek(mStreams[i], &avail);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ streamPos = avail;
+ }
+
+ // See if we have enough data in the current stream.
+ if (DeprecatedAbs(remaining) < streamPos) {
+ rv = stream->Seek(NS_SEEK_END, remaining);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mCurrentStream = i;
+ mStartedReadingCurrent = true;
+
+ remaining = 0;
+ } else if (DeprecatedAbs(remaining) > streamPos) {
+ if (i > oldCurrentStream ||
+ (i == oldCurrentStream && !oldStartedReadingCurrent)) {
+ // We're already at start so no need to seek this stream
+ remaining += streamPos;
+ } else {
+ int64_t avail;
+ rv = TellMaybeSeek(stream, &avail);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int64_t newPos = streamPos + XPCOM_MIN(avail, DeprecatedAbs(remaining));
+
+ rv = stream->Seek(NS_SEEK_END, -newPos);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mCurrentStream = i;
+ mStartedReadingCurrent = true;
+
+ remaining += newPos;
+ }
+ } else {
+ NS_ASSERTION(remaining == streamPos, "Huh?");
+ remaining = 0;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ // other Seeks not implemented yet
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::Tell(int64_t* aResult)
+{
+ MutexAutoLock lock(mLock);
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ nsresult rv;
+ int64_t ret64 = 0;
+ uint32_t i, last;
+ last = mStartedReadingCurrent ? mCurrentStream + 1 : mCurrentStream;
+ for (i = 0; i < last; ++i) {
+ nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStreams[i]);
+ if (NS_WARN_IF(!stream)) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ int64_t pos;
+ rv = TellMaybeSeek(stream, &pos);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ ret64 += pos;
+ }
+ *aResult = ret64;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::SetEOF()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsMultiplexInputStreamConstructor(nsISupports* aOuter,
+ REFNSIID aIID,
+ void** aResult)
+{
+ *aResult = nullptr;
+
+ if (aOuter) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ RefPtr<nsMultiplexInputStream> inst = new nsMultiplexInputStream();
+
+ return inst->QueryInterface(aIID, aResult);
+}
+
+void
+nsMultiplexInputStream::Serialize(InputStreamParams& aParams,
+ FileDescriptorArray& aFileDescriptors)
+{
+ MutexAutoLock lock(mLock);
+
+ MultiplexInputStreamParams params;
+
+ uint32_t streamCount = mStreams.Length();
+
+ if (streamCount) {
+ InfallibleTArray<InputStreamParams>& streams = params.streams();
+
+ streams.SetCapacity(streamCount);
+ for (uint32_t index = 0; index < streamCount; index++) {
+ InputStreamParams childStreamParams;
+ SerializeInputStream(mStreams[index], childStreamParams,
+ aFileDescriptors);
+
+ streams.AppendElement(childStreamParams);
+ }
+ }
+
+ params.currentStream() = mCurrentStream;
+ params.status() = mStatus;
+ params.startedReadingCurrent() = mStartedReadingCurrent;
+
+ aParams = params;
+}
+
+bool
+nsMultiplexInputStream::Deserialize(const InputStreamParams& aParams,
+ const FileDescriptorArray& aFileDescriptors)
+{
+ if (aParams.type() !=
+ InputStreamParams::TMultiplexInputStreamParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const MultiplexInputStreamParams& params =
+ aParams.get_MultiplexInputStreamParams();
+
+ const InfallibleTArray<InputStreamParams>& streams = params.streams();
+
+ uint32_t streamCount = streams.Length();
+ for (uint32_t index = 0; index < streamCount; index++) {
+ nsCOMPtr<nsIInputStream> stream =
+ DeserializeInputStream(streams[index], aFileDescriptors);
+ if (!stream) {
+ NS_WARNING("Deserialize failed!");
+ return false;
+ }
+
+ if (NS_FAILED(AppendStream(stream))) {
+ NS_WARNING("AppendStream failed!");
+ return false;
+ }
+ }
+
+ mCurrentStream = params.currentStream();
+ mStatus = params.status();
+ mStartedReadingCurrent = params.startedReadingCurrent();
+
+ return true;
+}
+
+Maybe<uint64_t>
+nsMultiplexInputStream::ExpectedSerializedLength()
+{
+ MutexAutoLock lock(mLock);
+
+ bool lengthValueExists = false;
+ uint64_t expectedLength = 0;
+ uint32_t streamCount = mStreams.Length();
+ for (uint32_t index = 0; index < streamCount; index++) {
+ nsCOMPtr<nsIIPCSerializableInputStream> stream = do_QueryInterface(mStreams[index]);
+ if (!stream) {
+ continue;
+ }
+ Maybe<uint64_t> length = stream->ExpectedSerializedLength();
+ if (length.isNothing()) {
+ continue;
+ }
+ lengthValueExists = true;
+ expectedLength += length.value();
+ }
+ return lengthValueExists ? Some(expectedLength) : Nothing();
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::GetCloneable(bool* aCloneable)
+{
+ MutexAutoLock lock(mLock);
+ //XXXnsm Cloning a multiplex stream which has started reading is not permitted
+ //right now.
+ if (mCurrentStream > 0 || mStartedReadingCurrent) {
+ *aCloneable = false;
+ return NS_OK;
+ }
+
+ uint32_t len = mStreams.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ nsCOMPtr<nsICloneableInputStream> cis = do_QueryInterface(mStreams[i]);
+ if (!cis || !cis->GetCloneable()) {
+ *aCloneable = false;
+ return NS_OK;
+ }
+ }
+
+ *aCloneable = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMultiplexInputStream::Clone(nsIInputStream** aClone)
+{
+ MutexAutoLock lock(mLock);
+
+ //XXXnsm Cloning a multiplex stream which has started reading is not permitted
+ //right now.
+ if (mCurrentStream > 0 || mStartedReadingCurrent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsMultiplexInputStream> clone = new nsMultiplexInputStream();
+
+ nsresult rv;
+ uint32_t len = mStreams.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ nsCOMPtr<nsICloneableInputStream> substream = do_QueryInterface(mStreams[i]);
+ if (NS_WARN_IF(!substream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIInputStream> clonedSubstream;
+ rv = substream->Clone(getter_AddRefs(clonedSubstream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = clone->AppendStream(clonedSubstream);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ clone.forget(aClone);
+ return NS_OK;
+}
diff --git a/xpcom/io/nsMultiplexInputStream.h b/xpcom/io/nsMultiplexInputStream.h
new file mode 100644
index 000000000..381051dca
--- /dev/null
+++ b/xpcom/io/nsMultiplexInputStream.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The multiplex stream concatenates a list of input streams into a single
+ * stream.
+ */
+
+#ifndef _nsMultiplexInputStream_h_
+#define _nsMultiplexInputStream_h_
+
+#include "nsIMultiplexInputStream.h"
+
+#define NS_MULTIPLEXINPUTSTREAM_CONTRACTID "@mozilla.org/io/multiplex-input-stream;1"
+#define NS_MULTIPLEXINPUTSTREAM_CID \
+ { /* 565e3a2c-1dd2-11b2-8da1-b4cef17e568d */ \
+ 0x565e3a2c, \
+ 0x1dd2, \
+ 0x11b2, \
+ {0x8d, 0xa1, 0xb4, 0xce, 0xf1, 0x7e, 0x56, 0x8d} \
+ }
+
+extern nsresult nsMultiplexInputStreamConstructor(nsISupports* aOuter,
+ REFNSIID aIID,
+ void** aResult);
+
+#endif // _nsMultiplexInputStream_h_
diff --git a/xpcom/io/nsNativeCharsetUtils.cpp b/xpcom/io/nsNativeCharsetUtils.cpp
new file mode 100644
index 000000000..e53307af5
--- /dev/null
+++ b/xpcom/io/nsNativeCharsetUtils.cpp
@@ -0,0 +1,1044 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "xpcom-private.h"
+
+//-----------------------------------------------------------------------------
+// XP_MACOSX or ANDROID
+//-----------------------------------------------------------------------------
+#if defined(XP_MACOSX) || defined(ANDROID)
+
+#include "nsAString.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+
+nsresult
+NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput)
+{
+ CopyUTF8toUTF16(aInput, aOutput);
+ return NS_OK;
+}
+
+nsresult
+NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput)
+{
+ CopyUTF16toUTF8(aInput, aOutput);
+ return NS_OK;
+}
+
+void
+NS_StartupNativeCharsetUtils()
+{
+}
+
+void
+NS_ShutdownNativeCharsetUtils()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// XP_UNIX
+//-----------------------------------------------------------------------------
+#elif defined(XP_UNIX)
+
+#include <stdlib.h> // mbtowc, wctomb
+#include <locale.h> // setlocale
+#include "mozilla/Mutex.h"
+#include "nscore.h"
+#include "nsAString.h"
+#include "nsReadableUtils.h"
+
+using namespace mozilla;
+
+//
+// choose a conversion library. we used to use mbrtowc/wcrtomb under Linux,
+// but that doesn't work for non-BMP characters whether we use '-fshort-wchar'
+// or not (see bug 206811 and
+// news://news.mozilla.org:119/bajml3$fvr1@ripley.netscape.com). we now use
+// iconv for all platforms where nltypes.h and nllanginfo.h are present
+// along with iconv.
+//
+#if defined(HAVE_ICONV) && defined(HAVE_NL_TYPES_H) && defined(HAVE_LANGINFO_CODESET)
+#define USE_ICONV 1
+#else
+#define USE_STDCONV 1
+#endif
+
+static void
+isolatin1_to_utf16(const char** aInput, uint32_t* aInputLeft,
+ char16_t** aOutput, uint32_t* aOutputLeft)
+{
+ while (*aInputLeft && *aOutputLeft) {
+ **aOutput = (unsigned char)** aInput;
+ (*aInput)++;
+ (*aInputLeft)--;
+ (*aOutput)++;
+ (*aOutputLeft)--;
+ }
+}
+
+static void
+utf16_to_isolatin1(const char16_t** aInput, uint32_t* aInputLeft,
+ char** aOutput, uint32_t* aOutputLeft)
+{
+ while (*aInputLeft && *aOutputLeft) {
+ **aOutput = (unsigned char)**aInput;
+ (*aInput)++;
+ (*aInputLeft)--;
+ (*aOutput)++;
+ (*aOutputLeft)--;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// conversion using iconv
+//-----------------------------------------------------------------------------
+#if defined(USE_ICONV)
+#include <nl_types.h> // CODESET
+#include <langinfo.h> // nl_langinfo
+#include <iconv.h> // iconv_open, iconv, iconv_close
+#include <errno.h>
+#include "plstr.h"
+
+#if defined(HAVE_ICONV_WITH_CONST_INPUT)
+#define ICONV_INPUT(x) (x)
+#else
+#define ICONV_INPUT(x) ((char **)x)
+#endif
+
+// solaris definitely needs this, but we'll enable it by default
+// just in case... but we know for sure that iconv(3) in glibc
+// doesn't need this.
+#if !defined(__GLIBC__)
+#define ENABLE_UTF8_FALLBACK_SUPPORT
+#endif
+
+#define INVALID_ICONV_T ((iconv_t)-1)
+
+static inline size_t
+xp_iconv(iconv_t converter,
+ const char** aInput, size_t* aInputLeft,
+ char** aOutput, size_t* aOutputLeft)
+{
+ size_t res, outputAvail = *aOutputLeft;
+ res = iconv(converter, ICONV_INPUT(aInput), aInputLeft, aOutput, aOutputLeft);
+ if (res == (size_t)-1) {
+ // on some platforms (e.g., linux) iconv will fail with
+ // E2BIG if it cannot convert _all_ of its input. it'll
+ // still adjust all of the in/out params correctly, so we
+ // can ignore this error. the assumption is that we will
+ // be called again to complete the conversion.
+ if ((errno == E2BIG) && (*aOutputLeft < outputAvail)) {
+ res = 0;
+ }
+ }
+ return res;
+}
+
+static inline void
+xp_iconv_reset(iconv_t converter)
+{
+ // NOTE: the man pages on Solaris claim that you can pass nullptr
+ // for all parameter to reset the converter, but beware the
+ // evil Solaris crash if you go down this route >:-)
+
+ const char* zero_char_in_ptr = nullptr;
+ char* zero_char_out_ptr = nullptr;
+ size_t zero_size_in = 0;
+ size_t zero_size_out = 0;
+
+ xp_iconv(converter,
+ &zero_char_in_ptr,
+ &zero_size_in,
+ &zero_char_out_ptr,
+ &zero_size_out);
+}
+
+static inline iconv_t
+xp_iconv_open(const char** to_list, const char** from_list)
+{
+ iconv_t res;
+ const char** from_name;
+ const char** to_name;
+
+ // try all possible combinations to locate a converter.
+ to_name = to_list;
+ while (*to_name) {
+ if (**to_name) {
+ from_name = from_list;
+ while (*from_name) {
+ if (**from_name) {
+ res = iconv_open(*to_name, *from_name);
+ if (res != INVALID_ICONV_T) {
+ return res;
+ }
+ }
+ from_name++;
+ }
+ }
+ to_name++;
+ }
+
+ return INVALID_ICONV_T;
+}
+
+/*
+ * char16_t[] is NOT a UCS-2 array BUT a UTF-16 string. Therefore, we
+ * have to use UTF-16 with iconv(3) on platforms where it's supported.
+ * However, the way UTF-16 and UCS-2 are interpreted varies across platforms
+ * and implementations of iconv(3). On Tru64, it also depends on the environment
+ * variable. To avoid the trouble arising from byte-swapping
+ * (bug 208809), we have to try UTF-16LE/BE and UCS-2LE/BE before falling
+ * back to UTF-16 and UCS-2 and variants. We assume that UTF-16 and UCS-2
+ * on systems without UTF-16LE/BE and UCS-2LE/BE have the native endianness,
+ * which isn't the case of glibc 2.1.x, for which we use 'UNICODELITTLE'
+ * and 'UNICODEBIG'. It's also not true of Tru64 V4 when the environment
+ * variable ICONV_BYTEORDER is set to 'big-endian', about which not much
+ * can be done other than adding a note in the release notes. (bug 206811)
+ */
+static const char* UTF_16_NAMES[] = {
+#if defined(IS_LITTLE_ENDIAN)
+ "UTF-16LE",
+#if defined(__GLIBC__)
+ "UNICODELITTLE",
+#endif
+ "UCS-2LE",
+#else
+ "UTF-16BE",
+#if defined(__GLIBC__)
+ "UNICODEBIG",
+#endif
+ "UCS-2BE",
+#endif
+ "UTF-16",
+ "UCS-2",
+ "UCS2",
+ "UCS_2",
+ "ucs-2",
+ "ucs2",
+ "ucs_2",
+ nullptr
+};
+
+#if defined(ENABLE_UTF8_FALLBACK_SUPPORT)
+static const char* UTF_8_NAMES[] = {
+ "UTF-8",
+ "UTF8",
+ "UTF_8",
+ "utf-8",
+ "utf8",
+ "utf_8",
+ nullptr
+};
+#endif
+
+static const char* ISO_8859_1_NAMES[] = {
+ "ISO-8859-1",
+#if !defined(__GLIBC__)
+ "ISO8859-1",
+ "ISO88591",
+ "ISO_8859_1",
+ "ISO8859_1",
+ "iso-8859-1",
+ "iso8859-1",
+ "iso88591",
+ "iso_8859_1",
+ "iso8859_1",
+#endif
+ nullptr
+};
+
+class nsNativeCharsetConverter
+{
+public:
+ nsNativeCharsetConverter();
+ ~nsNativeCharsetConverter();
+
+ nsresult NativeToUnicode(const char** aInput, uint32_t* aInputLeft,
+ char16_t** aOutput, uint32_t* aOutputLeft);
+ nsresult UnicodeToNative(const char16_t** aInput, uint32_t* aInputLeft,
+ char** aOutput, uint32_t* aOutputLeft);
+
+ static void GlobalInit();
+ static void GlobalShutdown();
+ static bool IsNativeUTF8();
+
+private:
+ static iconv_t gNativeToUnicode;
+ static iconv_t gUnicodeToNative;
+#if defined(ENABLE_UTF8_FALLBACK_SUPPORT)
+ static iconv_t gNativeToUTF8;
+ static iconv_t gUTF8ToNative;
+ static iconv_t gUnicodeToUTF8;
+ static iconv_t gUTF8ToUnicode;
+#endif
+ static Mutex* gLock;
+ static bool gInitialized;
+ static bool gIsNativeUTF8;
+
+ static void LazyInit();
+
+ static void Lock()
+ {
+ if (gLock) {
+ gLock->Lock();
+ }
+ }
+ static void Unlock()
+ {
+ if (gLock) {
+ gLock->Unlock();
+ }
+ }
+};
+
+iconv_t nsNativeCharsetConverter::gNativeToUnicode = INVALID_ICONV_T;
+iconv_t nsNativeCharsetConverter::gUnicodeToNative = INVALID_ICONV_T;
+#if defined(ENABLE_UTF8_FALLBACK_SUPPORT)
+iconv_t nsNativeCharsetConverter::gNativeToUTF8 = INVALID_ICONV_T;
+iconv_t nsNativeCharsetConverter::gUTF8ToNative = INVALID_ICONV_T;
+iconv_t nsNativeCharsetConverter::gUnicodeToUTF8 = INVALID_ICONV_T;
+iconv_t nsNativeCharsetConverter::gUTF8ToUnicode = INVALID_ICONV_T;
+#endif
+Mutex* nsNativeCharsetConverter::gLock = nullptr;
+bool nsNativeCharsetConverter::gInitialized = false;
+bool nsNativeCharsetConverter::gIsNativeUTF8 = false;
+
+void
+nsNativeCharsetConverter::LazyInit()
+{
+ // LazyInit may be called before NS_StartupNativeCharsetUtils, but
+ // the setlocale it does has to be called before nl_langinfo. Like in
+ // NS_StartupNativeCharsetUtils, assume we are called early enough that
+ // we are the first to care about the locale's charset.
+ if (!gLock) {
+ setlocale(LC_CTYPE, "");
+ }
+ const char* blank_list[] = { "", nullptr };
+ const char** native_charset_list = blank_list;
+ const char* native_charset = nl_langinfo(CODESET);
+ if (!native_charset) {
+ NS_ERROR("native charset is unknown");
+ // fallback to ISO-8859-1
+ native_charset_list = ISO_8859_1_NAMES;
+ } else {
+ native_charset_list[0] = native_charset;
+ }
+
+ // Most, if not all, Unixen supporting UTF-8 and nl_langinfo(CODESET)
+ // return 'UTF-8' (or 'utf-8')
+ if (!PL_strcasecmp(native_charset, "UTF-8")) {
+ gIsNativeUTF8 = true;
+ }
+
+ gNativeToUnicode = xp_iconv_open(UTF_16_NAMES, native_charset_list);
+ gUnicodeToNative = xp_iconv_open(native_charset_list, UTF_16_NAMES);
+
+#if defined(ENABLE_UTF8_FALLBACK_SUPPORT)
+ if (gNativeToUnicode == INVALID_ICONV_T) {
+ gNativeToUTF8 = xp_iconv_open(UTF_8_NAMES, native_charset_list);
+ gUTF8ToUnicode = xp_iconv_open(UTF_16_NAMES, UTF_8_NAMES);
+ NS_ASSERTION(gNativeToUTF8 != INVALID_ICONV_T, "no native to utf-8 converter");
+ NS_ASSERTION(gUTF8ToUnicode != INVALID_ICONV_T, "no utf-8 to utf-16 converter");
+ }
+ if (gUnicodeToNative == INVALID_ICONV_T) {
+ gUnicodeToUTF8 = xp_iconv_open(UTF_8_NAMES, UTF_16_NAMES);
+ gUTF8ToNative = xp_iconv_open(native_charset_list, UTF_8_NAMES);
+ NS_ASSERTION(gUnicodeToUTF8 != INVALID_ICONV_T, "no utf-16 to utf-8 converter");
+ NS_ASSERTION(gUTF8ToNative != INVALID_ICONV_T, "no utf-8 to native converter");
+ }
+#else
+ NS_ASSERTION(gNativeToUnicode != INVALID_ICONV_T, "no native to utf-16 converter");
+ NS_ASSERTION(gUnicodeToNative != INVALID_ICONV_T, "no utf-16 to native converter");
+#endif
+
+ /*
+ * On Solaris 8 (and newer?), the iconv modules converting to UCS-2
+ * prepend a byte order mark unicode character (BOM, u+FEFF) during
+ * the first use of the iconv converter. The same is the case of
+ * glibc 2.2.9x and Tru64 V5 (see bug 208809) when 'UTF-16' is used.
+ * However, we use 'UTF-16LE/BE' in both cases, instead so that we
+ * should be safe. But just in case...
+ *
+ * This dummy conversion gets rid of the BOMs and fixes bug 153562.
+ */
+ char dummy_input[1] = { ' ' };
+ char dummy_output[4];
+
+ if (gNativeToUnicode != INVALID_ICONV_T) {
+ const char* input = dummy_input;
+ size_t input_left = sizeof(dummy_input);
+ char* output = dummy_output;
+ size_t output_left = sizeof(dummy_output);
+
+ xp_iconv(gNativeToUnicode, &input, &input_left, &output, &output_left);
+ }
+#if defined(ENABLE_UTF8_FALLBACK_SUPPORT)
+ if (gUTF8ToUnicode != INVALID_ICONV_T) {
+ const char* input = dummy_input;
+ size_t input_left = sizeof(dummy_input);
+ char* output = dummy_output;
+ size_t output_left = sizeof(dummy_output);
+
+ xp_iconv(gUTF8ToUnicode, &input, &input_left, &output, &output_left);
+ }
+#endif
+
+ gInitialized = true;
+}
+
+void
+nsNativeCharsetConverter::GlobalInit()
+{
+ gLock = new Mutex("nsNativeCharsetConverter.gLock");
+}
+
+void
+nsNativeCharsetConverter::GlobalShutdown()
+{
+ delete gLock;
+ gLock = nullptr;
+
+ if (gNativeToUnicode != INVALID_ICONV_T) {
+ iconv_close(gNativeToUnicode);
+ gNativeToUnicode = INVALID_ICONV_T;
+ }
+
+ if (gUnicodeToNative != INVALID_ICONV_T) {
+ iconv_close(gUnicodeToNative);
+ gUnicodeToNative = INVALID_ICONV_T;
+ }
+
+#if defined(ENABLE_UTF8_FALLBACK_SUPPORT)
+ if (gNativeToUTF8 != INVALID_ICONV_T) {
+ iconv_close(gNativeToUTF8);
+ gNativeToUTF8 = INVALID_ICONV_T;
+ }
+ if (gUTF8ToNative != INVALID_ICONV_T) {
+ iconv_close(gUTF8ToNative);
+ gUTF8ToNative = INVALID_ICONV_T;
+ }
+ if (gUnicodeToUTF8 != INVALID_ICONV_T) {
+ iconv_close(gUnicodeToUTF8);
+ gUnicodeToUTF8 = INVALID_ICONV_T;
+ }
+ if (gUTF8ToUnicode != INVALID_ICONV_T) {
+ iconv_close(gUTF8ToUnicode);
+ gUTF8ToUnicode = INVALID_ICONV_T;
+ }
+#endif
+
+ gInitialized = false;
+}
+
+nsNativeCharsetConverter::nsNativeCharsetConverter()
+{
+ Lock();
+ if (!gInitialized) {
+ LazyInit();
+ }
+}
+
+nsNativeCharsetConverter::~nsNativeCharsetConverter()
+{
+ // reset converters for next time
+ if (gNativeToUnicode != INVALID_ICONV_T) {
+ xp_iconv_reset(gNativeToUnicode);
+ }
+ if (gUnicodeToNative != INVALID_ICONV_T) {
+ xp_iconv_reset(gUnicodeToNative);
+ }
+#if defined(ENABLE_UTF8_FALLBACK_SUPPORT)
+ if (gNativeToUTF8 != INVALID_ICONV_T) {
+ xp_iconv_reset(gNativeToUTF8);
+ }
+ if (gUTF8ToNative != INVALID_ICONV_T) {
+ xp_iconv_reset(gUTF8ToNative);
+ }
+ if (gUnicodeToUTF8 != INVALID_ICONV_T) {
+ xp_iconv_reset(gUnicodeToUTF8);
+ }
+ if (gUTF8ToUnicode != INVALID_ICONV_T) {
+ xp_iconv_reset(gUTF8ToUnicode);
+ }
+#endif
+ Unlock();
+}
+
+nsresult
+nsNativeCharsetConverter::NativeToUnicode(const char** aInput,
+ uint32_t* aInputLeft,
+ char16_t** aOutput,
+ uint32_t* aOutputLeft)
+{
+ size_t res = 0;
+ size_t inLeft = (size_t)*aInputLeft;
+ size_t outLeft = (size_t)*aOutputLeft * 2;
+
+ if (gNativeToUnicode != INVALID_ICONV_T) {
+
+ res = xp_iconv(gNativeToUnicode, aInput, &inLeft, (char**)aOutput, &outLeft);
+
+ *aInputLeft = inLeft;
+ *aOutputLeft = outLeft / 2;
+ if (res != (size_t)-1) {
+ return NS_OK;
+ }
+
+ NS_WARNING("conversion from native to utf-16 failed");
+
+ // reset converter
+ xp_iconv_reset(gNativeToUnicode);
+ }
+#if defined(ENABLE_UTF8_FALLBACK_SUPPORT)
+ else if ((gNativeToUTF8 != INVALID_ICONV_T) &&
+ (gUTF8ToUnicode != INVALID_ICONV_T)) {
+ // convert first to UTF8, then from UTF8 to UCS2
+ const char* in = *aInput;
+
+ char ubuf[1024];
+
+ // we assume we're always called with enough space in |aOutput|,
+ // so convert many chars at a time...
+ while (inLeft) {
+ char* p = ubuf;
+ size_t n = sizeof(ubuf);
+ res = xp_iconv(gNativeToUTF8, &in, &inLeft, &p, &n);
+ if (res == (size_t)-1) {
+ NS_ERROR("conversion from native to utf-8 failed");
+ break;
+ }
+ NS_ASSERTION(outLeft > 0, "bad assumption");
+ p = ubuf;
+ n = sizeof(ubuf) - n;
+ res = xp_iconv(gUTF8ToUnicode, (const char**)&p, &n,
+ (char**)aOutput, &outLeft);
+ if (res == (size_t)-1) {
+ NS_ERROR("conversion from utf-8 to utf-16 failed");
+ break;
+ }
+ }
+
+ (*aInput) += (*aInputLeft - inLeft);
+ *aInputLeft = inLeft;
+ *aOutputLeft = outLeft / 2;
+
+ if (res != (size_t)-1) {
+ return NS_OK;
+ }
+
+ // reset converters
+ xp_iconv_reset(gNativeToUTF8);
+ xp_iconv_reset(gUTF8ToUnicode);
+ }
+#endif
+
+ // fallback: zero-pad and hope for the best
+ // XXX This is lame and we have to do better.
+ isolatin1_to_utf16(aInput, aInputLeft, aOutput, aOutputLeft);
+
+ return NS_OK;
+}
+
+nsresult
+nsNativeCharsetConverter::UnicodeToNative(const char16_t** aInput,
+ uint32_t* aInputLeft,
+ char** aOutput,
+ uint32_t* aOutputLeft)
+{
+ size_t res = 0;
+ size_t inLeft = (size_t)*aInputLeft * 2;
+ size_t outLeft = (size_t)*aOutputLeft;
+
+ if (gUnicodeToNative != INVALID_ICONV_T) {
+ res = xp_iconv(gUnicodeToNative, (const char**)aInput, &inLeft,
+ aOutput, &outLeft);
+
+ *aInputLeft = inLeft / 2;
+ *aOutputLeft = outLeft;
+ if (res != (size_t)-1) {
+ return NS_OK;
+ }
+
+ NS_ERROR("iconv failed");
+
+ // reset converter
+ xp_iconv_reset(gUnicodeToNative);
+ }
+#if defined(ENABLE_UTF8_FALLBACK_SUPPORT)
+ else if ((gUnicodeToUTF8 != INVALID_ICONV_T) &&
+ (gUTF8ToNative != INVALID_ICONV_T)) {
+ const char* in = (const char*)*aInput;
+
+ char ubuf[6]; // max utf-8 char length (really only needs to be 4 bytes)
+
+ // convert one uchar at a time...
+ while (inLeft && outLeft) {
+ char* p = ubuf;
+ size_t n = sizeof(ubuf), one_uchar = sizeof(char16_t);
+ res = xp_iconv(gUnicodeToUTF8, &in, &one_uchar, &p, &n);
+ if (res == (size_t)-1) {
+ NS_ERROR("conversion from utf-16 to utf-8 failed");
+ break;
+ }
+ p = ubuf;
+ n = sizeof(ubuf) - n;
+ res = xp_iconv(gUTF8ToNative, (const char**)&p, &n, aOutput, &outLeft);
+ if (res == (size_t)-1) {
+ if (errno == E2BIG) {
+ // not enough room for last uchar... back up and return.
+ in -= sizeof(char16_t);
+ res = 0;
+ } else {
+ NS_ERROR("conversion from utf-8 to native failed");
+ }
+ break;
+ }
+ inLeft -= sizeof(char16_t);
+ }
+
+ (*aInput) += (*aInputLeft - inLeft / 2);
+ *aInputLeft = inLeft / 2;
+ *aOutputLeft = outLeft;
+ if (res != (size_t)-1) {
+ return NS_OK;
+ }
+
+ // reset converters
+ xp_iconv_reset(gUnicodeToUTF8);
+ xp_iconv_reset(gUTF8ToNative);
+ }
+#endif
+
+ // fallback: truncate and hope for the best
+ // XXX This is lame and we have to do better.
+ utf16_to_isolatin1(aInput, aInputLeft, aOutput, aOutputLeft);
+
+ return NS_OK;
+}
+
+bool
+nsNativeCharsetConverter::IsNativeUTF8()
+{
+ if (!gInitialized) {
+ Lock();
+ if (!gInitialized) {
+ LazyInit();
+ }
+ Unlock();
+ }
+ return gIsNativeUTF8;
+}
+
+#endif // USE_ICONV
+
+//-----------------------------------------------------------------------------
+// conversion using mb[r]towc/wc[r]tomb
+//-----------------------------------------------------------------------------
+#if defined(USE_STDCONV)
+#if defined(HAVE_WCRTOMB) || defined(HAVE_MBRTOWC)
+#include <wchar.h> // mbrtowc, wcrtomb
+#endif
+
+class nsNativeCharsetConverter
+{
+public:
+ nsNativeCharsetConverter();
+
+ nsresult NativeToUnicode(const char** aInput, uint32_t* aInputLeft,
+ char16_t** aOutput, uint32_t* aOutputLeft);
+ nsresult UnicodeToNative(const char16_t** aInput, uint32_t* aInputLeft,
+ char** aOutput, uint32_t* aOutputLeft);
+
+ static void GlobalInit();
+ static void GlobalShutdown() { }
+ static bool IsNativeUTF8();
+
+private:
+ static bool gWCharIsUnicode;
+
+#if defined(HAVE_WCRTOMB) || defined(HAVE_MBRTOWC)
+ mbstate_t ps;
+#endif
+};
+
+bool nsNativeCharsetConverter::gWCharIsUnicode = false;
+
+nsNativeCharsetConverter::nsNativeCharsetConverter()
+{
+#if defined(HAVE_WCRTOMB) || defined(HAVE_MBRTOWC)
+ memset(&ps, 0, sizeof(ps));
+#endif
+}
+
+void
+nsNativeCharsetConverter::GlobalInit()
+{
+ // verify that wchar_t for the current locale is actually unicode.
+ // if it is not, then we should avoid calling mbtowc/wctomb and
+ // just fallback on zero-pad/truncation conversion.
+ //
+ // this test cannot be done at build time because the encoding of
+ // wchar_t may depend on the runtime locale. sad, but true!!
+ //
+ // so, if wchar_t is unicode then converting an ASCII character
+ // to wchar_t should not change its numeric value. we'll just
+ // check what happens with the ASCII 'a' character.
+ //
+ // this test is not perfect... obviously, it could yield false
+ // positives, but then at least ASCII text would be converted
+ // properly (or maybe just the 'a' character) -- oh well :(
+
+ char a = 'a';
+ unsigned int w = 0;
+
+ int res = mbtowc((wchar_t*)&w, &a, 1);
+
+ gWCharIsUnicode = (res != -1 && w == 'a');
+
+#ifdef DEBUG
+ if (!gWCharIsUnicode) {
+ NS_WARNING("wchar_t is not unicode (unicode conversion will be lossy)");
+ }
+#endif
+}
+
+nsresult
+nsNativeCharsetConverter::NativeToUnicode(const char** aInput,
+ uint32_t* aInputLeft,
+ char16_t** aOutput,
+ uint32_t* aOutputLeft)
+{
+ if (gWCharIsUnicode) {
+ int incr;
+
+ // cannot use wchar_t here since it may have been redefined (e.g.,
+ // via -fshort-wchar). hopefully, sizeof(tmp) is sufficient XP.
+ unsigned int tmp = 0;
+ while (*aInputLeft && *aOutputLeft) {
+#ifdef HAVE_MBRTOWC
+ incr = (int)mbrtowc((wchar_t*)&tmp, *aInput, *aInputLeft, &ps);
+#else
+ // XXX is this thread-safe?
+ incr = (int)mbtowc((wchar_t*)&tmp, *aInput, *aInputLeft);
+#endif
+ if (incr < 0) {
+ NS_WARNING("mbtowc failed: possible charset mismatch");
+ // zero-pad and hope for the best
+ tmp = (unsigned char)**aInput;
+ incr = 1;
+ }
+ ** aOutput = (char16_t)tmp;
+ (*aInput) += incr;
+ (*aInputLeft) -= incr;
+ (*aOutput)++;
+ (*aOutputLeft)--;
+ }
+ } else {
+ // wchar_t isn't unicode, so the best we can do is treat the
+ // input as if it is isolatin1 :(
+ isolatin1_to_utf16(aInput, aInputLeft, aOutput, aOutputLeft);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsNativeCharsetConverter::UnicodeToNative(const char16_t** aInput,
+ uint32_t* aInputLeft,
+ char** aOutput,
+ uint32_t* aOutputLeft)
+{
+ if (gWCharIsUnicode) {
+ int incr;
+
+ while (*aInputLeft && *aOutputLeft >= MB_CUR_MAX) {
+#ifdef HAVE_WCRTOMB
+ incr = (int)wcrtomb(*aOutput, (wchar_t)**aInput, &ps);
+#else
+ // XXX is this thread-safe?
+ incr = (int)wctomb(*aOutput, (wchar_t)**aInput);
+#endif
+ if (incr < 0) {
+ NS_WARNING("mbtowc failed: possible charset mismatch");
+ ** aOutput = (unsigned char)**aInput; // truncate
+ incr = 1;
+ }
+ // most likely we're dead anyways if this assertion should fire
+ NS_ASSERTION(uint32_t(incr) <= *aOutputLeft, "wrote beyond end of string");
+ (*aOutput) += incr;
+ (*aOutputLeft) -= incr;
+ (*aInput)++;
+ (*aInputLeft)--;
+ }
+ } else {
+ // wchar_t isn't unicode, so the best we can do is treat the
+ // input as if it is isolatin1 :(
+ utf16_to_isolatin1(aInput, aInputLeft, aOutput, aOutputLeft);
+ }
+
+ return NS_OK;
+}
+
+// XXX : for now, return false
+bool
+nsNativeCharsetConverter::IsNativeUTF8()
+{
+ return false;
+}
+
+#endif // USE_STDCONV
+
+//-----------------------------------------------------------------------------
+// API implementation
+//-----------------------------------------------------------------------------
+
+nsresult
+NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput)
+{
+ aOutput.Truncate();
+
+ uint32_t inputLen = aInput.Length();
+
+ nsACString::const_iterator iter;
+ aInput.BeginReading(iter);
+
+ //
+ // OPTIMIZATION: preallocate space for largest possible result; convert
+ // directly into the result buffer to avoid intermediate buffer copy.
+ //
+ // this will generally result in a larger allocation, but that seems
+ // better than an extra buffer copy.
+ //
+ if (!aOutput.SetLength(inputLen, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ nsAString::iterator out_iter;
+ aOutput.BeginWriting(out_iter);
+
+ char16_t* result = out_iter.get();
+ uint32_t resultLeft = inputLen;
+
+ const char* buf = iter.get();
+ uint32_t bufLeft = inputLen;
+
+ nsNativeCharsetConverter conv;
+ nsresult rv = conv.NativeToUnicode(&buf, &bufLeft, &result, &resultLeft);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ASSERTION(bufLeft == 0, "did not consume entire input buffer");
+ aOutput.SetLength(inputLen - resultLeft);
+ }
+ return rv;
+}
+
+nsresult
+NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput)
+{
+ aOutput.Truncate();
+
+ nsAString::const_iterator iter, end;
+ aInput.BeginReading(iter);
+ aInput.EndReading(end);
+
+ // cannot easily avoid intermediate buffer copy.
+ char temp[4096];
+
+ nsNativeCharsetConverter conv;
+
+ const char16_t* buf = iter.get();
+ uint32_t bufLeft = Distance(iter, end);
+ while (bufLeft) {
+ char* p = temp;
+ uint32_t tempLeft = sizeof(temp);
+
+ nsresult rv = conv.UnicodeToNative(&buf, &bufLeft, &p, &tempLeft);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (tempLeft < sizeof(temp)) {
+ aOutput.Append(temp, sizeof(temp) - tempLeft);
+ }
+ }
+ return NS_OK;
+}
+
+bool
+NS_IsNativeUTF8()
+{
+ return nsNativeCharsetConverter::IsNativeUTF8();
+}
+
+void
+NS_StartupNativeCharsetUtils()
+{
+ //
+ // need to initialize the locale or else charset conversion will fail.
+ // better not delay this in case some other component alters the locale
+ // settings.
+ //
+ // XXX we assume that we are called early enough that we should
+ // always be the first to care about the locale's charset.
+ //
+ setlocale(LC_CTYPE, "");
+
+ nsNativeCharsetConverter::GlobalInit();
+}
+
+void
+NS_ShutdownNativeCharsetUtils()
+{
+ nsNativeCharsetConverter::GlobalShutdown();
+}
+
+//-----------------------------------------------------------------------------
+// XP_WIN
+//-----------------------------------------------------------------------------
+#elif defined(XP_WIN)
+
+#include <windows.h>
+#include "nsString.h"
+#include "nsAString.h"
+#include "nsReadableUtils.h"
+
+using namespace mozilla;
+
+nsresult
+NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput)
+{
+ uint32_t inputLen = aInput.Length();
+
+ nsACString::const_iterator iter;
+ aInput.BeginReading(iter);
+
+ const char* buf = iter.get();
+
+ // determine length of result
+ uint32_t resultLen = 0;
+ int n = ::MultiByteToWideChar(CP_ACP, 0, buf, inputLen, nullptr, 0);
+ if (n > 0) {
+ resultLen += n;
+ }
+
+ // allocate sufficient space
+ if (!aOutput.SetLength(resultLen, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (resultLen > 0) {
+ nsAString::iterator out_iter;
+ aOutput.BeginWriting(out_iter);
+
+ char16_t* result = out_iter.get();
+
+ ::MultiByteToWideChar(CP_ACP, 0, buf, inputLen, wwc(result), resultLen);
+ }
+ return NS_OK;
+}
+
+nsresult
+NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput)
+{
+ uint32_t inputLen = aInput.Length();
+
+ nsAString::const_iterator iter;
+ aInput.BeginReading(iter);
+
+ char16ptr_t buf = iter.get();
+
+ // determine length of result
+ uint32_t resultLen = 0;
+
+ int n = ::WideCharToMultiByte(CP_ACP, 0, buf, inputLen, nullptr, 0,
+ nullptr, nullptr);
+ if (n > 0) {
+ resultLen += n;
+ }
+
+ // allocate sufficient space
+ if (!aOutput.SetLength(resultLen, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (resultLen > 0) {
+ nsACString::iterator out_iter;
+ aOutput.BeginWriting(out_iter);
+
+ // default "defaultChar" is '?', which is an illegal character on windows
+ // file system. That will cause file uncreatable. Change it to '_'
+ const char defaultChar = '_';
+
+ char* result = out_iter.get();
+
+ ::WideCharToMultiByte(CP_ACP, 0, buf, inputLen, result, resultLen,
+ &defaultChar, nullptr);
+ }
+ return NS_OK;
+}
+
+// moved from widget/windows/nsToolkit.cpp
+int32_t
+NS_ConvertAtoW(const char* aStrInA, int aBufferSize, char16_t* aStrOutW)
+{
+ return MultiByteToWideChar(CP_ACP, 0, aStrInA, -1, wwc(aStrOutW), aBufferSize);
+}
+
+int32_t
+NS_ConvertWtoA(const char16_t* aStrInW, int aBufferSizeOut,
+ char* aStrOutA, const char* aDefault)
+{
+ if ((!aStrInW) || (!aStrOutA) || (aBufferSizeOut <= 0)) {
+ return 0;
+ }
+
+ int numCharsConverted = WideCharToMultiByte(CP_ACP, 0, char16ptr_t(aStrInW), -1,
+ aStrOutA, aBufferSizeOut,
+ aDefault, nullptr);
+
+ if (!numCharsConverted) {
+ if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ // Overflow, add missing null termination but return 0
+ aStrOutA[aBufferSizeOut - 1] = '\0';
+ } else {
+ // Other error, clear string and return 0
+ aStrOutA[0] = '\0';
+ }
+ } else if (numCharsConverted < aBufferSizeOut) {
+ // Add 2nd null (really necessary?)
+ aStrOutA[numCharsConverted] = '\0';
+ }
+
+ return numCharsConverted;
+}
+
+#else
+
+#include "nsReadableUtils.h"
+
+nsresult
+NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput)
+{
+ CopyASCIItoUTF16(aInput, aOutput);
+ return NS_OK;
+}
+
+nsresult
+NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput)
+{
+ LossyCopyUTF16toASCII(aInput, aOutput);
+ return NS_OK;
+}
+
+void
+NS_StartupNativeCharsetUtils()
+{
+}
+
+void
+NS_ShutdownNativeCharsetUtils()
+{
+}
+
+#endif
diff --git a/xpcom/io/nsNativeCharsetUtils.h b/xpcom/io/nsNativeCharsetUtils.h
new file mode 100644
index 000000000..5c1e670d5
--- /dev/null
+++ b/xpcom/io/nsNativeCharsetUtils.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNativeCharsetUtils_h__
+#define nsNativeCharsetUtils_h__
+
+
+/*****************************************************************************\
+ * *
+ * **** NOTICE **** *
+ * *
+ * *** THESE ARE NOT GENERAL PURPOSE CONVERTERS *** *
+ * *
+ * NS_CopyNativeToUnicode / NS_CopyUnicodeToNative should only be used *
+ * for converting *FILENAMES* between native and unicode. They are not *
+ * designed or tested for general encoding converter use. *
+ * *
+\*****************************************************************************/
+
+/**
+ * thread-safe conversion routines that do not depend on uconv libraries.
+ */
+nsresult NS_CopyNativeToUnicode(const nsACString& aInput, nsAString& aOutput);
+nsresult NS_CopyUnicodeToNative(const nsAString& aInput, nsACString& aOutput);
+
+/*
+ * This function indicates whether the character encoding used in the file
+ * system (more exactly what's used for |GetNativeFoo| and |SetNativeFoo|
+ * of |nsIFile|) is UTF-8 or not. Knowing that helps us avoid an
+ * unncessary encoding conversion in some cases. For instance, to get the leaf
+ * name in UTF-8 out of nsIFile, we can just use |GetNativeLeafName| rather
+ * than using |GetLeafName| and converting the result to UTF-8 if the file
+ * system encoding is UTF-8.
+ * On Unix (but not on Mac OS X), it depends on the locale and is not known
+ * in advance (at the compilation time) so that this function needs to be
+ * a real function. On Mac OS X it's always UTF-8 while on Windows
+ * and other platforms (e.g. OS2), it's never UTF-8.
+ */
+#if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(ANDROID)
+bool NS_IsNativeUTF8();
+#else
+inline bool
+NS_IsNativeUTF8()
+{
+#if defined(XP_MACOSX) || defined(ANDROID)
+ return true;
+#else
+ return false;
+#endif
+}
+#endif
+
+
+/**
+ * internal
+ */
+void NS_StartupNativeCharsetUtils();
+void NS_ShutdownNativeCharsetUtils();
+
+#endif // nsNativeCharsetUtils_h__
diff --git a/xpcom/io/nsPipe.h b/xpcom/io/nsPipe.h
new file mode 100644
index 000000000..29ce0ce96
--- /dev/null
+++ b/xpcom/io/nsPipe.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPipe_h__
+#define nsPipe_h__
+
+#define NS_PIPE_CONTRACTID \
+ "@mozilla.org/pipe;1"
+#define NS_PIPE_CID \
+{ /* e4a0ee4e-0775-457b-9118-b3ae97a7c758 */ \
+ 0xe4a0ee4e, \
+ 0x0775, \
+ 0x457b, \
+ {0x91,0x18,0xb3,0xae,0x97,0xa7,0xc7,0x58} \
+}
+
+// Generic factory constructor for the nsPipe class
+nsresult
+nsPipeConstructor(nsISupports* outer, REFNSIID iid, void** result);
+
+#endif // !defined(nsPipe_h__)
diff --git a/xpcom/io/nsPipe3.cpp b/xpcom/io/nsPipe3.cpp
new file mode 100644
index 000000000..56932adfc
--- /dev/null
+++ b/xpcom/io/nsPipe3.cpp
@@ -0,0 +1,2007 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+#include "mozilla/Attributes.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "nsIBufferedStreams.h"
+#include "nsICloneableInputStream.h"
+#include "nsIPipe.h"
+#include "nsIEventTarget.h"
+#include "nsISeekableStream.h"
+#include "mozilla/RefPtr.h"
+#include "nsSegmentedBuffer.h"
+#include "nsStreamUtils.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "mozilla/Logging.h"
+#include "nsIClassInfoImpl.h"
+#include "nsAlgorithm.h"
+#include "nsMemory.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+
+using namespace mozilla;
+
+#ifdef LOG
+#undef LOG
+#endif
+//
+// set MOZ_LOG=nsPipe:5
+//
+static LazyLogModule sPipeLog("nsPipe");
+#define LOG(args) MOZ_LOG(sPipeLog, mozilla::LogLevel::Debug, args)
+
+#define DEFAULT_SEGMENT_SIZE 4096
+#define DEFAULT_SEGMENT_COUNT 16
+
+class nsPipe;
+class nsPipeEvents;
+class nsPipeInputStream;
+class nsPipeOutputStream;
+class AutoReadSegment;
+
+namespace {
+
+enum MonitorAction
+{
+ DoNotNotifyMonitor,
+ NotifyMonitor
+};
+
+enum SegmentChangeResult
+{
+ SegmentNotChanged,
+ SegmentAdvanceBufferRead
+};
+
+} // namespace
+
+//-----------------------------------------------------------------------------
+
+// this class is used to delay notifications until the end of a particular
+// scope. it helps avoid the complexity of issuing callbacks while inside
+// a critical section.
+class nsPipeEvents
+{
+public:
+ nsPipeEvents() { }
+ ~nsPipeEvents();
+
+ inline void NotifyInputReady(nsIAsyncInputStream* aStream,
+ nsIInputStreamCallback* aCallback)
+ {
+ mInputList.AppendElement(InputEntry(aStream, aCallback));
+ }
+
+ inline void NotifyOutputReady(nsIAsyncOutputStream* aStream,
+ nsIOutputStreamCallback* aCallback)
+ {
+ NS_ASSERTION(!mOutputCallback, "already have an output event");
+ mOutputStream = aStream;
+ mOutputCallback = aCallback;
+ }
+
+private:
+ struct InputEntry
+ {
+ InputEntry(nsIAsyncInputStream* aStream, nsIInputStreamCallback* aCallback)
+ : mStream(aStream)
+ , mCallback(aCallback)
+ {
+ MOZ_ASSERT(mStream);
+ MOZ_ASSERT(mCallback);
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ };
+
+ nsTArray<InputEntry> mInputList;
+
+ nsCOMPtr<nsIAsyncOutputStream> mOutputStream;
+ nsCOMPtr<nsIOutputStreamCallback> mOutputCallback;
+};
+
+//-----------------------------------------------------------------------------
+
+// This class is used to maintain input stream state. Its broken out from the
+// nsPipeInputStream class because generally the nsPipe should be modifying
+// this state and not the input stream itself.
+struct nsPipeReadState
+{
+ nsPipeReadState()
+ : mReadCursor(nullptr)
+ , mReadLimit(nullptr)
+ , mSegment(0)
+ , mAvailable(0)
+ , mActiveRead(false)
+ , mNeedDrain(false)
+ { }
+
+ char* mReadCursor;
+ char* mReadLimit;
+ int32_t mSegment;
+ uint32_t mAvailable;
+
+ // This flag is managed using the AutoReadSegment RAII stack class.
+ bool mActiveRead;
+
+ // Set to indicate that the input stream has closed and should be drained,
+ // but that drain has been delayed due to an active read. When the read
+ // completes, this flag indicate the drain should then be performed.
+ bool mNeedDrain;
+};
+
+//-----------------------------------------------------------------------------
+
+// an input end of a pipe (maintained as a list of refs within the pipe)
+class nsPipeInputStream final
+ : public nsIAsyncInputStream
+ , public nsISeekableStream
+ , public nsISearchableInputStream
+ , public nsICloneableInputStream
+ , public nsIClassInfo
+ , public nsIBufferedInputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSISEARCHABLEINPUTSTREAM
+ NS_DECL_NSICLONEABLEINPUTSTREAM
+ NS_DECL_NSICLASSINFO
+ NS_DECL_NSIBUFFEREDINPUTSTREAM
+
+ explicit nsPipeInputStream(nsPipe* aPipe)
+ : mPipe(aPipe)
+ , mLogicalOffset(0)
+ , mInputStatus(NS_OK)
+ , mBlocking(true)
+ , mBlocked(false)
+ , mCallbackFlags(0)
+ { }
+
+ explicit nsPipeInputStream(const nsPipeInputStream& aOther)
+ : mPipe(aOther.mPipe)
+ , mLogicalOffset(aOther.mLogicalOffset)
+ , mInputStatus(aOther.mInputStatus)
+ , mBlocking(aOther.mBlocking)
+ , mBlocked(false)
+ , mCallbackFlags(0)
+ , mReadState(aOther.mReadState)
+ { }
+
+ nsresult Fill();
+ void SetNonBlocking(bool aNonBlocking)
+ {
+ mBlocking = !aNonBlocking;
+ }
+
+ uint32_t Available();
+
+ // synchronously wait for the pipe to become readable.
+ nsresult Wait();
+
+ // These two don't acquire the monitor themselves. Instead they
+ // expect their caller to have done so and to pass the monitor as
+ // evidence.
+ MonitorAction OnInputReadable(uint32_t aBytesWritten, nsPipeEvents&,
+ const ReentrantMonitorAutoEnter& ev);
+ MonitorAction OnInputException(nsresult, nsPipeEvents&,
+ const ReentrantMonitorAutoEnter& ev);
+
+ nsPipeReadState& ReadState()
+ {
+ return mReadState;
+ }
+
+ const nsPipeReadState& ReadState() const
+ {
+ return mReadState;
+ }
+
+ nsresult Status() const;
+
+ // A version of Status() that doesn't acquire the monitor.
+ nsresult Status(const ReentrantMonitorAutoEnter& ev) const;
+
+private:
+ virtual ~nsPipeInputStream();
+
+ RefPtr<nsPipe> mPipe;
+
+ int64_t mLogicalOffset;
+ // Individual input streams can be closed without effecting the rest of the
+ // pipe. So track individual input stream status separately. |mInputStatus|
+ // is protected by |mPipe->mReentrantMonitor|.
+ nsresult mInputStatus;
+ bool mBlocking;
+
+ // these variables can only be accessed while inside the pipe's monitor
+ bool mBlocked;
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ uint32_t mCallbackFlags;
+
+ // requires pipe's monitor; usually treat as an opaque token to pass to nsPipe
+ nsPipeReadState mReadState;
+};
+
+//-----------------------------------------------------------------------------
+
+// the output end of a pipe (allocated as a member of the pipe).
+class nsPipeOutputStream
+ : public nsIAsyncOutputStream
+ , public nsIClassInfo
+{
+public:
+ // since this class will be allocated as a member of the pipe, we do not
+ // need our own ref count. instead, we share the lifetime (the ref count)
+ // of the entire pipe. this macro is just convenience since it does not
+ // declare a mRefCount variable; however, don't let the name fool you...
+ // we are not inheriting from nsPipe ;-)
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+ NS_DECL_NSICLASSINFO
+
+ explicit nsPipeOutputStream(nsPipe* aPipe)
+ : mPipe(aPipe)
+ , mWriterRefCnt(0)
+ , mLogicalOffset(0)
+ , mBlocking(true)
+ , mBlocked(false)
+ , mWritable(true)
+ , mCallbackFlags(0)
+ { }
+
+ void SetNonBlocking(bool aNonBlocking)
+ {
+ mBlocking = !aNonBlocking;
+ }
+ void SetWritable(bool aWritable)
+ {
+ mWritable = aWritable;
+ }
+
+ // synchronously wait for the pipe to become writable.
+ nsresult Wait();
+
+ MonitorAction OnOutputWritable(nsPipeEvents&);
+ MonitorAction OnOutputException(nsresult, nsPipeEvents&);
+
+private:
+ nsPipe* mPipe;
+
+ // separate refcnt so that we know when to close the producer
+ mozilla::ThreadSafeAutoRefCnt mWriterRefCnt;
+ int64_t mLogicalOffset;
+ bool mBlocking;
+
+ // these variables can only be accessed while inside the pipe's monitor
+ bool mBlocked;
+ bool mWritable;
+ nsCOMPtr<nsIOutputStreamCallback> mCallback;
+ uint32_t mCallbackFlags;
+};
+
+//-----------------------------------------------------------------------------
+
+class nsPipe final : public nsIPipe
+{
+public:
+ friend class nsPipeInputStream;
+ friend class nsPipeOutputStream;
+ friend class AutoReadSegment;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPIPE
+
+ // nsPipe methods:
+ nsPipe();
+
+private:
+ ~nsPipe();
+
+ //
+ // Methods below may only be called while inside the pipe's monitor. Some
+ // of these methods require passing a ReentrantMonitorAutoEnter to prove the
+ // monitor is held.
+ //
+
+ void PeekSegment(const nsPipeReadState& aReadState, uint32_t aIndex,
+ char*& aCursor, char*& aLimit);
+ SegmentChangeResult AdvanceReadSegment(nsPipeReadState& aReadState,
+ const ReentrantMonitorAutoEnter &ev);
+ bool ReadSegmentBeingWritten(nsPipeReadState& aReadState);
+ uint32_t CountSegmentReferences(int32_t aSegment);
+ void SetAllNullReadCursors();
+ bool AllReadCursorsMatchWriteCursor();
+ void RollBackAllReadCursors(char* aWriteCursor);
+ void UpdateAllReadCursors(char* aWriteCursor);
+ void ValidateAllReadCursors();
+ uint32_t GetBufferSegmentCount(const nsPipeReadState& aReadState,
+ const ReentrantMonitorAutoEnter& ev) const;
+ bool IsAdvanceBufferFull(const ReentrantMonitorAutoEnter& ev) const;
+
+ //
+ // methods below may be called while outside the pipe's monitor
+ //
+
+ void DrainInputStream(nsPipeReadState& aReadState, nsPipeEvents& aEvents);
+ nsresult GetWriteSegment(char*& aSegment, uint32_t& aSegmentLen);
+ void AdvanceWriteCursor(uint32_t aCount);
+
+ void OnInputStreamException(nsPipeInputStream* aStream, nsresult aReason);
+ void OnPipeException(nsresult aReason, bool aOutputOnly = false);
+
+ nsresult CloneInputStream(nsPipeInputStream* aOriginal,
+ nsIInputStream** aCloneOut);
+
+ // methods below should only be called by AutoReadSegment
+ nsresult GetReadSegment(nsPipeReadState& aReadState, const char*& aSegment,
+ uint32_t& aLength);
+ void ReleaseReadSegment(nsPipeReadState& aReadState,
+ nsPipeEvents& aEvents);
+ void AdvanceReadCursor(nsPipeReadState& aReadState, uint32_t aCount);
+
+ // We can't inherit from both nsIInputStream and nsIOutputStream
+ // because they collide on their Close method. Consequently we nest their
+ // implementations to avoid the extra object allocation.
+ nsPipeOutputStream mOutput;
+
+ // Since the input stream can be cloned, we may have more than one. Use
+ // a weak reference as the streams will clear their entry here in their
+ // destructor. Using a strong reference would create a reference cycle.
+ // Only usable while mReentrantMonitor is locked.
+ nsTArray<nsPipeInputStream*> mInputList;
+
+ // But hold a strong ref to our original input stream. For backward
+ // compatibility we need to be able to consistently return this same
+ // object from GetInputStream(). Note, mOriginalInput is also stored
+ // in mInputList as a weak ref.
+ RefPtr<nsPipeInputStream> mOriginalInput;
+
+ ReentrantMonitor mReentrantMonitor;
+ nsSegmentedBuffer mBuffer;
+
+ // The maximum number of segments to allow to be buffered in advance
+ // of the fastest reader. This is collection of segments is called
+ // the "advance buffer".
+ uint32_t mMaxAdvanceBufferSegmentCount;
+
+ int32_t mWriteSegment;
+ char* mWriteCursor;
+ char* mWriteLimit;
+
+ // |mStatus| is protected by |mReentrantMonitor|.
+ nsresult mStatus;
+ bool mInited;
+};
+
+//-----------------------------------------------------------------------------
+
+// RAII class representing an active read segment. When it goes out of scope
+// it automatically updates the read cursor and releases the read segment.
+class MOZ_STACK_CLASS AutoReadSegment final
+{
+public:
+ AutoReadSegment(nsPipe* aPipe, nsPipeReadState& aReadState,
+ uint32_t aMaxLength)
+ : mPipe(aPipe)
+ , mReadState(aReadState)
+ , mStatus(NS_ERROR_FAILURE)
+ , mSegment(nullptr)
+ , mLength(0)
+ , mOffset(0)
+ {
+ MOZ_ASSERT(mPipe);
+ MOZ_ASSERT(!mReadState.mActiveRead);
+ mStatus = mPipe->GetReadSegment(mReadState, mSegment, mLength);
+ if (NS_SUCCEEDED(mStatus)) {
+ MOZ_ASSERT(mReadState.mActiveRead);
+ MOZ_ASSERT(mSegment);
+ mLength = std::min(mLength, aMaxLength);
+ MOZ_ASSERT(mLength);
+ }
+ }
+
+ ~AutoReadSegment()
+ {
+ if (NS_SUCCEEDED(mStatus)) {
+ if (mOffset) {
+ mPipe->AdvanceReadCursor(mReadState, mOffset);
+ } else {
+ nsPipeEvents events;
+ mPipe->ReleaseReadSegment(mReadState, events);
+ }
+ }
+ MOZ_ASSERT(!mReadState.mActiveRead);
+ }
+
+ nsresult Status() const
+ {
+ return mStatus;
+ }
+
+ const char* Data() const
+ {
+ MOZ_ASSERT(NS_SUCCEEDED(mStatus));
+ MOZ_ASSERT(mSegment);
+ return mSegment + mOffset;
+ }
+
+ uint32_t Length() const
+ {
+ MOZ_ASSERT(NS_SUCCEEDED(mStatus));
+ MOZ_ASSERT(mLength >= mOffset);
+ return mLength - mOffset;
+ }
+
+ void
+ Advance(uint32_t aCount)
+ {
+ MOZ_ASSERT(NS_SUCCEEDED(mStatus));
+ MOZ_ASSERT(aCount <= (mLength - mOffset));
+ mOffset += aCount;
+ }
+
+ nsPipeReadState&
+ ReadState() const
+ {
+ return mReadState;
+ }
+
+private:
+ // guaranteed to remain alive due to limited stack lifetime of AutoReadSegment
+ nsPipe* mPipe;
+ nsPipeReadState& mReadState;
+ nsresult mStatus;
+ const char* mSegment;
+ uint32_t mLength;
+ uint32_t mOffset;
+};
+
+//
+// NOTES on buffer architecture:
+//
+// +-----------------+ - - mBuffer.GetSegment(0)
+// | |
+// + - - - - - - - - + - - nsPipeReadState.mReadCursor
+// |/////////////////|
+// |/////////////////|
+// |/////////////////|
+// |/////////////////|
+// +-----------------+ - - nsPipeReadState.mReadLimit
+// |
+// +-----------------+
+// |/////////////////|
+// |/////////////////|
+// |/////////////////|
+// |/////////////////|
+// |/////////////////|
+// |/////////////////|
+// +-----------------+
+// |
+// +-----------------+ - - mBuffer.GetSegment(mWriteSegment)
+// |/////////////////|
+// |/////////////////|
+// |/////////////////|
+// + - - - - - - - - + - - mWriteCursor
+// | |
+// | |
+// +-----------------+ - - mWriteLimit
+//
+// (shaded region contains data)
+//
+// NOTE: Each input stream produced by the nsPipe contains its own, separate
+// nsPipeReadState. This means there are multiple mReadCursor and
+// mReadLimit values in play. The pipe cannot discard old data until
+// all mReadCursors have moved beyond that point in the stream.
+//
+// Likewise, each input stream reader will have it's own amount of
+// buffered data. The pipe size threshold, however, is only applied
+// to the input stream that is being read fastest. We call this
+// the "advance buffer" in that its in advance of all readers. We
+// allow slower input streams to buffer more data so that we don't
+// stall processing of the faster input stream.
+//
+// NOTE: on some systems (notably OS/2), the heap allocator uses an arena for
+// small allocations (e.g., 64 byte allocations). this means that buffers may
+// be allocated back-to-back. in the diagram above, for example, mReadLimit
+// would actually be pointing at the beginning of the next segment. when
+// making changes to this file, please keep this fact in mind.
+//
+
+//-----------------------------------------------------------------------------
+// nsPipe methods:
+//-----------------------------------------------------------------------------
+
+nsPipe::nsPipe()
+ : mOutput(this)
+ , mOriginalInput(new nsPipeInputStream(this))
+ , mReentrantMonitor("nsPipe.mReentrantMonitor")
+ , mMaxAdvanceBufferSegmentCount(0)
+ , mWriteSegment(-1)
+ , mWriteCursor(nullptr)
+ , mWriteLimit(nullptr)
+ , mStatus(NS_OK)
+ , mInited(false)
+{
+ mInputList.AppendElement(mOriginalInput);
+}
+
+nsPipe::~nsPipe()
+{
+}
+
+NS_IMPL_ADDREF(nsPipe)
+NS_IMPL_QUERY_INTERFACE(nsPipe, nsIPipe)
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsPipe::Release()
+{
+ MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
+ nsrefcnt count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "nsPipe");
+ if (count == 0) {
+ delete (this);
+ return 0;
+ }
+ // Avoid racing on |mOriginalInput| by only looking at it when
+ // the refcount is 1, that is, we are the only pointer (hence only
+ // thread) to access it.
+ if (count == 1 && mOriginalInput) {
+ mOriginalInput = nullptr;
+ return 1;
+ }
+ return count;
+}
+
+NS_IMETHODIMP
+nsPipe::Init(bool aNonBlockingIn,
+ bool aNonBlockingOut,
+ uint32_t aSegmentSize,
+ uint32_t aSegmentCount)
+{
+ mInited = true;
+
+ if (aSegmentSize == 0) {
+ aSegmentSize = DEFAULT_SEGMENT_SIZE;
+ }
+ if (aSegmentCount == 0) {
+ aSegmentCount = DEFAULT_SEGMENT_COUNT;
+ }
+
+ // protect against overflow
+ uint32_t maxCount = uint32_t(-1) / aSegmentSize;
+ if (aSegmentCount > maxCount) {
+ aSegmentCount = maxCount;
+ }
+
+ // The internal buffer is always "infinite" so that we can allow
+ // the size to expand when cloned streams are read at different
+ // rates. We enforce a limit on how much data can be buffered
+ // ahead of the fastest reader in GetWriteSegment().
+ nsresult rv = mBuffer.Init(aSegmentSize, UINT32_MAX);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mMaxAdvanceBufferSegmentCount = aSegmentCount;
+
+ mOutput.SetNonBlocking(aNonBlockingOut);
+ mOriginalInput->SetNonBlocking(aNonBlockingIn);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPipe::GetInputStream(nsIAsyncInputStream** aInputStream)
+{
+ if (NS_WARN_IF(!mInited)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ RefPtr<nsPipeInputStream> ref = mOriginalInput;
+ ref.forget(aInputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPipe::GetOutputStream(nsIAsyncOutputStream** aOutputStream)
+{
+ if (NS_WARN_IF(!mInited)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ NS_ADDREF(*aOutputStream = &mOutput);
+ return NS_OK;
+}
+
+void
+nsPipe::PeekSegment(const nsPipeReadState& aReadState, uint32_t aIndex,
+ char*& aCursor, char*& aLimit)
+{
+ if (aIndex == 0) {
+ NS_ASSERTION(!aReadState.mReadCursor || mBuffer.GetSegmentCount(),
+ "unexpected state");
+ aCursor = aReadState.mReadCursor;
+ aLimit = aReadState.mReadLimit;
+ } else {
+ uint32_t absoluteIndex = aReadState.mSegment + aIndex;
+ uint32_t numSegments = mBuffer.GetSegmentCount();
+ if (absoluteIndex >= numSegments) {
+ aCursor = aLimit = nullptr;
+ } else {
+ aCursor = mBuffer.GetSegment(absoluteIndex);
+ if (mWriteSegment == (int32_t)absoluteIndex) {
+ aLimit = mWriteCursor;
+ } else {
+ aLimit = aCursor + mBuffer.GetSegmentSize();
+ }
+ }
+ }
+}
+
+nsresult
+nsPipe::GetReadSegment(nsPipeReadState& aReadState, const char*& aSegment,
+ uint32_t& aLength)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (aReadState.mReadCursor == aReadState.mReadLimit) {
+ return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // The input stream locks the pipe while getting the buffer to read from,
+ // but then unlocks while actual data copying is taking place. In
+ // order to avoid deleting the buffer out from under this lockless read
+ // set a flag to indicate a read is active. This flag is only modified
+ // while the lock is held.
+ MOZ_ASSERT(!aReadState.mActiveRead);
+ aReadState.mActiveRead = true;
+
+ aSegment = aReadState.mReadCursor;
+ aLength = aReadState.mReadLimit - aReadState.mReadCursor;
+
+ return NS_OK;
+}
+
+void
+nsPipe::ReleaseReadSegment(nsPipeReadState& aReadState, nsPipeEvents& aEvents)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ MOZ_ASSERT(aReadState.mActiveRead);
+ aReadState.mActiveRead = false;
+
+ // When a read completes and releases the mActiveRead flag, we may have blocked
+ // a drain from completing. This occurs when the input stream is closed during
+ // the read. In these cases, we need to complete the drain as soon as the
+ // active read completes.
+ if (aReadState.mNeedDrain) {
+ aReadState.mNeedDrain = false;
+ DrainInputStream(aReadState, aEvents);
+ }
+}
+
+void
+nsPipe::AdvanceReadCursor(nsPipeReadState& aReadState, uint32_t aBytesRead)
+{
+ NS_ASSERTION(aBytesRead, "don't call if no bytes read");
+
+ nsPipeEvents events;
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ LOG(("III advancing read cursor by %u\n", aBytesRead));
+ NS_ASSERTION(aBytesRead <= mBuffer.GetSegmentSize(), "read too much");
+
+ aReadState.mReadCursor += aBytesRead;
+ NS_ASSERTION(aReadState.mReadCursor <= aReadState.mReadLimit,
+ "read cursor exceeds limit");
+
+ MOZ_ASSERT(aReadState.mAvailable >= aBytesRead);
+ aReadState.mAvailable -= aBytesRead;
+
+ // Check to see if we're at the end of the available read data. If we
+ // are, and this segment is not still being written, then we can possibly
+ // free up the segment.
+ if (aReadState.mReadCursor == aReadState.mReadLimit &&
+ !ReadSegmentBeingWritten(aReadState)) {
+
+ // Advance the segment position. If we have read any segments from the
+ // advance buffer then we can potentially notify blocked writers.
+ if (AdvanceReadSegment(aReadState, mon) == SegmentAdvanceBufferRead &&
+ mOutput.OnOutputWritable(events) == NotifyMonitor) {
+ mon.NotifyAll();
+ }
+ }
+
+ ReleaseReadSegment(aReadState, events);
+ }
+}
+
+SegmentChangeResult
+nsPipe::AdvanceReadSegment(nsPipeReadState& aReadState,
+ const ReentrantMonitorAutoEnter &ev)
+{
+ // Calculate how many segments are buffered for this stream to start.
+ uint32_t startBufferSegments = GetBufferSegmentCount(aReadState, ev);
+
+ int32_t currentSegment = aReadState.mSegment;
+
+ // Move to the next segment to read
+ aReadState.mSegment += 1;
+
+ // If this was the last reference to the first segment, then remove it.
+ if (currentSegment == 0 && CountSegmentReferences(currentSegment) == 0) {
+
+ // shift write and read segment index (-1 indicates an empty buffer).
+ mWriteSegment -= 1;
+
+ // Directly modify the current read state. If the associated input
+ // stream is closed simultaneous with reading, then it may not be
+ // in the mInputList any more.
+ aReadState.mSegment -= 1;
+
+ for (uint32_t i = 0; i < mInputList.Length(); ++i) {
+ // Skip the current read state structure since we modify it manually
+ // before entering this loop.
+ if (&mInputList[i]->ReadState() == &aReadState) {
+ continue;
+ }
+ mInputList[i]->ReadState().mSegment -= 1;
+ }
+
+ // done with this segment
+ mBuffer.DeleteFirstSegment();
+ LOG(("III deleting first segment\n"));
+ }
+
+ if (mWriteSegment < aReadState.mSegment) {
+ // read cursor has hit the end of written data, so reset it
+ MOZ_ASSERT(mWriteSegment == (aReadState.mSegment - 1));
+ aReadState.mReadCursor = nullptr;
+ aReadState.mReadLimit = nullptr;
+ // also, the buffer is completely empty, so reset the write cursor
+ if (mWriteSegment == -1) {
+ mWriteCursor = nullptr;
+ mWriteLimit = nullptr;
+ }
+ } else {
+ // advance read cursor and limit to next buffer segment
+ aReadState.mReadCursor = mBuffer.GetSegment(aReadState.mSegment);
+ if (mWriteSegment == aReadState.mSegment) {
+ aReadState.mReadLimit = mWriteCursor;
+ } else {
+ aReadState.mReadLimit = aReadState.mReadCursor + mBuffer.GetSegmentSize();
+ }
+ }
+
+ // Calculate how many segments are buffered for the stream after
+ // reading.
+ uint32_t endBufferSegments = GetBufferSegmentCount(aReadState, ev);
+
+ // If the stream has read a segment out of the set of advanced buffer
+ // segments, then the writer may advance.
+ if (startBufferSegments >= mMaxAdvanceBufferSegmentCount &&
+ endBufferSegments < mMaxAdvanceBufferSegmentCount) {
+ return SegmentAdvanceBufferRead;
+ }
+
+ // Otherwise there are no significant changes to the segment structure.
+ return SegmentNotChanged;
+}
+
+void
+nsPipe::DrainInputStream(nsPipeReadState& aReadState, nsPipeEvents& aEvents)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // If a segment is actively being read in ReadSegments() for this input
+ // stream, then we cannot drain the stream. This can happen because
+ // ReadSegments() does not hold the lock while copying from the buffer.
+ // If we detect this condition, simply note that we need a drain once
+ // the read completes and return immediately.
+ if (aReadState.mActiveRead) {
+ MOZ_ASSERT(!aReadState.mNeedDrain);
+ aReadState.mNeedDrain = true;
+ return;
+ }
+
+ aReadState.mAvailable = 0;
+
+ while(mWriteSegment >= aReadState.mSegment) {
+
+ // If the last segment to free is still being written to, we're done
+ // draining. We can't free any more.
+ if (ReadSegmentBeingWritten(aReadState)) {
+ break;
+ }
+
+ // Don't bother checking if this results in an advance buffer segment
+ // read. Since we are draining the entire stream we will read an
+ // advance buffer segment no matter what.
+ AdvanceReadSegment(aReadState, mon);
+ }
+
+ // If we have read any segments from the advance buffer then we can
+ // potentially notify blocked writers.
+ if (!IsAdvanceBufferFull(mon) &&
+ mOutput.OnOutputWritable(aEvents) == NotifyMonitor) {
+ mon.NotifyAll();
+ }
+}
+
+bool
+nsPipe::ReadSegmentBeingWritten(nsPipeReadState& aReadState)
+{
+ mReentrantMonitor.AssertCurrentThreadIn();
+ bool beingWritten = mWriteSegment == aReadState.mSegment &&
+ mWriteLimit > mWriteCursor;
+ NS_ASSERTION(!beingWritten || aReadState.mReadLimit == mWriteCursor,
+ "unexpected state");
+ return beingWritten;
+}
+
+nsresult
+nsPipe::GetWriteSegment(char*& aSegment, uint32_t& aSegmentLen)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ // write cursor and limit may both be null indicating an empty buffer.
+ if (mWriteCursor == mWriteLimit) {
+ // The pipe is full if we have hit our limit on advance data buffering.
+ // This means the fastest reader is still reading slower than data is
+ // being written into the pipe.
+ if (IsAdvanceBufferFull(mon)) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ // The nsSegmentedBuffer is configured to be "infinite", so this
+ // should never return nullptr here.
+ char* seg = mBuffer.AppendNewSegment();
+ if (!seg) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ LOG(("OOO appended new segment\n"));
+ mWriteCursor = seg;
+ mWriteLimit = mWriteCursor + mBuffer.GetSegmentSize();
+ ++mWriteSegment;
+ }
+
+ // make sure read cursor is initialized
+ SetAllNullReadCursors();
+
+ // check to see if we can roll-back our read and write cursors to the
+ // beginning of the current/first segment. this is purely an optimization.
+ if (mWriteSegment == 0 && AllReadCursorsMatchWriteCursor()) {
+ char* head = mBuffer.GetSegment(0);
+ LOG(("OOO rolling back write cursor %u bytes\n", mWriteCursor - head));
+ RollBackAllReadCursors(head);
+ mWriteCursor = head;
+ }
+
+ aSegment = mWriteCursor;
+ aSegmentLen = mWriteLimit - mWriteCursor;
+ return NS_OK;
+}
+
+void
+nsPipe::AdvanceWriteCursor(uint32_t aBytesWritten)
+{
+ NS_ASSERTION(aBytesWritten, "don't call if no bytes written");
+
+ nsPipeEvents events;
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ LOG(("OOO advancing write cursor by %u\n", aBytesWritten));
+
+ char* newWriteCursor = mWriteCursor + aBytesWritten;
+ NS_ASSERTION(newWriteCursor <= mWriteLimit, "write cursor exceeds limit");
+
+ // update read limit if reading in the same segment
+ UpdateAllReadCursors(newWriteCursor);
+
+ mWriteCursor = newWriteCursor;
+
+ ValidateAllReadCursors();
+
+ // update the writable flag on the output stream
+ if (mWriteCursor == mWriteLimit) {
+ mOutput.SetWritable(!IsAdvanceBufferFull(mon));
+ }
+
+ // notify input stream that pipe now contains additional data
+ bool needNotify = false;
+ for (uint32_t i = 0; i < mInputList.Length(); ++i) {
+ if (mInputList[i]->OnInputReadable(aBytesWritten, events, mon)
+ == NotifyMonitor) {
+ needNotify = true;
+ }
+ }
+
+ if (needNotify) {
+ mon.NotifyAll();
+ }
+ }
+}
+
+void
+nsPipe::OnInputStreamException(nsPipeInputStream* aStream, nsresult aReason)
+{
+ MOZ_ASSERT(NS_FAILED(aReason));
+
+ nsPipeEvents events;
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // Its possible to re-enter this method when we call OnPipeException() or
+ // OnInputExection() below. If there is a caller stuck in our synchronous
+ // Wait() method, then they will get woken up with a failure code which
+ // re-enters this method. Therefore, gracefully handle unknown streams
+ // here.
+
+ // If we only have one stream open and it is the given stream, then shut
+ // down the entire pipe.
+ if (mInputList.Length() == 1) {
+ if (mInputList[0] == aStream) {
+ OnPipeException(aReason);
+ }
+ return;
+ }
+
+ // Otherwise just close the particular stream that hit an exception.
+ for (uint32_t i = 0; i < mInputList.Length(); ++i) {
+ if (mInputList[i] != aStream) {
+ continue;
+ }
+
+ MonitorAction action = mInputList[i]->OnInputException(aReason, events,
+ mon);
+ mInputList.RemoveElementAt(i);
+
+ // Notify after element is removed in case we re-enter as a result.
+ if (action == NotifyMonitor) {
+ mon.NotifyAll();
+ }
+
+ return;
+ }
+ }
+}
+
+void
+nsPipe::OnPipeException(nsresult aReason, bool aOutputOnly)
+{
+ LOG(("PPP nsPipe::OnPipeException [reason=%x output-only=%d]\n",
+ aReason, aOutputOnly));
+
+ nsPipeEvents events;
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // if we've already hit an exception, then ignore this one.
+ if (NS_FAILED(mStatus)) {
+ return;
+ }
+
+ mStatus = aReason;
+
+ bool needNotify = false;
+
+ nsTArray<nsPipeInputStream*> tmpInputList;
+ for (uint32_t i = 0; i < mInputList.Length(); ++i) {
+ // an output-only exception applies to the input end if the pipe has
+ // zero bytes available.
+ if (aOutputOnly && mInputList[i]->Available()) {
+ tmpInputList.AppendElement(mInputList[i]);
+ continue;
+ }
+
+ if (mInputList[i]->OnInputException(aReason, events, mon)
+ == NotifyMonitor) {
+ needNotify = true;
+ }
+ }
+ mInputList = tmpInputList;
+
+ if (mOutput.OnOutputException(aReason, events) == NotifyMonitor) {
+ needNotify = true;
+ }
+
+ // Notify after we have removed any input streams from mInputList
+ if (needNotify) {
+ mon.NotifyAll();
+ }
+ }
+}
+
+nsresult
+nsPipe::CloneInputStream(nsPipeInputStream* aOriginal,
+ nsIInputStream** aCloneOut)
+{
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ RefPtr<nsPipeInputStream> ref = new nsPipeInputStream(*aOriginal);
+ mInputList.AppendElement(ref);
+ nsCOMPtr<nsIAsyncInputStream> downcast = ref.forget();
+ downcast.forget(aCloneOut);
+ return NS_OK;
+}
+
+uint32_t
+nsPipe::CountSegmentReferences(int32_t aSegment)
+{
+ mReentrantMonitor.AssertCurrentThreadIn();
+ uint32_t count = 0;
+ for (uint32_t i = 0; i < mInputList.Length(); ++i) {
+ if (aSegment >= mInputList[i]->ReadState().mSegment) {
+ count += 1;
+ }
+ }
+ return count;
+}
+
+void
+nsPipe::SetAllNullReadCursors()
+{
+ mReentrantMonitor.AssertCurrentThreadIn();
+ for (uint32_t i = 0; i < mInputList.Length(); ++i) {
+ nsPipeReadState& readState = mInputList[i]->ReadState();
+ if (!readState.mReadCursor) {
+ NS_ASSERTION(mWriteSegment == readState.mSegment,
+ "unexpected null read cursor");
+ readState.mReadCursor = readState.mReadLimit = mWriteCursor;
+ }
+ }
+}
+
+bool
+nsPipe::AllReadCursorsMatchWriteCursor()
+{
+ mReentrantMonitor.AssertCurrentThreadIn();
+ for (uint32_t i = 0; i < mInputList.Length(); ++i) {
+ const nsPipeReadState& readState = mInputList[i]->ReadState();
+ if (readState.mSegment != mWriteSegment ||
+ readState.mReadCursor != mWriteCursor) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void
+nsPipe::RollBackAllReadCursors(char* aWriteCursor)
+{
+ mReentrantMonitor.AssertCurrentThreadIn();
+ for (uint32_t i = 0; i < mInputList.Length(); ++i) {
+ nsPipeReadState& readState = mInputList[i]->ReadState();
+ MOZ_ASSERT(mWriteSegment == readState.mSegment);
+ MOZ_ASSERT(mWriteCursor == readState.mReadCursor);
+ MOZ_ASSERT(mWriteCursor == readState.mReadLimit);
+ readState.mReadCursor = aWriteCursor;
+ readState.mReadLimit = aWriteCursor;
+ }
+}
+
+void
+nsPipe::UpdateAllReadCursors(char* aWriteCursor)
+{
+ mReentrantMonitor.AssertCurrentThreadIn();
+ for (uint32_t i = 0; i < mInputList.Length(); ++i) {
+ nsPipeReadState& readState = mInputList[i]->ReadState();
+ if (mWriteSegment == readState.mSegment &&
+ readState.mReadLimit == mWriteCursor) {
+ readState.mReadLimit = aWriteCursor;
+ }
+ }
+}
+
+void
+nsPipe::ValidateAllReadCursors()
+{
+ mReentrantMonitor.AssertCurrentThreadIn();
+ // The only way mReadCursor == mWriteCursor is if:
+ //
+ // - mReadCursor is at the start of a segment (which, based on how
+ // nsSegmentedBuffer works, means that this segment is the "first"
+ // segment)
+ // - mWriteCursor points at the location past the end of the current
+ // write segment (so the current write filled the current write
+ // segment, so we've incremented mWriteCursor to point past the end
+ // of it)
+ // - the segment to which data has just been written is located
+ // exactly one segment's worth of bytes before the first segment
+ // where mReadCursor is located
+ //
+ // Consequently, the byte immediately after the end of the current
+ // write segment is the first byte of the first segment, so
+ // mReadCursor == mWriteCursor. (Another way to think about this is
+ // to consider the buffer architecture diagram above, but consider it
+ // with an arena allocator which allocates from the *end* of the
+ // arena to the *beginning* of the arena.)
+#ifdef DEBUG
+ for (uint32_t i = 0; i < mInputList.Length(); ++i) {
+ const nsPipeReadState& state = mInputList[i]->ReadState();
+ NS_ASSERTION(state.mReadCursor != mWriteCursor ||
+ (mBuffer.GetSegment(state.mSegment) == state.mReadCursor &&
+ mWriteCursor == mWriteLimit),
+ "read cursor is bad");
+ }
+#endif
+}
+
+uint32_t
+nsPipe::GetBufferSegmentCount(const nsPipeReadState& aReadState,
+ const ReentrantMonitorAutoEnter& ev) const
+{
+ // The write segment can be smaller than the current reader position
+ // in some cases. For example, when the first write segment has not
+ // been allocated yet mWriteSegment is negative. In these cases
+ // the stream is effectively using zero segments.
+ if (mWriteSegment < aReadState.mSegment) {
+ return 0;
+ }
+
+ MOZ_ASSERT(mWriteSegment >= 0);
+ MOZ_ASSERT(aReadState.mSegment >= 0);
+
+ // Otherwise at least one segment is being used. We add one here
+ // since a single segment is being used when the write and read
+ // segment indices are the same.
+ return 1 + mWriteSegment - aReadState.mSegment;
+}
+
+bool
+nsPipe::IsAdvanceBufferFull(const ReentrantMonitorAutoEnter& ev) const
+{
+ // If we have fewer total segments than the limit we can immediately
+ // determine we are not full. Note, we must add one to mWriteSegment
+ // to convert from a index to a count.
+ MOZ_DIAGNOSTIC_ASSERT(mWriteSegment >= -1);
+ MOZ_DIAGNOSTIC_ASSERT(mWriteSegment < INT32_MAX);
+ uint32_t totalWriteSegments = mWriteSegment + 1;
+ if (totalWriteSegments < mMaxAdvanceBufferSegmentCount) {
+ return false;
+ }
+
+ // Otherwise we must inspect all of our reader streams. We need
+ // to determine the buffer depth of the fastest reader.
+ uint32_t minBufferSegments = UINT32_MAX;
+ for (uint32_t i = 0; i < mInputList.Length(); ++i) {
+ // Only count buffer segments from input streams that are open.
+ if (NS_FAILED(mInputList[i]->Status(ev))) {
+ continue;
+ }
+ const nsPipeReadState& state = mInputList[i]->ReadState();
+ uint32_t bufferSegments = GetBufferSegmentCount(state, ev);
+ minBufferSegments = std::min(minBufferSegments, bufferSegments);
+ // We only care if any reader has fewer segments buffered than
+ // our threshold. We can stop once we hit that threshold.
+ if (minBufferSegments < mMaxAdvanceBufferSegmentCount) {
+ return false;
+ }
+ }
+
+ // Note, its possible for minBufferSegments to exceed our
+ // mMaxAdvanceBufferSegmentCount here. This happens when a cloned
+ // reader gets far behind, but then the fastest reader stream is
+ // closed. This leaves us with a single stream that is buffered
+ // beyond our max. Naturally we continue to indicate the pipe
+ // is full at this point.
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// nsPipeEvents methods:
+//-----------------------------------------------------------------------------
+
+nsPipeEvents::~nsPipeEvents()
+{
+ // dispatch any pending events
+
+ for (uint32_t i = 0; i < mInputList.Length(); ++i) {
+ mInputList[i].mCallback->OnInputStreamReady(mInputList[i].mStream);
+ }
+ mInputList.Clear();
+
+ if (mOutputCallback) {
+ mOutputCallback->OnOutputStreamReady(mOutputStream);
+ mOutputCallback = nullptr;
+ mOutputStream = nullptr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// nsPipeInputStream methods:
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(nsPipeInputStream);
+NS_IMPL_RELEASE(nsPipeInputStream);
+
+NS_INTERFACE_TABLE_HEAD(nsPipeInputStream)
+ NS_INTERFACE_TABLE_BEGIN
+ NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIAsyncInputStream)
+ NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsISeekableStream)
+ NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsISearchableInputStream)
+ NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsICloneableInputStream)
+ NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIBufferedInputStream)
+ NS_INTERFACE_TABLE_ENTRY(nsPipeInputStream, nsIClassInfo)
+ NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsPipeInputStream, nsIInputStream,
+ nsIAsyncInputStream)
+ NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsPipeInputStream, nsISupports,
+ nsIAsyncInputStream)
+ NS_INTERFACE_TABLE_END
+NS_INTERFACE_TABLE_TAIL
+
+NS_IMPL_CI_INTERFACE_GETTER(nsPipeInputStream,
+ nsIInputStream,
+ nsIAsyncInputStream,
+ nsISeekableStream,
+ nsISearchableInputStream,
+ nsICloneableInputStream,
+ nsIBufferedInputStream)
+
+NS_IMPL_THREADSAFE_CI(nsPipeInputStream)
+
+NS_IMETHODIMP
+nsPipeInputStream::Init(nsIInputStream*, uint32_t)
+{
+ MOZ_CRASH("nsPipeInputStream should never be initialized with "
+ "nsIBufferedInputStream::Init!\n");
+}
+
+uint32_t
+nsPipeInputStream::Available()
+{
+ mPipe->mReentrantMonitor.AssertCurrentThreadIn();
+ return mReadState.mAvailable;
+}
+
+nsresult
+nsPipeInputStream::Wait()
+{
+ NS_ASSERTION(mBlocking, "wait on non-blocking pipe input stream");
+
+ ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor);
+
+ while (NS_SUCCEEDED(Status(mon)) && (mReadState.mAvailable == 0)) {
+ LOG(("III pipe input: waiting for data\n"));
+
+ mBlocked = true;
+ mon.Wait();
+ mBlocked = false;
+
+ LOG(("III pipe input: woke up [status=%x available=%u]\n",
+ Status(mon), mReadState.mAvailable));
+ }
+
+ return Status(mon) == NS_BASE_STREAM_CLOSED ? NS_OK : Status(mon);
+}
+
+MonitorAction
+nsPipeInputStream::OnInputReadable(uint32_t aBytesWritten,
+ nsPipeEvents& aEvents,
+ const ReentrantMonitorAutoEnter& ev)
+{
+ MonitorAction result = DoNotNotifyMonitor;
+
+ mPipe->mReentrantMonitor.AssertCurrentThreadIn();
+ mReadState.mAvailable += aBytesWritten;
+
+ if (mCallback && !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
+ aEvents.NotifyInputReady(this, mCallback);
+ mCallback = nullptr;
+ mCallbackFlags = 0;
+ } else if (mBlocked) {
+ result = NotifyMonitor;
+ }
+
+ return result;
+}
+
+MonitorAction
+nsPipeInputStream::OnInputException(nsresult aReason, nsPipeEvents& aEvents,
+ const ReentrantMonitorAutoEnter& ev)
+{
+ LOG(("nsPipeInputStream::OnInputException [this=%x reason=%x]\n",
+ this, aReason));
+
+ MonitorAction result = DoNotNotifyMonitor;
+
+ NS_ASSERTION(NS_FAILED(aReason), "huh? successful exception");
+
+ if (NS_SUCCEEDED(mInputStatus)) {
+ mInputStatus = aReason;
+ }
+
+ // force count of available bytes to zero.
+ mPipe->DrainInputStream(mReadState, aEvents);
+
+ if (mCallback) {
+ aEvents.NotifyInputReady(this, mCallback);
+ mCallback = nullptr;
+ mCallbackFlags = 0;
+ } else if (mBlocked) {
+ result = NotifyMonitor;
+ }
+
+ return result;
+}
+
+NS_IMETHODIMP
+nsPipeInputStream::CloseWithStatus(nsresult aReason)
+{
+ LOG(("III CloseWithStatus [this=%x reason=%x]\n", this, aReason));
+
+ ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor);
+
+ if (NS_FAILED(mInputStatus)) {
+ return NS_OK;
+ }
+
+ if (NS_SUCCEEDED(aReason)) {
+ aReason = NS_BASE_STREAM_CLOSED;
+ }
+
+ mPipe->OnInputStreamException(this, aReason);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPipeInputStream::Close()
+{
+ return CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP
+nsPipeInputStream::Available(uint64_t* aResult)
+{
+ // nsPipeInputStream supports under 4GB stream only
+ ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor);
+
+ // return error if closed
+ if (!mReadState.mAvailable && NS_FAILED(Status(mon))) {
+ return Status(mon);
+ }
+
+ *aResult = (uint64_t)mReadState.mAvailable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPipeInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure,
+ uint32_t aCount,
+ uint32_t* aReadCount)
+{
+ LOG(("III ReadSegments [this=%x count=%u]\n", this, aCount));
+
+ nsresult rv = NS_OK;
+
+ *aReadCount = 0;
+ while (aCount) {
+ AutoReadSegment segment(mPipe, mReadState, aCount);
+ rv = segment.Status();
+ if (NS_FAILED(rv)) {
+ // ignore this error if we've already read something.
+ if (*aReadCount > 0) {
+ rv = NS_OK;
+ break;
+ }
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ // pipe is empty
+ if (!mBlocking) {
+ break;
+ }
+ // wait for some data to be written to the pipe
+ rv = Wait();
+ if (NS_SUCCEEDED(rv)) {
+ continue;
+ }
+ }
+ // ignore this error, just return.
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ rv = NS_OK;
+ break;
+ }
+ mPipe->OnInputStreamException(this, rv);
+ break;
+ }
+
+ uint32_t writeCount;
+ while (segment.Length()) {
+ writeCount = 0;
+
+ rv = aWriter(static_cast<nsIAsyncInputStream*>(this), aClosure,
+ segment.Data(), *aReadCount, segment.Length(), &writeCount);
+
+ if (NS_FAILED(rv) || writeCount == 0) {
+ aCount = 0;
+ // any errors returned from the writer end here: do not
+ // propagate to the caller of ReadSegments.
+ rv = NS_OK;
+ break;
+ }
+
+ NS_ASSERTION(writeCount <= segment.Length(), "wrote more than expected");
+ segment.Advance(writeCount);
+ aCount -= writeCount;
+ *aReadCount += writeCount;
+ mLogicalOffset += writeCount;
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPipeInputStream::Read(char* aToBuf, uint32_t aBufLen, uint32_t* aReadCount)
+{
+ return ReadSegments(NS_CopySegmentToBuffer, aToBuf, aBufLen, aReadCount);
+}
+
+NS_IMETHODIMP
+nsPipeInputStream::IsNonBlocking(bool* aNonBlocking)
+{
+ *aNonBlocking = !mBlocking;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPipeInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aTarget)
+{
+ LOG(("III AsyncWait [this=%x]\n", this));
+
+ nsPipeEvents pipeEvents;
+ {
+ ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor);
+
+ // replace a pending callback
+ mCallback = nullptr;
+ mCallbackFlags = 0;
+
+ if (!aCallback) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStreamCallback> proxy;
+ if (aTarget) {
+ proxy = NS_NewInputStreamReadyEvent(aCallback, aTarget);
+ aCallback = proxy;
+ }
+
+ if (NS_FAILED(Status(mon)) ||
+ (mReadState.mAvailable && !(aFlags & WAIT_CLOSURE_ONLY))) {
+ // stream is already closed or readable; post event.
+ pipeEvents.NotifyInputReady(this, aCallback);
+ } else {
+ // queue up callback object to be notified when data becomes available
+ mCallback = aCallback;
+ mCallbackFlags = aFlags;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPipeInputStream::Seek(int32_t aWhence, int64_t aOffset)
+{
+ NS_NOTREACHED("nsPipeInputStream::Seek");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsPipeInputStream::Tell(int64_t* aOffset)
+{
+ ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor);
+
+ // return error if closed
+ if (!mReadState.mAvailable && NS_FAILED(Status(mon))) {
+ return Status(mon);
+ }
+
+ *aOffset = mLogicalOffset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPipeInputStream::SetEOF()
+{
+ NS_NOTREACHED("nsPipeInputStream::SetEOF");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+static bool strings_equal(bool aIgnoreCase,
+ const char* aS1, const char* aS2, uint32_t aLen)
+{
+ return aIgnoreCase
+ ? !nsCRT::strncasecmp(aS1, aS2, aLen) : !nsCRT::strncmp(aS1, aS2, aLen);
+}
+
+NS_IMETHODIMP
+nsPipeInputStream::Search(const char* aForString,
+ bool aIgnoreCase,
+ bool* aFound,
+ uint32_t* aOffsetSearchedTo)
+{
+ LOG(("III Search [for=%s ic=%u]\n", aForString, aIgnoreCase));
+
+ ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor);
+
+ char* cursor1;
+ char* limit1;
+ uint32_t index = 0, offset = 0;
+ uint32_t strLen = strlen(aForString);
+
+ mPipe->PeekSegment(mReadState, 0, cursor1, limit1);
+ if (cursor1 == limit1) {
+ *aFound = false;
+ *aOffsetSearchedTo = 0;
+ LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo));
+ return NS_OK;
+ }
+
+ while (true) {
+ uint32_t i, len1 = limit1 - cursor1;
+
+ // check if the string is in the buffer segment
+ for (i = 0; i < len1 - strLen + 1; i++) {
+ if (strings_equal(aIgnoreCase, &cursor1[i], aForString, strLen)) {
+ *aFound = true;
+ *aOffsetSearchedTo = offset + i;
+ LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo));
+ return NS_OK;
+ }
+ }
+
+ // get the next segment
+ char* cursor2;
+ char* limit2;
+ uint32_t len2;
+
+ index++;
+ offset += len1;
+
+ mPipe->PeekSegment(mReadState, index, cursor2, limit2);
+ if (cursor2 == limit2) {
+ *aFound = false;
+ *aOffsetSearchedTo = offset - strLen + 1;
+ LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo));
+ return NS_OK;
+ }
+ len2 = limit2 - cursor2;
+
+ // check if the string is straddling the next buffer segment
+ uint32_t lim = XPCOM_MIN(strLen, len2 + 1);
+ for (i = 0; i < lim; ++i) {
+ uint32_t strPart1Len = strLen - i - 1;
+ uint32_t strPart2Len = strLen - strPart1Len;
+ const char* strPart2 = &aForString[strLen - strPart2Len];
+ uint32_t bufSeg1Offset = len1 - strPart1Len;
+ if (strings_equal(aIgnoreCase, &cursor1[bufSeg1Offset], aForString, strPart1Len) &&
+ strings_equal(aIgnoreCase, cursor2, strPart2, strPart2Len)) {
+ *aFound = true;
+ *aOffsetSearchedTo = offset - strPart1Len;
+ LOG((" result [aFound=%u offset=%u]\n", *aFound, *aOffsetSearchedTo));
+ return NS_OK;
+ }
+ }
+
+ // finally continue with the next buffer
+ cursor1 = cursor2;
+ limit1 = limit2;
+ }
+
+ NS_NOTREACHED("can't get here");
+ return NS_ERROR_UNEXPECTED; // keep compiler happy
+}
+
+NS_IMETHODIMP
+nsPipeInputStream::GetCloneable(bool* aCloneableOut)
+{
+ *aCloneableOut = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPipeInputStream::Clone(nsIInputStream** aCloneOut)
+{
+ return mPipe->CloneInputStream(this, aCloneOut);
+}
+
+nsresult
+nsPipeInputStream::Status(const ReentrantMonitorAutoEnter& ev) const
+{
+ if (NS_FAILED(mInputStatus)) {
+ return mInputStatus;
+ }
+
+ if (mReadState.mAvailable) {
+ // Still something to read and this input stream state is OK.
+ return NS_OK;
+ }
+
+ // Nothing to read, just fall through to the pipe's state that
+ // may reflect state of its output stream side (already closed).
+ return mPipe->mStatus;
+}
+
+nsresult
+nsPipeInputStream::Status() const
+{
+ ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor);
+ return Status(mon);
+}
+
+nsPipeInputStream::~nsPipeInputStream()
+{
+ Close();
+}
+
+//-----------------------------------------------------------------------------
+// nsPipeOutputStream methods:
+//-----------------------------------------------------------------------------
+
+NS_IMPL_QUERY_INTERFACE(nsPipeOutputStream,
+ nsIOutputStream,
+ nsIAsyncOutputStream,
+ nsIClassInfo)
+
+NS_IMPL_CI_INTERFACE_GETTER(nsPipeOutputStream,
+ nsIOutputStream,
+ nsIAsyncOutputStream)
+
+NS_IMPL_THREADSAFE_CI(nsPipeOutputStream)
+
+nsresult
+nsPipeOutputStream::Wait()
+{
+ NS_ASSERTION(mBlocking, "wait on non-blocking pipe output stream");
+
+ ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor);
+
+ if (NS_SUCCEEDED(mPipe->mStatus) && !mWritable) {
+ LOG(("OOO pipe output: waiting for space\n"));
+ mBlocked = true;
+ mon.Wait();
+ mBlocked = false;
+ LOG(("OOO pipe output: woke up [pipe-status=%x writable=%u]\n",
+ mPipe->mStatus, mWritable));
+ }
+
+ return mPipe->mStatus == NS_BASE_STREAM_CLOSED ? NS_OK : mPipe->mStatus;
+}
+
+MonitorAction
+nsPipeOutputStream::OnOutputWritable(nsPipeEvents& aEvents)
+{
+ MonitorAction result = DoNotNotifyMonitor;
+
+ mWritable = true;
+
+ if (mCallback && !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
+ aEvents.NotifyOutputReady(this, mCallback);
+ mCallback = nullptr;
+ mCallbackFlags = 0;
+ } else if (mBlocked) {
+ result = NotifyMonitor;
+ }
+
+ return result;
+}
+
+MonitorAction
+nsPipeOutputStream::OnOutputException(nsresult aReason, nsPipeEvents& aEvents)
+{
+ LOG(("nsPipeOutputStream::OnOutputException [this=%x reason=%x]\n",
+ this, aReason));
+
+ MonitorAction result = DoNotNotifyMonitor;
+
+ NS_ASSERTION(NS_FAILED(aReason), "huh? successful exception");
+ mWritable = false;
+
+ if (mCallback) {
+ aEvents.NotifyOutputReady(this, mCallback);
+ mCallback = nullptr;
+ mCallbackFlags = 0;
+ } else if (mBlocked) {
+ result = NotifyMonitor;
+ }
+
+ return result;
+}
+
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsPipeOutputStream::AddRef()
+{
+ ++mWriterRefCnt;
+ return mPipe->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsPipeOutputStream::Release()
+{
+ if (--mWriterRefCnt == 0) {
+ Close();
+ }
+ return mPipe->Release();
+}
+
+NS_IMETHODIMP
+nsPipeOutputStream::CloseWithStatus(nsresult aReason)
+{
+ LOG(("OOO CloseWithStatus [this=%x reason=%x]\n", this, aReason));
+
+ if (NS_SUCCEEDED(aReason)) {
+ aReason = NS_BASE_STREAM_CLOSED;
+ }
+
+ // input stream may remain open
+ mPipe->OnPipeException(aReason, true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPipeOutputStream::Close()
+{
+ return CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP
+nsPipeOutputStream::WriteSegments(nsReadSegmentFun aReader,
+ void* aClosure,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ LOG(("OOO WriteSegments [this=%x count=%u]\n", this, aCount));
+
+ nsresult rv = NS_OK;
+
+ char* segment;
+ uint32_t segmentLen;
+
+ *aWriteCount = 0;
+ while (aCount) {
+ rv = mPipe->GetWriteSegment(segment, segmentLen);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ // pipe is full
+ if (!mBlocking) {
+ // ignore this error if we've already written something
+ if (*aWriteCount > 0) {
+ rv = NS_OK;
+ }
+ break;
+ }
+ // wait for the pipe to have an empty segment.
+ rv = Wait();
+ if (NS_SUCCEEDED(rv)) {
+ continue;
+ }
+ }
+ mPipe->OnPipeException(rv);
+ break;
+ }
+
+ // write no more than aCount
+ if (segmentLen > aCount) {
+ segmentLen = aCount;
+ }
+
+ uint32_t readCount, originalLen = segmentLen;
+ while (segmentLen) {
+ readCount = 0;
+
+ rv = aReader(this, aClosure, segment, *aWriteCount, segmentLen, &readCount);
+
+ if (NS_FAILED(rv) || readCount == 0) {
+ aCount = 0;
+ // any errors returned from the aReader end here: do not
+ // propagate to the caller of WriteSegments.
+ rv = NS_OK;
+ break;
+ }
+
+ NS_ASSERTION(readCount <= segmentLen, "read more than expected");
+ segment += readCount;
+ segmentLen -= readCount;
+ aCount -= readCount;
+ *aWriteCount += readCount;
+ mLogicalOffset += readCount;
+ }
+
+ if (segmentLen < originalLen) {
+ mPipe->AdvanceWriteCursor(originalLen - segmentLen);
+ }
+ }
+
+ return rv;
+}
+
+static nsresult
+nsReadFromRawBuffer(nsIOutputStream* aOutStr,
+ void* aClosure,
+ char* aToRawSegment,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t* aReadCount)
+{
+ const char* fromBuf = (const char*)aClosure;
+ memcpy(aToRawSegment, &fromBuf[aOffset], aCount);
+ *aReadCount = aCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPipeOutputStream::Write(const char* aFromBuf,
+ uint32_t aBufLen,
+ uint32_t* aWriteCount)
+{
+ return WriteSegments(nsReadFromRawBuffer, (void*)aFromBuf, aBufLen, aWriteCount);
+}
+
+NS_IMETHODIMP
+nsPipeOutputStream::Flush(void)
+{
+ // nothing to do
+ return NS_OK;
+}
+
+static nsresult
+nsReadFromInputStream(nsIOutputStream* aOutStr,
+ void* aClosure,
+ char* aToRawSegment,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t* aReadCount)
+{
+ nsIInputStream* fromStream = (nsIInputStream*)aClosure;
+ return fromStream->Read(aToRawSegment, aCount, aReadCount);
+}
+
+NS_IMETHODIMP
+nsPipeOutputStream::WriteFrom(nsIInputStream* aFromStream,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ return WriteSegments(nsReadFromInputStream, aFromStream, aCount, aWriteCount);
+}
+
+NS_IMETHODIMP
+nsPipeOutputStream::IsNonBlocking(bool* aNonBlocking)
+{
+ *aNonBlocking = !mBlocking;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPipeOutputStream::AsyncWait(nsIOutputStreamCallback* aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aTarget)
+{
+ LOG(("OOO AsyncWait [this=%x]\n", this));
+
+ nsPipeEvents pipeEvents;
+ {
+ ReentrantMonitorAutoEnter mon(mPipe->mReentrantMonitor);
+
+ // replace a pending callback
+ mCallback = nullptr;
+ mCallbackFlags = 0;
+
+ if (!aCallback) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIOutputStreamCallback> proxy;
+ if (aTarget) {
+ proxy = NS_NewOutputStreamReadyEvent(aCallback, aTarget);
+ aCallback = proxy;
+ }
+
+ if (NS_FAILED(mPipe->mStatus) ||
+ (mWritable && !(aFlags & WAIT_CLOSURE_ONLY))) {
+ // stream is already closed or writable; post event.
+ pipeEvents.NotifyOutputReady(this, aCallback);
+ } else {
+ // queue up callback object to be notified when data becomes available
+ mCallback = aCallback;
+ mCallbackFlags = aFlags;
+ }
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult
+NS_NewPipe(nsIInputStream** aPipeIn,
+ nsIOutputStream** aPipeOut,
+ uint32_t aSegmentSize,
+ uint32_t aMaxSize,
+ bool aNonBlockingInput,
+ bool aNonBlockingOutput)
+{
+ if (aSegmentSize == 0) {
+ aSegmentSize = DEFAULT_SEGMENT_SIZE;
+ }
+
+ // Handle aMaxSize of UINT32_MAX as a special case
+ uint32_t segmentCount;
+ if (aMaxSize == UINT32_MAX) {
+ segmentCount = UINT32_MAX;
+ } else {
+ segmentCount = aMaxSize / aSegmentSize;
+ }
+
+ nsIAsyncInputStream* in;
+ nsIAsyncOutputStream* out;
+ nsresult rv = NS_NewPipe2(&in, &out, aNonBlockingInput, aNonBlockingOutput,
+ aSegmentSize, segmentCount);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *aPipeIn = in;
+ *aPipeOut = out;
+ return NS_OK;
+}
+
+nsresult
+NS_NewPipe2(nsIAsyncInputStream** aPipeIn,
+ nsIAsyncOutputStream** aPipeOut,
+ bool aNonBlockingInput,
+ bool aNonBlockingOutput,
+ uint32_t aSegmentSize,
+ uint32_t aSegmentCount)
+{
+ nsPipe* pipe = new nsPipe();
+ nsresult rv = pipe->Init(aNonBlockingInput,
+ aNonBlockingOutput,
+ aSegmentSize,
+ aSegmentCount);
+ if (NS_FAILED(rv)) {
+ NS_ADDREF(pipe);
+ NS_RELEASE(pipe);
+ return rv;
+ }
+
+ // These always succeed because the pipe is initialized above.
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(aPipeIn));
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(aPipeOut));
+ return NS_OK;
+}
+
+nsresult
+nsPipeConstructor(nsISupports* aOuter, REFNSIID aIID, void** aResult)
+{
+ if (aOuter) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+ nsPipe* pipe = new nsPipe();
+ NS_ADDREF(pipe);
+ nsresult rv = pipe->QueryInterface(aIID, aResult);
+ NS_RELEASE(pipe);
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/xpcom/io/nsScriptableBase64Encoder.cpp b/xpcom/io/nsScriptableBase64Encoder.cpp
new file mode 100644
index 000000000..a8ffce8cd
--- /dev/null
+++ b/xpcom/io/nsScriptableBase64Encoder.cpp
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsScriptableBase64Encoder.h"
+#include "mozilla/Base64.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsScriptableBase64Encoder, nsIScriptableBase64Encoder)
+
+NS_IMETHODIMP
+nsScriptableBase64Encoder::EncodeToCString(nsIInputStream* aStream,
+ uint32_t aLength,
+ nsACString& aResult)
+{
+ return Base64EncodeInputStream(aStream, aResult, aLength);
+}
+
+NS_IMETHODIMP
+nsScriptableBase64Encoder::EncodeToString(nsIInputStream* aStream,
+ uint32_t aLength,
+ nsAString& aResult)
+{
+ return Base64EncodeInputStream(aStream, aResult, aLength);
+}
diff --git a/xpcom/io/nsScriptableBase64Encoder.h b/xpcom/io/nsScriptableBase64Encoder.h
new file mode 100644
index 000000000..d9121e5de
--- /dev/null
+++ b/xpcom/io/nsScriptableBase64Encoder.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsScriptableBase64Encoder_h__
+#define nsScriptableBase64Encoder_h__
+
+#include "nsIScriptableBase64Encoder.h"
+#include "mozilla/Attributes.h"
+
+#define NS_SCRIPTABLEBASE64ENCODER_CID \
+ {0xaaf68860, 0xf849, 0x40ee, \
+ {0xbb, 0x7a, 0xb2, 0x29, 0xbc, 0xe0, 0x36, 0xa3} }
+#define NS_SCRIPTABLEBASE64ENCODER_CONTRACTID \
+ "@mozilla.org/scriptablebase64encoder;1"
+
+class nsScriptableBase64Encoder final : public nsIScriptableBase64Encoder
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCRIPTABLEBASE64ENCODER
+private:
+ ~nsScriptableBase64Encoder()
+ {
+ }
+};
+
+#endif
diff --git a/xpcom/io/nsScriptableInputStream.cpp b/xpcom/io/nsScriptableInputStream.cpp
new file mode 100644
index 000000000..6f8022238
--- /dev/null
+++ b/xpcom/io/nsScriptableInputStream.cpp
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsScriptableInputStream.h"
+#include "nsMemory.h"
+#include "nsString.h"
+
+NS_IMPL_ISUPPORTS(nsScriptableInputStream, nsIScriptableInputStream)
+
+// nsIScriptableInputStream methods
+NS_IMETHODIMP
+nsScriptableInputStream::Close()
+{
+ if (!mInputStream) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ return mInputStream->Close();
+}
+
+NS_IMETHODIMP
+nsScriptableInputStream::Init(nsIInputStream* aInputStream)
+{
+ if (!aInputStream) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ mInputStream = aInputStream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptableInputStream::Available(uint64_t* aResult)
+{
+ if (!mInputStream) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ return mInputStream->Available(aResult);
+}
+
+NS_IMETHODIMP
+nsScriptableInputStream::Read(uint32_t aCount, char** aResult)
+{
+ nsresult rv = NS_OK;
+ uint64_t count64 = 0;
+ char* buffer = nullptr;
+
+ if (!mInputStream) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ rv = mInputStream->Available(&count64);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // bug716556 - Ensure count+1 doesn't overflow
+ uint32_t count =
+ XPCOM_MIN((uint32_t)XPCOM_MIN<uint64_t>(count64, aCount), UINT32_MAX - 1);
+ buffer = (char*)malloc(count + 1); // make room for '\0'
+ if (!buffer) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = ReadHelper(buffer, count);
+ if (NS_FAILED(rv)) {
+ free(buffer);
+ return rv;
+ }
+
+ buffer[count] = '\0';
+ *aResult = buffer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScriptableInputStream::ReadBytes(uint32_t aCount, nsACString& aResult)
+{
+ if (!mInputStream) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (!aResult.SetLength(aCount, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ MOZ_ASSERT(aResult.Length() == aCount);
+ char* ptr = aResult.BeginWriting();
+ nsresult rv = ReadHelper(ptr, aCount);
+ if (NS_FAILED(rv)) {
+ aResult.Truncate();
+ }
+ return rv;
+}
+
+nsresult
+nsScriptableInputStream::ReadHelper(char* aBuffer, uint32_t aCount)
+{
+ uint32_t totalBytesRead = 0;
+ while (1) {
+ uint32_t bytesRead;
+ nsresult rv = mInputStream->Read(aBuffer + totalBytesRead,
+ aCount - totalBytesRead,
+ &bytesRead);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ totalBytesRead += bytesRead;
+ if (totalBytesRead == aCount) {
+ break;
+ }
+
+ // If we have read zero bytes, we have hit EOF.
+ if (bytesRead == 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ }
+ return NS_OK;
+}
+
+nsresult
+nsScriptableInputStream::Create(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult)
+{
+ if (aOuter) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ RefPtr<nsScriptableInputStream> sis = new nsScriptableInputStream();
+ return sis->QueryInterface(aIID, aResult);
+}
diff --git a/xpcom/io/nsScriptableInputStream.h b/xpcom/io/nsScriptableInputStream.h
new file mode 100644
index 000000000..a84facd94
--- /dev/null
+++ b/xpcom/io/nsScriptableInputStream.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ___nsscriptableinputstream___h_
+#define ___nsscriptableinputstream___h_
+
+#include "nsIScriptableInputStream.h"
+#include "nsIInputStream.h"
+#include "nsCOMPtr.h"
+#include "mozilla/Attributes.h"
+
+#define NS_SCRIPTABLEINPUTSTREAM_CID \
+{ 0x7225c040, 0xa9bf, 0x11d3, { 0xa1, 0x97, 0x0, 0x50, 0x4, 0x1c, 0xaf, 0x44 } }
+
+#define NS_SCRIPTABLEINPUTSTREAM_CONTRACTID "@mozilla.org/scriptableinputstream;1"
+
+class nsScriptableInputStream final : public nsIScriptableInputStream
+{
+public:
+ // nsISupports methods
+ NS_DECL_ISUPPORTS
+
+ // nsIScriptableInputStream methods
+ NS_DECL_NSISCRIPTABLEINPUTSTREAM
+
+ // nsScriptableInputStream methods
+ nsScriptableInputStream()
+ {
+ }
+
+ static nsresult
+ Create(nsISupports* aOuter, REFNSIID aIID, void** aResult);
+
+private:
+ ~nsScriptableInputStream()
+ {
+ }
+
+ nsresult ReadHelper(char* aBuffer, uint32_t aCount);
+
+ nsCOMPtr<nsIInputStream> mInputStream;
+};
+
+#endif // ___nsscriptableinputstream___h_
diff --git a/xpcom/io/nsSegmentedBuffer.cpp b/xpcom/io/nsSegmentedBuffer.cpp
new file mode 100644
index 000000000..ab42a73c7
--- /dev/null
+++ b/xpcom/io/nsSegmentedBuffer.cpp
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSegmentedBuffer.h"
+#include "nsMemory.h"
+
+nsresult
+nsSegmentedBuffer::Init(uint32_t aSegmentSize, uint32_t aMaxSize)
+{
+ if (mSegmentArrayCount != 0) {
+ return NS_ERROR_FAILURE; // initialized more than once
+ }
+ mSegmentSize = aSegmentSize;
+ mMaxSize = aMaxSize;
+#if 0 // testing...
+ mSegmentArrayCount = 2;
+#else
+ mSegmentArrayCount = NS_SEGMENTARRAY_INITIAL_COUNT;
+#endif
+ return NS_OK;
+}
+
+char*
+nsSegmentedBuffer::AppendNewSegment()
+{
+ if (GetSize() >= mMaxSize) {
+ return nullptr;
+ }
+
+ if (!mSegmentArray) {
+ uint32_t bytes = mSegmentArrayCount * sizeof(char*);
+ mSegmentArray = (char**)moz_xmalloc(bytes);
+ if (!mSegmentArray) {
+ return nullptr;
+ }
+ memset(mSegmentArray, 0, bytes);
+ }
+
+ if (IsFull()) {
+ uint32_t newArraySize = mSegmentArrayCount * 2;
+ uint32_t bytes = newArraySize * sizeof(char*);
+ char** newSegArray = (char**)moz_xrealloc(mSegmentArray, bytes);
+ if (!newSegArray) {
+ return nullptr;
+ }
+ mSegmentArray = newSegArray;
+ // copy wrapped content to new extension
+ if (mFirstSegmentIndex > mLastSegmentIndex) {
+ // deal with wrap around case
+ memcpy(&mSegmentArray[mSegmentArrayCount],
+ mSegmentArray,
+ mLastSegmentIndex * sizeof(char*));
+ memset(mSegmentArray, 0, mLastSegmentIndex * sizeof(char*));
+ mLastSegmentIndex += mSegmentArrayCount;
+ memset(&mSegmentArray[mLastSegmentIndex], 0,
+ (newArraySize - mLastSegmentIndex) * sizeof(char*));
+ } else {
+ memset(&mSegmentArray[mLastSegmentIndex], 0,
+ (newArraySize - mLastSegmentIndex) * sizeof(char*));
+ }
+ mSegmentArrayCount = newArraySize;
+ }
+
+ char* seg = (char*)malloc(mSegmentSize);
+ if (!seg) {
+ return nullptr;
+ }
+ mSegmentArray[mLastSegmentIndex] = seg;
+ mLastSegmentIndex = ModSegArraySize(mLastSegmentIndex + 1);
+ return seg;
+}
+
+bool
+nsSegmentedBuffer::DeleteFirstSegment()
+{
+ NS_ASSERTION(mSegmentArray[mFirstSegmentIndex] != nullptr, "deleting bad segment");
+ free(mSegmentArray[mFirstSegmentIndex]);
+ mSegmentArray[mFirstSegmentIndex] = nullptr;
+ int32_t last = ModSegArraySize(mLastSegmentIndex - 1);
+ if (mFirstSegmentIndex == last) {
+ mLastSegmentIndex = last;
+ return true;
+ } else {
+ mFirstSegmentIndex = ModSegArraySize(mFirstSegmentIndex + 1);
+ return false;
+ }
+}
+
+bool
+nsSegmentedBuffer::DeleteLastSegment()
+{
+ int32_t last = ModSegArraySize(mLastSegmentIndex - 1);
+ NS_ASSERTION(mSegmentArray[last] != nullptr, "deleting bad segment");
+ free(mSegmentArray[last]);
+ mSegmentArray[last] = nullptr;
+ mLastSegmentIndex = last;
+ return (bool)(mLastSegmentIndex == mFirstSegmentIndex);
+}
+
+bool
+nsSegmentedBuffer::ReallocLastSegment(size_t aNewSize)
+{
+ int32_t last = ModSegArraySize(mLastSegmentIndex - 1);
+ NS_ASSERTION(mSegmentArray[last] != nullptr, "realloc'ing bad segment");
+ char* newSegment = (char*)realloc(mSegmentArray[last], aNewSize);
+ if (newSegment) {
+ mSegmentArray[last] = newSegment;
+ return true;
+ }
+ return false;
+}
+
+void
+nsSegmentedBuffer::Empty()
+{
+ if (mSegmentArray) {
+ for (uint32_t i = 0; i < mSegmentArrayCount; i++) {
+ if (mSegmentArray[i]) {
+ free(mSegmentArray[i]);
+ }
+ }
+ free(mSegmentArray);
+ mSegmentArray = nullptr;
+ }
+ mSegmentArrayCount = NS_SEGMENTARRAY_INITIAL_COUNT;
+ mFirstSegmentIndex = mLastSegmentIndex = 0;
+}
+
+#if 0
+void
+TestSegmentedBuffer()
+{
+ nsSegmentedBuffer* buf = new nsSegmentedBuffer();
+ NS_ASSERTION(buf, "out of memory");
+ buf->Init(4, 16);
+ char* seg;
+ bool empty;
+ seg = buf->AppendNewSegment();
+ NS_ASSERTION(seg, "AppendNewSegment failed");
+ seg = buf->AppendNewSegment();
+ NS_ASSERTION(seg, "AppendNewSegment failed");
+ seg = buf->AppendNewSegment();
+ NS_ASSERTION(seg, "AppendNewSegment failed");
+ empty = buf->DeleteFirstSegment();
+ NS_ASSERTION(!empty, "DeleteFirstSegment failed");
+ empty = buf->DeleteFirstSegment();
+ NS_ASSERTION(!empty, "DeleteFirstSegment failed");
+ seg = buf->AppendNewSegment();
+ NS_ASSERTION(seg, "AppendNewSegment failed");
+ seg = buf->AppendNewSegment();
+ NS_ASSERTION(seg, "AppendNewSegment failed");
+ seg = buf->AppendNewSegment();
+ NS_ASSERTION(seg, "AppendNewSegment failed");
+ empty = buf->DeleteFirstSegment();
+ NS_ASSERTION(!empty, "DeleteFirstSegment failed");
+ empty = buf->DeleteFirstSegment();
+ NS_ASSERTION(!empty, "DeleteFirstSegment failed");
+ empty = buf->DeleteFirstSegment();
+ NS_ASSERTION(!empty, "DeleteFirstSegment failed");
+ empty = buf->DeleteFirstSegment();
+ NS_ASSERTION(empty, "DeleteFirstSegment failed");
+ delete buf;
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
diff --git a/xpcom/io/nsSegmentedBuffer.h b/xpcom/io/nsSegmentedBuffer.h
new file mode 100644
index 000000000..37b3a8e4f
--- /dev/null
+++ b/xpcom/io/nsSegmentedBuffer.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSegmentedBuffer_h__
+#define nsSegmentedBuffer_h__
+
+#include "nsIMemory.h"
+
+class nsSegmentedBuffer
+{
+public:
+ nsSegmentedBuffer()
+ : mSegmentSize(0)
+ , mMaxSize(0)
+ , mSegmentArray(nullptr)
+ , mSegmentArrayCount(0)
+ , mFirstSegmentIndex(0)
+ , mLastSegmentIndex(0)
+ {
+ }
+
+ ~nsSegmentedBuffer()
+ {
+ Empty();
+ }
+
+
+ nsresult Init(uint32_t aSegmentSize, uint32_t aMaxSize);
+
+ char* AppendNewSegment(); // pushes at end
+
+ // returns true if no more segments remain:
+ bool DeleteFirstSegment(); // pops from beginning
+
+ // returns true if no more segments remain:
+ bool DeleteLastSegment(); // pops from beginning
+
+ // Call Realloc() on last segment. This is used to reduce memory
+ // consumption when data is not an exact multiple of segment size.
+ bool ReallocLastSegment(size_t aNewSize);
+
+ void Empty(); // frees all segments
+
+ inline uint32_t GetSegmentCount()
+ {
+ if (mFirstSegmentIndex <= mLastSegmentIndex) {
+ return mLastSegmentIndex - mFirstSegmentIndex;
+ } else {
+ return mSegmentArrayCount + mLastSegmentIndex - mFirstSegmentIndex;
+ }
+ }
+
+ inline uint32_t GetSegmentSize()
+ {
+ return mSegmentSize;
+ }
+ inline uint32_t GetMaxSize()
+ {
+ return mMaxSize;
+ }
+ inline uint32_t GetSize()
+ {
+ return GetSegmentCount() * mSegmentSize;
+ }
+
+ inline char* GetSegment(uint32_t aIndex)
+ {
+ NS_ASSERTION(aIndex < GetSegmentCount(), "index out of bounds");
+ int32_t i = ModSegArraySize(mFirstSegmentIndex + (int32_t)aIndex);
+ return mSegmentArray[i];
+ }
+
+protected:
+ inline int32_t ModSegArraySize(int32_t aIndex)
+ {
+ uint32_t result = aIndex & (mSegmentArrayCount - 1);
+ NS_ASSERTION(result == aIndex % mSegmentArrayCount,
+ "non-power-of-2 mSegmentArrayCount");
+ return result;
+ }
+
+ inline bool IsFull()
+ {
+ return ModSegArraySize(mLastSegmentIndex + 1) == mFirstSegmentIndex;
+ }
+
+protected:
+ uint32_t mSegmentSize;
+ uint32_t mMaxSize;
+ char** mSegmentArray;
+ uint32_t mSegmentArrayCount;
+ int32_t mFirstSegmentIndex;
+ int32_t mLastSegmentIndex;
+};
+
+// NS_SEGMENTARRAY_INITIAL_SIZE: This number needs to start out as a
+// power of 2 given how it gets used. We double the segment array
+// when we overflow it, and use that fact that it's a power of 2
+// to compute a fast modulus operation in IsFull.
+//
+// 32 segment array entries can accommodate 128k of data if segments
+// are 4k in size. That seems like a reasonable amount that will avoid
+// needing to grow the segment array.
+#define NS_SEGMENTARRAY_INITIAL_COUNT 32
+
+#endif // nsSegmentedBuffer_h__
diff --git a/xpcom/io/nsStorageStream.cpp b/xpcom/io/nsStorageStream.cpp
new file mode 100644
index 000000000..3af7bb853
--- /dev/null
+++ b/xpcom/io/nsStorageStream.cpp
@@ -0,0 +1,648 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * The storage stream provides an internal buffer that can be filled by a
+ * client using a single output stream. One or more independent input streams
+ * can be created to read the data out non-destructively. The implementation
+ * uses a segmented buffer internally to avoid realloc'ing of large buffers,
+ * with the attendant performance loss and heap fragmentation.
+ */
+
+#include "nsAlgorithm.h"
+#include "nsStorageStream.h"
+#include "nsSegmentedBuffer.h"
+#include "nsStreamUtils.h"
+#include "nsCOMPtr.h"
+#include "nsICloneableInputStream.h"
+#include "nsIInputStream.h"
+#include "nsIIPCSerializableInputStream.h"
+#include "nsISeekableStream.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+
+using mozilla::ipc::InputStreamParams;
+using mozilla::ipc::StringInputStreamParams;
+using mozilla::Maybe;
+using mozilla::Some;
+
+//
+// Log module for StorageStream logging...
+//
+// To enable logging (see prlog.h for full details):
+//
+// set MOZ_LOG=StorageStreamLog:5
+// set MOZ_LOG_FILE=storage.log
+//
+// This enables LogLevel::Debug level information and places all output in
+// the file storage.log.
+//
+static LazyLogModule sStorageStreamLog("nsStorageStream");
+#ifdef LOG
+#undef LOG
+#endif
+#define LOG(args) MOZ_LOG(sStorageStreamLog, mozilla::LogLevel::Debug, args)
+
+nsStorageStream::nsStorageStream()
+ : mSegmentedBuffer(0), mSegmentSize(0), mWriteInProgress(false),
+ mLastSegmentNum(-1), mWriteCursor(0), mSegmentEnd(0), mLogicalLength(0)
+{
+ LOG(("Creating nsStorageStream [%p].\n", this));
+}
+
+nsStorageStream::~nsStorageStream()
+{
+ delete mSegmentedBuffer;
+}
+
+NS_IMPL_ISUPPORTS(nsStorageStream,
+ nsIStorageStream,
+ nsIOutputStream)
+
+NS_IMETHODIMP
+nsStorageStream::Init(uint32_t aSegmentSize, uint32_t aMaxSize)
+{
+ mSegmentedBuffer = new nsSegmentedBuffer();
+ mSegmentSize = aSegmentSize;
+ mSegmentSizeLog2 = mozilla::FloorLog2(aSegmentSize);
+
+ // Segment size must be a power of two
+ if (mSegmentSize != ((uint32_t)1 << mSegmentSizeLog2)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return mSegmentedBuffer->Init(aSegmentSize, aMaxSize);
+}
+
+NS_IMETHODIMP
+nsStorageStream::GetOutputStream(int32_t aStartingOffset,
+ nsIOutputStream** aOutputStream)
+{
+ if (NS_WARN_IF(!aOutputStream)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!mSegmentedBuffer)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (mWriteInProgress) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = Seek(aStartingOffset);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Enlarge the last segment in the buffer so that it is the same size as
+ // all the other segments in the buffer. (It may have been realloc'ed
+ // smaller in the Close() method.)
+ if (mLastSegmentNum >= 0)
+ if (mSegmentedBuffer->ReallocLastSegment(mSegmentSize)) {
+ // Need to re-Seek, since realloc changed segment base pointer
+ rv = Seek(aStartingOffset);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ NS_ADDREF(this);
+ *aOutputStream = static_cast<nsIOutputStream*>(this);
+ mWriteInProgress = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStorageStream::Close()
+{
+ if (NS_WARN_IF(!mSegmentedBuffer)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ mWriteInProgress = false;
+
+ int32_t segmentOffset = SegOffset(mLogicalLength);
+
+ // Shrink the final segment in the segmented buffer to the minimum size
+ // needed to contain the data, so as to conserve memory.
+ if (segmentOffset) {
+ mSegmentedBuffer->ReallocLastSegment(segmentOffset);
+ }
+
+ mWriteCursor = 0;
+ mSegmentEnd = 0;
+
+ LOG(("nsStorageStream [%p] Close mWriteCursor=%x mSegmentEnd=%x\n",
+ this, mWriteCursor, mSegmentEnd));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStorageStream::Flush()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStorageStream::Write(const char* aBuffer, uint32_t aCount,
+ uint32_t* aNumWritten)
+{
+ if (NS_WARN_IF(!aNumWritten) || NS_WARN_IF(!aBuffer)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (NS_WARN_IF(!mSegmentedBuffer)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ const char* readCursor;
+ uint32_t count, availableInSegment, remaining;
+ nsresult rv = NS_OK;
+
+ LOG(("nsStorageStream [%p] Write mWriteCursor=%x mSegmentEnd=%x aCount=%d\n",
+ this, mWriteCursor, mSegmentEnd, aCount));
+
+ remaining = aCount;
+ readCursor = aBuffer;
+ // If no segments have been created yet, create one even if we don't have
+ // to write any data; this enables creating an input stream which reads from
+ // the very end of the data for any amount of data in the stream (i.e.
+ // this stream contains N bytes of data and newInputStream(N) is called),
+ // even for N=0 (with the caveat that we require .write("", 0) be called to
+ // initialize internal buffers).
+ bool firstTime = mSegmentedBuffer->GetSegmentCount() == 0;
+ while (remaining || MOZ_UNLIKELY(firstTime)) {
+ firstTime = false;
+ availableInSegment = mSegmentEnd - mWriteCursor;
+ if (!availableInSegment) {
+ mWriteCursor = mSegmentedBuffer->AppendNewSegment();
+ if (!mWriteCursor) {
+ mSegmentEnd = 0;
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto out;
+ }
+ mLastSegmentNum++;
+ mSegmentEnd = mWriteCursor + mSegmentSize;
+ availableInSegment = mSegmentEnd - mWriteCursor;
+ LOG(("nsStorageStream [%p] Write (new seg) mWriteCursor=%x mSegmentEnd=%x\n",
+ this, mWriteCursor, mSegmentEnd));
+ }
+
+ count = XPCOM_MIN(availableInSegment, remaining);
+ memcpy(mWriteCursor, readCursor, count);
+ remaining -= count;
+ readCursor += count;
+ mWriteCursor += count;
+ LOG(("nsStorageStream [%p] Writing mWriteCursor=%x mSegmentEnd=%x count=%d\n",
+ this, mWriteCursor, mSegmentEnd, count));
+ }
+
+out:
+ *aNumWritten = aCount - remaining;
+ mLogicalLength += *aNumWritten;
+
+ LOG(("nsStorageStream [%p] Wrote mWriteCursor=%x mSegmentEnd=%x numWritten=%d\n",
+ this, mWriteCursor, mSegmentEnd, *aNumWritten));
+ return rv;
+}
+
+NS_IMETHODIMP
+nsStorageStream::WriteFrom(nsIInputStream* aInStr, uint32_t aCount,
+ uint32_t* aResult)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsStorageStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure,
+ uint32_t aCount, uint32_t* aResult)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsStorageStream::IsNonBlocking(bool* aNonBlocking)
+{
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStorageStream::GetLength(uint32_t* aLength)
+{
+ *aLength = mLogicalLength;
+ return NS_OK;
+}
+
+// Truncate the buffer by deleting the end segments
+NS_IMETHODIMP
+nsStorageStream::SetLength(uint32_t aLength)
+{
+ if (NS_WARN_IF(!mSegmentedBuffer)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (mWriteInProgress) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (aLength > mLogicalLength) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ int32_t newLastSegmentNum = SegNum(aLength);
+ int32_t segmentOffset = SegOffset(aLength);
+ if (segmentOffset == 0) {
+ newLastSegmentNum--;
+ }
+
+ while (newLastSegmentNum < mLastSegmentNum) {
+ mSegmentedBuffer->DeleteLastSegment();
+ mLastSegmentNum--;
+ }
+
+ mLogicalLength = aLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStorageStream::GetWriteInProgress(bool* aWriteInProgress)
+{
+ *aWriteInProgress = mWriteInProgress;
+ return NS_OK;
+}
+
+nsresult
+nsStorageStream::Seek(int32_t aPosition)
+{
+ if (NS_WARN_IF(!mSegmentedBuffer)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ // An argument of -1 means "seek to end of stream"
+ if (aPosition == -1) {
+ aPosition = mLogicalLength;
+ }
+
+ // Seeking beyond the buffer end is illegal
+ if ((uint32_t)aPosition > mLogicalLength) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Seeking backwards in the write stream results in truncation
+ SetLength(aPosition);
+
+ // Special handling for seek to start-of-buffer
+ if (aPosition == 0) {
+ mWriteCursor = 0;
+ mSegmentEnd = 0;
+ LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n",
+ this, mWriteCursor, mSegmentEnd));
+ return NS_OK;
+ }
+
+ // Segment may have changed, so reset pointers
+ mWriteCursor = mSegmentedBuffer->GetSegment(mLastSegmentNum);
+ NS_ASSERTION(mWriteCursor, "null mWriteCursor");
+ mSegmentEnd = mWriteCursor + mSegmentSize;
+
+ // Adjust write cursor for current segment offset. This test is necessary
+ // because SegNum may reference the next-to-be-allocated segment, in which
+ // case we need to be pointing at the end of the last segment.
+ int32_t segmentOffset = SegOffset(aPosition);
+ if (segmentOffset == 0 && (SegNum(aPosition) > (uint32_t) mLastSegmentNum)) {
+ mWriteCursor = mSegmentEnd;
+ } else {
+ mWriteCursor += segmentOffset;
+ }
+
+ LOG(("nsStorageStream [%p] Seek mWriteCursor=%x mSegmentEnd=%x\n",
+ this, mWriteCursor, mSegmentEnd));
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// There can be many nsStorageInputStreams for a single nsStorageStream
+class nsStorageInputStream final
+ : public nsIInputStream
+ , public nsISeekableStream
+ , public nsIIPCSerializableInputStream
+ , public nsICloneableInputStream
+{
+public:
+ nsStorageInputStream(nsStorageStream* aStorageStream, uint32_t aSegmentSize)
+ : mStorageStream(aStorageStream), mReadCursor(0),
+ mSegmentEnd(0), mSegmentNum(0),
+ mSegmentSize(aSegmentSize), mLogicalCursor(0),
+ mStatus(NS_OK)
+ {
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+ NS_DECL_NSICLONEABLEINPUTSTREAM
+
+private:
+ ~nsStorageInputStream()
+ {
+ }
+
+protected:
+ nsresult Seek(uint32_t aPosition);
+
+ friend class nsStorageStream;
+
+private:
+ RefPtr<nsStorageStream> mStorageStream;
+ uint32_t mReadCursor; // Next memory location to read byte, or 0
+ uint32_t mSegmentEnd; // One byte past end of current buffer segment
+ uint32_t mSegmentNum; // Segment number containing read cursor
+ uint32_t mSegmentSize; // All segments, except the last, are of this size
+ uint32_t mLogicalCursor; // Logical offset into stream
+ nsresult mStatus;
+
+ uint32_t SegNum(uint32_t aPosition)
+ {
+ return aPosition >> mStorageStream->mSegmentSizeLog2;
+ }
+ uint32_t SegOffset(uint32_t aPosition)
+ {
+ return aPosition & (mSegmentSize - 1);
+ }
+};
+
+NS_IMPL_ISUPPORTS(nsStorageInputStream,
+ nsIInputStream,
+ nsISeekableStream,
+ nsIIPCSerializableInputStream,
+ nsICloneableInputStream)
+
+NS_IMETHODIMP
+nsStorageStream::NewInputStream(int32_t aStartingOffset,
+ nsIInputStream** aInputStream)
+{
+ if (NS_WARN_IF(!mSegmentedBuffer)) {
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ RefPtr<nsStorageInputStream> inputStream =
+ new nsStorageInputStream(this, mSegmentSize);
+
+ nsresult rv = inputStream->Seek(aStartingOffset);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ inputStream.forget(aInputStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStorageInputStream::Close()
+{
+ mStatus = NS_BASE_STREAM_CLOSED;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStorageInputStream::Available(uint64_t* aAvailable)
+{
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ *aAvailable = mStorageStream->mLogicalLength - mLogicalCursor;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStorageInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aNumRead)
+{
+ return ReadSegments(NS_CopySegmentToBuffer, aBuffer, aCount, aNumRead);
+}
+
+NS_IMETHODIMP
+nsStorageInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aNumRead)
+{
+ *aNumRead = 0;
+ if (mStatus == NS_BASE_STREAM_CLOSED) {
+ return NS_OK;
+ }
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ uint32_t count, availableInSegment, remainingCapacity, bytesConsumed;
+ nsresult rv;
+
+ remainingCapacity = aCount;
+ while (remainingCapacity) {
+ availableInSegment = mSegmentEnd - mReadCursor;
+ if (!availableInSegment) {
+ uint32_t available = mStorageStream->mLogicalLength - mLogicalCursor;
+ if (!available) {
+ goto out;
+ }
+
+ // We have data in the stream, but if mSegmentEnd is zero, then we
+ // were likely constructed prior to any data being written into
+ // the stream. Therefore, if mSegmentEnd is non-zero, we should
+ // move into the next segment; otherwise, we should stay in this
+ // segment so our input state can be updated and we can properly
+ // perform the initial read.
+ if (mSegmentEnd > 0) {
+ mSegmentNum++;
+ }
+ mReadCursor = 0;
+ mSegmentEnd = XPCOM_MIN(mSegmentSize, available);
+ availableInSegment = mSegmentEnd;
+ }
+ const char* cur = mStorageStream->mSegmentedBuffer->GetSegment(mSegmentNum);
+
+ count = XPCOM_MIN(availableInSegment, remainingCapacity);
+ rv = aWriter(this, aClosure, cur + mReadCursor, aCount - remainingCapacity,
+ count, &bytesConsumed);
+ if (NS_FAILED(rv) || (bytesConsumed == 0)) {
+ break;
+ }
+ remainingCapacity -= bytesConsumed;
+ mReadCursor += bytesConsumed;
+ mLogicalCursor += bytesConsumed;
+ }
+
+out:
+ *aNumRead = aCount - remainingCapacity;
+
+ bool isWriteInProgress = false;
+ if (NS_FAILED(mStorageStream->GetWriteInProgress(&isWriteInProgress))) {
+ isWriteInProgress = false;
+ }
+
+ if (*aNumRead == 0 && isWriteInProgress) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStorageInputStream::IsNonBlocking(bool* aNonBlocking)
+{
+ // TODO: This class should implement nsIAsyncInputStream so that callers
+ // have some way of dealing with NS_BASE_STREAM_WOULD_BLOCK errors.
+
+ *aNonBlocking = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStorageInputStream::Seek(int32_t aWhence, int64_t aOffset)
+{
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ int64_t pos = aOffset;
+
+ switch (aWhence) {
+ case NS_SEEK_SET:
+ break;
+ case NS_SEEK_CUR:
+ pos += mLogicalCursor;
+ break;
+ case NS_SEEK_END:
+ pos += mStorageStream->mLogicalLength;
+ break;
+ default:
+ NS_NOTREACHED("unexpected whence value");
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (pos == int64_t(mLogicalCursor)) {
+ return NS_OK;
+ }
+
+ return Seek(pos);
+}
+
+NS_IMETHODIMP
+nsStorageInputStream::Tell(int64_t* aResult)
+{
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ *aResult = mLogicalCursor;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStorageInputStream::SetEOF()
+{
+ NS_NOTREACHED("nsStorageInputStream::SetEOF");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult
+nsStorageInputStream::Seek(uint32_t aPosition)
+{
+ uint32_t length = mStorageStream->mLogicalLength;
+ if (aPosition > length) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (length == 0) {
+ return NS_OK;
+ }
+
+ mSegmentNum = SegNum(aPosition);
+ mReadCursor = SegOffset(aPosition);
+ uint32_t available = length - aPosition;
+ mSegmentEnd = mReadCursor + XPCOM_MIN(mSegmentSize - mReadCursor, available);
+ mLogicalCursor = aPosition;
+ return NS_OK;
+}
+
+void
+nsStorageInputStream::Serialize(InputStreamParams& aParams, FileDescriptorArray&)
+{
+ nsCString combined;
+ int64_t offset;
+ mozilla::DebugOnly<nsresult> rv = Tell(&offset);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ uint64_t remaining;
+ rv = Available(&remaining);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ combined.SetCapacity(remaining);
+ uint32_t numRead = 0;
+
+ rv = Read(combined.BeginWriting(), remaining, &numRead);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(numRead == remaining);
+ combined.SetLength(numRead);
+
+ rv = Seek(NS_SEEK_SET, offset);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ StringInputStreamParams params;
+ params.data() = combined;
+ aParams = params;
+}
+
+Maybe<uint64_t>
+nsStorageInputStream::ExpectedSerializedLength()
+{
+ uint64_t remaining = 0;
+ DebugOnly<nsresult> rv = Available(&remaining);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ return Some(remaining);
+}
+
+bool
+nsStorageInputStream::Deserialize(const InputStreamParams& aParams,
+ const FileDescriptorArray&)
+{
+ NS_NOTREACHED("We should never attempt to deserialize a storage input stream.");
+ return false;
+}
+
+NS_IMETHODIMP
+nsStorageInputStream::GetCloneable(bool* aCloneableOut)
+{
+ *aCloneableOut = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStorageInputStream::Clone(nsIInputStream** aCloneOut)
+{
+ return mStorageStream->NewInputStream(mLogicalCursor, aCloneOut);
+}
+
+nsresult
+NS_NewStorageStream(uint32_t aSegmentSize, uint32_t aMaxSize,
+ nsIStorageStream** aResult)
+{
+ RefPtr<nsStorageStream> storageStream = new nsStorageStream();
+ nsresult rv = storageStream->Init(aSegmentSize, aMaxSize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ storageStream.forget(aResult);
+ return NS_OK;
+}
+
+// Undefine LOG, so that other .cpp files (or their includes) won't complain
+// about it already being defined, when we build in unified mode.
+#undef LOG
diff --git a/xpcom/io/nsStorageStream.h b/xpcom/io/nsStorageStream.h
new file mode 100644
index 000000000..7fddd683e
--- /dev/null
+++ b/xpcom/io/nsStorageStream.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * The storage stream provides an internal buffer that can be filled by a
+ * client using a single output stream. One or more independent input streams
+ * can be created to read the data out non-destructively. The implementation
+ * uses a segmented buffer internally to avoid realloc'ing of large buffers,
+ * with the attendant performance loss and heap fragmentation.
+ */
+
+#ifndef _nsStorageStream_h_
+#define _nsStorageStream_h_
+
+#include "nsIStorageStream.h"
+#include "nsIOutputStream.h"
+#include "nsMemory.h"
+#include "mozilla/Attributes.h"
+
+#define NS_STORAGESTREAM_CID \
+{ /* 669a9795-6ff7-4ed4-9150-c34ce2971b63 */ \
+ 0x669a9795, \
+ 0x6ff7, \
+ 0x4ed4, \
+ {0x91, 0x50, 0xc3, 0x4c, 0xe2, 0x97, 0x1b, 0x63} \
+}
+
+#define NS_STORAGESTREAM_CONTRACTID "@mozilla.org/storagestream;1"
+
+class nsSegmentedBuffer;
+
+class nsStorageStream final
+ : public nsIStorageStream
+ , public nsIOutputStream
+{
+public:
+ nsStorageStream();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTORAGESTREAM
+ NS_DECL_NSIOUTPUTSTREAM
+
+ friend class nsStorageInputStream;
+
+private:
+ ~nsStorageStream();
+
+ nsSegmentedBuffer* mSegmentedBuffer;
+ uint32_t mSegmentSize; // All segments, except possibly the last, are of this size
+ // Must be power-of-2
+ uint32_t mSegmentSizeLog2; // log2(mSegmentSize)
+ bool mWriteInProgress; // true, if an un-Close'ed output stream exists
+ int32_t mLastSegmentNum; // Last segment # in use, -1 initially
+ char* mWriteCursor; // Pointer to next byte to be written
+ char* mSegmentEnd; // Pointer to one byte after end of segment
+ // containing the write cursor
+ uint32_t mLogicalLength; // Number of bytes written to stream
+
+ nsresult Seek(int32_t aPosition);
+ uint32_t SegNum(uint32_t aPosition)
+ {
+ return aPosition >> mSegmentSizeLog2;
+ }
+ uint32_t SegOffset(uint32_t aPosition)
+ {
+ return aPosition & (mSegmentSize - 1);
+ }
+};
+
+#endif // _nsStorageStream_h_
diff --git a/xpcom/io/nsStreamUtils.cpp b/xpcom/io/nsStreamUtils.cpp
new file mode 100644
index 000000000..891dad59f
--- /dev/null
+++ b/xpcom/io/nsStreamUtils.cpp
@@ -0,0 +1,957 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Mutex.h"
+#include "mozilla/Attributes.h"
+#include "nsStreamUtils.h"
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIPipe.h"
+#include "nsICloneableInputStream.h"
+#include "nsIEventTarget.h"
+#include "nsICancelableRunnable.h"
+#include "nsISafeOutputStream.h"
+#include "nsString.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIBufferedStreams.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+//-----------------------------------------------------------------------------
+
+// This is a nsICancelableRunnable because we can dispatch it to Workers and
+// those can be shut down at any time, and in these cases, Cancel() is called
+// instead of Run().
+class nsInputStreamReadyEvent final
+ : public CancelableRunnable
+ , public nsIInputStreamCallback
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsInputStreamReadyEvent(nsIInputStreamCallback* aCallback,
+ nsIEventTarget* aTarget)
+ : mCallback(aCallback)
+ , mTarget(aTarget)
+ {
+ }
+
+private:
+ ~nsInputStreamReadyEvent()
+ {
+ if (!mCallback) {
+ return;
+ }
+ //
+ // whoa!! looks like we never posted this event. take care to
+ // release mCallback on the correct thread. if mTarget lives on the
+ // calling thread, then we are ok. otherwise, we have to try to
+ // proxy the Release over the right thread. if that thread is dead,
+ // then there's nothing we can do... better to leak than crash.
+ //
+ bool val;
+ nsresult rv = mTarget->IsOnCurrentThread(&val);
+ if (NS_FAILED(rv) || !val) {
+ nsCOMPtr<nsIInputStreamCallback> event =
+ NS_NewInputStreamReadyEvent(mCallback, mTarget);
+ mCallback = nullptr;
+ if (event) {
+ rv = event->OnInputStreamReady(nullptr);
+ if (NS_FAILED(rv)) {
+ NS_NOTREACHED("leaking stream event");
+ nsISupports* sup = event;
+ NS_ADDREF(sup);
+ }
+ }
+ }
+ }
+
+public:
+ NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream* aStream) override
+ {
+ mStream = aStream;
+
+ nsresult rv =
+ mTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Dispatch failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (mCallback) {
+ if (mStream) {
+ mCallback->OnInputStreamReady(mStream);
+ }
+ mCallback = nullptr;
+ }
+ return NS_OK;
+ }
+
+ nsresult Cancel() override
+ {
+ mCallback = nullptr;
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIAsyncInputStream> mStream;
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(nsInputStreamReadyEvent, CancelableRunnable,
+ nsIInputStreamCallback)
+
+//-----------------------------------------------------------------------------
+
+// This is a nsICancelableRunnable because we can dispatch it to Workers and
+// those can be shut down at any time, and in these cases, Cancel() is called
+// instead of Run().
+class nsOutputStreamReadyEvent final
+ : public CancelableRunnable
+ , public nsIOutputStreamCallback
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsOutputStreamReadyEvent(nsIOutputStreamCallback* aCallback,
+ nsIEventTarget* aTarget)
+ : mCallback(aCallback)
+ , mTarget(aTarget)
+ {
+ }
+
+private:
+ ~nsOutputStreamReadyEvent()
+ {
+ if (!mCallback) {
+ return;
+ }
+ //
+ // whoa!! looks like we never posted this event. take care to
+ // release mCallback on the correct thread. if mTarget lives on the
+ // calling thread, then we are ok. otherwise, we have to try to
+ // proxy the Release over the right thread. if that thread is dead,
+ // then there's nothing we can do... better to leak than crash.
+ //
+ bool val;
+ nsresult rv = mTarget->IsOnCurrentThread(&val);
+ if (NS_FAILED(rv) || !val) {
+ nsCOMPtr<nsIOutputStreamCallback> event =
+ NS_NewOutputStreamReadyEvent(mCallback, mTarget);
+ mCallback = nullptr;
+ if (event) {
+ rv = event->OnOutputStreamReady(nullptr);
+ if (NS_FAILED(rv)) {
+ NS_NOTREACHED("leaking stream event");
+ nsISupports* sup = event;
+ NS_ADDREF(sup);
+ }
+ }
+ }
+ }
+
+public:
+ NS_IMETHOD OnOutputStreamReady(nsIAsyncOutputStream* aStream) override
+ {
+ mStream = aStream;
+
+ nsresult rv =
+ mTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("PostEvent failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ if (mCallback) {
+ if (mStream) {
+ mCallback->OnOutputStreamReady(mStream);
+ }
+ mCallback = nullptr;
+ }
+ return NS_OK;
+ }
+
+ nsresult Cancel() override
+ {
+ mCallback = nullptr;
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIAsyncOutputStream> mStream;
+ nsCOMPtr<nsIOutputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mTarget;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(nsOutputStreamReadyEvent, CancelableRunnable,
+ nsIOutputStreamCallback)
+
+//-----------------------------------------------------------------------------
+
+already_AddRefed<nsIInputStreamCallback>
+NS_NewInputStreamReadyEvent(nsIInputStreamCallback* aCallback,
+ nsIEventTarget* aTarget)
+{
+ NS_ASSERTION(aCallback, "null callback");
+ NS_ASSERTION(aTarget, "null target");
+ RefPtr<nsInputStreamReadyEvent> ev =
+ new nsInputStreamReadyEvent(aCallback, aTarget);
+ return ev.forget();
+}
+
+already_AddRefed<nsIOutputStreamCallback>
+NS_NewOutputStreamReadyEvent(nsIOutputStreamCallback* aCallback,
+ nsIEventTarget* aTarget)
+{
+ NS_ASSERTION(aCallback, "null callback");
+ NS_ASSERTION(aTarget, "null target");
+ RefPtr<nsOutputStreamReadyEvent> ev =
+ new nsOutputStreamReadyEvent(aCallback, aTarget);
+ return ev.forget();
+}
+
+//-----------------------------------------------------------------------------
+// NS_AsyncCopy implementation
+
+// abstract stream copier...
+class nsAStreamCopier
+ : public nsIInputStreamCallback
+ , public nsIOutputStreamCallback
+ , public CancelableRunnable
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsAStreamCopier()
+ : mLock("nsAStreamCopier.mLock")
+ , mCallback(nullptr)
+ , mProgressCallback(nullptr)
+ , mClosure(nullptr)
+ , mChunkSize(0)
+ , mEventInProcess(false)
+ , mEventIsPending(false)
+ , mCloseSource(true)
+ , mCloseSink(true)
+ , mCanceled(false)
+ , mCancelStatus(NS_OK)
+ {
+ }
+
+ // kick off the async copy...
+ nsresult Start(nsIInputStream* aSource,
+ nsIOutputStream* aSink,
+ nsIEventTarget* aTarget,
+ nsAsyncCopyCallbackFun aCallback,
+ void* aClosure,
+ uint32_t aChunksize,
+ bool aCloseSource,
+ bool aCloseSink,
+ nsAsyncCopyProgressFun aProgressCallback)
+ {
+ mSource = aSource;
+ mSink = aSink;
+ mTarget = aTarget;
+ mCallback = aCallback;
+ mClosure = aClosure;
+ mChunkSize = aChunksize;
+ mCloseSource = aCloseSource;
+ mCloseSink = aCloseSink;
+ mProgressCallback = aProgressCallback;
+
+ mAsyncSource = do_QueryInterface(mSource);
+ mAsyncSink = do_QueryInterface(mSink);
+
+ return PostContinuationEvent();
+ }
+
+ // implemented by subclasses, returns number of bytes copied and
+ // sets source and sink condition before returning.
+ virtual uint32_t DoCopy(nsresult* aSourceCondition,
+ nsresult* aSinkCondition) = 0;
+
+ void Process()
+ {
+ if (!mSource || !mSink) {
+ return;
+ }
+
+ nsresult cancelStatus;
+ bool canceled;
+ {
+ MutexAutoLock lock(mLock);
+ canceled = mCanceled;
+ cancelStatus = mCancelStatus;
+ }
+
+ // If the copy was canceled before Process() was even called, then
+ // sourceCondition and sinkCondition should be set to error results to
+ // ensure we don't call Finish() on a canceled nsISafeOutputStream.
+ MOZ_ASSERT(NS_FAILED(cancelStatus) == canceled, "cancel needs an error");
+ nsresult sourceCondition = cancelStatus;
+ nsresult sinkCondition = cancelStatus;
+
+ // Copy data from the source to the sink until we hit failure or have
+ // copied all the data.
+ for (;;) {
+ // Note: copyFailed will be true if the source or the sink have
+ // reported an error, or if we failed to write any bytes
+ // because we have consumed all of our data.
+ bool copyFailed = false;
+ if (!canceled) {
+ uint32_t n = DoCopy(&sourceCondition, &sinkCondition);
+ if (n > 0 && mProgressCallback) {
+ mProgressCallback(mClosure, n);
+ }
+ copyFailed = NS_FAILED(sourceCondition) ||
+ NS_FAILED(sinkCondition) || n == 0;
+
+ MutexAutoLock lock(mLock);
+ canceled = mCanceled;
+ cancelStatus = mCancelStatus;
+ }
+ if (copyFailed && !canceled) {
+ if (sourceCondition == NS_BASE_STREAM_WOULD_BLOCK && mAsyncSource) {
+ // need to wait for more data from source. while waiting for
+ // more source data, be sure to observe failures on output end.
+ mAsyncSource->AsyncWait(this, 0, 0, nullptr);
+
+ if (mAsyncSink)
+ mAsyncSink->AsyncWait(this,
+ nsIAsyncOutputStream::WAIT_CLOSURE_ONLY,
+ 0, nullptr);
+ break;
+ } else if (sinkCondition == NS_BASE_STREAM_WOULD_BLOCK && mAsyncSink) {
+ // need to wait for more room in the sink. while waiting for
+ // more room in the sink, be sure to observer failures on the
+ // input end.
+ mAsyncSink->AsyncWait(this, 0, 0, nullptr);
+
+ if (mAsyncSource)
+ mAsyncSource->AsyncWait(this,
+ nsIAsyncInputStream::WAIT_CLOSURE_ONLY,
+ 0, nullptr);
+ break;
+ }
+ }
+ if (copyFailed || canceled) {
+ if (mCloseSource) {
+ // close source
+ if (mAsyncSource)
+ mAsyncSource->CloseWithStatus(
+ canceled ? cancelStatus : sinkCondition);
+ else {
+ mSource->Close();
+ }
+ }
+ mAsyncSource = nullptr;
+ mSource = nullptr;
+
+ if (mCloseSink) {
+ // close sink
+ if (mAsyncSink)
+ mAsyncSink->CloseWithStatus(
+ canceled ? cancelStatus : sourceCondition);
+ else {
+ // If we have an nsISafeOutputStream, and our
+ // sourceCondition and sinkCondition are not set to a
+ // failure state, finish writing.
+ nsCOMPtr<nsISafeOutputStream> sostream =
+ do_QueryInterface(mSink);
+ if (sostream && NS_SUCCEEDED(sourceCondition) &&
+ NS_SUCCEEDED(sinkCondition)) {
+ sostream->Finish();
+ } else {
+ mSink->Close();
+ }
+ }
+ }
+ mAsyncSink = nullptr;
+ mSink = nullptr;
+
+ // notify state complete...
+ if (mCallback) {
+ nsresult status;
+ if (!canceled) {
+ status = sourceCondition;
+ if (NS_SUCCEEDED(status)) {
+ status = sinkCondition;
+ }
+ if (status == NS_BASE_STREAM_CLOSED) {
+ status = NS_OK;
+ }
+ } else {
+ status = cancelStatus;
+ }
+ mCallback(mClosure, status);
+ }
+ break;
+ }
+ }
+ }
+
+ nsresult Cancel(nsresult aReason)
+ {
+ MutexAutoLock lock(mLock);
+ if (mCanceled) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_SUCCEEDED(aReason)) {
+ NS_WARNING("cancel with non-failure status code");
+ aReason = NS_BASE_STREAM_CLOSED;
+ }
+
+ mCanceled = true;
+ mCancelStatus = aReason;
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnInputStreamReady(nsIAsyncInputStream* aSource) override
+ {
+ PostContinuationEvent();
+ return NS_OK;
+ }
+
+ NS_IMETHOD OnOutputStreamReady(nsIAsyncOutputStream* aSink) override
+ {
+ PostContinuationEvent();
+ return NS_OK;
+ }
+
+ // continuation event handler
+ NS_IMETHOD Run() override
+ {
+ Process();
+
+ // clear "in process" flag and post any pending continuation event
+ MutexAutoLock lock(mLock);
+ mEventInProcess = false;
+ if (mEventIsPending) {
+ mEventIsPending = false;
+ PostContinuationEvent_Locked();
+ }
+
+ return NS_OK;
+ }
+
+ nsresult Cancel() MOZ_MUST_OVERRIDE override = 0;
+
+ nsresult PostContinuationEvent()
+ {
+ // we cannot post a continuation event if there is currently
+ // an event in process. doing so could result in Process being
+ // run simultaneously on multiple threads, so we mark the event
+ // as pending, and if an event is already in process then we
+ // just let that existing event take care of posting the real
+ // continuation event.
+
+ MutexAutoLock lock(mLock);
+ return PostContinuationEvent_Locked();
+ }
+
+ nsresult PostContinuationEvent_Locked()
+ {
+ nsresult rv = NS_OK;
+ if (mEventInProcess) {
+ mEventIsPending = true;
+ } else {
+ rv = mTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+ if (NS_SUCCEEDED(rv)) {
+ mEventInProcess = true;
+ } else {
+ NS_WARNING("unable to post continuation event");
+ }
+ }
+ return rv;
+ }
+
+protected:
+ nsCOMPtr<nsIInputStream> mSource;
+ nsCOMPtr<nsIOutputStream> mSink;
+ nsCOMPtr<nsIAsyncInputStream> mAsyncSource;
+ nsCOMPtr<nsIAsyncOutputStream> mAsyncSink;
+ nsCOMPtr<nsIEventTarget> mTarget;
+ Mutex mLock;
+ nsAsyncCopyCallbackFun mCallback;
+ nsAsyncCopyProgressFun mProgressCallback;
+ void* mClosure;
+ uint32_t mChunkSize;
+ bool mEventInProcess;
+ bool mEventIsPending;
+ bool mCloseSource;
+ bool mCloseSink;
+ bool mCanceled;
+ nsresult mCancelStatus;
+
+ // virtual since subclasses call superclass Release()
+ virtual ~nsAStreamCopier()
+ {
+ }
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAStreamCopier,
+ CancelableRunnable,
+ nsIInputStreamCallback,
+ nsIOutputStreamCallback)
+
+class nsStreamCopierIB final : public nsAStreamCopier
+{
+public:
+ nsStreamCopierIB() : nsAStreamCopier()
+ {
+ }
+ virtual ~nsStreamCopierIB()
+ {
+ }
+
+ struct MOZ_STACK_CLASS ReadSegmentsState
+ {
+ // the nsIOutputStream will outlive the ReadSegmentsState on the stack
+ nsIOutputStream* MOZ_NON_OWNING_REF mSink;
+ nsresult mSinkCondition;
+ };
+
+ static nsresult ConsumeInputBuffer(nsIInputStream* aInStr,
+ void* aClosure,
+ const char* aBuffer,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t* aCountWritten)
+ {
+ ReadSegmentsState* state = (ReadSegmentsState*)aClosure;
+
+ nsresult rv = state->mSink->Write(aBuffer, aCount, aCountWritten);
+ if (NS_FAILED(rv)) {
+ state->mSinkCondition = rv;
+ } else if (*aCountWritten == 0) {
+ state->mSinkCondition = NS_BASE_STREAM_CLOSED;
+ }
+
+ return state->mSinkCondition;
+ }
+
+ uint32_t DoCopy(nsresult* aSourceCondition,
+ nsresult* aSinkCondition) override
+ {
+ ReadSegmentsState state;
+ state.mSink = mSink;
+ state.mSinkCondition = NS_OK;
+
+ uint32_t n;
+ *aSourceCondition =
+ mSource->ReadSegments(ConsumeInputBuffer, &state, mChunkSize, &n);
+ *aSinkCondition = state.mSinkCondition;
+ return n;
+ }
+
+ nsresult Cancel() override
+ {
+ return NS_OK;
+ }
+};
+
+class nsStreamCopierOB final : public nsAStreamCopier
+{
+public:
+ nsStreamCopierOB() : nsAStreamCopier()
+ {
+ }
+ virtual ~nsStreamCopierOB()
+ {
+ }
+
+ struct MOZ_STACK_CLASS WriteSegmentsState
+ {
+ // the nsIInputStream will outlive the WriteSegmentsState on the stack
+ nsIInputStream* MOZ_NON_OWNING_REF mSource;
+ nsresult mSourceCondition;
+ };
+
+ static nsresult FillOutputBuffer(nsIOutputStream* aOutStr,
+ void* aClosure,
+ char* aBuffer,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t* aCountRead)
+ {
+ WriteSegmentsState* state = (WriteSegmentsState*)aClosure;
+
+ nsresult rv = state->mSource->Read(aBuffer, aCount, aCountRead);
+ if (NS_FAILED(rv)) {
+ state->mSourceCondition = rv;
+ } else if (*aCountRead == 0) {
+ state->mSourceCondition = NS_BASE_STREAM_CLOSED;
+ }
+
+ return state->mSourceCondition;
+ }
+
+ uint32_t DoCopy(nsresult* aSourceCondition,
+ nsresult* aSinkCondition) override
+ {
+ WriteSegmentsState state;
+ state.mSource = mSource;
+ state.mSourceCondition = NS_OK;
+
+ uint32_t n;
+ *aSinkCondition =
+ mSink->WriteSegments(FillOutputBuffer, &state, mChunkSize, &n);
+ *aSourceCondition = state.mSourceCondition;
+ return n;
+ }
+
+ nsresult Cancel() override
+ {
+ return NS_OK;
+ }
+};
+
+//-----------------------------------------------------------------------------
+
+nsresult
+NS_AsyncCopy(nsIInputStream* aSource,
+ nsIOutputStream* aSink,
+ nsIEventTarget* aTarget,
+ nsAsyncCopyMode aMode,
+ uint32_t aChunkSize,
+ nsAsyncCopyCallbackFun aCallback,
+ void* aClosure,
+ bool aCloseSource,
+ bool aCloseSink,
+ nsISupports** aCopierCtx,
+ nsAsyncCopyProgressFun aProgressCallback)
+{
+ NS_ASSERTION(aTarget, "non-null target required");
+
+ nsresult rv;
+ nsAStreamCopier* copier;
+
+ if (aMode == NS_ASYNCCOPY_VIA_READSEGMENTS) {
+ copier = new nsStreamCopierIB();
+ } else {
+ copier = new nsStreamCopierOB();
+ }
+
+ // Start() takes an owning ref to the copier...
+ NS_ADDREF(copier);
+ rv = copier->Start(aSource, aSink, aTarget, aCallback, aClosure, aChunkSize,
+ aCloseSource, aCloseSink, aProgressCallback);
+
+ if (aCopierCtx) {
+ *aCopierCtx = static_cast<nsISupports*>(static_cast<nsIRunnable*>(copier));
+ NS_ADDREF(*aCopierCtx);
+ }
+ NS_RELEASE(copier);
+
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult
+NS_CancelAsyncCopy(nsISupports* aCopierCtx, nsresult aReason)
+{
+ nsAStreamCopier* copier =
+ static_cast<nsAStreamCopier*>(static_cast<nsIRunnable *>(aCopierCtx));
+ return copier->Cancel(aReason);
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult
+NS_ConsumeStream(nsIInputStream* aStream, uint32_t aMaxCount,
+ nsACString& aResult)
+{
+ nsresult rv = NS_OK;
+ aResult.Truncate();
+
+ while (aMaxCount) {
+ uint64_t avail64;
+ rv = aStream->Available(&avail64);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_BASE_STREAM_CLOSED) {
+ rv = NS_OK;
+ }
+ break;
+ }
+ if (avail64 == 0) {
+ break;
+ }
+
+ uint32_t avail = (uint32_t)XPCOM_MIN<uint64_t>(avail64, aMaxCount);
+
+ // resize aResult buffer
+ uint32_t length = aResult.Length();
+ if (avail > UINT32_MAX - length) {
+ return NS_ERROR_FILE_TOO_BIG;
+ }
+
+ aResult.SetLength(length + avail);
+ if (aResult.Length() != (length + avail)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ char* buf = aResult.BeginWriting() + length;
+
+ uint32_t n;
+ rv = aStream->Read(buf, avail, &n);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ if (n != avail) {
+ aResult.SetLength(length + n);
+ }
+ if (n == 0) {
+ break;
+ }
+ aMaxCount -= n;
+ }
+
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+
+static nsresult
+TestInputStream(nsIInputStream* aInStr,
+ void* aClosure,
+ const char* aBuffer,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t* aCountWritten)
+{
+ bool* result = static_cast<bool*>(aClosure);
+ *result = true;
+ return NS_ERROR_ABORT; // don't call me anymore
+}
+
+bool
+NS_InputStreamIsBuffered(nsIInputStream* aStream)
+{
+ nsCOMPtr<nsIBufferedInputStream> bufferedIn = do_QueryInterface(aStream);
+ if (bufferedIn) {
+ return true;
+ }
+
+ bool result = false;
+ uint32_t n;
+ nsresult rv = aStream->ReadSegments(TestInputStream, &result, 1, &n);
+ return result || NS_SUCCEEDED(rv);
+}
+
+static nsresult
+TestOutputStream(nsIOutputStream* aOutStr,
+ void* aClosure,
+ char* aBuffer,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t* aCountRead)
+{
+ bool* result = static_cast<bool*>(aClosure);
+ *result = true;
+ return NS_ERROR_ABORT; // don't call me anymore
+}
+
+bool
+NS_OutputStreamIsBuffered(nsIOutputStream* aStream)
+{
+ nsCOMPtr<nsIBufferedOutputStream> bufferedOut = do_QueryInterface(aStream);
+ if (bufferedOut) {
+ return true;
+ }
+
+ bool result = false;
+ uint32_t n;
+ aStream->WriteSegments(TestOutputStream, &result, 1, &n);
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult
+NS_CopySegmentToStream(nsIInputStream* aInStr,
+ void* aClosure,
+ const char* aBuffer,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t* aCountWritten)
+{
+ nsIOutputStream* outStr = static_cast<nsIOutputStream*>(aClosure);
+ *aCountWritten = 0;
+ while (aCount) {
+ uint32_t n;
+ nsresult rv = outStr->Write(aBuffer, aCount, &n);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ aBuffer += n;
+ aCount -= n;
+ *aCountWritten += n;
+ }
+ return NS_OK;
+}
+
+nsresult
+NS_CopySegmentToBuffer(nsIInputStream* aInStr,
+ void* aClosure,
+ const char* aBuffer,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t* aCountWritten)
+{
+ char* toBuf = static_cast<char*>(aClosure);
+ memcpy(&toBuf[aOffset], aBuffer, aCount);
+ *aCountWritten = aCount;
+ return NS_OK;
+}
+
+nsresult
+NS_CopySegmentToBuffer(nsIOutputStream* aOutStr,
+ void* aClosure,
+ char* aBuffer,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t* aCountRead)
+{
+ const char* fromBuf = static_cast<const char*>(aClosure);
+ memcpy(aBuffer, &fromBuf[aOffset], aCount);
+ *aCountRead = aCount;
+ return NS_OK;
+}
+
+nsresult
+NS_DiscardSegment(nsIInputStream* aInStr,
+ void* aClosure,
+ const char* aBuffer,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t* aCountWritten)
+{
+ *aCountWritten = aCount;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+
+nsresult
+NS_WriteSegmentThunk(nsIInputStream* aInStr,
+ void* aClosure,
+ const char* aBuffer,
+ uint32_t aOffset,
+ uint32_t aCount,
+ uint32_t* aCountWritten)
+{
+ nsWriteSegmentThunk* thunk = static_cast<nsWriteSegmentThunk*>(aClosure);
+ return thunk->mFun(thunk->mStream, thunk->mClosure, aBuffer, aOffset, aCount,
+ aCountWritten);
+}
+
+nsresult
+NS_FillArray(FallibleTArray<char>& aDest, nsIInputStream* aInput,
+ uint32_t aKeep, uint32_t* aNewBytes)
+{
+ MOZ_ASSERT(aInput, "null stream");
+ MOZ_ASSERT(aKeep <= aDest.Length(), "illegal keep count");
+
+ char* aBuffer = aDest.Elements();
+ int64_t keepOffset = int64_t(aDest.Length()) - aKeep;
+ if (aKeep != 0 && keepOffset > 0) {
+ memmove(aBuffer, aBuffer + keepOffset, aKeep);
+ }
+
+ nsresult rv =
+ aInput->Read(aBuffer + aKeep, aDest.Capacity() - aKeep, aNewBytes);
+ if (NS_FAILED(rv)) {
+ *aNewBytes = 0;
+ }
+ // NOTE: we rely on the fact that the new slots are NOT initialized by
+ // SetLengthAndRetainStorage here, see nsTArrayElementTraits::Construct()
+ // in nsTArray.h:
+ aDest.SetLengthAndRetainStorage(aKeep + *aNewBytes);
+
+ MOZ_ASSERT(aDest.Length() <= aDest.Capacity(), "buffer overflow");
+ return rv;
+}
+
+bool
+NS_InputStreamIsCloneable(nsIInputStream* aSource)
+{
+ if (!aSource) {
+ return false;
+ }
+
+ nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(aSource);
+ return cloneable && cloneable->GetCloneable();
+}
+
+nsresult
+NS_CloneInputStream(nsIInputStream* aSource, nsIInputStream** aCloneOut,
+ nsIInputStream** aReplacementOut)
+{
+ if (NS_WARN_IF(!aSource)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Attempt to perform the clone directly on the source stream
+ nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(aSource);
+ if (cloneable && cloneable->GetCloneable()) {
+ if (aReplacementOut) {
+ *aReplacementOut = nullptr;
+ }
+ return cloneable->Clone(aCloneOut);
+ }
+
+ // If we failed the clone and the caller does not want to replace their
+ // original stream, then we are done. Return error.
+ if (!aReplacementOut) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // The caller has opted-in to the fallback clone support that replaces
+ // the original stream. Copy the data to a pipe and return two cloned
+ // input streams.
+
+ nsCOMPtr<nsIInputStream> reader;
+ nsCOMPtr<nsIInputStream> readerClone;
+ nsCOMPtr<nsIOutputStream> writer;
+
+ nsresult rv = NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer),
+ 0, 0, // default segment size and max size
+ true, true); // non-blocking
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ cloneable = do_QueryInterface(reader);
+ MOZ_ASSERT(cloneable && cloneable->GetCloneable());
+
+ rv = cloneable->Clone(getter_AddRefs(readerClone));
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ rv = NS_AsyncCopy(aSource, writer, target, NS_ASYNCCOPY_VIA_WRITESEGMENTS);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ readerClone.forget(aCloneOut);
+ reader.forget(aReplacementOut);
+
+ return NS_OK;
+}
diff --git a/xpcom/io/nsStreamUtils.h b/xpcom/io/nsStreamUtils.h
new file mode 100644
index 000000000..957eb2713
--- /dev/null
+++ b/xpcom/io/nsStreamUtils.h
@@ -0,0 +1,295 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStreamUtils_h__
+#define nsStreamUtils_h__
+
+#include "nsCOMPtr.h"
+#include "nsStringFwd.h"
+#include "nsIInputStream.h"
+#include "nsTArray.h"
+
+class nsIOutputStream;
+class nsIInputStreamCallback;
+class nsIOutputStreamCallback;
+class nsIEventTarget;
+
+/**
+ * A "one-shot" proxy of the OnInputStreamReady callback. The resulting
+ * proxy object's OnInputStreamReady function may only be called once! The
+ * proxy object ensures that the real notify object will be free'd on the
+ * thread corresponding to the given event target regardless of what thread
+ * the proxy object is destroyed on.
+ *
+ * This function is designed to be used to implement AsyncWait when the
+ * aTarget parameter is non-null.
+ */
+extern already_AddRefed<nsIInputStreamCallback>
+NS_NewInputStreamReadyEvent(nsIInputStreamCallback* aNotify,
+ nsIEventTarget* aTarget);
+
+/**
+ * A "one-shot" proxy of the OnOutputStreamReady callback. The resulting
+ * proxy object's OnOutputStreamReady function may only be called once! The
+ * proxy object ensures that the real notify object will be free'd on the
+ * thread corresponding to the given event target regardless of what thread
+ * the proxy object is destroyed on.
+ *
+ * This function is designed to be used to implement AsyncWait when the
+ * aTarget parameter is non-null.
+ */
+extern already_AddRefed<nsIOutputStreamCallback>
+NS_NewOutputStreamReadyEvent(nsIOutputStreamCallback* aNotify,
+ nsIEventTarget* aTarget);
+
+/* ------------------------------------------------------------------------- */
+
+enum nsAsyncCopyMode {
+ NS_ASYNCCOPY_VIA_READSEGMENTS,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS
+};
+
+/**
+ * This function is called when a new chunk of data has been copied. The
+ * reported count is the size of the current chunk.
+ */
+typedef void (* nsAsyncCopyProgressFun)(void* closure, uint32_t count);
+
+/**
+ * This function is called when the async copy process completes. The reported
+ * status is NS_OK on success and some error code on failure.
+ */
+typedef void (* nsAsyncCopyCallbackFun)(void* closure, nsresult status);
+
+/**
+ * This function asynchronously copies data from the source to the sink. All
+ * data transfer occurs on the thread corresponding to the given event target.
+ * A null event target is not permitted.
+ *
+ * The copier handles blocking or non-blocking streams transparently. If a
+ * stream operation returns NS_BASE_STREAM_WOULD_BLOCK, then the stream will
+ * be QI'd to nsIAsync{In,Out}putStream and its AsyncWait method will be used
+ * to determine when to resume copying.
+ *
+ * Source and sink are closed by default when copying finishes or when error
+ * occurs. Caller can prevent closing source or sink by setting aCloseSource
+ * or aCloseSink to false.
+ *
+ * Caller can obtain aCopierCtx to be able to cancel copying.
+ */
+extern nsresult
+NS_AsyncCopy(nsIInputStream* aSource,
+ nsIOutputStream* aSink,
+ nsIEventTarget* aTarget,
+ nsAsyncCopyMode aMode = NS_ASYNCCOPY_VIA_READSEGMENTS,
+ uint32_t aChunkSize = 4096,
+ nsAsyncCopyCallbackFun aCallbackFun = nullptr,
+ void* aCallbackClosure = nullptr,
+ bool aCloseSource = true,
+ bool aCloseSink = true,
+ nsISupports** aCopierCtx = nullptr,
+ nsAsyncCopyProgressFun aProgressCallbackFun = nullptr);
+
+/**
+ * This function cancels copying started by function NS_AsyncCopy.
+ *
+ * @param aCopierCtx
+ * Copier context returned by NS_AsyncCopy.
+ * @param aReason
+ * A failure code indicating why the operation is being canceled.
+ * It is an error to pass a success code.
+ */
+extern nsresult
+NS_CancelAsyncCopy(nsISupports* aCopierCtx, nsresult aReason);
+
+/**
+ * This function copies all of the available data from the stream (up to at
+ * most aMaxCount bytes) into the given buffer. The buffer is truncated at
+ * the start of the function.
+ *
+ * If an error occurs while reading from the stream or while attempting to
+ * resize the buffer, then the corresponding error code is returned from this
+ * function, and any data that has already been read will be returned in the
+ * output buffer. This allows one to use this function with a non-blocking
+ * input stream that may return NS_BASE_STREAM_WOULD_BLOCK if it only has
+ * partial data available.
+ *
+ * @param aSource
+ * The input stream to read.
+ * @param aMaxCount
+ * The maximum number of bytes to consume from the stream. Pass the
+ * value UINT32_MAX to consume the entire stream. The number of
+ * bytes actually read is given by the length of aBuffer upon return.
+ * @param aBuffer
+ * The string object that will contain the stream data upon return.
+ * Note: The data copied to the string may contain null bytes and may
+ * contain non-ASCII values.
+ */
+extern nsresult
+NS_ConsumeStream(nsIInputStream* aSource, uint32_t aMaxCount,
+ nsACString& aBuffer);
+
+/**
+ * This function tests whether or not the input stream is buffered. A buffered
+ * input stream is one that implements readSegments. The test for this is to
+ * 1/ check whether the input stream implements nsIBufferedInputStream;
+ * 2/ if not, call readSegments, without actually consuming any data from the
+ * stream, to verify that it functions.
+ *
+ * NOTE: If the stream is non-blocking and has no data available yet, then this
+ * test will fail. In that case, we return false even though the test is not
+ * really conclusive.
+ *
+ * PERFORMANCE NOTE: If the stream does not implement nsIBufferedInputStream,
+ * calling readSegments may cause I/O. Therefore, you should avoid calling
+ * this function from the main thread.
+ *
+ * @param aInputStream
+ * The input stream to test.
+ */
+extern bool
+NS_InputStreamIsBuffered(nsIInputStream* aInputStream);
+
+/**
+ * This function tests whether or not the output stream is buffered. A
+ * buffered output stream is one that implements writeSegments. The test for
+ * this is to:
+ * 1/ check whether the output stream implements nsIBufferedOutputStream;
+ * 2/ if not, call writeSegments, without actually writing any data into
+ * the stream, to verify that it functions.
+ *
+ * NOTE: If the stream is non-blocking and has no available space yet, then
+ * this test will fail. In that case, we return false even though the test is
+ * not really conclusive.
+ *
+ * PERFORMANCE NOTE: If the stream does not implement nsIBufferedOutputStream,
+ * calling writeSegments may cause I/O. Therefore, you should avoid calling
+ * this function from the main thread.
+ *
+ * @param aOutputStream
+ * The output stream to test.
+ */
+extern bool
+NS_OutputStreamIsBuffered(nsIOutputStream* aOutputStream);
+
+/**
+ * This function is intended to be passed to nsIInputStream::ReadSegments to
+ * copy data from the nsIInputStream into a nsIOutputStream passed as the
+ * aClosure parameter to the ReadSegments function.
+ *
+ * @see nsIInputStream.idl for a description of this function's parameters.
+ */
+extern nsresult
+NS_CopySegmentToStream(nsIInputStream* aInputStream, void* aClosure,
+ const char* aFromSegment, uint32_t aToOffset,
+ uint32_t aCount, uint32_t* aWriteCount);
+
+/**
+ * This function is intended to be passed to nsIInputStream::ReadSegments to
+ * copy data from the nsIInputStream into a character buffer passed as the
+ * aClosure parameter to the ReadSegments function. The character buffer
+ * must be at least as large as the aCount parameter passed to ReadSegments.
+ *
+ * @see nsIInputStream.idl for a description of this function's parameters.
+ */
+extern nsresult
+NS_CopySegmentToBuffer(nsIInputStream* aInputStream, void* aClosure,
+ const char* aFromSegment, uint32_t aToOffset,
+ uint32_t aCount, uint32_t* aWriteCount);
+
+/**
+ * This function is intended to be passed to nsIOutputStream::WriteSegments to
+ * copy data into the nsIOutputStream from a character buffer passed as the
+ * aClosure parameter to the WriteSegments function.
+ *
+ * @see nsIOutputStream.idl for a description of this function's parameters.
+ */
+extern nsresult
+NS_CopySegmentToBuffer(nsIOutputStream* aOutputStream, void* aClosure,
+ char* aToSegment, uint32_t aFromOffset,
+ uint32_t aCount, uint32_t* aReadCount);
+
+/**
+ * This function is intended to be passed to nsIInputStream::ReadSegments to
+ * discard data from the nsIInputStream. This can be used to efficiently read
+ * data from the stream without actually copying any bytes.
+ *
+ * @see nsIInputStream.idl for a description of this function's parameters.
+ */
+extern nsresult
+NS_DiscardSegment(nsIInputStream* aInputStream, void* aClosure,
+ const char* aFromSegment, uint32_t aToOffset,
+ uint32_t aCount, uint32_t* aWriteCount);
+
+/**
+ * This function is intended to be passed to nsIInputStream::ReadSegments to
+ * adjust the aInputStream parameter passed to a consumer's WriteSegmentFun.
+ * The aClosure parameter must be a pointer to a nsWriteSegmentThunk object.
+ * The mStream and mClosure members of that object will be passed to the mFun
+ * function, with the remainder of the parameters being what are passed to
+ * NS_WriteSegmentThunk.
+ *
+ * This function comes in handy when implementing ReadSegments in terms of an
+ * inner stream's ReadSegments.
+ */
+extern nsresult
+NS_WriteSegmentThunk(nsIInputStream* aInputStream, void* aClosure,
+ const char* aFromSegment, uint32_t aToOffset,
+ uint32_t aCount, uint32_t* aWriteCount);
+
+struct MOZ_STACK_CLASS nsWriteSegmentThunk
+{
+ nsCOMPtr<nsIInputStream> mStream;
+ nsWriteSegmentFun mFun;
+ void* mClosure;
+};
+
+/**
+ * Read data from aInput and store in aDest. A non-zero aKeep will keep that
+ * many bytes from aDest (from the end). New data is appended after the kept
+ * bytes (if any). aDest's new length on returning from this function is
+ * aKeep + aNewBytes and is guaranteed to be less than or equal to aDest's
+ * current capacity.
+ * @param aDest the array to fill
+ * @param aInput the stream to read from
+ * @param aKeep number of bytes to keep (0 <= aKeep <= aDest.Length())
+ * @param aNewBytes (out) number of bytes read from aInput or zero if Read()
+ * failed
+ * @return the result from aInput->Read(...)
+ */
+extern nsresult
+NS_FillArray(FallibleTArray<char>& aDest, nsIInputStream* aInput,
+ uint32_t aKeep, uint32_t* aNewBytes);
+
+/**
+ * Return true if the given stream can be directly cloned.
+ */
+extern bool
+NS_InputStreamIsCloneable(nsIInputStream* aSource);
+
+/**
+ * Clone the provided source stream in the most efficient way possible. This
+ * first attempts to QI to nsICloneableInputStream to use Clone(). If that is
+ * not supported or its cloneable attribute is false, then a fallback clone is
+ * provided by copying the source to a pipe. In this case the caller must
+ * replace the source stream with the resulting replacement stream. The clone
+ * and the replacement stream are then cloneable using nsICloneableInputStream
+ * without duplicating memory. This fallback clone using the pipe is only
+ * performed if a replacement stream parameter is also passed in.
+ * @param aSource The input stream to clone.
+ * @param aCloneOut Required out parameter to hold resulting clone.
+ * @param aReplacementOut Optional out parameter to hold stream to replace
+ * original source stream after clone. If not
+ * provided then the fallback clone process is not
+ * supported and a non-cloneable source will result
+ * in failure. Replacement streams are non-blocking.
+ * @return NS_OK on successful clone. Error otherwise.
+ */
+extern nsresult
+NS_CloneInputStream(nsIInputStream* aSource, nsIInputStream** aCloneOut,
+ nsIInputStream** aReplacementOut = nullptr);
+
+#endif // !nsStreamUtils_h__
diff --git a/xpcom/io/nsStringStream.cpp b/xpcom/io/nsStringStream.cpp
new file mode 100644
index 000000000..b65242c14
--- /dev/null
+++ b/xpcom/io/nsStringStream.cpp
@@ -0,0 +1,452 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Based on original code from nsIStringStream.cpp
+ */
+
+#include "ipc/IPCMessageUtils.h"
+
+#include "nsStringStream.h"
+#include "nsStreamUtils.h"
+#include "nsReadableUtils.h"
+#include "nsICloneableInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsISupportsPrimitives.h"
+#include "nsCRT.h"
+#include "prerror.h"
+#include "plstr.h"
+#include "nsIClassInfoImpl.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "nsIIPCSerializableInputStream.h"
+
+using namespace mozilla::ipc;
+using mozilla::Maybe;
+using mozilla::Some;
+
+//-----------------------------------------------------------------------------
+// nsIStringInputStream implementation
+//-----------------------------------------------------------------------------
+
+class nsStringInputStream final
+ : public nsIStringInputStream
+ , public nsISeekableStream
+ , public nsISupportsCString
+ , public nsIIPCSerializableInputStream
+ , public nsICloneableInputStream
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSISTRINGINPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSISUPPORTSPRIMITIVE
+ NS_DECL_NSISUPPORTSCSTRING
+ NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+ NS_DECL_NSICLONEABLEINPUTSTREAM
+
+ nsStringInputStream()
+ {
+ Clear();
+ }
+
+ explicit nsStringInputStream(const nsStringInputStream& aOther)
+ : mOffset(aOther.mOffset)
+ {
+ // Use Assign() here because we don't want the life of the clone to be
+ // dependent on the life of the original stream.
+ mData.Assign(aOther.mData);
+ }
+
+private:
+ ~nsStringInputStream()
+ {
+ }
+
+ uint32_t Length() const
+ {
+ return mData.Length();
+ }
+
+ uint32_t LengthRemaining() const
+ {
+ return Length() - mOffset;
+ }
+
+ void Clear()
+ {
+ mData.SetIsVoid(true);
+ }
+
+ bool Closed()
+ {
+ return mData.IsVoid();
+ }
+
+ nsDependentCSubstring mData;
+ uint32_t mOffset;
+};
+
+// This class needs to support threadsafe refcounting since people often
+// allocate a string stream, and then read it from a background thread.
+NS_IMPL_ADDREF(nsStringInputStream)
+NS_IMPL_RELEASE(nsStringInputStream)
+
+NS_IMPL_CLASSINFO(nsStringInputStream, nullptr, nsIClassInfo::THREADSAFE,
+ NS_STRINGINPUTSTREAM_CID)
+NS_IMPL_QUERY_INTERFACE_CI(nsStringInputStream,
+ nsIStringInputStream,
+ nsIInputStream,
+ nsISupportsCString,
+ nsISeekableStream,
+ nsIIPCSerializableInputStream,
+ nsICloneableInputStream)
+NS_IMPL_CI_INTERFACE_GETTER(nsStringInputStream,
+ nsIStringInputStream,
+ nsIInputStream,
+ nsISupportsCString,
+ nsISeekableStream,
+ nsICloneableInputStream)
+
+/////////
+// nsISupportsCString implementation
+/////////
+
+NS_IMETHODIMP
+nsStringInputStream::GetType(uint16_t* aType)
+{
+ *aType = TYPE_CSTRING;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringInputStream::GetData(nsACString& data)
+{
+ // The stream doesn't have any data when it is closed. We could fake it
+ // and return an empty string here, but it seems better to keep this return
+ // value consistent with the behavior of the other 'getter' methods.
+ if (NS_WARN_IF(Closed())) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ data.Assign(mData);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringInputStream::SetData(const nsACString& aData)
+{
+ mData.Assign(aData);
+ mOffset = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringInputStream::ToString(char** aResult)
+{
+ // NOTE: This method may result in data loss, so we do not implement it.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/////////
+// nsIStringInputStream implementation
+/////////
+
+NS_IMETHODIMP
+nsStringInputStream::SetData(const char* aData, int32_t aDataLen)
+{
+ if (NS_WARN_IF(!aData)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ mData.Assign(aData, aDataLen);
+ mOffset = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringInputStream::AdoptData(char* aData, int32_t aDataLen)
+{
+ if (NS_WARN_IF(!aData)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ mData.Adopt(aData, aDataLen);
+ mOffset = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringInputStream::ShareData(const char* aData, int32_t aDataLen)
+{
+ if (NS_WARN_IF(!aData)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aDataLen < 0) {
+ aDataLen = strlen(aData);
+ }
+
+ mData.Rebind(aData, aDataLen);
+ mOffset = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(size_t)
+nsStringInputStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
+{
+ size_t n = aMallocSizeOf(this);
+ n += mData.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ return n;
+}
+
+/////////
+// nsIInputStream implementation
+/////////
+
+NS_IMETHODIMP
+nsStringInputStream::Close()
+{
+ Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringInputStream::Available(uint64_t* aLength)
+{
+ NS_ASSERTION(aLength, "null ptr");
+
+ if (Closed()) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ *aLength = LengthRemaining();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* aReadCount)
+{
+ NS_ASSERTION(aBuf, "null ptr");
+ return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount);
+}
+
+NS_IMETHODIMP
+nsStringInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* aResult)
+{
+ NS_ASSERTION(aResult, "null ptr");
+ NS_ASSERTION(Length() >= mOffset, "bad stream state");
+
+ if (Closed()) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ // We may be at end-of-file
+ uint32_t maxCount = LengthRemaining();
+ if (maxCount == 0) {
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ if (aCount > maxCount) {
+ aCount = maxCount;
+ }
+ nsresult rv = aWriter(this, aClosure, mData.BeginReading() + mOffset, 0,
+ aCount, aResult);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ASSERTION(*aResult <= aCount,
+ "writer should not write more than we asked it to write");
+ mOffset += *aResult;
+ }
+
+ // errors returned from the writer end here!
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringInputStream::IsNonBlocking(bool* aNonBlocking)
+{
+ *aNonBlocking = true;
+ return NS_OK;
+}
+
+/////////
+// nsISeekableStream implementation
+/////////
+
+NS_IMETHODIMP
+nsStringInputStream::Seek(int32_t aWhence, int64_t aOffset)
+{
+ if (Closed()) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ // Compute new stream position. The given offset may be a negative value.
+
+ int64_t newPos = aOffset;
+ switch (aWhence) {
+ case NS_SEEK_SET:
+ break;
+ case NS_SEEK_CUR:
+ newPos += mOffset;
+ break;
+ case NS_SEEK_END:
+ newPos += Length();
+ break;
+ default:
+ NS_ERROR("invalid aWhence");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (NS_WARN_IF(newPos < 0) || NS_WARN_IF(newPos > Length())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mOffset = (uint32_t)newPos;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringInputStream::Tell(int64_t* aOutWhere)
+{
+ if (Closed()) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ *aOutWhere = mOffset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringInputStream::SetEOF()
+{
+ if (Closed()) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ mOffset = Length();
+ return NS_OK;
+}
+
+/////////
+// nsIIPCSerializableInputStream implementation
+/////////
+
+void
+nsStringInputStream::Serialize(InputStreamParams& aParams,
+ FileDescriptorArray& /* aFDs */)
+{
+ StringInputStreamParams params;
+ params.data() = PromiseFlatCString(mData);
+ aParams = params;
+}
+
+bool
+nsStringInputStream::Deserialize(const InputStreamParams& aParams,
+ const FileDescriptorArray& /* aFDs */)
+{
+ if (aParams.type() != InputStreamParams::TStringInputStreamParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const StringInputStreamParams& params =
+ aParams.get_StringInputStreamParams();
+
+ if (NS_FAILED(SetData(params.data()))) {
+ NS_WARNING("SetData failed!");
+ return false;
+ }
+
+ return true;
+}
+
+Maybe<uint64_t>
+nsStringInputStream::ExpectedSerializedLength()
+{
+ return Some(static_cast<uint64_t>(Length()));
+}
+
+/////////
+// nsICloneableInputStream implementation
+/////////
+
+NS_IMETHODIMP
+nsStringInputStream::GetCloneable(bool* aCloneableOut)
+{
+ *aCloneableOut = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStringInputStream::Clone(nsIInputStream** aCloneOut)
+{
+ RefPtr<nsIInputStream> ref = new nsStringInputStream(*this);
+ ref.forget(aCloneOut);
+ return NS_OK;
+}
+
+nsresult
+NS_NewByteInputStream(nsIInputStream** aStreamResult,
+ const char* aStringToRead, int32_t aLength,
+ nsAssignmentType aAssignment)
+{
+ NS_PRECONDITION(aStreamResult, "null out ptr");
+
+ RefPtr<nsStringInputStream> stream = new nsStringInputStream();
+
+ nsresult rv;
+ switch (aAssignment) {
+ case NS_ASSIGNMENT_COPY:
+ rv = stream->SetData(aStringToRead, aLength);
+ break;
+ case NS_ASSIGNMENT_DEPEND:
+ rv = stream->ShareData(aStringToRead, aLength);
+ break;
+ case NS_ASSIGNMENT_ADOPT:
+ rv = stream->AdoptData(const_cast<char*>(aStringToRead), aLength);
+ break;
+ default:
+ NS_ERROR("invalid assignment type");
+ rv = NS_ERROR_INVALID_ARG;
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ stream.forget(aStreamResult);
+ return NS_OK;
+}
+
+nsresult
+NS_NewCStringInputStream(nsIInputStream** aStreamResult,
+ const nsACString& aStringToRead)
+{
+ NS_PRECONDITION(aStreamResult, "null out ptr");
+
+ RefPtr<nsStringInputStream> stream = new nsStringInputStream();
+
+ stream->SetData(aStringToRead);
+
+ stream.forget(aStreamResult);
+ return NS_OK;
+}
+
+// factory method for constructing a nsStringInputStream object
+nsresult
+nsStringInputStreamConstructor(nsISupports* aOuter, REFNSIID aIID,
+ void** aResult)
+{
+ *aResult = nullptr;
+
+ if (NS_WARN_IF(aOuter)) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ RefPtr<nsStringInputStream> inst = new nsStringInputStream();
+ return inst->QueryInterface(aIID, aResult);
+}
diff --git a/xpcom/io/nsStringStream.h b/xpcom/io/nsStringStream.h
new file mode 100644
index 000000000..8c09530eb
--- /dev/null
+++ b/xpcom/io/nsStringStream.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsStringStream_h__
+#define nsStringStream_h__
+
+#include "nsIStringStream.h"
+#include "nsStringGlue.h"
+#include "nsMemory.h"
+
+/**
+ * Implements:
+ * nsIStringInputStream
+ * nsIInputStream
+ * nsISeekableStream
+ * nsISupportsCString
+ */
+#define NS_STRINGINPUTSTREAM_CONTRACTID "@mozilla.org/io/string-input-stream;1"
+#define NS_STRINGINPUTSTREAM_CID \
+{ /* 0abb0835-5000-4790-af28-61b3ba17c295 */ \
+ 0x0abb0835, \
+ 0x5000, \
+ 0x4790, \
+ {0xaf, 0x28, 0x61, 0xb3, 0xba, 0x17, 0xc2, 0x95} \
+}
+
+/**
+ * Factory method to get an nsInputStream from a byte buffer. Result will
+ * implement nsIStringInputStream and nsISeekableStream.
+ *
+ * If aAssignment is NS_ASSIGNMENT_COPY, then the resulting stream holds a copy
+ * of the given buffer (aStringToRead), and the caller is free to discard
+ * aStringToRead after this function returns.
+ *
+ * If aAssignment is NS_ASSIGNMENT_DEPEND, then the resulting stream refers
+ * directly to the given buffer (aStringToRead), so the caller must ensure that
+ * the buffer remains valid for the lifetime of the stream object. Use with
+ * care!!
+ *
+ * If aAssignment is NS_ASSIGNMENT_ADOPT, then the resulting stream refers
+ * directly to the given buffer (aStringToRead) and will free aStringToRead
+ * once the stream is closed.
+ *
+ * If aLength is less than zero, then the length of aStringToRead will be
+ * determined by scanning the buffer for the first null byte.
+ */
+extern nsresult
+NS_NewByteInputStream(nsIInputStream** aStreamResult,
+ const char* aStringToRead, int32_t aLength = -1,
+ nsAssignmentType aAssignment = NS_ASSIGNMENT_DEPEND);
+
+/**
+ * Factory method to get an nsInputStream from an nsACString. Result will
+ * implement nsIStringInputStream and nsISeekableStream.
+ */
+extern nsresult
+NS_NewCStringInputStream(nsIInputStream** aStreamResult,
+ const nsACString& aStringToRead);
+
+#endif // nsStringStream_h__
diff --git a/xpcom/io/nsUnicharInputStream.cpp b/xpcom/io/nsUnicharInputStream.cpp
new file mode 100644
index 000000000..27c074c09
--- /dev/null
+++ b/xpcom/io/nsUnicharInputStream.cpp
@@ -0,0 +1,398 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsUnicharInputStream.h"
+#include "nsIInputStream.h"
+#include "nsIServiceManager.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+#include "nsCRT.h"
+#include "nsStreamUtils.h"
+#include "nsUTF8Utils.h"
+#include "mozilla/Attributes.h"
+#include <fcntl.h>
+#if defined(XP_WIN)
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
+#define STRING_BUFFER_SIZE 8192
+
+class StringUnicharInputStream final : public nsIUnicharInputStream
+{
+public:
+ explicit StringUnicharInputStream(const nsAString& aString) :
+ mString(aString), mPos(0), mLen(aString.Length()) { }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUNICHARINPUTSTREAM
+
+ nsString mString;
+ uint32_t mPos;
+ uint32_t mLen;
+
+private:
+ ~StringUnicharInputStream() { }
+};
+
+NS_IMETHODIMP
+StringUnicharInputStream::Read(char16_t* aBuf,
+ uint32_t aCount,
+ uint32_t* aReadCount)
+{
+ if (mPos >= mLen) {
+ *aReadCount = 0;
+ return NS_OK;
+ }
+ nsAString::const_iterator iter;
+ mString.BeginReading(iter);
+ const char16_t* us = iter.get();
+ uint32_t amount = mLen - mPos;
+ if (amount > aCount) {
+ amount = aCount;
+ }
+ memcpy(aBuf, us + mPos, sizeof(char16_t) * amount);
+ mPos += amount;
+ *aReadCount = amount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StringUnicharInputStream::ReadSegments(nsWriteUnicharSegmentFun aWriter,
+ void* aClosure,
+ uint32_t aCount, uint32_t* aReadCount)
+{
+ uint32_t bytesWritten;
+ uint32_t totalBytesWritten = 0;
+
+ nsresult rv;
+ aCount = XPCOM_MIN(mString.Length() - mPos, aCount);
+
+ nsAString::const_iterator iter;
+ mString.BeginReading(iter);
+
+ while (aCount) {
+ rv = aWriter(this, aClosure, iter.get() + mPos,
+ totalBytesWritten, aCount, &bytesWritten);
+
+ if (NS_FAILED(rv)) {
+ // don't propagate errors to the caller
+ break;
+ }
+
+ aCount -= bytesWritten;
+ totalBytesWritten += bytesWritten;
+ mPos += bytesWritten;
+ }
+
+ *aReadCount = totalBytesWritten;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StringUnicharInputStream::ReadString(uint32_t aCount, nsAString& aString,
+ uint32_t* aReadCount)
+{
+ if (mPos >= mLen) {
+ *aReadCount = 0;
+ return NS_OK;
+ }
+ uint32_t amount = mLen - mPos;
+ if (amount > aCount) {
+ amount = aCount;
+ }
+ aString = Substring(mString, mPos, amount);
+ mPos += amount;
+ *aReadCount = amount;
+ return NS_OK;
+}
+
+nsresult
+StringUnicharInputStream::Close()
+{
+ mPos = mLen;
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(StringUnicharInputStream, nsIUnicharInputStream)
+
+//----------------------------------------------------------------------
+
+class UTF8InputStream final : public nsIUnicharInputStream
+{
+public:
+ UTF8InputStream();
+ nsresult Init(nsIInputStream* aStream);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUNICHARINPUTSTREAM
+
+private:
+ ~UTF8InputStream();
+
+protected:
+ int32_t Fill(nsresult* aErrorCode);
+
+ static void CountValidUTF8Bytes(const char* aBuf, uint32_t aMaxBytes,
+ uint32_t& aValidUTF8bytes,
+ uint32_t& aValidUTF16CodeUnits);
+
+ nsCOMPtr<nsIInputStream> mInput;
+ FallibleTArray<char> mByteData;
+ FallibleTArray<char16_t> mUnicharData;
+
+ uint32_t mByteDataOffset;
+ uint32_t mUnicharDataOffset;
+ uint32_t mUnicharDataLength;
+};
+
+UTF8InputStream::UTF8InputStream() :
+ mByteDataOffset(0),
+ mUnicharDataOffset(0),
+ mUnicharDataLength(0)
+{
+}
+
+nsresult
+UTF8InputStream::Init(nsIInputStream* aStream)
+{
+ if (!mByteData.SetCapacity(STRING_BUFFER_SIZE, mozilla::fallible) ||
+ !mUnicharData.SetCapacity(STRING_BUFFER_SIZE, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mInput = aStream;
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(UTF8InputStream, nsIUnicharInputStream)
+
+UTF8InputStream::~UTF8InputStream()
+{
+ Close();
+}
+
+nsresult
+UTF8InputStream::Close()
+{
+ mInput = nullptr;
+ mByteData.Clear();
+ mUnicharData.Clear();
+ return NS_OK;
+}
+
+nsresult
+UTF8InputStream::Read(char16_t* aBuf, uint32_t aCount, uint32_t* aReadCount)
+{
+ NS_ASSERTION(mUnicharDataLength >= mUnicharDataOffset, "unsigned madness");
+ uint32_t readCount = mUnicharDataLength - mUnicharDataOffset;
+ nsresult errorCode;
+ if (0 == readCount) {
+ // Fill the unichar buffer
+ int32_t bytesRead = Fill(&errorCode);
+ if (bytesRead <= 0) {
+ *aReadCount = 0;
+ return errorCode;
+ }
+ readCount = bytesRead;
+ }
+ if (readCount > aCount) {
+ readCount = aCount;
+ }
+ memcpy(aBuf, mUnicharData.Elements() + mUnicharDataOffset,
+ readCount * sizeof(char16_t));
+ mUnicharDataOffset += readCount;
+ *aReadCount = readCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UTF8InputStream::ReadSegments(nsWriteUnicharSegmentFun aWriter,
+ void* aClosure,
+ uint32_t aCount, uint32_t* aReadCount)
+{
+ NS_ASSERTION(mUnicharDataLength >= mUnicharDataOffset, "unsigned madness");
+ uint32_t bytesToWrite = mUnicharDataLength - mUnicharDataOffset;
+ nsresult rv = NS_OK;
+ if (0 == bytesToWrite) {
+ // Fill the unichar buffer
+ int32_t bytesRead = Fill(&rv);
+ if (bytesRead <= 0) {
+ *aReadCount = 0;
+ return rv;
+ }
+ bytesToWrite = bytesRead;
+ }
+
+ if (bytesToWrite > aCount) {
+ bytesToWrite = aCount;
+ }
+
+ uint32_t bytesWritten;
+ uint32_t totalBytesWritten = 0;
+
+ while (bytesToWrite) {
+ rv = aWriter(this, aClosure,
+ mUnicharData.Elements() + mUnicharDataOffset,
+ totalBytesWritten, bytesToWrite, &bytesWritten);
+
+ if (NS_FAILED(rv)) {
+ // don't propagate errors to the caller
+ break;
+ }
+
+ bytesToWrite -= bytesWritten;
+ totalBytesWritten += bytesWritten;
+ mUnicharDataOffset += bytesWritten;
+ }
+
+ *aReadCount = totalBytesWritten;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UTF8InputStream::ReadString(uint32_t aCount, nsAString& aString,
+ uint32_t* aReadCount)
+{
+ NS_ASSERTION(mUnicharDataLength >= mUnicharDataOffset, "unsigned madness");
+ uint32_t readCount = mUnicharDataLength - mUnicharDataOffset;
+ nsresult errorCode;
+ if (0 == readCount) {
+ // Fill the unichar buffer
+ int32_t bytesRead = Fill(&errorCode);
+ if (bytesRead <= 0) {
+ *aReadCount = 0;
+ return errorCode;
+ }
+ readCount = bytesRead;
+ }
+ if (readCount > aCount) {
+ readCount = aCount;
+ }
+ const char16_t* buf = mUnicharData.Elements() + mUnicharDataOffset;
+ aString.Assign(buf, readCount);
+
+ mUnicharDataOffset += readCount;
+ *aReadCount = readCount;
+ return NS_OK;
+}
+
+int32_t
+UTF8InputStream::Fill(nsresult* aErrorCode)
+{
+ if (!mInput) {
+ // We already closed the stream!
+ *aErrorCode = NS_BASE_STREAM_CLOSED;
+ return -1;
+ }
+
+ NS_ASSERTION(mByteData.Length() >= mByteDataOffset, "unsigned madness");
+ uint32_t remainder = mByteData.Length() - mByteDataOffset;
+ mByteDataOffset = remainder;
+ uint32_t nb;
+ *aErrorCode = NS_FillArray(mByteData, mInput, remainder, &nb);
+ if (nb == 0) {
+ // Because we assume a many to one conversion, the lingering data
+ // in the byte buffer must be a partial conversion
+ // fragment. Because we know that we have received no more new
+ // data to add to it, we can't convert it. Therefore, we discard
+ // it.
+ return nb;
+ }
+ NS_ASSERTION(remainder + nb == mByteData.Length(), "bad nb");
+
+ // Now convert as much of the byte buffer to unicode as possible
+ uint32_t srcLen, dstLen;
+ CountValidUTF8Bytes(mByteData.Elements(), remainder + nb, srcLen, dstLen);
+
+ // the number of UCS2 characters should always be <= the number of
+ // UTF8 chars
+ NS_ASSERTION(remainder + nb >= srcLen, "cannot be longer than out buffer");
+ NS_ASSERTION(dstLen <= mUnicharData.Capacity(),
+ "Ouch. I would overflow my buffer if I wasn't so careful.");
+ if (dstLen > mUnicharData.Capacity()) {
+ return 0;
+ }
+
+ ConvertUTF8toUTF16 converter(mUnicharData.Elements());
+
+ nsASingleFragmentCString::const_char_iterator start = mByteData.Elements();
+ nsASingleFragmentCString::const_char_iterator end = mByteData.Elements() + srcLen;
+
+ copy_string(start, end, converter);
+ if (converter.Length() != dstLen) {
+ *aErrorCode = NS_BASE_STREAM_BAD_CONVERSION;
+ return -1;
+ }
+
+ mUnicharDataOffset = 0;
+ mUnicharDataLength = dstLen;
+ mByteDataOffset = srcLen;
+
+ return dstLen;
+}
+
+void
+UTF8InputStream::CountValidUTF8Bytes(const char* aBuffer, uint32_t aMaxBytes,
+ uint32_t& aValidUTF8bytes,
+ uint32_t& aValidUTF16CodeUnits)
+{
+ const char* c = aBuffer;
+ const char* end = aBuffer + aMaxBytes;
+ const char* lastchar = c; // pre-initialize in case of 0-length buffer
+ uint32_t utf16length = 0;
+ while (c < end && *c) {
+ lastchar = c;
+ utf16length++;
+
+ if (UTF8traits::isASCII(*c)) {
+ c++;
+ } else if (UTF8traits::is2byte(*c)) {
+ c += 2;
+ } else if (UTF8traits::is3byte(*c)) {
+ c += 3;
+ } else if (UTF8traits::is4byte(*c)) {
+ c += 4;
+ utf16length++; // add 1 more because this will be converted to a
+ // surrogate pair.
+ } else if (UTF8traits::is5byte(*c)) {
+ c += 5;
+ } else if (UTF8traits::is6byte(*c)) {
+ c += 6;
+ } else {
+ NS_WARNING("Unrecognized UTF8 string in UTF8InputStream::CountValidUTF8Bytes()");
+ break; // Otherwise we go into an infinite loop. But what happens now?
+ }
+ }
+ if (c > end) {
+ c = lastchar;
+ utf16length--;
+ }
+
+ aValidUTF8bytes = c - aBuffer;
+ aValidUTF16CodeUnits = utf16length;
+}
+
+nsresult
+NS_NewUnicharInputStream(nsIInputStream* aStreamToWrap,
+ nsIUnicharInputStream** aResult)
+{
+ *aResult = nullptr;
+
+ // Create converter input stream
+ RefPtr<UTF8InputStream> it = new UTF8InputStream();
+ nsresult rv = it->Init(aStreamToWrap);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ it.forget(aResult);
+ return NS_OK;
+}
diff --git a/xpcom/io/nsUnicharInputStream.h b/xpcom/io/nsUnicharInputStream.h
new file mode 100644
index 000000000..d4631af7e
--- /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 000000000..9125cbbb8
--- /dev/null
+++ b/xpcom/io/nsWildCard.cpp
@@ -0,0 +1,481 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* *
+ *
+ *
+ * nsWildCard.cpp: shell-like wildcard match routines
+ *
+ * See nsIZipReader.findEntries documentation in nsIZipReader.idl for
+ * a description of the syntax supported by the routines in this file.
+ *
+ * Rob McCool
+ *
+ */
+
+#include "nsWildCard.h"
+#include "nsXPCOM.h"
+#include "nsCRTGlue.h"
+#include "nsCharTraits.h"
+
+/* -------------------- ASCII-specific character methods ------------------- */
+
+typedef int static_assert_character_code_arrangement['a' > 'A' ? 1 : -1];
+
+template<class T>
+static int
+alpha(T aChar)
+{
+ return ('a' <= aChar && aChar <= 'z') ||
+ ('A' <= aChar && aChar <= 'Z');
+}
+
+template<class T>
+static int
+alphanumeric(T aChar)
+{
+ return ('0' <= aChar && aChar <= '9') || ::alpha(aChar);
+}
+
+template<class T>
+static int
+lower(T aChar)
+{
+ return ('A' <= aChar && aChar <= 'Z') ? aChar + ('a' - 'A') : aChar;
+}
+
+template<class T>
+static int
+upper(T aChar)
+{
+ return ('a' <= aChar && aChar <= 'z') ? aChar - ('a' - 'A') : aChar;
+}
+
+/* ----------------------------- _valid_subexp ---------------------------- */
+
+template<class T>
+static int
+_valid_subexp(const T* aExpr, T aStop1, T aStop2)
+{
+ int x;
+ int nsc = 0; /* Number of special characters */
+ int np; /* Number of pipe characters in union */
+ int tld = 0; /* Number of tilde characters */
+
+ for (x = 0; aExpr[x] && (aExpr[x] != aStop1) && (aExpr[x] != aStop2); ++x) {
+ switch (aExpr[x]) {
+ case '~':
+ if (tld) { /* at most one exclusion */
+ return INVALID_SXP;
+ }
+ if (aStop1) { /* no exclusions within unions */
+ return INVALID_SXP;
+ }
+ if (!aExpr[x + 1]) { /* exclusion cannot be last character */
+ return INVALID_SXP;
+ }
+ if (!x) { /* exclusion cannot be first character */
+ return INVALID_SXP;
+ }
+ ++tld;
+ MOZ_FALLTHROUGH;
+ case '*':
+ case '?':
+ case '$':
+ ++nsc;
+ break;
+ case '[':
+ ++nsc;
+ if ((!aExpr[++x]) || (aExpr[x] == ']')) {
+ return INVALID_SXP;
+ }
+ for (; aExpr[x] && (aExpr[x] != ']'); ++x) {
+ if (aExpr[x] == '\\' && !aExpr[++x]) {
+ return INVALID_SXP;
+ }
+ }
+ if (!aExpr[x]) {
+ return INVALID_SXP;
+ }
+ break;
+ case '(':
+ ++nsc;
+ if (aStop1) { /* no nested unions */
+ return INVALID_SXP;
+ }
+ np = -1;
+ do {
+ int t = ::_valid_subexp(&aExpr[++x], T(')'), T('|'));
+ if (t == 0 || t == INVALID_SXP) {
+ return INVALID_SXP;
+ }
+ x += t;
+ if (!aExpr[x]) {
+ return INVALID_SXP;
+ }
+ ++np;
+ } while (aExpr[x] == '|');
+ if (np < 1) { /* must be at least one pipe */
+ return INVALID_SXP;
+ }
+ break;
+ case ')':
+ case ']':
+ case '|':
+ return INVALID_SXP;
+ case '\\':
+ ++nsc;
+ if (!aExpr[++x]) {
+ return INVALID_SXP;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (!aStop1 && !nsc) { /* must be at least one special character */
+ return NON_SXP;
+ }
+ return ((aExpr[x] == aStop1 || aExpr[x] == aStop2) ? x : INVALID_SXP);
+}
+
+
+template<class T>
+int
+NS_WildCardValid_(const T* aExpr)
+{
+ int x = ::_valid_subexp(aExpr, T('\0'), T('\0'));
+ return (x < 0 ? x : VALID_SXP);
+}
+
+int
+NS_WildCardValid(const char* aExpr)
+{
+ return NS_WildCardValid_(aExpr);
+}
+
+int
+NS_WildCardValid(const char16_t* aExpr)
+{
+ return NS_WildCardValid_(aExpr);
+}
+
+/* ----------------------------- _shexp_match ----------------------------- */
+
+
+#define MATCH 0
+#define NOMATCH 1
+#define ABORTED -1
+
+template<class T>
+static int
+_shexp_match(const T* aStr, const T* aExpr, bool aCaseInsensitive,
+ unsigned int aLevel);
+
+/**
+ * Count characters until we reach a NUL character or either of the
+ * two delimiter characters, stop1 or stop2. If we encounter a bracketed
+ * expression, look only for NUL or ']' inside it. Do not look for stop1
+ * or stop2 inside it. Return ABORTED if bracketed expression is unterminated.
+ * Handle all escaping.
+ * Return index in input string of first stop found, or ABORTED if not found.
+ * If "dest" is non-nullptr, copy counted characters to it and null terminate.
+ */
+template<class T>
+static int
+_scan_and_copy(const T* aExpr, T aStop1, T aStop2, T* aDest)
+{
+ int sx; /* source index */
+ T cc;
+
+ for (sx = 0; (cc = aExpr[sx]) && cc != aStop1 && cc != aStop2; ++sx) {
+ if (cc == '\\') {
+ if (!aExpr[++sx]) {
+ return ABORTED; /* should be impossible */
+ }
+ } else if (cc == '[') {
+ while ((cc = aExpr[++sx]) && cc != ']') {
+ if (cc == '\\' && !aExpr[++sx]) {
+ return ABORTED;
+ }
+ }
+ if (!cc) {
+ return ABORTED; /* should be impossible */
+ }
+ }
+ }
+ if (aDest && sx) {
+ /* Copy all but the closing delimiter. */
+ memcpy(aDest, aExpr, sx * sizeof(T));
+ aDest[sx] = 0;
+ }
+ return cc ? sx : ABORTED; /* index of closing delimiter */
+}
+
+/* On input, expr[0] is the opening parenthesis of a union.
+ * See if any of the alternatives in the union matches as a pattern.
+ * The strategy is to take each of the alternatives, in turn, and append
+ * the rest of the expression (after the closing ')' that marks the end of
+ * this union) to that alternative, and then see if the resultant expression
+ * matches the input string. Repeat this until some alternative matches,
+ * or we have an abort.
+ */
+template<class T>
+static int
+_handle_union(const T* aStr, const T* aExpr, bool aCaseInsensitive,
+ unsigned int aLevel)
+{
+ int sx; /* source index */
+ int cp; /* source index of closing parenthesis */
+ int count;
+ int ret = NOMATCH;
+ T* e2;
+
+ /* Find the closing parenthesis that ends this union in the expression */
+ cp = ::_scan_and_copy(aExpr, T(')'), T('\0'), static_cast<T*>(nullptr));
+ if (cp == ABORTED || cp < 4) { /* must be at least "(a|b" before ')' */
+ return ABORTED;
+ }
+ ++cp; /* now index of char after closing parenthesis */
+ e2 = (T*)moz_xmalloc((1 + nsCharTraits<T>::length(aExpr)) * sizeof(T));
+ if (!e2) {
+ return ABORTED;
+ }
+ for (sx = 1; ; ++sx) {
+ /* Here, aExpr[sx] is one character past the preceding '(' or '|'. */
+ /* Copy everything up to the next delimiter to e2 */
+ count = ::_scan_and_copy(aExpr + sx, T(')'), T('|'), e2);
+ if (count == ABORTED || !count) {
+ ret = ABORTED;
+ break;
+ }
+ sx += count;
+ /* Append everything after closing parenthesis to e2. This is safe. */
+ nsCharTraits<T>::copy(e2 + count, aExpr + cp,
+ nsCharTraits<T>::length(aExpr + cp) + 1);
+ ret = ::_shexp_match(aStr, e2, aCaseInsensitive, aLevel + 1);
+ if (ret != NOMATCH || !aExpr[sx] || aExpr[sx] == ')') {
+ break;
+ }
+ }
+ free(e2);
+ if (sx < 2) {
+ ret = ABORTED;
+ }
+ return ret;
+}
+
+/* returns 1 if val is in range from start..end, case insensitive. */
+static int
+_is_char_in_range(unsigned char aStart, unsigned char aEnd, unsigned char aVal)
+{
+ char map[256];
+ memset(map, 0, sizeof(map));
+ while (aStart <= aEnd) {
+ map[lower(aStart++)] = 1;
+ }
+ return map[lower(aVal)];
+}
+
+template<class T>
+static int
+_shexp_match(const T* aStr, const T* aExpr, bool aCaseInsensitive,
+ unsigned int aLevel)
+{
+ int x; /* input string index */
+ int y; /* expression index */
+ int ret, neg;
+
+ if (aLevel > 20) { /* Don't let the stack get too deep. */
+ return ABORTED;
+ }
+ for (x = 0, y = 0; aExpr[y]; ++y, ++x) {
+ if (!aStr[x] && aExpr[y] != '$' && aExpr[y] != '*') {
+ return NOMATCH;
+ }
+ switch (aExpr[y]) {
+ case '$':
+ if (aStr[x]) {
+ return NOMATCH;
+ }
+ --x; /* we don't want loop to increment x */
+ break;
+ case '*':
+ while (aExpr[++y] == '*') {
+ }
+ if (!aExpr[y]) {
+ return MATCH;
+ }
+ while (aStr[x]) {
+ ret = ::_shexp_match(&aStr[x++], &aExpr[y], aCaseInsensitive,
+ aLevel + 1);
+ switch (ret) {
+ case NOMATCH:
+ continue;
+ case ABORTED:
+ return ABORTED;
+ default:
+ return MATCH;
+ }
+ }
+ if (aExpr[y] == '$' && aExpr[y + 1] == '\0' && !aStr[x]) {
+ return MATCH;
+ } else {
+ return NOMATCH;
+ }
+ case '[': {
+ T start, end = 0;
+ int i;
+ ++y;
+ neg = (aExpr[y] == '^' && aExpr[y + 1] != ']');
+ if (neg) {
+ ++y;
+ }
+ i = y;
+ start = aExpr[i++];
+ if (start == '\\') {
+ start = aExpr[i++];
+ }
+ if (::alphanumeric(start) && aExpr[i++] == '-') {
+ end = aExpr[i++];
+ if (end == '\\') {
+ end = aExpr[i++];
+ }
+ }
+ if (::alphanumeric(end) && aExpr[i] == ']') {
+ /* This is a range form: a-b */
+ T val = aStr[x];
+ if (end < start) { /* swap them */
+ T tmp = end;
+ end = start;
+ start = tmp;
+ }
+ if (aCaseInsensitive && ::alpha(val)) {
+ val = ::_is_char_in_range((unsigned char)start,
+ (unsigned char)end,
+ (unsigned char)val);
+ if (neg == val) {
+ return NOMATCH;
+ }
+ } else if (neg != (val < start || val > end)) {
+ return NOMATCH;
+ }
+ y = i;
+ } else {
+ /* Not range form */
+ int matched = 0;
+ for (; aExpr[y] != ']'; ++y) {
+ if (aExpr[y] == '\\') {
+ ++y;
+ }
+ if (aCaseInsensitive) {
+ matched |= (::upper(aStr[x]) == ::upper(aExpr[y]));
+ } else {
+ matched |= (aStr[x] == aExpr[y]);
+ }
+ }
+ if (neg == matched) {
+ return NOMATCH;
+ }
+ }
+ }
+ break;
+ case '(':
+ if (!aExpr[y + 1]) {
+ return ABORTED;
+ }
+ return ::_handle_union(&aStr[x], &aExpr[y], aCaseInsensitive,
+ aLevel + 1);
+ case '?':
+ break;
+ case ')':
+ case ']':
+ case '|':
+ return ABORTED;
+ case '\\':
+ ++y;
+ MOZ_FALLTHROUGH;
+ default:
+ if (aCaseInsensitive) {
+ if (::upper(aStr[x]) != ::upper(aExpr[y])) {
+ return NOMATCH;
+ }
+ } else {
+ if (aStr[x] != aExpr[y]) {
+ return NOMATCH;
+ }
+ }
+ break;
+ }
+ }
+ return (aStr[x] ? NOMATCH : MATCH);
+}
+
+template<class T>
+static int
+ns_WildCardMatch(const T* aStr, const T* aXp, bool aCaseInsensitive)
+{
+ T* expr = nullptr;
+ int ret = MATCH;
+
+ if (!nsCharTraits<T>::find(aXp, nsCharTraits<T>::length(aXp), T('~'))) {
+ return ::_shexp_match(aStr, aXp, aCaseInsensitive, 0);
+ }
+
+ expr = (T*)moz_xmalloc((nsCharTraits<T>::length(aXp) + 1) * sizeof(T));
+ if (!expr) {
+ return NOMATCH;
+ }
+ memcpy(expr, aXp, (nsCharTraits<T>::length(aXp) + 1) * sizeof(T));
+
+ int x = ::_scan_and_copy(expr, T('~'), T('\0'), static_cast<T*>(nullptr));
+ if (x != ABORTED && expr[x] == '~') {
+ expr[x++] = '\0';
+ ret = ::_shexp_match(aStr, &expr[x], aCaseInsensitive, 0);
+ switch (ret) {
+ case NOMATCH:
+ ret = MATCH;
+ break;
+ case MATCH:
+ ret = NOMATCH;
+ break;
+ default:
+ break;
+ }
+ }
+ if (ret == MATCH) {
+ ret = ::_shexp_match(aStr, expr, aCaseInsensitive, 0);
+ }
+
+ free(expr);
+ return ret;
+}
+
+template<class T>
+int
+NS_WildCardMatch_(const T* aStr, const T* aExpr, bool aCaseInsensitive)
+{
+ int is_valid = NS_WildCardValid(aExpr);
+ switch (is_valid) {
+ case INVALID_SXP:
+ return -1;
+ default:
+ return ::ns_WildCardMatch(aStr, aExpr, aCaseInsensitive);
+ }
+}
+
+int
+NS_WildCardMatch(const char* aStr, const char* aXp, bool aCaseInsensitive)
+{
+ return NS_WildCardMatch_(aStr, aXp, aCaseInsensitive);
+}
+
+int
+NS_WildCardMatch(const char16_t* aStr, const char16_t* aXp,
+ bool aCaseInsensitive)
+{
+ return NS_WildCardMatch_(aStr, aXp, aCaseInsensitive);
+}
diff --git a/xpcom/io/nsWildCard.h b/xpcom/io/nsWildCard.h
new file mode 100644
index 000000000..a077382bb
--- /dev/null
+++ b/xpcom/io/nsWildCard.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * nsWildCard.h: Defines and prototypes for shell exp. match routines
+ *
+ * See nsIZipReader.findEntries docs in nsIZipReader.idl for a description of
+ * the supported expression syntax.
+ *
+ * Note that the syntax documentation explicitly says the results of certain
+ * expressions are undefined. This is intentional to require less robustness
+ * in the code. Regular expression parsing is hard; the smaller the set of
+ * features and interactions this code must support, the easier it is to
+ * ensure it works.
+ *
+ */
+
+#ifndef nsWildCard_h__
+#define nsWildCard_h__
+
+#include "nscore.h"
+
+/* --------------------------- Public routines ---------------------------- */
+
+
+/*
+ * NS_WildCardValid takes a shell expression exp as input. It returns:
+ *
+ * NON_SXP if exp is a standard string
+ * INVALID_SXP if exp is a shell expression, but invalid
+ * VALID_SXP if exp is a valid shell expression
+ */
+
+#define NON_SXP -1
+#define INVALID_SXP -2
+#define VALID_SXP 1
+
+int NS_WildCardValid(const char* aExpr);
+
+int NS_WildCardValid(const char16_t* aExpr);
+
+/* return values for the search routines */
+#define MATCH 0
+#define NOMATCH 1
+#define ABORTED -1
+
+/*
+ * NS_WildCardMatch
+ *
+ * Takes a prevalidated shell expression exp, and a string str.
+ *
+ * Returns 0 on match and 1 on non-match.
+ */
+
+int NS_WildCardMatch(const char* aStr, const char* aExpr,
+ bool aCaseInsensitive);
+
+int NS_WildCardMatch(const char16_t* aStr, const char16_t* aExpr,
+ bool aCaseInsensitive);
+
+#endif /* nsWildCard_h__ */