60 votes

Javascript : Télécharger un fichier... sans fichier

J'essaie de simuler un téléchargement de fichier sans utiliser réellement une entrée de fichier de l'utilisateur. Le contenu du fichier sera généré dynamiquement à partir d'une chaîne de caractères.

Est-ce possible ? Quelqu'un l'a-t-il déjà fait ? Existe-t-il des exemples/une théorie ?

Pour clarifier, je sais comment télécharger un fichier à l'aide des techniques AJAX en utilisant une iframe cachée et ses amis - le problème est de télécharger un fichier qui n'est pas dans le formulaire.

J'utilise ExtJS, mais jQuery est également utilisable puisque ExtJS peut s'y brancher (ext-jquery-base).

0 votes

Cela semble être la mauvaise solution à votre problème (si vous avez le contrôle du côté serveur). Si le contenu du fichier est généré à partir d'une chaîne de caractères, pourquoi ne pas simplement POST cette chaîne et créer le fichier sur le serveur (en utilisant PHP ou autre) ? Si vous téléchargez un fichier vers une destination tierce, ignorez ce commentaire.

0 votes

@JonathanJulian, quoi qu'il en soit, ce cas d'utilisation sent le vrai hack-value -), astuce géniale !

44voto

Josa Points 11

Si vous n'avez pas besoin de la prise en charge des navigateurs plus anciens, vous pouvez utiliser la fonction FormData Objet, qui fait partie de l'API Fichier :

const formData = new FormData();
const blob = new Blob(['Lorem ipsum'], { type: 'plain/text' });
formData.append('file', blob, 'readme.txt');

const request = new XMLHttpRequest();
request.open('POST', 'http://example.org/upload');
request.send(formData);

L'API fichier est prise en charge par tous les navigateurs actuels (IE10+).

2 votes

J'évite d'écrire mes propres XMLHttpRequests. C'est définitivement la réponse que je préfère !

1 votes

Cela devrait être la réponse acceptée - j'ai passé 8 heures à passer au peigne fin divers messages, et c'est ce qui a fonctionné, et en très peu de lignes de code.

36voto

Andy E Points 132925

Pourquoi ne pas simplement utiliser XMLHttpRequest() avec POST ?

function beginQuoteFileUnquoteUpload(data)
{
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "http://www.mysite.com/myuploadhandler.php", true);
    xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xhr.onreadystatechange = function ()
    {
        if (xhr.readyState == 4 && xhr.status == 200)
            alert("File uploaded!");
    }
    xhr.send("filedata="+encodeURIComponent(data));
}

Le gestionnaire script du serveur écrit simplement les données du fichier dans un fichier.

EDIT
Le téléchargement de fichiers est toujours un post http avec un type de contenu différent. Vous pouvez utiliser ce type de contenu et séparer votre contenu avec des frontières :

function beginQuoteFileUnquoteUpload(data)
{
    // Define a boundary, I stole this from IE but you can use any string AFAIK
    var boundary = "---------------------------7da24f2e50046";
    var xhr = new XMLHttpRequest();
    var body = '--' + boundary + '\r\n'
             // Parameter name is "file" and local filename is "temp.txt"
             + 'Content-Disposition: form-data; name="file";'
             + 'filename="temp.txt"\r\n'
             // Add the file's mime-type
             + 'Content-type: plain/text\r\n\r\n'
             + data + '\r\n'
             + boundary + '--';

    xhr.open("POST", "http://www.mysite.com/myuploadhandler.php", true);
    xhr.setRequestHeader(
        "Content-type", "multipart/form-data; boundary="+boundary

    );
    xhr.onreadystatechange = function ()
    {
        if (xhr.readyState == 4 && xhr.status == 200)
            alert("File uploaded!");
    }
    xhr.send(body);
}

Si vous souhaitez envoyer des données supplémentaires, il vous suffit de séparer chaque section par une frontière et de décrire les en-têtes content-disposition et content-type pour chaque section. Chaque en-tête est séparé par une nouvelle ligne et le corps est séparé des en-têtes par une nouvelle ligne supplémentaire. Naturellement, le téléchargement de données binaires de cette manière serait légèrement plus difficile :-)

Modification supplémentaire : j'ai oublié de mentionner que la chaîne de délimitation ne doit pas se trouver dans le "fichier" texte que vous envoyez, sinon elle sera traitée comme une délimitation.

0 votes

Parce que le serveur ne le reconnaîtra pas comme un "fichier" téléchargé.

0 votes

Je pense qu'il veut savoir comment générer data .

0 votes

@LiraNuna : Pourquoi est-ce important si tu génères le contenu à partir d'une chaîne de caractères ? Ne peut-il pas simplement le reconnaître comme une chaîne et l'écrire ?

13voto

LiraNuna Points 21565

Je partage simplement le résultat final, qui fonctionne - et permet d'ajouter/supprimer des paramètres sans coder en dur.

var boundary = '-----------------------------' +
            Math.floor(Math.random() * Math.pow(10, 8));

    /* Parameters go here */
var params = {
    file: {
        type: 'text/plain',
        filename: Path.utils.basename(currentTab.id),
        content: GET_CONTENT() /* File content goes here */
    },
    action: 'upload',
    overwrite: 'true',
    destination: '/'
};

var content = [];
for(var i in params) {
    content.push('--' + boundary);

    var mimeHeader = 'Content-Disposition: form-data; name="'+i+'"; ';
    if(params[i].filename)
        mimeHeader += 'filename="'+ params[i].filename +'";';
    content.push(mimeHeader);

    if(params[i].type)
        content.push('Content-Type: ' + params[i].type);

    content.push('');
    content.push(params[i].content || params[i]);
};

    /* Use your favorite toolkit here */
    /* it should still work if you can control headers and POST raw data */
Ext.Ajax.request({
    method: 'POST',
    url: 'www.example.com/upload.php',
    jsonData: content.join('\r\n'),
    headers: {
        'Content-Type': 'multipart/form-data; boundary=' + boundary,
        'Content-Length': content.length
    }
});

Il a été testé pour fonctionner sur tous les navigateurs modernes, y compris mais sans s'y limiter :

  • IE6+.
  • FF 1.5+
  • Opera 9+.
  • Chrome 1.0 et plus
  • Safari 3.0 et plus

0 votes

+1 Bonne solution. Mais je pense qu'il y a un problème avec votre algorithme. Pourquoi vous utilisez un for in pour l'objet params ? Il semble qu'il soit préparé pour plus d'un fichier mais comment le second fichier sera-t-il nommé dans l'objet ? Où sont action , overwrite et destination et comment ne pas casser le code à l'intérieur de l'application. for in ?

0 votes

@Protron : La raison pour laquelle j'utilise for( in ) est de récupérer les clés de l'objet description. Le code détectera si filename est défini sur un objet imbriqué (qui décrit un fichier à télécharger). Les autres paramètres ( overwrite , action , destination ) sont juste des paramètres supplémentaires passés comme si vous utilisiez un formulaire.

2 votes

@LiraNuna, je vous vois tous devenir magiques à propos de la ----------------------------- la seule exigence de la spécification MIME (voir RFC 1341, sec 7.2.1) est que la limite commence par -- suivi d'un jeton valide (voir RFC 1341 sec.4). J'espère que cela aidera d'autres personnes à connaître leur liberté aussi :-)

6voto

Rubens Farias Points 33357

Un téléchargement de fichier, c'est juste un POST avec le contenu de ce fichier correctement encodé et avec un code spécial. multipart/formdata en-tête. Vous devez utiliser cette <input type=file /> car la sécurité de votre navigateur vous interdit d'accéder directement au disque de l'utilisateur.

Comme vous n'avez pas besoin de lire le disque utilisateur, OUI vous pouvez le simuler en utilisant Javascript. Ce sera juste un XMLHttpRequest . Pour falsifier une demande de téléchargement "authentique", vous pouvez installer Fiddler et inspecter votre demande de sortie.

Vous devrez encoder ce fichier correctement, ce lien peut donc vous être très utile : RFC 2388 : Retourner les valeurs des formulaires : multipart/form-data

0 votes

Que doit contenir cette demande ? Comment ce protocole est-il défini ? Comment le falsifier ?

0 votes

Ce n'est pas un protocole, c'est juste une requête HTTP ordinaire ; j'ai mis à jour ma réponse

0 votes

Je n'ai pas utilisé Fiddler (utilisateur Linux ici), mais Firebug montre comment cela devrait être. Cela me rapproche un peu plus. Je vote pour car c'est utile, mais je ne sélectionne pas encore la réponse.

3voto

Tom Bartel Points 1995

Je viens d'attraper cette chaîne POST_DATA avec l'addon Firefox TamperData. J'ai soumis un formulaire avec une type="file" nommé "myfile" et un bouton d'envoi nommé "btn-submit" avec la valeur "Upload". Le contenu du fichier téléchargé est le suivant

Line One
Line Two
Line Three

Voici donc la chaîne POST_DATA :

-----------------------------192642264827446\r\n
Content-Disposition: form-data;    \n
name="myfile"; filename="local-file-name.txt"\r\n
Content-Type: text/plain\r\n
\r\n
Line \n
One\r\n
Line Two\r\n
Line Three\r\n
\r\n
-----------------------------192642264827446\n
\r\n
Content-Disposition: form-data; name="btn-submit"\r\n
\r\n
Upload\n
\r\n
-----------------------------192642264827446--\r\n

Je ne suis pas sûr de la signification du numéro (192642264827446), mais cela ne devrait pas être trop difficile à trouver.

0 votes

J'ai reformaté le POST_DATA pour le rendre plus facile à lire, le 192642264827446 ressemble à une borne.

0 votes

Merci, gnibbler. Oui, j'ai pensé que ça pouvait être quelque chose comme un marqueur de frontière, probablement juste un nombre aléatoire.

1 votes

Ouais, c'est un marqueur de frontière. Si vous vérifiez le multipart/form-data l'en-tête, la frontière le suivra. Le nombre aléatoire à la fin sert à éviter tout conflit avec les données envoyées.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X