hoot.services.utils.MultipartSerializer.java Source code

Java tutorial

Introduction

Here is the source code for hoot.services.utils.MultipartSerializer.java

Source

/*
 * This file is part of Hootenanny.
 *
 * Hootenanny is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * --------------------------------------------------------------------
 *
 * The following copyright notices are generated automatically. If you
 * have a new notice to add, please use the format:
 * " * @copyright Copyright ..."
 * This will properly maintain the copyright information. DigitalGlobe
 * copyrights will be updated automatically.
 *
 * @copyright Copyright (C) 2016 DigitalGlobe (http://www.digitalglobe.com/)
 */
package hoot.services.utils;

import static hoot.services.HootProperties.HOME_FOLDER;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.glassfish.jersey.media.multipart.BodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class MultipartSerializer {
    private static final Logger logger = LoggerFactory.getLogger(MultipartSerializer.class);

    private MultipartSerializer() {
    }

    private static void serializeFGDB(List<BodyPart> fileItems, String jobId, Map<String, String> uploadedFiles,
            Map<String, String> uploadedFilesPaths) {
        try {
            Map<String, String> folderMap = new HashMap<>();

            for (BodyPart fileItem : fileItems) {
                String fileName = fileItem.getContentDisposition().getFileName();

                String relPath = FilenameUtils.getPath(fileName);
                if (relPath.endsWith("/")) {
                    relPath = relPath.substring(0, relPath.length() - 1);
                }
                fileName = FilenameUtils.getName(fileName);

                String fgdbFolderPath = HOME_FOLDER + "/upload/" + jobId + "/" + relPath;
                boolean isPathSafe = validatePath(HOME_FOLDER + "/upload", fgdbFolderPath);

                if (isPathSafe) {
                    String pathVal = folderMap.get(fgdbFolderPath);
                    if (pathVal == null) {
                        File folderPath = new File(fgdbFolderPath);
                        FileUtils.forceMkdir(folderPath);
                        folderMap.put(fgdbFolderPath, relPath);
                    }

                    if (fileName == null) {
                        throw new IOException("File name cannot be null!");
                    }

                    String uploadedPath = fgdbFolderPath + "/" + fileName;
                    boolean isFileSafe = validatePath(fgdbFolderPath, uploadedPath);
                    if (isFileSafe) {
                        try (InputStream fileStream = fileItem.getEntityAs(InputStream.class)) {
                            File file = new File(uploadedPath);
                            FileUtils.copyInputStreamToFile(fileStream, file);
                        }
                    } else {
                        throw new IOException("Illegal file path:" + uploadedPath);
                    }
                } else {
                    throw new IOException("Illegal path:" + fgdbFolderPath);
                }
            }

            for (Map.Entry<String, String> pairs : folderMap.entrySet()) {
                String nameOnly = "";
                String fgdbName = pairs.getValue();
                String[] nParts = fgdbName.split("\\.");

                for (int i = 0; i < (nParts.length - 1); i++) {
                    if (!nameOnly.isEmpty()) {
                        nameOnly += ".";
                    }
                    nameOnly += nParts[i];
                }
                uploadedFiles.put(nameOnly, "GDB");
                uploadedFilesPaths.put(nameOnly, fgdbName);
            }
        } catch (Exception e) {
            throw new RuntimeException("Error trying to serialize FGDB!", e);
        }
    }

    private static void serializeUploadedFiles(List<BodyPart> fileItems, Map<String, String> uploadedFiles,
            Map<String, String> uploadedFilesPaths, String repFolderPath) {
        try {
            for (BodyPart fileItem : fileItems) {
                String fileName = fileItem.getContentDisposition().getFileName();

                if (fileName == null) {
                    throw new RuntimeException("A valid file name was not specified.");
                }

                String uploadedPath = repFolderPath + "/" + fileName;

                boolean isPathSafe = validatePath(HOME_FOLDER + "/upload", uploadedPath);
                if (isPathSafe) {
                    try (InputStream fileStream = fileItem.getEntityAs(InputStream.class)) {
                        File file = new File(uploadedPath);
                        FileUtils.copyInputStreamToFile(fileStream, file);
                    }

                    String[] nameParts = fileName.split("\\.");
                    if (nameParts.length > 1) {
                        String extension = nameParts[nameParts.length - 1].toUpperCase();
                        String[] subArr = ArrayUtils.removeElement(nameParts, nameParts[nameParts.length - 1]);
                        String filename = StringUtils.join(subArr, '.');
                        if (extension.equalsIgnoreCase("OSM") || extension.equalsIgnoreCase("GEONAMES")
                                || extension.equalsIgnoreCase("SHP") || extension.equalsIgnoreCase("ZIP")
                                || extension.equalsIgnoreCase("PBF")) {
                            uploadedFiles.put(filename, extension);
                            uploadedFilesPaths.put(filename, fileName);
                            logger.debug("Saving uploaded:{}", filename);
                        }
                    }
                } else {
                    throw new IOException("Illegal path: " + uploadedPath);
                }
            }
        } catch (Exception ioe) {
            throw new RuntimeException("Error trying to serialize uploaded files!", ioe);
        }
    }

    /**
     * Serializes uploaded multipart data into files. It can handle file or
     * folder type.
     * 
     * @param jobId
     *            = unique id to identify uploaded files group
     * @param inputType
     *            = ["FILE" | "DIR"] where DIR type is treated as FGDB
     * @param uploadedFiles
     *            = The list of files uploaded
     * @param uploadedFilesPaths
     *            = The list of uploaded files paths
     * @param multiPart
     *            = The request object that holds post data
     */
    public static void serializeUpload(String jobId, String inputType, Map<String, String> uploadedFiles,
            Map<String, String> uploadedFilesPaths, FormDataMultiPart multiPart) {

        try {
            // Uploaded data container folder path. It is unique to each job
            String repFolderPath = HOME_FOLDER + "/upload/" + jobId;
            boolean isPathSafe = validatePath(HOME_FOLDER + "/upload", repFolderPath);

            if (isPathSafe) {
                File dir = new File(repFolderPath);
                FileUtils.forceMkdir(dir);

                List<BodyPart> bodyParts = multiPart.getBodyParts();

                // If user request type is DIR then treat it as FGDB folder
                if (inputType.equalsIgnoreCase("DIR")) {
                    serializeFGDB(bodyParts, jobId, uploadedFiles, uploadedFilesPaths);
                } else {
                    // Can be shapefile or zip file
                    serializeUploadedFiles(bodyParts, uploadedFiles, uploadedFilesPaths, repFolderPath);
                }
            } else {
                throw new IOException("Illegal path: " + repFolderPath);
            }
        } catch (Exception e) {
            throw new RuntimeException("Error trying to serialize upload!", e);
        }
    }

    // See #6760
    // Stop file path manipulation vulnerability by validating the new path is
    // within container path
    private static boolean validatePath(String basePath, String newPath) {
        boolean isValidated = false;

        try {
            File f = new File(newPath);
            String potentialPath = f.getCanonicalPath();

            f = new File(basePath);
            String containerPath = f.getCanonicalPath();

            // verify that newPath is within basePath
            if (potentialPath.indexOf(containerPath) == 0) {
                isValidated = true;
            }
        } catch (IOException ex) {
            logger.error("Failed to validate MultipartSerializer path: {}", ex.getMessage());
        }

        return isValidated;
    }
}