web-dev-qa-db-ja.com

Firefoxを作成し、Chrome特定の名前で画像をダウンロードする

与えられたhttps://www.example.com/image-list

...
<a href="/image/1337">
  <img src="//static.example.com/thumbnails/86fb269d190d2c85f6e0468ceca42a20.png"/>
</a>
<a href="//static.example.com/full/86fb269d190d2c85f6e0468ceca42a20.png"
   download="1337 - Hello world!.png">
  Download
</a>
...

これはユーザースクリプト環境であるため、サーバー構成にアクセスできません。など:

  1. サーバーにhttps://static.example.com/full/86fb269d190d2c85f6e0468ceca42a20 - 1337 - Hello World!.pngのようなユーザーフレンドリーなファイル名を受け入れさせることはできません。
  2. クロスオリジンリソース共有を設定できません。 www.example.comstatic.example.comは、設計によりCORS壁によって分離されています。

Firefoxの作成方法とChrome=表示ファイル名を付けて保存ダイアログと推奨ファイル名 "1337-Hello world!.png"ユーザーが[ダウンロード]リンクをクリックすると?

いくつかの失敗とグーグルの後、私はこれらの問題を学びました:

  1. Firefoxは、一部の画像MIMEタイプのdownload属性の存在を完全に無視します。
  2. Firefoxは、クロスサイトリンク上のdownload属性の存在を完全に無視します。
  3. Chromeは、クロスサイトリンクのdownload属性の値を完全に無視します。

これらのすべての点は私にはany意味をなさない、すべては「機能にランダムな無意味な制限を付けよう」のように見えるが、私はしなければならないそれが私の環境であるとして受け入れてください。

問題を解決する方法はありますか?


背景:MD5ハッシュをファイル名として使用するイメージボード用のユーザースクリプトを書いています。わかりやすい名前で保存しやすくしたい。これに私を近づける何かが役立つでしょう。

オブジェクトURLを使用してブロブと、ハッキングされたCORSヘッダーを持つローカルプロキシを使用することで制限を回避できると思いますが、この設定は明らかに妥当な範囲を超えています。キャンバスを介した保存は機能します(この場合も画像はCORSによって「保護」されますか?)。ただし、JPEGファイルが適切でない場合、二重の非可逆圧縮または非可逆から可逆への変換を強制します。

19
Athari

最新のブラウザはすべて、クロスオリジンURLのアンカータグのダウンロード属性を無視します。

リファレンス: https://html.spec.whatwg.org/multipage/links.html#downloading-resources

スペックメーカーによると、これはユーザーが安全なサイトを閲覧しているときに悪意のあるファイルをダウンロードするように仕向けられ、ファイルも同じ安全なサイトから発信されていると考えて、セキュリティの抜け穴を表します。

Firefoxブラウザーでこの機能を実装するための興味深い会話は、ここにあります: https://bugzilla.mozilla.org/show_bug.cgi?id=676619


[アサリ編集]

仕様からの引用:

これは、たとえば、悪意のあるサーバーがユーザーに無意識のうちに個人情報をダウンロードさせ、データを悪意のあるサーバーからのものだと思わせることで、悪意のあるサーバーに再アップロードしようとする可能性があるためです。

したがって、ユーザーの関心事は、問題のリソースがまったく異なるソースからのものであることをユーザーに何らかの形で通知することであり、混乱を避けるために、潜在的に悪意のあるインターフェイスOriginからの推奨ファイル名は無視する必要があります。

神秘的なシナリオの明確化:

cORSダウンロードのより深刻な問題は、悪意のあるサイトが正当なサイトからファイルのダウンロードを強制し、そのコンテンツに何らかの方法でアクセスする場合です。ユーザーのGmailの受信トレイページをダウンロードして、そのメッセージを調べるとしましょう。

この場合、悪意のあるサイトはユーザーをだましてファイルをダウンロードしてサーバーにアップロードする必要があるため、gmail.com/inbox.htmlには実際にすべてのユーザーメールメッセージが含まれており、攻撃者サイトは別の邪悪なサイトにアップロードする必要があるクーポンファイルのダウンロードリンク。クーポンは新しいiPadで30%の割引を提供すると思われます。ダウンロードリンクは実際にgmail.com/inbox.htmlを指し、「30off.coupon」としてダウンロードします。ユーザーがこのファイルをダウンロードし、コンテンツを確認せずにアップロードすると、悪サイトはユーザーに「クーポン」を取得しますそしてその受信箱の内容。

重要な注意事項:

  1. GoogleはもともとCORSによるダウンロード属性を制限していなかったため、明示的にこれに反対していました。後で調整を強制されましたChrome実装。

    GoogleはこれにCORSを使用することに反対しました。

  2. 代替ソリューションが提案され、ユーザーにクロスオリジンのダウンロードに関する警告を提供しました。それらは無視されました。

    別のOriginからダウンロードするときに、通知または拒否/許可のメカニズムを使用できます(たとえば、ジオロケーションAPIの場合)。または、ダウンロード属性を持つクロスオリジンリクエストの場合にCookieを送信しないようにします。

  3. 一部の開発者は、制限が強すぎ、機能の使用を厳しく制限し、これを行うユーザーが実行可能ファイルを簡単にダウンロードして実行するほど複雑であるという意見を共有しています。彼らの意見は無視されました。

    クロスオリジンのダウンロードを許可しない場合は、[悪]サイト(たとえば、discountipads.com)の訪問者が無意識のうちに自分の個人情報(たとえばgmail.com)を含むサイトからファイルをダウンロードして保存できるという前提に基づいています。誤解を招く名前(「discount.coupon」など)を使用してディスクに保存し、その後、ダウンロードしたばかりの同じファイルを手動でアップロードする別の悪意のあるページに進みます。私の意見ではこれは非常に大げさなものであり、そのような些細な策略に屈するだろう人は、おそらく最初はオンラインに属さないでしょう。私は特別な割引オファーをダウンロードして、特別なフォームから再アップロードするにはここをクリックしてください!マジ?特別オファーをダウンロードして、このYahooアドレスにメールで送信すると、大幅な割引が受けられます。これらのことにfallする人々は、電子メールの添付ファイルを行う方法を知っていますか?

    私はすべてブラウザのセキュリティに専念していますが、Chromiumの善良な人々がこれに問題がない場合、Firefoxが完全にそれを排除しなければならない理由はわかりません。少なくともabout:configの設定を見て、「上級」ユーザー向けにクロスオリジンの@downloadを有効にします(デフォルトはfalseです)。 「このページは暗号化されていますが、このフォームから送信する情報は暗号化されません」または「このページはアドオンのインストールを要求しています」または「Webからダウンロードしたファイルに損害を与える可能性がありますお使いのコンピューター」または「このページのセキュリティ証明書が無効です」...ユーザーの意識を高め、これが安全でない可能性があることを通知する無数の方法があります。追加のクリック1回と短い(または長い?)遅延でリスクを評価できます。

    Webが拡大し、CDNの使用が拡大し、高度なWebアプリの存在が拡大し、サーバー間でホストされるファイルを管理する必要性が拡大するにつれて、@ downloadなどの機能がより重要になります。そして、Chromeのようなブラウザが完全にサポートしているのに対し、Firefoxはサポートしていない場合、これはFirefoxにとって有利なことではありません。

    要するに、クロスオリジンシナリオで属性を単に無視することで、@ downloadの潜在的な悪用を軽減することは、非常によく考えられていない動きだと思います。私は、リスクがまったく存在しないと言っているのではなく、まったく逆です。彼が日々オンラインで行っている危険なことはたくさんあると言っています。よく考えられたユーザーエクスペリエンスでその問題を回避してみませんか?

全体的に、CDNの広範な使用と、ユーザー生成コンテンツを意図的に別のドメインに配置することを考慮して、ダウンロード属性の主な用途は、blobダウンロードのファイル名を指定することです(URL.createObjectURL)など。多くの構成で使用することはできず、ユーザースクリプトではあまり役に立ちません。

7
23nigam

次のようなものを試してください:

  1. 最初にサーバーに外部イメージを取得します
  2. サーバーから取得した画像を返します。
  3. download nameと.click() itでアンカーを動的に作成します!

上記は非常に短いヒントリストでしたが...これを試してみてください:

www.example.comfetch-image.phpをこのコンテンツで配置します:

<?php
$url = $_GET["url"];                     // the image URL
$info = getimagesize($url);              // get image data
header("Content-type: ". $info['mime']); // act as image with right MIME type
readfile($url);                          // read binary image data
die();

または同じことを達成する他のサーバー側言語で。

上記は、ドメインにある外部画像を返します。

image-listページで、今すぐ試すことができます:

<a 
  href="//static.example.com/thumbnails/86fb269d190d2c85f6e0468ceca42a20.png" 
  download="1337 - Hello world!.png">DOWNLOAD</a>

そしてこのJS:

function fetchImageAndDownload (e) {
    e.preventDefault(); // Prevent browser's default download stuff...

    const url = this.getAttribute("href");       // Anchor href 
    const downloadName = this.download;          // Anchor download name

    const img = document.createElement("img");   // Create in-memory image
    img.addEventListener("load", () => {
        const a = document.createElement("a");   // Create in-memory anchor
        a.href = img.src;                        // href toward your server-image
        a.download = downloadName;               // :)
        a.click();                               // Trigger click (download)
    });
    img.src = 'fetch-image.php?url='+ url;       // Request image from your server

}

[...document.querySelectorAll("[download]")].forEach( el => 
    el.addEventListener("click", fetchImageAndDownload)
);

最終的にダウンロードされた画像が表示されます

1337-Hello world!.png

86fb269d190d2c85f6e0468ceca42a20.pngの代わりに、そうでした。

注意fetch-image.phpに対する同時リクエストの影響についてはわかりません-テスト、テストを確認してください。

5
Roko C. Buljan

バックエンドコードとフロントエンドコードの両方にアクセスできる場合は、次の手順が役立ちます。

使用しているバックエンド言語のタイプがわからないので、コードサンプルなしで何をする必要があるかを説明します。

バックエンドでは、プレビューのために、クエリ文字列に?download=trueその後、バックエンドは、処分されたコンテンツとしてファイルをパックする必要があります。つまり、content-disposition応答ヘッダー。これにより、filenameなどの追加の属性をコンテンツに追加できるようになるため、次のような単純なものになります。

Content-Disposition: attachment; filename="filename.jpg"

これで、フロントエンドでは、ダウンロードボタンとして動作するリンクには?download=true in hrefクエリパラメータAND target="_blank"これは、ダウンロードのためにブラウザで別のタブを一時的に開きます。

<a href="/image/1337">
  <img src="//static.example.com/thumbnails/86fb269d190d2c85f6e0468ceca42a20.png"/>
</a>
<a href="//static.example.com/full/86fb269d190d2c85f6e0468ceca42a20.png?download=true" target="_blank" title="1337 - Hello world!.png">
  Download Full size
</a>

これはCORSセットアップなしで機能し、ユーザーがダウンロードリンクをクリックした場合でも機能することはわかっていますが、ブラウザで[名前を付けて保存]ダイアログをテストしたことはありません。

4
Milan Jaric

これを試すことができます

var downloadHandler = function(){
    var url = this.dataset.url;
    var name = this.dataset.name;
    // by this you can automaticaly convert any supportable image type to other, it is destination image format
    var mime = this.dataset.type || 'image/jpg';
    var image = new Image();
    //We need image and canvas for converting url to blob.
    //Image is better then recieve blob through XHR request, because of crossOrigin mode
image.crossOrigin = "Anonymous";
   
    
    image.onload = function(oEvent) {
      //draw image on canvas
      var canvas = document.createElement('canvas');
      canvas.width = this.naturalWidth;
      canvas.height = this.naturalHeight;
      canvas.getContext('2d').drawImage(this, 0, 0, canvas.width, canvas.height);
      // get image from canvas as blob
      var binStr = atob( canvas.toDataURL(mime).split(',')[1] ),
            len = binStr.length,
            arr = new Uint8Array(len);

        for (var i = 0; i < len; i++ ) {
          arr[i] = binStr.charCodeAt(i);
        }

      var blob = new Blob( [arr], {type: mime} );
      //IE not works with a.click() for downloading
      if (window.navigator && window.navigator.msSaveOrOpenBlob)                         {
          window.navigator.msSaveOrOpenBlob(blob, name);
      } else {
          var a = document.createElement("a");  
          a.href = URL.createObjectURL(blob);                     
          a.download = name;              
          a.click();  
      }
    };

    image.src = url;
}

document.querySelector("[download]").addEventListener("click", downloadHandler)
<button 
data-name="file.png" 
data-url="https://tpc.googlesyndication.com/simgad/14257743829768205599"
data-type="image/png"
download>
download
</button>
var downloadHandler = function(){
  var url = this.dataset.url;
  var name = this.dataset.name;
  fetch(url).then(function(response) {
    return response.blob();
  }).then(function(blob) {
    //IE and Edge not works with a.click() for downloading
      if (window.navigator && window.navigator.msSaveOrOpenBlob)                         {
          window.navigator.msSaveOrOpenBlob(blob, name);
      } else {
          var a = document.createElement("a");  
          a.href = URL.createObjectURL(blob);                     
          a.download = name;              
          a.click();  
      }
  });
};

document.querySelector("[download]").addEventListener("click", downloadHandler)
<button 
data-name="file.png" 
data-url="https://tpc.googlesyndication.com/simgad/14257743829768205599"
download>
download
</button>
1
Alex Nikulin

関連するChromium API ...

https://developer.chrome.com/extensions/downloads#event-onDeterminingFilename

例...

chrome.downloads.onDeterminingFilename.addListener(function(item,suggest){

 suggest({filename:"downloads/"+item.filename}); // suggest only the folder

 suggest({filename:"downloads/image23.png"}); // suggest folder and filename

});

おっと...あなたはサーバー側ですが、私はクライアント側を想定しました!誰かがそれを必要とする場合のために、私はこれをここに残します。

0
user4723924