/* -*- 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 "nsUCSupport.h" #include "nsUnicodeDecodeHelper.h" #include "nsUnicodeEncodeHelper.h" #include "mozilla/CheckedInt.h" #include <algorithm> #define DEFAULT_BUFFER_CAPACITY 16 // XXX review the buffer growth limitation code //---------------------------------------------------------------------- // Class nsBasicDecoderSupport [implementation] nsBasicDecoderSupport::nsBasicDecoderSupport() : mErrBehavior(kOnError_Recover) { } nsBasicDecoderSupport::~nsBasicDecoderSupport() { } //---------------------------------------------------------------------- // Interface nsISupports [implementation] #ifdef DEBUG NS_IMPL_ISUPPORTS(nsBasicDecoderSupport, nsIUnicodeDecoder, nsIBasicDecoder) #else NS_IMPL_ISUPPORTS(nsBasicDecoderSupport, nsIUnicodeDecoder) #endif //---------------------------------------------------------------------- // Interface nsIUnicodeDecoder [implementation] void nsBasicDecoderSupport::SetInputErrorBehavior(int32_t aBehavior) { MOZ_ASSERT(aBehavior == kOnError_Recover || aBehavior == kOnError_Signal, "Unknown behavior for SetInputErrorBehavior"); mErrBehavior = aBehavior; } char16_t nsBasicDecoderSupport::GetCharacterForUnMapped() { return char16_t(0xfffd); // Unicode REPLACEMENT CHARACTER } //---------------------------------------------------------------------- // Class nsBufferDecoderSupport [implementation] nsBufferDecoderSupport::nsBufferDecoderSupport(uint32_t aMaxLengthFactor) : nsBasicDecoderSupport(), mMaxLengthFactor(aMaxLengthFactor) { mBufferCapacity = DEFAULT_BUFFER_CAPACITY; mBuffer = new char[mBufferCapacity]; Reset(); } nsBufferDecoderSupport::~nsBufferDecoderSupport() { delete [] mBuffer; } void nsBufferDecoderSupport::FillBuffer(const char ** aSrc, int32_t aSrcLength) { int32_t bcr = std::min(mBufferCapacity - mBufferLength, aSrcLength); memcpy(mBuffer + mBufferLength, *aSrc, bcr); mBufferLength += bcr; (*aSrc) += bcr; } //---------------------------------------------------------------------- // Subclassing of nsBasicDecoderSupport class [implementation] NS_IMETHODIMP nsBufferDecoderSupport::Convert(const char* aSrc, int32_t* aSrcLength, char16_t* aDest, int32_t* aDestLength) { // we do all operations using pointers internally const char* src = aSrc; const char* srcEnd = aSrc + *aSrcLength; char16_t* dest = aDest; char16_t* destEnd = aDest + *aDestLength; int32_t bcr, bcw; // byte counts for read & write; nsresult res = NS_OK; // do we have some residual data from the last conversion? if (mBufferLength > 0) { if (dest == destEnd) { res = NS_OK_UDEC_MOREOUTPUT; } else { for (;;) { // we need new data to add to the buffer if (src == srcEnd) { res = NS_OK_UDEC_MOREINPUT; break; } // fill that buffer int32_t buffLen = mBufferLength; // initial buffer length FillBuffer(&src, srcEnd - src); // convert that buffer bcr = mBufferLength; bcw = destEnd - dest; res = ConvertNoBuff(mBuffer, &bcr, dest, &bcw); dest += bcw; // Detect invalid input character if (res == NS_ERROR_ILLEGAL_INPUT && mErrBehavior == kOnError_Signal) { break; } if ((res == NS_OK_UDEC_MOREINPUT) && (bcw == 0)) { res = NS_ERROR_UNEXPECTED; #if defined(DEBUG_yokoyama) || defined(DEBUG_ftang) NS_ERROR("This should not happen. Internal buffer may be corrupted."); #endif break; } else { if (bcr < buffLen) { // we didn't convert that residual data - unfill the buffer src -= mBufferLength - buffLen; mBufferLength = buffLen; #if defined(DEBUG_yokoyama) || defined(DEBUG_ftang) NS_ERROR("This should not happen. Internal buffer may be corrupted."); #endif } else { // the buffer and some extra data was converted - unget the rest src -= mBufferLength - bcr; mBufferLength = 0; res = NS_OK; } break; } } } } if (res == NS_OK) { bcr = srcEnd - src; bcw = destEnd - dest; res = ConvertNoBuff(src, &bcr, dest, &bcw); src += bcr; dest += bcw; // if we have partial input, store it in our internal buffer. if (res == NS_OK_UDEC_MOREINPUT) { bcr = srcEnd - src; // make sure buffer is large enough if (bcr > mBufferCapacity) { // somehow we got into an error state and the buffer is growing out // of control res = NS_ERROR_UNEXPECTED; } else { FillBuffer(&src, bcr); } } } *aSrcLength -= srcEnd - src; *aDestLength -= destEnd - dest; return res; } NS_IMETHODIMP nsBufferDecoderSupport::Reset() { mBufferLength = 0; return NS_OK; } NS_IMETHODIMP nsBufferDecoderSupport::GetMaxLength(const char* aSrc, int32_t aSrcLength, int32_t* aDestLength) { NS_ASSERTION(mMaxLengthFactor != 0, "Must override GetMaxLength!"); mozilla::CheckedInt32 length = aSrcLength; length *= mMaxLengthFactor; if (!length.isValid()) { return NS_ERROR_OUT_OF_MEMORY; } *aDestLength = length.value(); return NS_OK; } //---------------------------------------------------------------------- // Class nsMultiTableDecoderSupport [implementation] nsMultiTableDecoderSupport::nsMultiTableDecoderSupport( int32_t aTableCount, const uRange* aRangeArray, uScanClassID* aScanClassArray, uMappingTable** aMappingTable, uint32_t aMaxLengthFactor) : nsBufferDecoderSupport(aMaxLengthFactor) { mTableCount = aTableCount; mRangeArray = aRangeArray; mScanClassArray = aScanClassArray; mMappingTable = aMappingTable; } nsMultiTableDecoderSupport::~nsMultiTableDecoderSupport() { } //---------------------------------------------------------------------- // Subclassing of nsBufferDecoderSupport class [implementation] NS_IMETHODIMP nsMultiTableDecoderSupport::ConvertNoBuff(const char* aSrc, int32_t* aSrcLength, char16_t* aDest, int32_t* aDestLength) { return nsUnicodeDecodeHelper::ConvertByMultiTable(aSrc, aSrcLength, aDest, aDestLength, mTableCount, mRangeArray, mScanClassArray, mMappingTable, mErrBehavior == kOnError_Signal); } //---------------------------------------------------------------------- // Class nsOneByteDecoderSupport [implementation] nsOneByteDecoderSupport::nsOneByteDecoderSupport( uMappingTable* aMappingTable) : nsBasicDecoderSupport() , mMappingTable(aMappingTable) , mFastTableCreated(false) , mFastTableMutex("nsOneByteDecoderSupport mFastTableMutex") { } nsOneByteDecoderSupport::~nsOneByteDecoderSupport() { } //---------------------------------------------------------------------- // Subclassing of nsBasicDecoderSupport class [implementation] NS_IMETHODIMP nsOneByteDecoderSupport::Convert(const char* aSrc, int32_t* aSrcLength, char16_t* aDest, int32_t* aDestLength) { if (!mFastTableCreated) { // Probably better to make this non-lazy and get rid of the mutex mozilla::MutexAutoLock autoLock(mFastTableMutex); if (!mFastTableCreated) { nsresult res = nsUnicodeDecodeHelper::CreateFastTable( mMappingTable, mFastTable, ONE_BYTE_TABLE_SIZE); if (NS_FAILED(res)) return res; mFastTableCreated = true; } } return nsUnicodeDecodeHelper::ConvertByFastTable(aSrc, aSrcLength, aDest, aDestLength, mFastTable, ONE_BYTE_TABLE_SIZE, mErrBehavior == kOnError_Signal); } NS_IMETHODIMP nsOneByteDecoderSupport::GetMaxLength(const char* aSrc, int32_t aSrcLength, int32_t* aDestLength) { // single byte to Unicode converter *aDestLength = aSrcLength; return NS_OK_UDEC_EXACTLENGTH; } NS_IMETHODIMP nsOneByteDecoderSupport::Reset() { // nothing to reset, no internal state in this case return NS_OK; } //---------------------------------------------------------------------- // Class nsBasicEncoder [implementation] nsBasicEncoder::nsBasicEncoder() { } nsBasicEncoder::~nsBasicEncoder() { } //---------------------------------------------------------------------- // Interface nsISupports [implementation] NS_IMPL_ADDREF(nsBasicEncoder) NS_IMPL_RELEASE(nsBasicEncoder) #ifdef DEBUG NS_IMPL_QUERY_INTERFACE(nsBasicEncoder, nsIUnicodeEncoder, nsIBasicEncoder) #else NS_IMPL_QUERY_INTERFACE(nsBasicEncoder, nsIUnicodeEncoder) #endif //---------------------------------------------------------------------- // Class nsEncoderSupport [implementation] nsEncoderSupport::nsEncoderSupport(uint32_t aMaxLengthFactor) : mMaxLengthFactor(aMaxLengthFactor) { mBufferCapacity = DEFAULT_BUFFER_CAPACITY; mBuffer = new char[mBufferCapacity]; mErrBehavior = kOnError_Signal; mErrChar = 0; Reset(); } nsEncoderSupport::~nsEncoderSupport() { delete [] mBuffer; } NS_IMETHODIMP nsEncoderSupport::ConvertNoBuff(const char16_t* aSrc, int32_t* aSrcLength, char* aDest, int32_t* aDestLength) { // we do all operations using pointers internally const char16_t* src = aSrc; const char16_t* srcEnd = aSrc + *aSrcLength; char* dest = aDest; char* destEnd = aDest + *aDestLength; int32_t bcr, bcw; // byte counts for read & write; nsresult res; for (;;) { bcr = srcEnd - src; bcw = destEnd - dest; res = ConvertNoBuffNoErr(src, &bcr, dest, &bcw); src += bcr; dest += bcw; if (res == NS_ERROR_UENC_NOMAPPING) { if (mErrBehavior == kOnError_Replace) { const char16_t buff[] = {mErrChar}; bcr = 1; bcw = destEnd - dest; src--; // back the input: maybe the guy won't consume consume anything. res = ConvertNoBuffNoErr(buff, &bcr, dest, &bcw); src += bcr; dest += bcw; if (res != NS_OK) break; } else if (mErrBehavior == kOnError_CallBack) { bcw = destEnd - dest; src--; res = mErrEncoder->Convert(*src, dest, &bcw); dest += bcw; // if enought output space then the last char was used if (res != NS_OK_UENC_MOREOUTPUT) src++; if (res != NS_OK) break; } else break; } else break; } *aSrcLength -= srcEnd - src; *aDestLength -= destEnd - dest; return res; } NS_IMETHODIMP nsEncoderSupport::FinishNoBuff(char* aDest, int32_t* aDestLength) { *aDestLength = 0; return NS_OK; } nsresult nsEncoderSupport::FlushBuffer(char** aDest, const char* aDestEnd) { int32_t bcr, bcw; // byte counts for read & write; nsresult res = NS_OK; char* dest = *aDest; if (mBufferStart < mBufferEnd) { bcr = mBufferEnd - mBufferStart; bcw = aDestEnd - dest; if (bcw < bcr) bcr = bcw; memcpy(dest, mBufferStart, bcr); dest += bcr; mBufferStart += bcr; if (mBufferStart < mBufferEnd) res = NS_OK_UENC_MOREOUTPUT; } *aDest = dest; return res; } //---------------------------------------------------------------------- // Interface nsIUnicodeEncoder [implementation] NS_IMETHODIMP nsEncoderSupport::Convert(const char16_t* aSrc, int32_t* aSrcLength, char* aDest, int32_t* aDestLength) { // we do all operations using pointers internally const char16_t* src = aSrc; const char16_t* srcEnd = aSrc + *aSrcLength; char* dest = aDest; char* destEnd = aDest + *aDestLength; int32_t bcr, bcw; // byte counts for read & write; nsresult res; res = FlushBuffer(&dest, destEnd); if (res == NS_OK_UENC_MOREOUTPUT) goto final; bcr = srcEnd - src; bcw = destEnd - dest; res = ConvertNoBuff(src, &bcr, dest, &bcw); src += bcr; dest += bcw; if ((res == NS_OK_UENC_MOREOUTPUT) && (dest < destEnd)) { // convert exactly one character into the internal buffer // at this point, there should be at least a char in the input for (;;) { bcr = 1; bcw = mBufferCapacity; res = ConvertNoBuff(src, &bcr, mBuffer, &bcw); if (res == NS_OK_UENC_MOREOUTPUT) { delete [] mBuffer; mBufferCapacity *= 2; mBuffer = new char [mBufferCapacity]; } else { src += bcr; mBufferStart = mBufferEnd = mBuffer; mBufferEnd += bcw; break; } } res = FlushBuffer(&dest, destEnd); } final: *aSrcLength -= srcEnd - src; *aDestLength -= destEnd - dest; return res; } NS_IMETHODIMP nsEncoderSupport::Finish(char* aDest, int32_t* aDestLength) { // we do all operations using pointers internally char* dest = aDest; char* destEnd = aDest + *aDestLength; int32_t bcw; // byte count for write; nsresult res; res = FlushBuffer(&dest, destEnd); if (res == NS_OK_UENC_MOREOUTPUT) goto final; // do the finish into the internal buffer. for (;;) { bcw = mBufferCapacity; res = FinishNoBuff(mBuffer, &bcw); if (res == NS_OK_UENC_MOREOUTPUT) { delete [] mBuffer; mBufferCapacity *= 2; mBuffer = new char [mBufferCapacity]; } else { mBufferStart = mBufferEnd = mBuffer; mBufferEnd += bcw; break; } } res = FlushBuffer(&dest, destEnd); final: *aDestLength -= destEnd - dest; return res; } NS_IMETHODIMP nsEncoderSupport::Reset() { mBufferStart = mBufferEnd = mBuffer; return NS_OK; } NS_IMETHODIMP nsEncoderSupport::SetOutputErrorBehavior( int32_t aBehavior, nsIUnicharEncoder* aEncoder, char16_t aChar) { if (aBehavior == kOnError_CallBack && !aEncoder) return NS_ERROR_NULL_POINTER; mErrEncoder = aEncoder; mErrBehavior = aBehavior; mErrChar = aChar; return NS_OK; } NS_IMETHODIMP nsEncoderSupport::GetMaxLength(const char16_t* aSrc, int32_t aSrcLength, int32_t* aDestLength) { mozilla::CheckedInt32 length = aSrcLength; length *= mMaxLengthFactor; if (!length.isValid()) { return NS_ERROR_OUT_OF_MEMORY; } *aDestLength = length.value(); return NS_OK; } //---------------------------------------------------------------------- // Class nsTableEncoderSupport [implementation] nsTableEncoderSupport::nsTableEncoderSupport(uScanClassID aScanClass, uShiftOutTable* aShiftOutTable, uMappingTable* aMappingTable, uint32_t aMaxLengthFactor) : nsEncoderSupport(aMaxLengthFactor) { mScanClass = aScanClass; mShiftOutTable = aShiftOutTable, mMappingTable = aMappingTable; } nsTableEncoderSupport::nsTableEncoderSupport(uScanClassID aScanClass, uMappingTable* aMappingTable, uint32_t aMaxLengthFactor) : nsEncoderSupport(aMaxLengthFactor) { mScanClass = aScanClass; mShiftOutTable = nullptr; mMappingTable = aMappingTable; } nsTableEncoderSupport::~nsTableEncoderSupport() { } //---------------------------------------------------------------------- // Subclassing of nsEncoderSupport class [implementation] NS_IMETHODIMP nsTableEncoderSupport::ConvertNoBuffNoErr( const char16_t* aSrc, int32_t* aSrcLength, char* aDest, int32_t* aDestLength) { return nsUnicodeEncodeHelper::ConvertByTable(aSrc, aSrcLength, aDest, aDestLength, mScanClass, mShiftOutTable, mMappingTable); } //---------------------------------------------------------------------- // Class nsMultiTableEncoderSupport [implementation] nsMultiTableEncoderSupport::nsMultiTableEncoderSupport( int32_t aTableCount, uScanClassID* aScanClassArray, uShiftOutTable** aShiftOutTable, uMappingTable** aMappingTable, uint32_t aMaxLengthFactor) : nsEncoderSupport(aMaxLengthFactor) { mTableCount = aTableCount; mScanClassArray = aScanClassArray; mShiftOutTable = aShiftOutTable; mMappingTable = aMappingTable; } nsMultiTableEncoderSupport::~nsMultiTableEncoderSupport() { } //---------------------------------------------------------------------- // Subclassing of nsEncoderSupport class [implementation] NS_IMETHODIMP nsMultiTableEncoderSupport::ConvertNoBuffNoErr( const char16_t* aSrc, int32_t* aSrcLength, char* aDest, int32_t* aDestLength) { return nsUnicodeEncodeHelper::ConvertByMultiTable(aSrc, aSrcLength, aDest, aDestLength, mTableCount, mScanClassArray, mShiftOutTable, mMappingTable); }