Bonjour à tous :)
Je suis nouveau sur le forum et ai appris nodejs grâce aux très précieux tutoriels de Grafikart :)

Pour les besoins de l'apprentissage j'essai pour commencer de tout réaliser sans framework et librairies. Le problème est d'enregistrer sur le disque les fichiers reçus par formulaire multi form data html. Non pas à cause de la réception et parsing de ces données, mais qu'on reçoit une grande chaine de caractère comme montré plus bas, et que je ne sais pas comment enregistrer ce fichier sous le bon encodage pour que ça marche et n'affiche plus l'erreur : "fichier impossible à ouvrir"

Cet exemple est un formulaire avec deux champs inputs et un fichier jpg en tout dernier :

------WebKitFormBoundaryde6aU6ihpbf1zqFK
Content-Disposition: form-data; name="nom"

De la Tour
------WebKitFormBoundaryde6aU6ihpbf1zqFK
Content-Disposition: form-data; name="prenom"

Jean
------WebKitFormBoundaryde6aU6ihpbf1zqFK
Content-Disposition: form-data; name="myFiles"; filename="monfichier.jpg"
Content-Type: image/jpeg

I$g�p9�����c�#Ӭ��d����\u0019XL� �=i�\u0000�V���\u0000���\u0000\u001a���]�A�z�D]n��\u0011���_�\c�{�\u000b0�\r��)>��t�\u00125���[�h�~\"��:5�W\u0012O���\u001b4�\u0017d~c��ԟ|b�\u0012;���I���\u0000�RԎ��G\u0003�W&�n'Kx���\u0000<�p�9�My&��\u000f\u0014�����l�\"����j\"K�x�$��\u0017NT�H��\u0016���kz����'��\u0018�e\u0015��\u0001\b\u0016\bc�\b�q�\u001c\u0016pwP�i[�F{Ƙ�.�\u001d��w_j[i��c\u001c�vH�yO=EL-�ƼkE�\u001b�]�\b�;��ۜ��/�Z���\u001c�EԈד��w�\f\u0002�\b�*\f~Y�\u000f�캄?\u001cm�ִMV�W��y俖�4\u00009�H#\fq\u0012�X�|��M\u000bV����L�;��t�� (etc...)
------WebKitFormBoundaryde6aU6ihpbf1zqFK

Le fichier est reçu dans une grande chaine de caractères. Mon problème est de l'enregistrer sur le serveur et que cette image soit à nouveau lisible. Aujourd'hui ça me dit "Ce fichier est impossible à ouvrir"

Je parse ce multiform-data avec mon code ci-dessous :

    http.createServer((req, res) => {
        var data = "";
        var obj = this;
        req.on('data', function(sample) {
            data += sample;                            // On receptionne tous les morceaux
        });
        req.on('end', function() {                  //Quand tout est envoyé...
            data = data.split('\r\n');                 //... on fait un tableau avec chaque ligne envoyée
            var fd = [];                                       //... fd pour formidable, c'est juste un tableau intermédiaire
            var tempNumber = -1;
            for(let i in data){
                if(data[i].slice(0, 6) == "------"){      // ...si la ligne commence par 6 tirets alors c'est une nouvelle variable
                    tempNumber++;
                    fd[tempNumber] = [];
                    continue;
                }
                fd[tempNumber].push(data[i]);    // ... Maintenant ce tableau contient un tableau pour chaque variable, contenant un tableau pour chaque lignes la décrivant
            };
            fd.pop();                                                //... on enlève la dernière ligne car c'était "--------" qui servaient à fermer la variable

            var fields = {};                                       // ... Comme dans formidable on mettra les variables dans des objets fields et files
            var files = {};
            for(let i in fd){
                if(fd[i][0].indexOf('filename') == -1){                      // On regarde si cette variable est un fichier ou non si il y a un attribut filename
                    var name = fd[i][0].split("\\").join('');
                    name = name.slice(name.indexOf(' name')+7);
                    name = name.slice(0, name.indexOf('"'));
                    fields[name] = fd[i].slice(2).join('');
                }
                else{ // Dans ce cas c'est un fichier
                    var racine = fd[i][0].split("\\").join('');
                    name = racine.slice(racine.indexOf(' name')+7);
                    name = name.slice(0, name.indexOf('"'));
                    var fileName = racine.slice(racine.indexOf(' filename') + 11);
                    fileName = fileName.slice(0, fileName.indexOf('"'));
                    var content = fd[i].slice(3).join('');
                    files[name] = {fileName,content};
                    fs.writeFile('testtt.jpg', content);              //C'est ici qu'il y a un problème quand on sauvegarde ce contenu comme ça dans un fichier il est illisible, pourtant on met tout dans un fichier sans rien oublier. Certainement une question d'encodage
                }
            }

            res.end(JSON.stringify(files));                                  //J'affiche le résultat pour bien voir si ça a marché et c'est le cas, c'est juste l'enregistrement du fichier qui ne marche pas
        });

Comment peut on faire pour enregistrer un fichier reçu en multiform data, depuis la string qu'on reçoit telle quelle ?

Merci infiniment de m'avoir lu jusqu'au bout :')
Je n'ai trouvé personne ne parlant de ce problème car tout le monde suggère directement l'utilisation d'un framework.
C'est hyper intéressant de se pencher là dessuset j'espère que les solutions si on les trouve pourront aider les suivants,

Merci, à bientôt,
Guillaume

7 réponses


DariosDjimado
Réponse acceptée

Il s'agit en partie d'un problème d'encodage. Lorsque tu reçois des fichiers tels que les images ou autres tu dois changer le format d'encodage et le mettre en binaire au tout debut de la requête :

req.setEncoding('binary');
 req.on('data', function(sample) {
    .....
 }

Pour enregistrer tu dois aussi utliser ce format

fs.writeFile('testtt.jpg', content,'binary',(err)=>{

     if(err){
         // gerer les erreurs
     }else{
        console.log('fichier enregistré avec succès');
     }
 }); 

Dans ton cas il faut aussi corriger la ligne suivant :

//var content = fd[i].slice(3).join('');
var content = fd[i].slice(3).join('\r\n');

Parceque la ligne ci-dessous a aussi détérioré le fichier

data.split('\r\n');

Voici le code que j'avais écrit il y a un moment qui évite cette recomposition du fichier.
je l'ai un peu adapté à ton cas

let http=require('http');
let fs=require('fs');
http.createServer((req, res) => {
    let contentType=req.headers['content-type'];
    // Vérifier qu'il s'agit bien d'une requête multipart/form-data
    if(req.method!=='POST' || contentType.indexOf('multipart/form-data')===-1){
        return res.end('bad request')
    }
    // changer l'encodage par défaut
    req.setEncoding('binary');
    // Les navigateurs précisent le séparateur des champs du formulaire automatiquement
    // par boundary présent dans les entêtes de la requête
    let boundary=contentType.substring(contentType.indexOf('boundary=')+9,contentType.length);
    let data = '';

    req.on('data', function (sample) {
        data += sample;
    });
    req.on('end', ()=> {
        data = data.split(boundary); // séparer les champs
        let dataLength=data.length;
        let fields={};
        let files={};

        for (let i=1;i<dataLength-1;i++){
            if(data[i].indexOf('filename')===-1){
                // stocker les champs
                let element=data[i].split('\r\n'); // supprimer les sauts de ligne
                let indexOfName=element[1].indexOf('name=')+6;
                fields[element[1].substring(indexOfName,element[1].length-1)]=element[3];
            } else {
                // stocker le fichier
                let element=data[i].split('Content-Type'); // garder le contenu dans le deuxième champ du tableau
                let indexOfFileName=element[0].indexOf('filename=')+9;
                let indexOfContent=element[1].indexOf('\r\n\r\n')+4; // le contenu se trouve alors après les deux premiers sauts à la ligne

                let filename=element[0].substring(indexOfFileName,element[0].length-1);
                let fileContent=element[1].substring(indexOfContent,element[1].length-3);

                files['file']={filename,fileContent};
                fs.writeFile('image.png', fileContent,'binary',function(err){
                    if(!err) console.log('fichier enregistré avec succès')
                });
            }
        }
        console.log(fields);
        res.end(JSON.stringify(files));
    })
}).listen(8000);

Pour tester

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <form action="http://localhost:8000" method="POST" enctype="multipart/form-data">
        <label>
            email
            <input type="email"  name="email" required autofocus>
        </label>
        <label>
            password
            <input type="password" name="password" required>
        </label>
        <input type="file"  name="file" required>
        <button type="submit">Submit</button>
    </form>
</body>
</html>

Salut,

tu peux regarder du côté du module multiparty qui est relativement simple à mettre en oeuvre.

Merci Infiniment Darios !!!! Je n'ai pas les mots, ça marche ! Tu viens de résoudre un problème m'empêchant de dormir depuis plus de deux semaines et auquel personne de mon entourage n'a sut répondre (informatique quand même, même si pas nodejs) et dont même google avait des lacunes.
Réponse complète et fonctionnelle !
Tu as tout mon respect, merci :)

Ravi d'avoir rendu service ^^

Hey tout le monde, et @Darios si tu es là et que tu peux encore me sauve svp j'ai à nouveau une question ^^'

Quand on met en setEncoding("binary"), il ne comprend plus les accents en UTF-8
Soit on le met et on reçoit les images mais si la personne met dans un champ un accent par exemple : "écouter" ça rendra "écouter" que je n'arrive pas à retransformer en utf8

Soit on ne le met pas, les accents des champs seront bon, mais on ne pourra pas encoder les fichiers.

Comment on peut faire pour avoir les deux ? Champs avec accents et fichiers ?

Merci o:)

Tu peux utiliser la classe string_decoder de NodeJS:

const StringDecoder = require('string_decoder').StringDecoder;
// créer un décodeur en utf-8
const decoder = new StringDecoder('utf8');
.
.
.
let fieldName=decoder.write(new Buffer(element[3],"binary")) // où element[3] est le nom à enregistrer (cf mon exemple)

Il ne faut juste pas oublier de préciser binary comme second paramètre du buffer.

Exceptionnel encore une fois !! :) Merci beacoup Darios ça marche !