michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "Base64.h" michael@0: michael@0: #include "nsIInputStream.h" michael@0: #include "nsString.h" michael@0: michael@0: #include "plbase64.h" michael@0: michael@0: namespace { michael@0: michael@0: // BEGIN base64 encode code copied and modified from NSPR michael@0: const unsigned char *base = (unsigned char *)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; michael@0: michael@0: template michael@0: static void michael@0: Encode3to4(const unsigned char *src, T *dest) michael@0: { michael@0: uint32_t b32 = (uint32_t)0; michael@0: int i, j = 18; michael@0: michael@0: for( i = 0; i < 3; i++ ) michael@0: { michael@0: b32 <<= 8; michael@0: b32 |= (uint32_t)src[i]; michael@0: } michael@0: michael@0: for( i = 0; i < 4; i++ ) michael@0: { michael@0: dest[i] = base[ (uint32_t)((b32>>j) & 0x3F) ]; michael@0: j -= 6; michael@0: } michael@0: } michael@0: michael@0: template michael@0: static void michael@0: Encode2to4(const unsigned char *src, T *dest) michael@0: { michael@0: dest[0] = base[ (uint32_t)((src[0]>>2) & 0x3F) ]; michael@0: dest[1] = base[ (uint32_t)(((src[0] & 0x03) << 4) | ((src[1] >> 4) & 0x0F)) ]; michael@0: dest[2] = base[ (uint32_t)((src[1] & 0x0F) << 2) ]; michael@0: dest[3] = (unsigned char)'='; michael@0: } michael@0: michael@0: template michael@0: static void michael@0: Encode1to4(const unsigned char *src, T *dest) michael@0: { michael@0: dest[0] = base[ (uint32_t)((src[0]>>2) & 0x3F) ]; michael@0: dest[1] = base[ (uint32_t)((src[0] & 0x03) << 4) ]; michael@0: dest[2] = (unsigned char)'='; michael@0: dest[3] = (unsigned char)'='; michael@0: } michael@0: michael@0: template michael@0: static void michael@0: Encode(const unsigned char *src, uint32_t srclen, T *dest) michael@0: { michael@0: while( srclen >= 3 ) michael@0: { michael@0: Encode3to4(src, dest); michael@0: src += 3; michael@0: dest += 4; michael@0: srclen -= 3; michael@0: } michael@0: michael@0: switch( srclen ) michael@0: { michael@0: case 2: michael@0: Encode2to4(src, dest); michael@0: break; michael@0: case 1: michael@0: Encode1to4(src, dest); michael@0: break; michael@0: case 0: michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("coding error"); michael@0: } michael@0: } michael@0: michael@0: // END base64 encode code copied and modified from NSPR. michael@0: michael@0: template michael@0: struct EncodeInputStream_State { michael@0: unsigned char c[3]; michael@0: uint8_t charsOnStack; michael@0: typename T::char_type* buffer; michael@0: }; michael@0: michael@0: template michael@0: NS_METHOD michael@0: EncodeInputStream_Encoder(nsIInputStream *aStream, michael@0: void *aClosure, michael@0: const char *aFromSegment, michael@0: uint32_t aToOffset, michael@0: uint32_t aCount, michael@0: uint32_t *aWriteCount) michael@0: { michael@0: NS_ASSERTION(aCount > 0, "Er, what?"); michael@0: michael@0: EncodeInputStream_State* state = michael@0: static_cast*>(aClosure); michael@0: michael@0: // If we have any data left from last time, encode it now. michael@0: uint32_t countRemaining = aCount; michael@0: const unsigned char *src = (const unsigned char*)aFromSegment; michael@0: if (state->charsOnStack) { michael@0: unsigned char firstSet[4]; michael@0: if (state->charsOnStack == 1) { michael@0: firstSet[0] = state->c[0]; michael@0: firstSet[1] = src[0]; michael@0: firstSet[2] = (countRemaining > 1) ? src[1] : '\0'; michael@0: firstSet[3] = '\0'; michael@0: } else /* state->charsOnStack == 2 */ { michael@0: firstSet[0] = state->c[0]; michael@0: firstSet[1] = state->c[1]; michael@0: firstSet[2] = src[0]; michael@0: firstSet[3] = '\0'; michael@0: } michael@0: Encode(firstSet, 3, state->buffer); michael@0: state->buffer += 4; michael@0: countRemaining -= (3 - state->charsOnStack); michael@0: src += (3 - state->charsOnStack); michael@0: state->charsOnStack = 0; michael@0: } michael@0: michael@0: // Encode the bulk of the michael@0: uint32_t encodeLength = countRemaining - countRemaining % 3; michael@0: NS_ABORT_IF_FALSE(encodeLength % 3 == 0, michael@0: "Should have an exact number of triplets!"); michael@0: Encode(src, encodeLength, state->buffer); michael@0: state->buffer += (encodeLength / 3) * 4; michael@0: src += encodeLength; michael@0: countRemaining -= encodeLength; michael@0: michael@0: // We must consume all data, so if there's some data left stash it michael@0: *aWriteCount = aCount; michael@0: michael@0: if (countRemaining) { michael@0: // We should never have a full triplet left at this point. michael@0: NS_ABORT_IF_FALSE(countRemaining < 3, "We should have encoded more!"); michael@0: state->c[0] = src[0]; michael@0: state->c[1] = (countRemaining == 2) ? src[1] : '\0'; michael@0: state->charsOnStack = countRemaining; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: template michael@0: nsresult michael@0: EncodeInputStream(nsIInputStream *aInputStream, michael@0: T &aDest, michael@0: uint32_t aCount, michael@0: uint32_t aOffset) michael@0: { michael@0: nsresult rv; michael@0: uint64_t count64 = aCount; michael@0: michael@0: if (!aCount) { michael@0: rv = aInputStream->Available(&count64); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: // if count64 is over 4GB, it will be failed at the below condition, michael@0: // then will return NS_ERROR_OUT_OF_MEMORY michael@0: aCount = (uint32_t)count64; michael@0: } michael@0: michael@0: uint64_t countlong = michael@0: (count64 + 2) / 3 * 4; // +2 due to integer math. michael@0: if (countlong + aOffset > UINT32_MAX) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: uint32_t count = uint32_t(countlong); michael@0: michael@0: aDest.SetLength(count + aOffset); michael@0: if (aDest.Length() != count + aOffset) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: EncodeInputStream_State state; michael@0: state.charsOnStack = 0; michael@0: state.c[2] = '\0'; michael@0: state.buffer = aOffset + aDest.BeginWriting(); michael@0: michael@0: while (1) { michael@0: uint32_t read = 0; michael@0: michael@0: rv = aInputStream->ReadSegments(&EncodeInputStream_Encoder, michael@0: (void*)&state, michael@0: aCount, michael@0: &read); michael@0: if (NS_FAILED(rv)) { michael@0: if (rv == NS_BASE_STREAM_WOULD_BLOCK) michael@0: NS_RUNTIMEABORT("Not implemented for async streams!"); michael@0: if (rv == NS_ERROR_NOT_IMPLEMENTED) michael@0: NS_RUNTIMEABORT("Requires a stream that implements ReadSegments!"); michael@0: return rv; michael@0: } michael@0: michael@0: if (!read) michael@0: break; michael@0: } michael@0: michael@0: // Finish encoding if anything is left michael@0: if (state.charsOnStack) michael@0: Encode(state.c, state.charsOnStack, state.buffer); michael@0: michael@0: if (aDest.Length()) michael@0: // May belong to an nsCString with an unallocated buffer, so only null michael@0: // terminate if there is a need to. michael@0: *aDest.EndWriting() = '\0'; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace (anonymous) michael@0: michael@0: namespace mozilla { michael@0: michael@0: nsresult michael@0: Base64EncodeInputStream(nsIInputStream *aInputStream, michael@0: nsACString &aDest, michael@0: uint32_t aCount, michael@0: uint32_t aOffset) michael@0: { michael@0: return EncodeInputStream(aInputStream, aDest, aCount, aOffset); michael@0: } michael@0: michael@0: nsresult michael@0: Base64EncodeInputStream(nsIInputStream *aInputStream, michael@0: nsAString &aDest, michael@0: uint32_t aCount, michael@0: uint32_t aOffset) michael@0: { michael@0: return EncodeInputStream(aInputStream, aDest, aCount, aOffset); michael@0: } michael@0: michael@0: nsresult michael@0: Base64Encode(const nsACString &aBinaryData, nsACString &aString) michael@0: { michael@0: // Check for overflow. michael@0: if (aBinaryData.Length() > (UINT32_MAX / 4) * 3) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Don't ask PR_Base64Encode to encode empty strings michael@0: if (aBinaryData.IsEmpty()) { michael@0: aString.Truncate(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t stringLen = ((aBinaryData.Length() + 2) / 3) * 4; michael@0: michael@0: char *buffer; michael@0: michael@0: // Add one byte for null termination. michael@0: if (aString.SetCapacity(stringLen + 1, fallible_t()) && michael@0: (buffer = aString.BeginWriting()) && michael@0: PL_Base64Encode(aBinaryData.BeginReading(), aBinaryData.Length(), buffer)) { michael@0: // PL_Base64Encode doesn't null terminate the buffer for us when we pass michael@0: // the buffer in. Do that manually. michael@0: buffer[stringLen] = '\0'; michael@0: michael@0: aString.SetLength(stringLen); michael@0: return NS_OK; michael@0: } michael@0: michael@0: aString.Truncate(); michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: nsresult michael@0: Base64Encode(const nsAString &aString, nsAString &aBinaryData) michael@0: { michael@0: NS_LossyConvertUTF16toASCII string(aString); michael@0: nsAutoCString binaryData; michael@0: michael@0: nsresult rv = Base64Encode(string, binaryData); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: CopyASCIItoUTF16(binaryData, aBinaryData); michael@0: } else { michael@0: aBinaryData.Truncate(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: Base64Decode(const nsACString &aString, nsACString &aBinaryData) michael@0: { michael@0: // Check for overflow. michael@0: if (aString.Length() > UINT32_MAX / 3) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Don't ask PR_Base64Decode to decode the empty string michael@0: if (aString.IsEmpty()) { michael@0: aBinaryData.Truncate(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t binaryDataLen = ((aString.Length() * 3) / 4); michael@0: michael@0: char *buffer; michael@0: michael@0: // Add one byte for null termination. michael@0: if (aBinaryData.SetCapacity(binaryDataLen + 1, fallible_t()) && michael@0: (buffer = aBinaryData.BeginWriting()) && michael@0: PL_Base64Decode(aString.BeginReading(), aString.Length(), buffer)) { michael@0: // PL_Base64Decode doesn't null terminate the buffer for us when we pass michael@0: // the buffer in. Do that manually, taking into account the number of '=' michael@0: // characters we were passed. michael@0: if (!aString.IsEmpty() && aString[aString.Length() - 1] == '=') { michael@0: if (aString.Length() > 1 && aString[aString.Length() - 2] == '=') { michael@0: binaryDataLen -= 2; michael@0: } else { michael@0: binaryDataLen -= 1; michael@0: } michael@0: } michael@0: buffer[binaryDataLen] = '\0'; michael@0: michael@0: aBinaryData.SetLength(binaryDataLen); michael@0: return NS_OK; michael@0: } michael@0: michael@0: aBinaryData.Truncate(); michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: nsresult michael@0: Base64Decode(const nsAString &aBinaryData, nsAString &aString) michael@0: { michael@0: NS_LossyConvertUTF16toASCII binaryData(aBinaryData); michael@0: nsAutoCString string; michael@0: michael@0: nsresult rv = Base64Decode(binaryData, string); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: CopyASCIItoUTF16(string, aString); michael@0: } else { michael@0: aString.Truncate(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: } // namespace mozilla