javascript - extension - mozilla firefox
Hacer que Firefox y Chrome descarguen la imagen con un nombre especĂfico (6)
Dado 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>
...
Este es un entorno de script de usuario, por lo que no tengo acceso a la configuración del servidor. Como tal:
- No puedo hacer que el servidor acepte nombres de archivos fáciles de usar como
https://static.example.com/full/86fb269d190d2c85f6e0468ceca42a20 - 1337 - Hello World!.png
. - No puedo configurar el uso compartido de recursos de origen cruzado.
www.example.com
ystatic.example.com
están separados por el muro CORS por diseño.
¿Cómo hacer que Firefox y Chrome muestren el cuadro de diálogo Guardar archivo como con el nombre de archivo sugerido "1337 - Hello world! .Png" cuando un usuario hace clic en el enlace "Descargar"?
Después de algunos fallos y googlear, aprendí estos problemas:
- Firefox ignora por completo la existencia del atributo de
download
en algunos tipos de imágenes MIME. - Firefox ignora por completo la existencia del atributo de
download
en los enlaces entre sitios. - Chrome ignora completamente el valor del atributo de
download
en los enlaces entre sitios.
Todos estos puntos no tienen ningún sentido para mí, todos parecen "vamos a poner limitaciones no sensuales al azar en la función", pero tengo que aceptarlas ya que es mi entorno.
¿Existen formas de resolver el problema?
Antecedentes: estoy escribiendo un script de usuario para un tablero de imágenes que utiliza hashes MD5 como nombres de archivo. Quiero hacer más fácil guardar con nombres fáciles de usar. Cualquier cosa que me acerque a esto sería útil.
Supongo que puedo sortear las limitaciones mediante el uso de URL de objeto para blobs y un proxy local con encabezados CORS pirateados, pero esta configuración obviamente es más que razonable. El ahorro a través del lienzo podría funcionar (¿las imágenes están "protegidas" por CORS en este caso también?), Pero forzará la compresión de doble pérdida o la conversión de pérdida a pérdida, dados los archivos JPEG, ninguno de los cuales es bueno.
API de Chromium relevante ...
https://developer.chrome.com/extensions/downloads#event-onDeterminingFilename
Ejemplo...
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
});
Vaya ... estás en el lado del servidor, pero asumí el lado del cliente! Dejaré esto aquí aunque en caso de que alguien lo necesite.
Intenta algo como:
- Obtenga la imagen externa a su servidor primero
- Devuelve la imagen recuperada de tu servidor.
- Cree dinámicamente un anclaje con el nombre de la
download
y.click()
él.
mientras que lo anterior era solo una lista bastante breve de consejos ... prueba esto:
en www.example.com
coloque un fetch-image.php
con este contenido:
<?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();
o con cualquier otro lenguaje del lado del servidor que logre lo mismo.
Lo anterior debe devolver cualquier imagen externa, ya que se encuentra en su dominio.
En tu página de image-list
, lo que puedes probar ahora es:
<a
href="//static.example.com/thumbnails/86fb269d190d2c85f6e0468ceca42a20.png"
download="1337 - Hello world!.png">DOWNLOAD</a>
y este 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)
);
Deberías ver finalmente la imagen descargada como
1337 - ¡Hola mundo! .Png
en lugar de 86fb269d190d2c85f6e0468ceca42a20.png
como fue el caso.
Aviso : No estoy seguro de las implicaciones de las solicitudes simultáneas hacia fetch-image.php
: asegúrese de realizar pruebas y pruebas.
Puedes intentar hacer esto
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>
Si tiene acceso a ambos códigos, backend y frontend, aquí hay algunos pasos que podrían ayudarle
No estoy seguro de qué tipo de lenguaje de fondo está utilizando, por lo que solo explicaré lo que debe hacerse sin el ejemplo de código.
En el backend, para obtener una vista previa, su código debería funcionar como está, si obtiene en la cadena de consulta algo como ?download=true
entonces su backend debería empaquetar el archivo como contenido desechado, en otras palabras, usaría el encabezado de respuesta de content-disposition
. Esto abrirá la posibilidad de agregar atributos adicionales al contenido, como filename
, por lo que podría ser así.
Content-Disposition: attachment; filename="filename.jpg"
Ahora, al frente, cualquier enlace que deba comportarse como botón de descarga debe contener ?download=true
en el parámetro de consulta href Y target="_blank"
que abrirá temporalmente otra pestaña en el navegador para fines de descarga.
<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>
Sé que esto funciona sin la configuración de CORS y si el usuario hace clic en el enlace de descarga, pero nunca probé el cuadro de diálogo Guardar como en el navegador ... y tomará un tiempo para volver a compilarlo, así que inténtelo.
Soy capaz de cambiar el nombre de las imágenes base64 con un campo de guardar como entrada.
Creo que lo mejor que puedes hacer es crear tu propia casilla "guardar como". Cuando un usuario hace clic en "descargar", muestra un "Nombre de archivo: {campo de entrada}" y un botón para guardar. Haga que el botón guardar cambie el nombre del archivo y luego llame a la función de descarga.
function save2() {
var gh = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADNQTFRFAAAAPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09EvTTBAAAABB0Uk5TAA8fLz9PX29/j5+vv8/f7/rfIY4AAARsSURBVHja7VvZkusqDAyrAQvQ/3/teZjExrHDZoGrbl0/z0RtLY1oya/X/8+Nh/NHzYuAGPRz9llERET1GACJiIgYngaAkmvNnwTgERHtgwDefngawDofgDkAwPlp4I4AzHQA/gggstk0jF/P7ELQ3wBQPBoBRPRzTyI8P/bBGvh79FMstFXCvDSAt0kHzyBYNv7jj/iAx48DEiwzEfj0AFi/EEw4F+2B/5mfXQsbB4ZDQOKGwM2ioE+4hUdEm3jCjzybRbw4gIXkrxfbySnqCfYhS48rG23fs/wRGYdcGIQv1PsOcIgTkp//xTcs4WbyTEjs67pmFfh8+3+X1s0Jy3z7rxezaZ9EdTjI2MC1MpA37LqN65kjdoJuPmtUUpC40NmvLy2WntM3OcH09RupE8KdMLjefufgBE1gvz2blnj/2pDY7wikSPold9M+dCVSWpDuln1HUMLuCfsHEndP2H+9uO+kJEfVaicNq+zin9udxY6gQcrRlFeNHUG1oCfpjpIjAtmaukQXHRabpJwdMNlFSzZFdL3Dv4WkrlH4lyH6Y6jOgj0BSPUGWV0InrQAztISr2UgahFe1r3XJgHRC9C+qhK3CqC/4H6Sm1XV64ApCKt5NegOgFTGGGPMIlnhx22NA64zhUsppTxVMtcuvY5hcCqX31DhgAu+EgZ+WLjSjoPJvF6mBH5lIFvC7wHBJ7kAAAByjFdkAvdDg0o7/PPByiOCSSIvbfhBo6HExvES/ftwjOs7v7iyoZCl0qhMhHWpDQoX9QvH/xJd+osriAbr9ZktEQONCm3yAD5EEU833YWIlgsA1PD5UwGAGz4DLAAIw0eAeQBs/CTaZi2o8VNYyAIwP2qAHsCSZYGR6xD5xtgPTwGeBzB+I0Xlj+Oajo2kCEK+GRqfg2sWwEAaKhCNLDdsRCkgnwLg8kEeDyDmLQwHoAp3w+EA1kJPPBoAL6lEYnAZmuLtfCwRbToZLwEYNP7X5Vs33NEFuI15BS6U7+auuydmGkoKXI1Kt9RlIZPHIIllLbfzWwboCm2AF480b7WUQkipDWySkhPlg7ggU9apWPFqkWzV2TZC1Am1a1UMltMWW8F6Xve4qpRCX86U3ZQkcEtFF79UKtW8RSJnsvr+IDK7N23HRScH+mrtWQ/RCF3D+DYOaM337bOKftvQ78iKps3fjbDIrkeX22cVLqAKAovVFfD1DzRi/V4AgbWmDMW8ivmO7Qto9FlV/FvGr5xsZilj3/hXI00UTPcKi6PYgkrXR5qnb/72ZuRho03fSF5E1xOGg7qvb5VPz2akTmcbnT48LExDCysycxitdGfRcWUbar2gvj59cDfqyH3NoMpNyt+k5r77t1B+tb/eZNzJtTt1y+4umXM49b9g1AmFUPvloDdzqsppDweA/RuSOoDLv6D7GvRAKPUP5ceo3DWbX4nFXm5iy8ubEfqCWiut22HDDqZcyBuP6zL6s0euLVzbBqunfWbFpTZmhfdjjVFy9seO/6nnH0Mpp/3TjvofAAAAAElFTkSuQmCC"
var a = document.createElement(''a'');
a.href = gh;
a.download = document.getElementById("input").value;
a.click()
}
Filename: <input id="input" name="input" placeholder="enter image name"></insput><button onclick="save2()">Save</button>
El código anterior le permitirá nombrar el archivo para el usuario. Si desea que el usuario pueda nombrar el archivo por sí mismo, debe crear un campo de entrada que actualice a.download = ''imagetest.png''; a través de una función de escucha o onkeychange. Tengo que trabajar a través de "a.download = document.getElementById (" input "). Value;". Es increíble lo que puede hacer getElementByID.
Los navegadores son muy limitados en la capacidad de nombrar archivos para que las personas los descarguen. Me imagino que es una falta de funcionalidad que se ha pasado por alto.
Si desea ir un paso más allá, puede ocultar el campo de entrada "nombre de archivo:" con pantalla: oculto ;. Y puede tener un botón de "descarga" con una función onclick que establece que el "nombre de archivo: entrada / guardar" div ya no está configurado para mostrar: oculto ;. Esto también se puede hacer a través de getelementbyID.
Sin embargo, noté que parece haber un problema con el cambio de nombre de la imagen con una URL en lugar de base64. Así que traté de integrar mi código con el tuyo, aunque ninguno de los códigos de abajo parecía funcionar correctamente al hacer clic en el enlace de descarga. Todavía creo que es lo suficientemente cerca como para que puedas jugar con él si esta es la ruta a la que quieres ir:
<script>
function save2() {
var gh = "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"
var a = document.createElement(''a'');
a.href = gh;
a.download = document.getElementById("input").value;
a.click()
}
function downloadit(){
var x = document.getElementById("input").value;
document.getElementById("dl").getAttribute("download") = x;
}
document.getElementById("dl").onclick = function() {
document.getElementById("dl").download= a.download = document.getElementById("input").value;
return false;
}
</script>
<a href="/image/1337">
<img src="//static.example.com/thumbnails/86fb269d190d2c85f6e0468ceca42a20.png"/>
</a>
<a onclick=''downloadit()'' id="dl" href="//static.example.com/full/86fb269d190d2c85f6e0468ceca42a20.png"
download="1337 - Hello world!.png">
Download
</a>
Filename: <input id="input" name="input" placeholder="enter image name"></insput><button onclick="save2()">Save</button>
Finalmente, pude cambiar el nombre de su imagen de example.com con la siguiente secuencia de comandos. Aunque cuando lo intento con una imagen de google que funciona no parece cambiar el nombre. Así que es posible que desees meterte con esto también.
document.getElementById("dl").onclick = function() {
document.getElementById("dl").download=document.getElementById("input").value;
}
Filename: <input id="input" name="input" placeholder="enter image name"></input>
<a id="dl" href="//static.example.com/full/86fb269d190d2c85f6e0468ceca42a20.png"
download="1337 - Hello world!.png">
Download
</a>
Todos los navegadores modernos ignorarán el atributo de descarga en la etiqueta de anclaje para las URL de origen cruzado.
Referencia: https://html.spec.whatwg.org/multipage/links.html#downloading-resources
De acuerdo con los fabricantes de especificaciones, esto representa una laguna de seguridad, ya que un usuario podría ser engañado para que descargue archivos maliciosos mientras navega por un sitio seguro, creyendo que el archivo también se origina en el mismo sitio seguro.
Cualquier conversación interesante para implementar esta función en el navegador Firefox se puede encontrar aquí: https://bugzilla.mozilla.org/show_bug.cgi?id=676619
[Editar por Athari]
Cita de la especificación:
Esto podría ser peligroso, porque, por ejemplo, un servidor hostil podría estar intentando que un usuario descargue información privada sin saberlo y luego vuelva a subirla al servidor hostil, engañando al usuario para que piense que los datos provienen del servidor hostil.
Por lo tanto, es de interés para el usuario que se le notifique de alguna manera que el recurso en cuestión proviene de una fuente muy diferente y, para evitar confusiones, se debe ignorar cualquier nombre de archivo sugerido desde el origen de la interfaz potencialmente hostil.
Aclaración sobre el misterioso escenario:
El problema más grave con las descargas de CORS es si un sitio malicioso fuerza una descarga de un archivo desde un sitio legítimo y de alguna manera cómo se accede a su contenido. así que digamos que descargo la página de la bandeja de entrada de gmail del usuario y exploro sus mensajes.
en este caso, un sitio malvado tendrá que engañar al usuario para que descargue el archivo y lo vuelva a cargar en el servidor, así que digamos que tenemos un gmail.com/inbox.html que en realidad contiene todos los mensajes de correo del usuario y los sitios de atacantes ofrecen una enlace de descarga para un archivo de cupón, que debe ser cargado en otro sitio malvado. El cupón supuestamente ofrecerá un descuento del 30% en un nuevo Ipad. el enlace de descarga realmente apuntará a gmail.com/inbox.html y lo descargará como "30off.coupon", y si el usuario descarga este archivo y lo carga sin verificar su contenido, el sitio malo obtendrá el "cupón" del usuario y así su contenido en la bandeja de entrada.
Notas importantes:
Originalmente, Google no limitó el atributo de descarga de CORS y estaba explícitamente en contra de esto. Más tarde se vio obligado a ajustar la implementación de Chrome.
Google se opuso al uso de CORS para esto.
Se propusieron soluciones alternativas con advertir a un usuario sobre descargas de origen cruzado. Fueron ignorados.
Bueno, puede haber un mecanismo de notificación o denegación / autorización al descargar desde otro origen (por ejemplo, como en el caso de la API de geolocalización). O no enviar cookies en caso de solicitud de origen cruzado con atributo de descarga.
Algunos desarrolladores comparten la opinión de que la restricción es demasiado fuerte, limita severamente el uso de la función y que el escenario es tan complicado que el usuario que haría esto fácilmente descargaría y ejecutaría un archivo ejecutable. Su opinión fue desestimada.
El caso en contra de permitir descargas de origen cruzado se centra en la premisa de que los visitantes de un sitio [malvado] (por ejemplo, discountipads.com) podrían, sin saberlo, descargar un archivo de un sitio que contenga su propia información personal (por ejemplo, gmail.com) y guardar a su disco usando un nombre engañoso (por ejemplo, "discount.coupon") Y LUEGO vaya a otra página maliciosa en la que cargan manualmente el mismo archivo que acaban de descargar. En mi opinión, esto es bastante inverosímil, y cualquier persona que sucumbiría a un truco tan trivial tal vez no esté en línea en primer lugar. Quiero decir, vamos ... Haga clic aquí para descargar nuestra oferta de descuento especial y luego vuelva a subirla a través de nuestro formulario especial. ¿Seriamente? ¡Descargue nuestra oferta especial y luego envíela por correo electrónico a esta dirección de Yahoo para obtener un gran descuento! ¿Las personas que caen en estas cosas saben cómo hacer archivos adjuntos de correo electrónico?
Estoy a favor de la seguridad del navegador, pero si la buena gente de Chromium no tiene ningún problema con esto, no veo por qué Firefox tiene que eliminarlo por completo. Por lo menos me gustaría ver una preferencia en about: config para habilitar @download de origen cruzado para usuarios "avanzados" (por defecto es falso). Aún mejor sería un cuadro de confirmación similar a: "Aunque esta página está cifrada, la información que envíe a través de este formulario no será" o: "Esta página solicita la instalación de complementos" o: "Los archivos descargados de la web pueden dañar su computadora "o incluso:" El certificado de seguridad de esta página no es válido "... ¿saben a qué me refiero? Hay innumerables formas de aumentar la conciencia del usuario e informarles que esto podría no ser seguro. Un clic adicional y una demora corta (¿o larga?) Es suficiente para permitirles evaluar el riesgo.
A medida que la web crece, y el uso de CDN crece, y la presencia de aplicaciones web avanzadas crece, y la necesidad de administrar los archivos alojados en servidores crece, las características como @download serán cada vez más importantes. Y cuando un navegador como Chrome lo admite por completo, mientras que Firefox no lo hace, esto no es una victoria para Firefox.
En resumen, creo que mitigar los usos malvados potenciales de @download simplemente ignorando el atributo en escenarios de origen cruzado es un movimiento muy mal pensado. No estoy diciendo que el riesgo sea totalmente inexistente, todo lo contrario: estoy diciendo que hay muchas cosas arriesgadas que uno hace en línea en el transcurso de su día ... descargar CUALQUIER archivo es alto entre ellos. ¿Por qué no solucionar ese problema con una experiencia de usuario bien pensada?
En general, considerando el uso generalizado de CDN y el contenido generado por el usuario en un dominio diferente, el uso principal para el atributo de descarga es especificar un nombre de archivo para las descargas de blobs ( URL.createObjectURL
) y similares. No se puede usar en muchas configuraciones y, ciertamente, no es muy útil en los scripts de usuario.