michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * 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: michael@0: // michael@0: // Part of the reason these routines are all in once place is so that as new michael@0: // data flavors are added that are known to be one-byte or two-byte strings, or even michael@0: // raw binary data, then we just have to go to one place to change how the data michael@0: // moves into/out of the primitives and native line endings. michael@0: // michael@0: // If you add new flavors that have special consideration (binary data or one-byte michael@0: // char* strings), please update all the helper classes in this file. michael@0: // michael@0: // For now, this is the assumption that we are making: michael@0: // - text/plain is always a char* michael@0: // - anything else is a char16_t* michael@0: // michael@0: michael@0: michael@0: #include "nsPrimitiveHelpers.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsXPCOM.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsITransferable.h" michael@0: #include "nsIComponentManager.h" michael@0: #include "nsLinebreakConverter.h" michael@0: #include "nsReadableUtils.h" michael@0: michael@0: #include "nsIServiceManager.h" michael@0: #include "nsICharsetConverterManager.h" michael@0: // unicode conversion michael@0: # include "nsIPlatformCharset.h" michael@0: #include "nsISaveAsCharset.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "mozilla/Likely.h" michael@0: michael@0: michael@0: // michael@0: // CreatePrimitiveForData michael@0: // michael@0: // Given some data and the flavor it corresponds to, creates the appropriate michael@0: // nsISupports* wrapper for passing across IDL boundaries. Right now, everything michael@0: // creates a two-byte |nsISupportsString|, except for "text/plain" and native michael@0: // platform HTML (CF_HTML on win32) michael@0: // michael@0: void michael@0: nsPrimitiveHelpers :: CreatePrimitiveForData ( const char* aFlavor, const void* aDataBuff, michael@0: uint32_t aDataLen, nsISupports** aPrimitive ) michael@0: { michael@0: if ( !aPrimitive ) michael@0: return; michael@0: michael@0: if ( strcmp(aFlavor,kTextMime) == 0 || strcmp(aFlavor,kNativeHTMLMime) == 0 ) { michael@0: nsCOMPtr primitive = michael@0: do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID); michael@0: if ( primitive ) { michael@0: const char * start = reinterpret_cast(aDataBuff); michael@0: primitive->SetData(Substring(start, start + aDataLen)); michael@0: NS_ADDREF(*aPrimitive = primitive); michael@0: } michael@0: } michael@0: else { michael@0: nsCOMPtr primitive = michael@0: do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID); michael@0: if (primitive ) { michael@0: if (aDataLen % 2) { michael@0: nsAutoArrayPtr buffer(new char[aDataLen + 1]); michael@0: if (!MOZ_LIKELY(buffer)) michael@0: return; michael@0: michael@0: memcpy(buffer, aDataBuff, aDataLen); michael@0: buffer[aDataLen] = 0; michael@0: const char16_t* start = reinterpret_cast(buffer.get()); michael@0: // recall that length takes length as characters, not bytes michael@0: primitive->SetData(Substring(start, start + (aDataLen + 1) / 2)); michael@0: } else { michael@0: const char16_t* start = reinterpret_cast(aDataBuff); michael@0: // recall that length takes length as characters, not bytes michael@0: primitive->SetData(Substring(start, start + (aDataLen / 2))); michael@0: } michael@0: NS_ADDREF(*aPrimitive = primitive); michael@0: } michael@0: } michael@0: michael@0: } // CreatePrimitiveForData michael@0: michael@0: michael@0: // michael@0: // CreateDataFromPrimitive michael@0: // michael@0: // Given a nsISupports* primitive and the flavor it represents, creates a new data michael@0: // buffer with the data in it. This data will be null terminated, but the length michael@0: // parameter does not reflect that. michael@0: // michael@0: void michael@0: nsPrimitiveHelpers :: CreateDataFromPrimitive ( const char* aFlavor, nsISupports* aPrimitive, michael@0: void** aDataBuff, uint32_t aDataLen ) michael@0: { michael@0: if ( !aDataBuff ) michael@0: return; michael@0: michael@0: *aDataBuff = nullptr; michael@0: michael@0: if ( strcmp(aFlavor,kTextMime) == 0 ) { michael@0: nsCOMPtr plainText ( do_QueryInterface(aPrimitive) ); michael@0: if ( plainText ) { michael@0: nsAutoCString data; michael@0: plainText->GetData ( data ); michael@0: *aDataBuff = ToNewCString(data); michael@0: } michael@0: } michael@0: else { michael@0: nsCOMPtr doubleByteText ( do_QueryInterface(aPrimitive) ); michael@0: if ( doubleByteText ) { michael@0: nsAutoString data; michael@0: doubleByteText->GetData ( data ); michael@0: *aDataBuff = ToNewUnicode(data); michael@0: } michael@0: } michael@0: michael@0: } michael@0: michael@0: michael@0: // michael@0: // ConvertUnicodeToPlatformPlainText michael@0: // michael@0: // Given a unicode buffer (flavor text/unicode), this converts it to plain text using michael@0: // the appropriate platform charset encoding. |inUnicodeLen| is the length of the input michael@0: // string, not the # of bytes in the buffer. The |outPlainTextData| is null terminated, michael@0: // but its length parameter, |outPlainTextLen|, does not reflect that. michael@0: // michael@0: nsresult michael@0: nsPrimitiveHelpers :: ConvertUnicodeToPlatformPlainText ( char16_t* inUnicode, int32_t inUnicodeLen, michael@0: char** outPlainTextData, int32_t* outPlainTextLen ) michael@0: { michael@0: if ( !outPlainTextData || !outPlainTextLen ) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // get the charset michael@0: nsresult rv; michael@0: nsCOMPtr platformCharsetService = do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv); michael@0: michael@0: nsAutoCString platformCharset; michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = platformCharsetService->GetCharset(kPlatformCharsetSel_PlainTextInClipboard, platformCharset); michael@0: if (NS_FAILED(rv)) michael@0: platformCharset.AssignLiteral("ISO-8859-1"); michael@0: michael@0: // use transliterate to convert things like smart quotes to normal quotes for plain text michael@0: michael@0: nsCOMPtr converter = do_CreateInstance("@mozilla.org/intl/saveascharset;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = converter->Init(platformCharset.get(), michael@0: nsISaveAsCharset::attr_EntityAfterCharsetConv + michael@0: nsISaveAsCharset::attr_FallbackQuestionMark, michael@0: nsIEntityConverter::transliterate); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = converter->Convert(inUnicode, outPlainTextData); michael@0: *outPlainTextLen = *outPlainTextData ? strlen(*outPlainTextData) : 0; michael@0: michael@0: NS_ASSERTION ( NS_SUCCEEDED(rv), "Error converting unicode to plain text" ); michael@0: michael@0: return rv; michael@0: } // ConvertUnicodeToPlatformPlainText michael@0: michael@0: michael@0: // michael@0: // ConvertPlatformPlainTextToUnicode michael@0: // michael@0: // Given a char buffer (flavor text/plaikn), this converts it to unicode using michael@0: // the appropriate platform charset encoding. |outUnicode| is null terminated, michael@0: // but its length parameter, |outUnicodeLen|, does not reflect that. |outUnicodeLen| is michael@0: // the length of the string in characters, not bytes. michael@0: // michael@0: nsresult michael@0: nsPrimitiveHelpers :: ConvertPlatformPlainTextToUnicode ( const char* inText, int32_t inTextLen, michael@0: char16_t** outUnicode, int32_t* outUnicodeLen ) michael@0: { michael@0: if ( !outUnicode || !outUnicodeLen ) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // Get the appropriate unicode decoder. We're guaranteed that this won't change michael@0: // through the life of the app so we can cache it. michael@0: nsresult rv = NS_OK; michael@0: static nsCOMPtr decoder; michael@0: static bool hasConverter = false; michael@0: if ( !hasConverter ) { michael@0: // get the charset michael@0: nsAutoCString platformCharset; michael@0: nsCOMPtr platformCharsetService = do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = platformCharsetService->GetCharset(kPlatformCharsetSel_PlainTextInClipboard, platformCharset); michael@0: if (NS_FAILED(rv)) michael@0: platformCharset.AssignLiteral("ISO-8859-1"); michael@0: michael@0: // get the decoder michael@0: nsCOMPtr ccm = michael@0: do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); michael@0: rv = ccm->GetUnicodeDecoderRaw(platformCharset.get(), michael@0: getter_AddRefs(decoder)); michael@0: michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "GetUnicodeEncoderRaw failed."); michael@0: if (NS_FAILED(rv)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: hasConverter = true; michael@0: } michael@0: michael@0: // Estimate out length and allocate the buffer based on a worst-case estimate, then do michael@0: // the conversion. michael@0: decoder->GetMaxLength(inText, inTextLen, outUnicodeLen); // |outUnicodeLen| is number of chars michael@0: if ( *outUnicodeLen ) { michael@0: *outUnicode = reinterpret_cast(nsMemory::Alloc((*outUnicodeLen + 1) * sizeof(char16_t))); michael@0: if ( *outUnicode ) { michael@0: rv = decoder->Convert(inText, &inTextLen, *outUnicode, outUnicodeLen); michael@0: (*outUnicode)[*outUnicodeLen] = '\0'; // null terminate. Convert() doesn't do it for us michael@0: } michael@0: } // if valid length michael@0: michael@0: NS_ASSERTION ( NS_SUCCEEDED(rv), "Error converting plain text to unicode" ); michael@0: michael@0: return rv; michael@0: } // ConvertPlatformPlainTextToUnicode michael@0: michael@0: michael@0: // michael@0: // ConvertPlatformToDOMLinebreaks michael@0: // michael@0: // Given some data, convert from the platform linebreaks into the LF expected by the michael@0: // DOM. This will attempt to convert the data in place, but the buffer may still need to michael@0: // be reallocated regardless (disposing the old buffer is taken care of internally, see michael@0: // the note below). michael@0: // michael@0: // NOTE: this assumes that it can use nsMemory to dispose of the old buffer. michael@0: // michael@0: nsresult michael@0: nsLinebreakHelpers :: ConvertPlatformToDOMLinebreaks ( const char* inFlavor, void** ioData, michael@0: int32_t* ioLengthInBytes ) michael@0: { michael@0: NS_ASSERTION ( ioData && *ioData && ioLengthInBytes, "Bad Params"); michael@0: if ( !(ioData && *ioData && ioLengthInBytes) ) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: nsresult retVal = NS_OK; michael@0: michael@0: if ( strcmp(inFlavor, "text/plain") == 0 ) { michael@0: char* buffAsChars = reinterpret_cast(*ioData); michael@0: char* oldBuffer = buffAsChars; michael@0: retVal = nsLinebreakConverter::ConvertLineBreaksInSitu ( &buffAsChars, nsLinebreakConverter::eLinebreakAny, michael@0: nsLinebreakConverter::eLinebreakContent, michael@0: *ioLengthInBytes, ioLengthInBytes ); michael@0: if ( NS_SUCCEEDED(retVal) ) { michael@0: if ( buffAsChars != oldBuffer ) // check if buffer was reallocated michael@0: nsMemory::Free ( oldBuffer ); michael@0: *ioData = buffAsChars; michael@0: } michael@0: } michael@0: else if ( strcmp(inFlavor, "image/jpeg") == 0 ) { michael@0: // I'd assume we don't want to do anything for binary data.... michael@0: } michael@0: else { michael@0: char16_t* buffAsUnichar = reinterpret_cast(*ioData); michael@0: char16_t* oldBuffer = buffAsUnichar; michael@0: int32_t newLengthInChars; michael@0: retVal = nsLinebreakConverter::ConvertUnicharLineBreaksInSitu ( &buffAsUnichar, nsLinebreakConverter::eLinebreakAny, michael@0: nsLinebreakConverter::eLinebreakContent, michael@0: *ioLengthInBytes / sizeof(char16_t), &newLengthInChars ); michael@0: if ( NS_SUCCEEDED(retVal) ) { michael@0: if ( buffAsUnichar != oldBuffer ) // check if buffer was reallocated michael@0: nsMemory::Free ( oldBuffer ); michael@0: *ioData = buffAsUnichar; michael@0: *ioLengthInBytes = newLengthInChars * sizeof(char16_t); michael@0: } michael@0: } michael@0: michael@0: return retVal; michael@0: michael@0: } // ConvertPlatformToDOMLinebreaks