Bug 1437281 - OSX dragging image to desktop changes OSX File associations r?mystor draft
authorHaik Aftandilian <haftandilian@mozilla.com>
Wed, 14 Mar 2018 16:37:46 -0700
changeset 770108 62da5ea9ca4cdb980368c243fca25f28afaed071
parent 770107 56a0a0bf725cc3e1317f0055f3d18caf8b622399
push id103324
push userhaftandilian@mozilla.com
push dateTue, 20 Mar 2018 19:14:59 +0000
reviewersmystor
bugs1437281
milestone61.0a1
Bug 1437281 - OSX dragging image to desktop changes OSX File associations r?mystor 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. Move the MIME-extension check to the parent process to avoid issues caused by content process sandboxing. MozReview-Commit-ID: 3cb4fCr6GnL
dom/base/nsContentAreaDragDrop.cpp
dom/events/DataTransfer.cpp
dom/events/DataTransfer.h
widget/nsITransferable.idl
--- a/dom/base/nsContentAreaDragDrop.cpp
+++ b/dom/base/nsContentAreaDragDrop.cpp
@@ -73,17 +73,18 @@ public:
                    nsISelection** aSelection,
                    nsIContent** aDragNode,
                    nsACString& aPrincipalURISpec);
 
 private:
   void AddString(DataTransfer* aDataTransfer,
                  const nsAString& aFlavor,
                  const nsAString& aData,
-                 nsIPrincipal* aPrincipal);
+                 nsIPrincipal* aPrincipal,
+                 bool aHidden=false);
   nsresult AddStringsToDataTransfer(nsIContent* aDragNode,
                                     DataTransfer* aDataTransfer);
   static nsresult GetDraggableSelectionData(nsISelection* inSelection,
                                             nsIContent* inRealTargetNode,
                                             nsIContent **outImageOrLinkNode,
                                             bool* outDragSelectedText);
   static already_AddRefed<nsIContent> FindParentLinkNode(nsIContent* inNode);
   static MOZ_MUST_USE nsresult
@@ -95,16 +96,17 @@ private:
   nsCOMPtr<nsPIDOMWindowOuter> mWindow;
   nsCOMPtr<nsIContent> mTarget;
   nsCOMPtr<nsIContent> mSelectionTargetNode;
   bool mIsAltKeyPressed;
 
   nsString mUrlString;
   nsString mImageSourceString;
   nsString mImageDestFileName;
+  nsString mImageRequestMime;
   nsString mTitleString;
   // will be filled automatically if you fill urlstring
   nsString mHtmlString;
   nsString mContextString;
   nsString mInfoString;
 
   bool mIsAnchor;
   nsCOMPtr<imgIContainer> mImage;
@@ -168,16 +170,59 @@ nsContentAreaDragDropDataProvider::SaveU
 
   // referrer policy can be anything since the referrer is nullptr
   return persist->SavePrivacyAwareURI(sourceURI, 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,
+                            nsCString* aPrimaryExtension)
+{
+  nsresult rv;
+
+  nsCOMPtr<nsIMIMEService> mimeService =
+    do_GetService("@mozilla.org/mime;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIMIMEInfo> 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:
 //
 // 1. Someone put a kFilePromiseURLMime flavor into the transferable
 //    with the source URI of the file to save (as a string). We did
 //    that in AddStringsToDataTransfer.
 //
 // 2. Someone put a kFilePromiseDirectoryMime flavor into the
@@ -221,16 +266,76 @@ nsContentAreaDragDropDataProvider::GetFl
     if (!supportsString)
       return NS_ERROR_FAILURE;
 
     nsAutoString targetFilename;
     supportsString->GetData(targetFilename);
     if (targetFilename.IsEmpty())
       return NS_ERROR_FAILURE;
 
+    // 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. Do this in the parent process because
+    // sandboxing may block 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<nsIURI> imageURI;
+        rv = NS_NewURI(getter_AddRefs(imageURI), sourceURLString);
+        NS_ENSURE_SUCCESS(rv, rv);
+        nsCOMPtr<nsIURL> imageURL = do_QueryInterface(imageURI, &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.
+          rv = NS_MutateURI(imageURL)
+            .Apply(NS_MutatorMethod(&nsIURLMutator::SetFileExtension,
+                                    primaryExtension, nullptr))
+            .Finalize(imageURL);
+          NS_ENSURE_SUCCESS(rv, rv);
+
+          nsAutoCString newFileName;
+          rv = imageURL->GetFileName(newFileName);
+          NS_ENSURE_SUCCESS(rv, rv);
+
+          targetFilename = NS_ConvertUTF8toUTF16(newFileName);
+        }
+      }
+    }
+
+    targetFilename.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS,
+                               '-');
+
     // get the target directory from the kFilePromiseDirectoryMime
     // flavor
     nsCOMPtr<nsISupports> dirPrimitive;
     dataSize = 0;
     aTransferable->GetTransferData(kFilePromiseDirectoryMime,
                                    getter_AddRefs(dirPrimitive), &dataSize);
     nsCOMPtr<nsIFile> destDirectory = do_QueryInterface(dirPrimitive);
     if (!destDirectory)
@@ -560,76 +665,49 @@ DragDataProducer::Produce(DataTransfer* 
         }
 
         nsCOMPtr<imgIRequest> imgRequest;
 
         // grab the image data, and its request.
         nsCOMPtr<imgIContainer> img =
           nsContentUtils::GetImageFromContent(image,
                                               getter_AddRefs(imgRequest));
-
-        nsCOMPtr<nsIMIMEService> mimeService =
-          do_GetService("@mozilla.org/mime;1");
-
-        // Fix the file extension in the URL if necessary
-        if (imgRequest && mimeService) {
+        if (imgRequest) {
           nsCOMPtr<nsIURI> imgUri;
           imgRequest->GetURI(getter_AddRefs(imgUri));
 
           nsCOMPtr<nsIURL> imgUrl(do_QueryInterface(imgUri));
-
           if (imgUrl) {
-            nsAutoCString extension;
-            imgUrl->GetFileExtension(extension);
-
+            // 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.
             nsCString mimeType;
             imgRequest->GetMimeType(getter_Copies(mimeType));
-
-            nsCOMPtr<nsIMIMEInfo> mimeInfo;
-            mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(),
-                                                 getter_AddRefs(mimeInfo));
-
-            if (mimeInfo) {
-              nsAutoCString spec;
-              rv = imgUrl->GetSpec(spec);
-              NS_ENSURE_SUCCESS(rv, rv);
+            CopyUTF8toUTF16(mimeType, mImageRequestMime);
 
-              // pass out the image source string
-              CopyUTF8toUTF16(spec, mImageSourceString);
+            nsAutoCString spec;
+            rv = imgUrl->GetSpec(spec);
+            NS_ENSURE_SUCCESS(rv, rv);
 
-              bool validExtension;
-              if (extension.IsEmpty() ||
-                  NS_FAILED(mimeInfo->ExtensionExists(extension,
-                                                      &validExtension)) ||
-                  !validExtension) {
-                // Fix the file extension in the URL
-                nsAutoCString primaryExtension;
-                mimeInfo->GetPrimaryExtension(primaryExtension);
+            // pass out the image source string
+            CopyUTF8toUTF16(spec, mImageSourceString);
 
-                rv = NS_MutateURI(imgUrl)
-                       .Apply(NS_MutatorMethod(&nsIURLMutator::SetFileExtension,
-                                               primaryExtension, nullptr))
-                       .Finalize(imgUrl);
-                NS_ENSURE_SUCCESS(rv, rv);
-              }
+            nsAutoCString fileName;
+            imgUrl->GetFileName(fileName);
 
-              nsAutoCString fileName;
-              imgUrl->GetFileName(fileName);
+            NS_UnescapeURL(fileName);
 
-              NS_UnescapeURL(fileName);
-
-              // make the filename safe for the filesystem
-              fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS,
-                                   '-');
+            // make the filename safe for the filesystem
+            fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS,
+                                 '-');
 
-              CopyUTF8toUTF16(fileName, mImageDestFileName);
+            CopyUTF8toUTF16(fileName, mImageDestFileName);
 
-              // and the image object
-              mImage = img;
-            }
+            // and the image object
+            mImage = img;
           }
         }
 
         if (parentLink) {
           // If we are dragging around an image in an anchor, then we
           // are dragging the entire anchor
           linkNode = parentLink;
           nodeToSerialize = linkNode;
@@ -726,21 +804,22 @@ DragDataProducer::Produce(DataTransfer* 
   NS_IF_ADDREF(*aDragNode = dragNode);
   return NS_OK;
 }
 
 void
 DragDataProducer::AddString(DataTransfer* aDataTransfer,
                             const nsAString& aFlavor,
                             const nsAString& aData,
-                            nsIPrincipal* aPrincipal)
+                            nsIPrincipal* aPrincipal,
+                            bool aHidden)
 {
   RefPtr<nsVariantCC> variant = new nsVariantCC();
   variant->SetAsAString(aData);
-  aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal);
+  aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal, aHidden);
 }
 
 nsresult
 DragDataProducer::AddStringsToDataTransfer(nsIContent* aDragNode,
                                            DataTransfer* aDataTransfer)
 {
   NS_ASSERTION(aDragNode, "adding strings for null node");
 
@@ -806,16 +885,18 @@ DragDataProducer::AddStringsToDataTransf
       aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kFilePromiseMime),
                                           variant, 0, principal);
     }
 
     AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseURLMime),
               mImageSourceString, principal);
     AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseDestFilename),
               mImageDestFileName, principal);
+    AddString(aDataTransfer, NS_LITERAL_STRING(kImageRequestMime),
+              mImageRequestMime, principal, /* aHidden= */ true);
 
     // if not an anchor, add the image url
     if (!mIsAnchor) {
       AddString(aDataTransfer, NS_LITERAL_STRING(kURLDataMime), mUrlString, principal);
       AddString(aDataTransfer, NS_LITERAL_STRING("text/uri-list"), mUrlString, principal);
     }
   }
 
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -893,17 +893,17 @@ DataTransfer::GetTransferable(uint32_t a
   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 };
+    kMozTextInternal, kHTMLContext, kHTMLInfo, kImageRequestMime };
 
   /*
    * 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.
    *
@@ -1197,26 +1197,27 @@ DataTransfer::MozItemCount() const
 {
   return mItems->MozItemCount();
 }
 
 nsresult
 DataTransfer::SetDataWithPrincipal(const nsAString& aFormat,
                                    nsIVariant* aData,
                                    uint32_t aIndex,
-                                   nsIPrincipal* aPrincipal)
+                                   nsIPrincipal* aPrincipal,
+                                   bool aHidden)
 {
   nsAutoString format;
   GetRealFormat(aFormat, format);
 
   ErrorResult rv;
   RefPtr<DataTransferItem> item =
     mItems->SetDataWithPrincipal(format, aData, aIndex, aPrincipal,
                                  /* aInsertOnly = */ false,
-                                 /* aHidden= */ false,
+                                 aHidden,
                                  rv);
   return rv.StealNSResult();
 }
 
 void
 DataTransfer::SetDataWithPrincipalFromOtherProcess(const nsAString& aFormat,
                                                    nsIVariant* aData,
                                                    uint32_t aIndex,
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -369,17 +369,18 @@ public:
   void ClearAll();
 
   // Similar to SetData except also specifies the principal to store.
   // aData may be null when called from CacheExternalDragFormats or
   // CacheExternalClipboardFormats.
   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.
   void SetDataWithPrincipalFromOtherProcess(const nsAString& aFormat,
                                             nsIVariant* aData,
                                             uint32_t aIndex,
                                             nsIPrincipal* aPrincipal,
                                             bool aHidden);
--- a/widget/nsITransferable.idl
+++ b/widget/nsITransferable.idl
@@ -36,16 +36,22 @@ interface nsIPrincipal;
 
 // These are used to indicate the context for a fragment of HTML source, such
 // that some parent structure and style can be preserved. kHTMLContext
 // contains the serialized ancestor elements, whereas kHTMLInfo are numbers
 // identifying where in the context the fragment was from.
 #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
 #define kFilePromiseDestFilename    "application/x-moz-file-promise-dest-filename"
 // a dataless flavor used to interact with the OS during file drags
 #define kFilePromiseMime            "application/x-moz-file-promise"
 // a synthetic flavor, put into the transferable once we know the destination directory of a file drag
 #define kFilePromiseDirectoryMime   "application/x-moz-file-promise-dir"