Gestion des champs non-fichier dans les formulaires multipart avec Apache Commons FileUpload

Lorsqu’un formulaire HTML utilise enctype="multipart/form-data", le conteneur servlet ne remplit plus automatiquement les paramètres dans la map retournée par request.getParameter(). Cela est dû au fait que l’intégralité du corps de la requête est traitée comme un flux binaire structuré en parties (parts), conformément à la spécification RFC 7578. Tous les champs — y compris les champs texte, cachés ou boutons — sont encapsulés sous forme d’items dans le même flux que les fichiers.

Pour extraire ces valeurs, il faut analyser manuellement la requête multipart à l’aide d’une bibliothèque dédiée comme Apache Commons FileUpload. Cette bibliothèque permet de parcourir chaque FileItem, et de distinguer les champs simples (isFormField() == true) des éléments binaires (isFormField() == false).

Exemple HTML minimal

<form id="uploadForm" action="/upload" method="post" enctype="multipart/form-data">
  <input type="file" name="attachment" required>
  <input type="hidden" name="targetDir" value="/uploads/images/">
  <input type="text" name="description" placeholder="Description optionnelle">
  <button type="submit">Envoyer</button>
</form>

Implémentation côté serveur (Servlet Java)

Voici une version modernisée et sécurisée du traitement, utilisant FileItem avec gestion explicite des champs non-fichiesr :

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MultipartUploadHandler extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        // Configuration de la factory avec encodage UTF-8
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setCharset(StandardCharsets.UTF_8);

        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setHeaderEncoding("UTF-8");

        try {
            List<FileItem> items = upload.parseRequest(req);

            // Extraction des champs textuels dans une map
            Map<String, String> formFields = new HashMap<>();
            FileItem uploadedFile = null;

            for (FileItem item : items) {
                if (item.isFormField()) {
                    formFields.put(item.getFieldName(), item.getString(StandardCharsets.UTF_8));
                } else {
                    uploadedFile = item;
                }
            }

            // Récupération des valeurs
            String targetDir = formFields.getOrDefault("targetDir", "/default/");
            String description = formFields.get("description");

            // Chemin de sauvegarde
            String realPath = req.getServletContext().getRealPath("/WEB-INF" + targetDir);
            File storageDir = new File(realPath);
            if (!storageDir.exists()) {
                storageDir.mkdirs();
            }

            // Enregistrement du fichier si présent
            if (uploadedFile != null && uploadedFile.getSize() > 0) {
                String fileName = new File(uploadedFile.getName()).getName();
                File dest = new File(storageDir, fileName);
                uploadedFile.write(dest);
                uploadedFile.delete(); // Nettoyage temporaire
            }

            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write("{\"status\":\"success\",\"dir\":\"" + targetDir +
                                   "\",\"desc\":\"" + description + "\"}");

        } catch (Exception e) {
            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            resp.getWriter().write("{\"error\":\"" + e.getMessage() + "\"}");
        }
    }
}

Cette approche garantit une extraction fiable des données non-binaires, tout en respectant les bonnes pratiques de sécurité (encodage explicite, gestion des chemins, nettoyage des ressources temporaires). Elle évite toute dépendance à l’ordre d’apparition danns la liste fileList, contrairement à l’accès par index utilisé dans l’exemple initial.

Étiquettes: apache-commons-fileupload multipart-form Servlet java-web rfc7578

Publié le 17 septembre à 10h40