already_AddRefed<nsITransferable> DataTransfer::GetTransferable(uint32_t aIndex, nsILoadContext* aLoadContext) { if (aIndex >= mItems.Length()) { return nullptr; } nsTArray<TransferItem>& item = mItems[aIndex]; uint32_t count = item.Length(); if (!count) { return nullptr; } nsCOMPtr<nsITransferable> transferable = do_CreateInstance("@mozilla.org/widget/transferable;1"); if (!transferable) { return nullptr; } transferable->Init(aLoadContext); nsCOMPtr<nsIStorageStream> storageStream; nsCOMPtr<nsIBinaryOutputStream> stream; bool added = false; bool handlingCustomFormats = true; uint32_t totalCustomLength = 0; const char* knownFormats[] = { kTextMime, kHTMLMime, kNativeHTMLMime, kRTFMime, kURLMime, kURLDataMime, kURLDescriptionMime, kURLPrivateMime, kPNGImageMime, kJPEGImageMime, kGIFImageMime, kNativeImageMime, kFileMime, kFilePromiseMime, kFilePromiseDirectoryMime, kMozTextInternal, kHTMLContext, kHTMLInfo }; /* * Two passes are made here to iterate over all of the types. First, look for * any types that are not in the list of known types. For this pass, handlingCustomFormats * will be true. Data that corresponds to unknown types will be pulled out and * inserted into a single type (kCustomTypesMime) by writing the data into a stream. * * The second pass will iterate over the formats looking for known types. These are * added as is. The unknown types are all then inserted as a single type (kCustomTypesMime) * in the same position of the first custom type. This model is used to maintain the * format order as best as possible. * * The format of the kCustomTypesMime type is one or more of the following stored sequentially: * <32-bit> type (only none or string is supported) * <32-bit> length of format * <wide string> format * <32-bit> length of data * <wide string> data * A type of eCustomClipboardTypeId_None ends the list, without any following data. */ do { for (uint32_t f = 0; f < count; f++) { const TransferItem& formatitem = item[f]; if (!formatitem.mData) { // skip empty items continue; } // If the data is of one of the well-known formats, use it directly. bool isCustomFormat = true; for (uint32_t f = 0; f < ArrayLength(knownFormats); f++) { if (formatitem.mFormat.EqualsASCII(knownFormats[f])) { isCustomFormat = false; break; } } uint32_t lengthInBytes; nsCOMPtr<nsISupports> convertedData; if (handlingCustomFormats) { if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &lengthInBytes)) { continue; } // When handling custom types, add the data to the stream if this is a // custom type. if (isCustomFormat) { // If it isn't a string, just ignore it. The dataTransfer is cached in the // drag sesion during drag-and-drop, so non-strings will be available when // dragging locally. nsCOMPtr<nsISupportsString> str(do_QueryInterface(convertedData)); if (str) { nsAutoString data; str->GetData(data); if (!stream) { // Create a storage stream to write to. NS_NewStorageStream(1024, UINT32_MAX, getter_AddRefs(storageStream)); nsCOMPtr<nsIOutputStream> outputStream; storageStream->GetOutputStream(0, getter_AddRefs(outputStream)); stream = do_CreateInstance("@mozilla.org/binaryoutputstream;1"); stream->SetOutputStream(outputStream); } int32_t formatLength = formatitem.mFormat.Length() * sizeof(nsString::char_type); stream->Write32(eCustomClipboardTypeId_String); stream->Write32(formatLength); stream->WriteBytes((const char *)formatitem.mFormat.get(), formatLength); stream->Write32(lengthInBytes); stream->WriteBytes((const char *)data.get(), lengthInBytes); // The total size of the stream is the format length, the data length, // two integers to hold the lengths and one integer for the string flag. totalCustomLength += formatLength + lengthInBytes + (sizeof(uint32_t) * 3); } } } else if (isCustomFormat && stream) { // This is the second pass of the loop (handlingCustomFormats is false). // When encountering the first custom format, append all of the stream // at this position. // Write out a terminator. totalCustomLength += sizeof(uint32_t); stream->Write32(eCustomClipboardTypeId_None); nsCOMPtr<nsIInputStream> inputStream; storageStream->NewInputStream(0, getter_AddRefs(inputStream)); RefPtr<nsStringBuffer> stringBuffer = nsStringBuffer::Alloc(totalCustomLength + 1); // Read the data from the string and add a null-terminator as ToString needs it. uint32_t amountRead; inputStream->Read(static_cast<char*>(stringBuffer->Data()), totalCustomLength, &amountRead); static_cast<char*>(stringBuffer->Data())[amountRead] = 0; nsCString str; stringBuffer->ToString(totalCustomLength, str); nsCOMPtr<nsISupportsCString> strSupports(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID)); strSupports->SetData(str); nsresult rv = transferable->SetTransferData(kCustomTypesMime, strSupports, totalCustomLength); if (NS_FAILED(rv)) { return nullptr; } added = true; // Clear the stream so it doesn't get used again. stream = nullptr; } else { // This is the second pass of the loop and a known type is encountered. // Add it as is. if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &lengthInBytes)) { continue; } // The underlying drag code uses text/unicode, so use that instead of text/plain const char* format; NS_ConvertUTF16toUTF8 utf8format(formatitem.mFormat); if (utf8format.EqualsLiteral(kTextMime)) { format = kUnicodeMime; } else { format = utf8format.get(); } // If a converter is set for a format, set the converter for the // transferable and don't add the item nsCOMPtr<nsIFormatConverter> converter = do_QueryInterface(convertedData); if (converter) { transferable->AddDataFlavor(format); transferable->SetConverter(converter); continue; } nsresult rv = transferable->SetTransferData(format, convertedData, lengthInBytes); if (NS_FAILED(rv)) { return nullptr; } added = true; } } handlingCustomFormats = !handlingCustomFormats; } while (!handlingCustomFormats); // only return the transferable if data was successfully added to it if (added) { return transferable.forget(); } return nullptr; }
bool DataTransfer::ConvertFromVariant(nsIVariant* aVariant, nsISupports** aSupports, uint32_t* aLength) { *aSupports = nullptr; *aLength = 0; uint16_t type; aVariant->GetDataType(&type); if (type == nsIDataType::VTYPE_INTERFACE || type == nsIDataType::VTYPE_INTERFACE_IS) { nsCOMPtr<nsISupports> data; if (NS_FAILED(aVariant->GetAsISupports(getter_AddRefs(data)))) return false; nsCOMPtr<nsIFlavorDataProvider> fdp = do_QueryInterface(data); if (fdp) { // for flavour data providers, use kFlavorHasDataProvider (which has the // value 0) as the length. NS_ADDREF(*aSupports = fdp); *aLength = nsITransferable::kFlavorHasDataProvider; } else { // wrap the item in an nsISupportsInterfacePointer nsCOMPtr<nsISupportsInterfacePointer> ptrSupports = do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID); if (!ptrSupports) return false; ptrSupports->SetData(data); NS_ADDREF(*aSupports = ptrSupports); *aLength = sizeof(nsISupportsInterfacePointer *); } return true; } char16_t* chrs; uint32_t len = 0; nsresult rv = aVariant->GetAsWStringWithSize(&len, &chrs); if (NS_FAILED(rv)) return false; nsAutoString str; str.Adopt(chrs, len); nsCOMPtr<nsISupportsString> strSupports(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); if (!strSupports) return false; strSupports->SetData(str); *aSupports = strSupports; NS_ADDREF(*aSupports); // each character is two bytes *aLength = str.Length() << 1; return true; }
already_AddRefed<nsITransferable> DataTransfer::GetTransferable(uint32_t aIndex, nsILoadContext* aLoadContext) { if (aIndex >= MozItemCount()) { return nullptr; } const nsTArray<RefPtr<DataTransferItem>>& item = *mItems->MozItemsAt(aIndex); uint32_t count = item.Length(); if (!count) { return nullptr; } nsCOMPtr<nsITransferable> transferable = do_CreateInstance("@mozilla.org/widget/transferable;1"); if (!transferable) { return nullptr; } transferable->Init(aLoadContext); nsCOMPtr<nsIStorageStream> storageStream; nsCOMPtr<nsIBinaryOutputStream> stream; bool added = false; bool handlingCustomFormats = true; // When writing the custom data, we need to ensure that there is sufficient // space for a (uint32_t) data ending type, and the null byte character at // the end of the nsCString. We claim that space upfront and store it in // baseLength. This value will be set to zero if a write error occurs // indicating that the data and length are no longer valid. const uint32_t baseLength = sizeof(uint32_t) + 1; uint32_t totalCustomLength = baseLength; const char* knownFormats[] = { kTextMime, kHTMLMime, kNativeHTMLMime, kRTFMime, kURLMime, kURLDataMime, kURLDescriptionMime, kURLPrivateMime, kPNGImageMime, kJPEGImageMime, kGIFImageMime, kNativeImageMime, kFileMime, kFilePromiseMime, kFilePromiseURLMime, kFilePromiseDestFilename, kFilePromiseDirectoryMime, kMozTextInternal, kHTMLContext, kHTMLInfo }; /* * Two passes are made here to iterate over all of the types. First, look for * any types that are not in the list of known types. For this pass, * handlingCustomFormats will be true. Data that corresponds to unknown types * will be pulled out and inserted into a single type (kCustomTypesMime) by * writing the data into a stream. * * The second pass will iterate over the formats looking for known types. * These are added as is. The unknown types are all then inserted as a single * type (kCustomTypesMime) in the same position of the first custom type. This * model is used to maintain the format order as best as possible. * * The format of the kCustomTypesMime type is one or more of the following * stored sequentially: * <32-bit> type (only none or string is supported) * <32-bit> length of format * <wide string> format * <32-bit> length of data * <wide string> data * A type of eCustomClipboardTypeId_None ends the list, without any following * data. */ do { for (uint32_t f = 0; f < count; f++) { RefPtr<DataTransferItem> formatitem = item[f]; nsCOMPtr<nsIVariant> variant = formatitem->DataNoSecurityCheck(); if (!variant) { // skip empty items continue; } nsAutoString type; formatitem->GetInternalType(type); // If the data is of one of the well-known formats, use it directly. bool isCustomFormat = true; for (uint32_t f = 0; f < ArrayLength(knownFormats); f++) { if (type.EqualsASCII(knownFormats[f])) { isCustomFormat = false; break; } } uint32_t lengthInBytes; nsCOMPtr<nsISupports> convertedData; if (handlingCustomFormats) { if (!ConvertFromVariant(variant, getter_AddRefs(convertedData), &lengthInBytes)) { continue; } // When handling custom types, add the data to the stream if this is a // custom type. If totalCustomLength is 0, then a write error occurred // on a previous item, so ignore any others. if (isCustomFormat && totalCustomLength > 0) { // If it isn't a string, just ignore it. The dataTransfer is cached in // the drag sesion during drag-and-drop, so non-strings will be // available when dragging locally. nsCOMPtr<nsISupportsString> str(do_QueryInterface(convertedData)); if (str) { nsAutoString data; str->GetData(data); if (!stream) { // Create a storage stream to write to. NS_NewStorageStream(1024, UINT32_MAX, getter_AddRefs(storageStream)); nsCOMPtr<nsIOutputStream> outputStream; storageStream->GetOutputStream(0, getter_AddRefs(outputStream)); stream = do_CreateInstance("@mozilla.org/binaryoutputstream;1"); stream->SetOutputStream(outputStream); } CheckedInt<uint32_t> formatLength = CheckedInt<uint32_t>(type.Length()) * sizeof(nsString::char_type); // The total size of the stream is the format length, the data // length, two integers to hold the lengths and one integer for // the string flag. Guard against large data by ignoring any that // don't fit. CheckedInt<uint32_t> newSize = formatLength + totalCustomLength + lengthInBytes + (sizeof(uint32_t) * 3); if (newSize.isValid()) { // If a write error occurs, set totalCustomLength to 0 so that // further processing gets ignored. nsresult rv = stream->Write32(eCustomClipboardTypeId_String); if (NS_WARN_IF(NS_FAILED(rv))) { totalCustomLength = 0; continue; } rv = stream->Write32(formatLength.value()); if (NS_WARN_IF(NS_FAILED(rv))) { totalCustomLength = 0; continue; } rv = stream->WriteBytes((const char *)type.get(), formatLength.value()); if (NS_WARN_IF(NS_FAILED(rv))) { totalCustomLength = 0; continue; } rv = stream->Write32(lengthInBytes); if (NS_WARN_IF(NS_FAILED(rv))) { totalCustomLength = 0; continue; } rv = stream->WriteBytes((const char *)data.get(), lengthInBytes); if (NS_WARN_IF(NS_FAILED(rv))) { totalCustomLength = 0; continue; } totalCustomLength = newSize.value(); } } } } else if (isCustomFormat && stream) { // This is the second pass of the loop (handlingCustomFormats is false). // When encountering the first custom format, append all of the stream // at this position. If totalCustomLength is 0 indicating a write error // occurred, or no data has been added to it, don't output anything, if (totalCustomLength > baseLength) { // Write out an end of data terminator. nsresult rv = stream->Write32(eCustomClipboardTypeId_None); if (NS_SUCCEEDED(rv)) { nsCOMPtr<nsIInputStream> inputStream; storageStream->NewInputStream(0, getter_AddRefs(inputStream)); RefPtr<nsStringBuffer> stringBuffer = nsStringBuffer::Alloc(totalCustomLength); // Subtract off the null terminator when reading. totalCustomLength--; // Read the data from the stream and add a null-terminator as // ToString needs it. uint32_t amountRead; rv = inputStream->Read(static_cast<char*>(stringBuffer->Data()), totalCustomLength, &amountRead); if (NS_SUCCEEDED(rv)) { static_cast<char*>(stringBuffer->Data())[amountRead] = 0; nsCString str; stringBuffer->ToString(totalCustomLength, str); nsCOMPtr<nsISupportsCString> strSupports(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID)); strSupports->SetData(str); nsresult rv = transferable->SetTransferData(kCustomTypesMime, strSupports, totalCustomLength); if (NS_FAILED(rv)) { return nullptr; } added = true; } } } // Clear the stream so it doesn't get used again. stream = nullptr; } else { // This is the second pass of the loop and a known type is encountered. // Add it as is. if (!ConvertFromVariant(variant, getter_AddRefs(convertedData), &lengthInBytes)) { continue; } // The underlying drag code uses text/unicode, so use that instead of // text/plain const char* format; NS_ConvertUTF16toUTF8 utf8format(type); if (utf8format.EqualsLiteral(kTextMime)) { format = kUnicodeMime; } else { format = utf8format.get(); } // If a converter is set for a format, set the converter for the // transferable and don't add the item nsCOMPtr<nsIFormatConverter> converter = do_QueryInterface(convertedData); if (converter) { transferable->AddDataFlavor(format); transferable->SetConverter(converter); continue; } nsresult rv = transferable->SetTransferData(format, convertedData, lengthInBytes); if (NS_FAILED(rv)) { return nullptr; } added = true; } } handlingCustomFormats = !handlingCustomFormats; } while (!handlingCustomFormats); // only return the transferable if data was successfully added to it if (added) { return transferable.forget(); } return nullptr; }