From c6b21edd1218a26066270db05fc96b75af509038 Mon Sep 17 00:00:00 2001 From: Haik Aftandilian Date: Tue, 27 Mar 2018 14:55:33 -0700 Subject: [PATCH] Bug 1437281 - OSX dragging image to desktop changes OSX File associations r=mystor On Mac, when dragging an image, add the image request's MIME type to the transfer so that the MIME-extension check can be done in the parent process to avoid content sandboxing issues. MozReview-Commit-ID: 3cb4fCr6GnL --HG-- extra : rebase_source : 43720237b467765401b5504c57bbc1b43d4dfdc0 --- dom/base/nsContentAreaDragDrop.cpp | 272 +++++++++++++++++++++-------- dom/base/nsContentAreaDragDrop.h | 2 +- dom/events/DataTransfer.cpp | 7 +- dom/events/DataTransfer.h | 3 +- widget/nsITransferable.idl | 6 + 5 files changed, 210 insertions(+), 80 deletions(-) diff --git a/dom/base/nsContentAreaDragDrop.cpp b/dom/base/nsContentAreaDragDrop.cpp index e4369e7eef0e..27f09a489bd3 100644 --- a/dom/base/nsContentAreaDragDrop.cpp +++ b/dom/base/nsContentAreaDragDrop.cpp @@ -77,9 +77,11 @@ private: void AddString(DataTransfer* aDataTransfer, const nsAString& aFlavor, const nsAString& aData, - nsIPrincipal* aPrincipal); + nsIPrincipal* aPrincipal, + bool aHidden=false); nsresult AddStringsToDataTransfer(nsIContent* aDragNode, DataTransfer* aDataTransfer); + nsresult GetImageData(imgIContainer* aImage, imgIRequest* aRequest); static nsresult GetDraggableSelectionData(nsISelection* inSelection, nsIContent* inRealTargetNode, nsIContent **outImageOrLinkNode, @@ -99,6 +101,9 @@ private: nsString mUrlString; nsString mImageSourceString; nsString mImageDestFileName; +#if defined (XP_MACOSX) + nsString mImageRequestMime; +#endif nsString mTitleString; // will be filled automatically if you fill urlstring nsString mHtmlString; @@ -138,22 +143,16 @@ NS_IMPL_ISUPPORTS(nsContentAreaDragDropDataProvider, nsIFlavorDataProvider) // used on platforms where it's possible to drag items (e.g. images) // into the file system nsresult -nsContentAreaDragDropDataProvider::SaveURIToFile(nsAString& inSourceURIString, +nsContentAreaDragDropDataProvider::SaveURIToFile(nsIURI* inSourceURI, nsIFile* inDestFile, bool isPrivate) { - nsCOMPtr sourceURI; - nsresult rv = NS_NewURI(getter_AddRefs(sourceURI), inSourceURIString); - if (NS_FAILED(rv)) { - return NS_ERROR_FAILURE; - } - - nsCOMPtr sourceURL = do_QueryInterface(sourceURI); + nsCOMPtr sourceURL = do_QueryInterface(inSourceURI); if (!sourceURL) { return NS_ERROR_NO_INTERFACE; } - rv = inDestFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + nsresult rv = inDestFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); NS_ENSURE_SUCCESS(rv, rv); // we rely on the fact that the WPB is refcounted by the channel etc, @@ -166,12 +165,56 @@ nsContentAreaDragDropDataProvider::SaveURIToFile(nsAString& inSourceURIString, persist->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION); // referrer policy can be anything since the referrer is nullptr - return persist->SavePrivacyAwareURI(sourceURI, nullptr, nullptr, + return persist->SavePrivacyAwareURI(inSourceURI, nullptr, nullptr, mozilla::net::RP_Unset, nullptr, nullptr, inDestFile, isPrivate); } +/* + * Check if the provided filename extension is valid for the MIME type and + * return the MIME type's primary extension. + * + * @param aExtension [in] the extension to check + * @param aMimeType [in] the MIME type to check the extension with + * @param aIsValidExtension [out] true if |aExtension| is valid for + * |aMimeType| + * @param aPrimaryExtension [out] the primary extension for the MIME type + * to potentially be used as a replacement + * for |aExtension| + */ +nsresult +CheckAndGetExtensionForMime(const nsCString& aExtension, + const nsCString& aMimeType, + bool* aIsValidExtension, + nsACString* aPrimaryExtension) +{ + nsresult rv; + + nsCOMPtr mimeService = do_GetService("@mozilla.org/mime;1"); + if (NS_WARN_IF(!mimeService)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr mimeInfo; + rv = mimeService->GetFromTypeAndExtension(aMimeType, EmptyCString(), + getter_AddRefs(mimeInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mimeInfo->GetPrimaryExtension(*aPrimaryExtension); + NS_ENSURE_SUCCESS(rv, rv); + + if (aExtension.IsEmpty()) { + *aIsValidExtension = false; + return NS_OK; + } + + rv = mimeInfo->ExtensionExists(aExtension, aIsValidExtension); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + // This is our nsIFlavorDataProvider callback. There are several // assumptions here that make this work: // @@ -214,6 +257,10 @@ nsContentAreaDragDropDataProvider::GetFlavorData(nsITransferable *aTransferable, if (sourceURLString.IsEmpty()) return NS_ERROR_FAILURE; + nsCOMPtr sourceURI; + rv = NS_NewURI(getter_AddRefs(sourceURI), sourceURLString); + NS_ENSURE_SUCCESS(rv, rv); + aTransferable->GetTransferData(kFilePromiseDestFilename, getter_AddRefs(tmp), &dataSize); supportsString = do_QueryInterface(tmp); @@ -225,6 +272,60 @@ nsContentAreaDragDropDataProvider::GetFlavorData(nsITransferable *aTransferable, if (targetFilename.IsEmpty()) return NS_ERROR_FAILURE; +#if defined(XP_MACOSX) + // Use the image request's MIME type to ensure the filename's + // extension is compatible with the OS's handler for this type. + // If it isn't, or is missing, replace the extension with the + // primary extension. On Mac, do this in the parent process + // because sandboxing blocks access to MIME-handler info from + // content processes. + if (XRE_IsParentProcess()) { + aTransferable->GetTransferData(kImageRequestMime, + getter_AddRefs(tmp), &dataSize); + supportsString = do_QueryInterface(tmp); + if (!supportsString) + return NS_ERROR_FAILURE; + + nsAutoString imageRequestMime; + supportsString->GetData(imageRequestMime); + + // If we have a MIME type, check the extension is compatible + if (!imageRequestMime.IsEmpty()) { + // Build a URL to get the filename extension + nsCOMPtr imageURL = do_QueryInterface(sourceURI, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString extension; + rv = imageURL->GetFileExtension(extension); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ConvertUTF16toUTF8 mimeCString(imageRequestMime); + bool isValidExtension; + nsAutoCString primaryExtension; + rv = CheckAndGetExtensionForMime(extension, + mimeCString, + &isValidExtension, + &primaryExtension); + NS_ENSURE_SUCCESS(rv, rv); + + if (!isValidExtension) { + // The filename extension is missing or incompatible + // with the MIME type, replace it with the primary + // extension. + nsAutoCString newFileName; + rv = imageURL->GetFileBaseName(newFileName); + NS_ENSURE_SUCCESS(rv, rv); + newFileName.Append("."); + newFileName.Append(primaryExtension); + targetFilename = NS_ConvertUTF8toUTF16(newFileName); + } + } + } + // make the filename safe for the filesystem + targetFilename.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, + '-'); +#endif /* defined(XP_MACOSX) */ + // get the target directory from the kFilePromiseDirectoryMime // flavor nsCOMPtr dirPrimitive; @@ -244,7 +345,7 @@ nsContentAreaDragDropDataProvider::GetFlavorData(nsITransferable *aTransferable, bool isPrivate; aTransferable->GetIsPrivateData(&isPrivate); - rv = SaveURIToFile(sourceURLString, file, isPrivate); + rv = SaveURIToFile(sourceURI, file, isPrivate); // send back an nsIFile if (NS_SUCCEEDED(rv)) { CallQueryInterface(file, aData); @@ -362,6 +463,80 @@ DragDataProducer::GetNodeString(nsIContent* inNode, } } +nsresult +DragDataProducer::GetImageData(imgIContainer* aImage, imgIRequest* aRequest) +{ + nsCOMPtr imgUri; + aRequest->GetURI(getter_AddRefs(imgUri)); + + nsCOMPtr imgUrl(do_QueryInterface(imgUri)); + if (imgUrl) { + nsAutoCString spec; + nsresult rv = imgUrl->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + // pass out the image source string + CopyUTF8toUTF16(spec, mImageSourceString); + + nsCString mimeType; + aRequest->GetMimeType(getter_Copies(mimeType)); + +#if defined(XP_MACOSX) + // Save the MIME type so we can make sure the extension + // is compatible (and replace it if it isn't) when the + // image is dropped. On Mac, we need to get the OS MIME + // handler information in the parent due to sandboxing. + CopyUTF8toUTF16(mimeType, mImageRequestMime); +#else + nsCOMPtr mimeService = do_GetService("@mozilla.org/mime;1"); + if (NS_WARN_IF(!mimeService)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr mimeInfo; + mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(), + getter_AddRefs(mimeInfo)); + if (mimeInfo) { + nsAutoCString extension; + imgUrl->GetFileExtension(extension); + + bool validExtension; + if (extension.IsEmpty() || + NS_FAILED(mimeInfo->ExtensionExists(extension, + &validExtension)) || + !validExtension) { + // Fix the file extension in the URL + nsAutoCString primaryExtension; + mimeInfo->GetPrimaryExtension(primaryExtension); + + rv = NS_MutateURI(imgUrl) + .Apply(NS_MutatorMethod(&nsIURLMutator::SetFileExtension, + primaryExtension, nullptr)) + .Finalize(imgUrl); + NS_ENSURE_SUCCESS(rv, rv); + } + } +#endif /* defined(XP_MACOSX) */ + + nsAutoCString fileName; + imgUrl->GetFileName(fileName); + + NS_UnescapeURL(fileName); + +#if !defined(XP_MACOSX) + // make the filename safe for the filesystem + fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, '-'); +#endif + + CopyUTF8toUTF16(fileName, mImageDestFileName); + + // and the image object + mImage = aImage; + } + + return NS_OK; +} + nsresult DragDataProducer::Produce(DataTransfer* aDataTransfer, bool* aCanDrag, @@ -564,67 +739,9 @@ DragDataProducer::Produce(DataTransfer* aDataTransfer, nsCOMPtr img = nsContentUtils::GetImageFromContent(image, getter_AddRefs(imgRequest)); - - nsCOMPtr mimeService = - do_GetService("@mozilla.org/mime;1"); - - // Fix the file extension in the URL if necessary - if (imgRequest && mimeService) { - nsCOMPtr imgUri; - imgRequest->GetURI(getter_AddRefs(imgUri)); - - nsCOMPtr imgUrl(do_QueryInterface(imgUri)); - - if (imgUrl) { - nsAutoCString extension; - imgUrl->GetFileExtension(extension); - - nsCString mimeType; - imgRequest->GetMimeType(getter_Copies(mimeType)); - - nsCOMPtr mimeInfo; - mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(), - getter_AddRefs(mimeInfo)); - - if (mimeInfo) { - nsAutoCString spec; - rv = imgUrl->GetSpec(spec); - NS_ENSURE_SUCCESS(rv, rv); - - // pass out the image source string - CopyUTF8toUTF16(spec, mImageSourceString); - - bool validExtension; - if (extension.IsEmpty() || - NS_FAILED(mimeInfo->ExtensionExists(extension, - &validExtension)) || - !validExtension) { - // Fix the file extension in the URL - nsAutoCString primaryExtension; - mimeInfo->GetPrimaryExtension(primaryExtension); - - rv = NS_MutateURI(imgUrl) - .Apply(NS_MutatorMethod(&nsIURLMutator::SetFileExtension, - primaryExtension, nullptr)) - .Finalize(imgUrl); - NS_ENSURE_SUCCESS(rv, rv); - } - - nsAutoCString fileName; - imgUrl->GetFileName(fileName); - - NS_UnescapeURL(fileName); - - // make the filename safe for the filesystem - fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, - '-'); - - CopyUTF8toUTF16(fileName, mImageDestFileName); - - // and the image object - mImage = img; - } - } + if (imgRequest) { + rv = GetImageData(img, imgRequest); + NS_ENSURE_SUCCESS(rv, rv); } if (parentLink) { @@ -730,11 +847,12 @@ void DragDataProducer::AddString(DataTransfer* aDataTransfer, const nsAString& aFlavor, const nsAString& aData, - nsIPrincipal* aPrincipal) + nsIPrincipal* aPrincipal, + bool aHidden) { RefPtr variant = new nsVariantCC(); variant->SetAsAString(aData); - aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal); + aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal, aHidden); } nsresult @@ -810,6 +928,10 @@ DragDataProducer::AddStringsToDataTransfer(nsIContent* aDragNode, mImageSourceString, principal); AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseDestFilename), mImageDestFileName, principal); +#if defined(XP_MACOSX) + AddString(aDataTransfer, NS_LITERAL_STRING(kImageRequestMime), + mImageRequestMime, principal, /* aHidden= */ true); +#endif // if not an anchor, add the image url if (!mIsAnchor) { diff --git a/dom/base/nsContentAreaDragDrop.h b/dom/base/nsContentAreaDragDrop.h index 68e7debf353a..978e74905414 100644 --- a/dom/base/nsContentAreaDragDrop.h +++ b/dom/base/nsContentAreaDragDrop.h @@ -76,7 +76,7 @@ public: NS_DECL_ISUPPORTS NS_DECL_NSIFLAVORDATAPROVIDER - nsresult SaveURIToFile(nsAString& inSourceURIString, + nsresult SaveURIToFile(nsIURI* inSourceURI, nsIFile* inDestFile, bool isPrivate); }; diff --git a/dom/events/DataTransfer.cpp b/dom/events/DataTransfer.cpp index 5ae2da54e372..9ec8321b7104 100644 --- a/dom/events/DataTransfer.cpp +++ b/dom/events/DataTransfer.cpp @@ -898,7 +898,7 @@ DataTransfer::GetTransferable(uint32_t aIndex, nsILoadContext* aLoadContext) kPNGImageMime, kJPEGImageMime, kGIFImageMime, kNativeImageMime, kFileMime, kFilePromiseMime, kFilePromiseURLMime, kFilePromiseDestFilename, kFilePromiseDirectoryMime, - kMozTextInternal, kHTMLContext, kHTMLInfo }; + kMozTextInternal, kHTMLContext, kHTMLInfo, kImageRequestMime }; /* * Two passes are made here to iterate over all of the types. First, look for @@ -1202,7 +1202,8 @@ nsresult DataTransfer::SetDataWithPrincipal(const nsAString& aFormat, nsIVariant* aData, uint32_t aIndex, - nsIPrincipal* aPrincipal) + nsIPrincipal* aPrincipal, + bool aHidden) { nsAutoString format; GetRealFormat(aFormat, format); @@ -1211,7 +1212,7 @@ DataTransfer::SetDataWithPrincipal(const nsAString& aFormat, RefPtr item = mItems->SetDataWithPrincipal(format, aData, aIndex, aPrincipal, /* aInsertOnly = */ false, - /* aHidden= */ false, + aHidden, rv); return rv.StealNSResult(); } diff --git a/dom/events/DataTransfer.h b/dom/events/DataTransfer.h index 721d76e74166..1f21c720a81d 100644 --- a/dom/events/DataTransfer.h +++ b/dom/events/DataTransfer.h @@ -374,7 +374,8 @@ public: nsresult SetDataWithPrincipal(const nsAString& aFormat, nsIVariant* aData, uint32_t aIndex, - nsIPrincipal* aPrincipal); + nsIPrincipal* aPrincipal, + bool aHidden=false); // Variation of SetDataWithPrincipal with handles extracting // kCustomTypesMime data into separate types. diff --git a/widget/nsITransferable.idl b/widget/nsITransferable.idl index e642c9557122..5a6178ce1202 100644 --- a/widget/nsITransferable.idl +++ b/widget/nsITransferable.idl @@ -41,6 +41,12 @@ interface nsIPrincipal; #define kHTMLContext "text/_moz_htmlcontext" #define kHTMLInfo "text/_moz_htmlinfo" +// Holds the MIME type from the image request. This is used to ensure the +// local application handler for the request's MIME type accepts images with +// the given filename extension (from kFilePromiseDestFilename). When the +// image is dragged out, we replace the extension with a compatible extension. +#define kImageRequestMime "text/_moz_requestmime" + // the source URL for a file promise #define kFilePromiseURLMime "application/x-moz-file-promise-url" // the destination filename for a file promise