web-dev-qa-db-fra.com

Faire Firefox et Chrome télécharger l'image sous un nom spécifique

Donné 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>
...

Ceci est un environnement de script utilisateur, donc je n'ai pas accès à la configuration du serveur. En tant que tel:

  1. Je ne peux pas faire accepter par le serveur des noms de fichiers conviviaux comme https://static.example.com/full/86fb269d190d2c85f6e0468ceca42a20 - 1337 - Hello World!.png.
  2. Je ne peux pas configurer le partage de ressources d'origine croisée. www.example.com et static.example.com sont séparés par le mur CORS par conception.

Comment faire Firefox et Chrome afficher Enregistrer le fichier sous avec le nom de fichier suggéré "1337 - Bonjour tout le monde! .Png" lorsqu'un utilisateur clique sur le lien "Télécharger"?

Après quelques échecs et googler, j'ai appris ces problèmes:

  1. Firefox ignore complètement l'existence de l'attribut download sur certains types d'images MIME.
  2. Firefox ignore complètement l'existence de l'attribut download sur les liens intersites.
  3. Chrome ignore complètement la valeur de l'attribut download sur les liens intersites.

Tous ces points n'ont aucun sens pour moi, tous ressemblent à "mettons des limitations aléatoires non sensorielles sur la fonctionnalité", mais je dois les accepter car c'est mon environnement.

Existe-t-il des moyens de résoudre le problème?


Contexte: J'écris un script utilisateur pour une carte d'image qui utilise des hachages MD5 comme noms de fichiers. Je veux faciliter l'enregistrement avec des noms conviviaux. Tout ce qui me rapproche de cela serait utile.

Je suppose que je peux contourner les limitations en utilisant des URL d'objet vers des objets blob et un proxy local avec des en-têtes CORS piratés, mais cette configuration est évidemment au-delà du raisonnable. L'enregistrement à travers le canevas pourrait fonctionner (les images sont-elles "protégées" par CORS dans ce cas également?), Mais cela forcera soit une compression double avec perte, soit une conversion sans perte avec perte, compte tenu des fichiers JPEG, qui ne sont pas bons.

19
Athari

Tous les navigateurs modernes ignoreront l'attribut de téléchargement dans la balise d'ancrage pour les URL d'origine croisée.

Référence: https://html.spec.whatwg.org/multipage/links.html#downloading-resources

Selon les fabricants de spécifications, cela représente une faille de sécurité car un utilisateur pourrait être amené à télécharger des fichiers malveillants tout en naviguant sur un site sécurisé, estimant que le fichier provient également du même site sécurisé.

Toute conversation intéressante pour implémenter cette fonctionnalité dans le navigateur Firefox peut être trouvée ici: https://bugzilla.mozilla.org/show_bug.cgi?id=676619


[Modifier par Athari]

Citation de spécification:

Cela pourrait être dangereux, car, par exemple, un serveur hostile pourrait essayer d'amener un utilisateur à télécharger sans le savoir des informations privées, puis à les télécharger à nouveau sur le serveur hostile, en incitant l'utilisateur à penser que les données proviennent du serveur hostile.

Ainsi, il est dans l'intérêt de l'utilisateur que l'utilisateur soit en quelque sorte informé que la ressource en question provient d'une source assez différente, et pour éviter toute confusion, tout nom de fichier suggéré provenant de l'interface potentiellement hostile Origin doit être ignoré.

Clarification sur le scénario mystérieux:

le problème le plus grave avec les téléchargements CORS est si un site malveillant force le téléchargement d'un fichier depuis un site légitime et comment l'accès à son contenu est possible. permet donc de dire que je télécharge la page de boîte de réception de l'utilisateur gmail et explore ses messages.

dans ce cas, un site malveillant devra tromper l'utilisateur en téléchargeant le fichier et en le téléchargeant sur le serveur, alors disons que nous avons un gmail.com/inbox.html contenant en fait tous les messages électroniques des utilisateurs et que les sites attaquants proposent un lien de téléchargement pour un fichier de coupon, qui devrait être téléchargé sur un autre site malveillant. le coupon offrira soi-disant une remise de 30% sur un nouvel Ipad. le lien de téléchargement pointera en fait vers gmail.com/inbox.html et le téléchargera comme "30off.coupon", si l'utilisateur télécharge ce fichier et le télécharge sans vérifier son contenu, le site malveillant obtiendra à l'utilisateur le "coupon" et donc son contenu de boîte de réception.

Notes IMPORTANTES:

  1. À l'origine, Google ne limitait pas l'attribut de téléchargement par CORS et s'y opposait explicitement. Il a ensuite été forcé d'ajuster Chrome implementation.

    Google était opposé à l'utilisation de CORS pour cela.

  2. Des solutions alternatives ont été proposées avec un avertissement à l'utilisateur concernant les téléchargements cross-Origin. Ils ont été ignorés.

    Eh bien, il peut y avoir un mécanisme de notification ou de refus/autorisation lors du téléchargement à partir d'une autre origine (par exemple, comme dans le cas d'une API de géolocalisation). Ou ne pas envoyer de cookies en cas de demande d'origine croisée avec attribut de téléchargement.

  3. Certains développeurs partagent l'opinion que la restriction est trop forte, limite sévèrement l'utilisation de la fonctionnalité et que le scénario est si compliqué que l'utilisateur qui le ferait pourrait facilement télécharger et exécuter un fichier exécutable. Leur opinion a été ignorée.

    L'argument contre l'autorisation des téléchargements cross-origin est centré sur la prémisse que les visiteurs d'un site [malveillant] (par exemple, discountipads.com) pourraient inconsciemment télécharger un fichier à partir d'un site contenant leurs propres informations personnelles (par exemple, gmail.com) et enregistrer sur leur disque en utilisant un nom trompeur (par exemple, "discount.coupon") ET PUIS passez à une autre page malveillante où ils téléchargent manuellement le même fichier qu'ils viennent de télécharger. C'est assez farfelu à mon avis, et quiconque succomberait à une telle supercherie triviale n'appartient peut-être pas en ligne en premier lieu. Je veux dire, allez ... Cliquez ici pour télécharger notre offre de remise spéciale, puis téléchargez-le à nouveau via notre formulaire spécial! Sérieusement? Téléchargez notre offre spéciale et envoyez-la par e-mail à cette adresse Yahoo pour une remise importante! Est-ce que les gens qui tombent amoureux de ces choses savent même comment faire des pièces jointes?

    Je suis pour la sécurité du navigateur, mais si les bonnes personnes de Chromium n'ont aucun problème avec cela, je ne vois pas pourquoi Firefox doit le bannir complètement. À tout le moins, j'aimerais voir une préférence dans about: config pour activer cross-Origin @download pour les utilisateurs "avancés" (par défaut, il vaut false). Encore mieux serait une boîte de confirmation semblable à: "Bien que cette page soit cryptée, les informations que vous soumettez via ce formulaire ne le seront pas" ou: "Cette page demande d'installer des modules complémentaires" ou: "Les fichiers téléchargés à partir du Web peuvent endommager votre ordinateur "ou même:" Le certificat de sécurité de cette page n'est pas valide "... vous savez ce que je veux dire? Il existe une myriade de façons de sensibiliser l'utilisateur et de l'informer que ce n'est pas sûr. Un clic supplémentaire et un délai court (ou long?) Suffisent pour leur permettre d'évaluer le risque.

    À mesure que le Web se développe, que l'utilisation des CDN se développe, que la présence d'applications Web avancées se développe et que la nécessité de gérer les fichiers hébergés sur les serveurs se développe, des fonctionnalités telles que @download deviendront plus importantes. Et lorsqu'un navigateur comme Chrome le supporte pleinement alors que Firefox ne le fait pas, ce n'est pas une victoire pour Firefox.

    En bref, je pense qu'atténuer les utilisations malveillantes potentielles de @download en ignorant simplement l'attribut dans les scénarios d'origine croisée est une décision lamentablement mal pensée. Je ne dis pas que le risque est entièrement inexistant, bien au contraire: je dis qu'il y a beaucoup de choses risquées que l'on fait en ligne au cours de sa journée ... le téléchargement de N'IMPORTE QUEL fichier est élevé parmi eux. Pourquoi ne pas contourner ce problème avec une expérience utilisateur bien pensée?

Dans l'ensemble, compte tenu de l'utilisation généralisée des CDN et du placement intentionnel de contenu généré par l'utilisateur sur un domaine différent, l'utilisation principale de l'attribut de téléchargement consiste à spécifier un nom de fichier pour les téléchargements d'objets blob (URL.createObjectURL) etc. Il ne peut pas être utilisé dans de nombreuses configurations et n'est certainement pas très utile dans les scripts utilisateur.

7
23nigam

Essayez quelque chose comme:

  1. Obtenez d'abord l'image externe sur votre serveur
  2. Renvoyez l'image récupérée depuis votre serveur.
  3. Créez dynamiquement une ancre avec download name et .click() it!

alors que ce qui précède n'était qu'une liste de conseils assez courte ... essayez ceci:

sur www.example.com placer un fetch-image.php avec ce contenu:

<?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();

ou avec tout autre langage côté serveur qui réalise la même chose.

Ce qui précède devrait renvoyer toute image externe lorsqu'elle se trouve sur votre domaine.

Sur ton image-list page, ce que vous pouvez essayer maintenant est:

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

et ce 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)
);

Vous devriez enfin voir l'image téléchargée en

1337 - Bonjour tout le monde! .Png

au lieu de 86fb269d190d2c85f6e0468ceca42a20.png comme si c'était le cas.

Remarque: Je ne suis pas sûr des implications des demandes simultanées vers fetch-image.php - assurez-vous de tester, tester.

5
Roko C. Buljan

Si vous avez accès au code backend et frontend, voici les étapes qui pourraient vous aider

Je ne sais pas quel type de langage backend vous utilisez, donc je vais simplement expliquer ce qui doit être fait sans exemple de code.

En backend, pour l'aperçu, votre code devrait fonctionner tel quel, si vous obtenez dans la chaîne de requête quelque chose comme ?download=true alors votre backend devrait emballer le fichier en tant que contenu disposé, en d'autres termes, vous utiliseriez content-disposition en-tête de réponse. Cela vous ouvrira la possibilité de mettre des attributs supplémentaires au contenu, comme filename, il pourrait donc être simething comme ceci

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

Maintenant, en face, tout lien qui devrait se comporter comme un bouton de téléchargement doit contenir ?download=true dans le paramètre de requête href ET target="_blank" qui ouvrira temporairement un autre onglet du navigateur à des fins de téléchargement.

<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>

Je sais que cela fonctionne sans configuration CORS et si l'utilisateur clique sur le lien de téléchargement, mais je n'ai jamais testé la boîte de dialogue Enregistrer sous dans le navigateur ... et cela prendra un certain temps pour la reconstruire, alors essayez.

4
Milan Jaric

Vous pouvez essayer de faire ça

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

API Chromium pertinente ...

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

Exemple...

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

});

Oups ... vous êtes côté serveur mais j'ai supposé côté client! Je laisserai cela ici au cas où quelqu'un en aurait besoin.

0
user4723924