com.niconico.mylasta.direction.sponsor.NiconicoMultipartRequestHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.niconico.mylasta.direction.sponsor.NiconicoMultipartRequestHandler.java

Source

/*
 * Copyright 2015-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.niconico.mylasta.direction.sponsor;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.dbflute.helper.message.ExceptionMessageBuilder;
import org.lastaflute.core.message.UserMessages;
import org.lastaflute.web.LastaWebKey;
import org.lastaflute.web.exception.Forced404NotFoundException;
import org.lastaflute.web.ruts.config.ModuleConfig;
import org.lastaflute.web.ruts.multipart.MultipartFormFile;
import org.lastaflute.web.ruts.multipart.MultipartRequestHandler;
import org.lastaflute.web.ruts.multipart.MultipartRequestWrapper;
import org.lastaflute.web.ruts.multipart.exception.MultipartExceededException;
import org.lastaflute.web.util.LaServletContextUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author modified by jflute (originated in Seasar)
 */
public class NiconicoMultipartRequestHandler implements MultipartRequestHandler {

    // ===================================================================================
    //                                                                          Definition
    //                                                                          ==========
    private static final Logger logger = LoggerFactory.getLogger(NiconicoMultipartRequestHandler.class);
    public static final long DEFAULT_SIZE_MAX = 250 * 1024 * 1024; // 250MB
    public static final int DEFAULT_SIZE_THRESHOLD = 256 * 1024; // 250KB
    protected static final String CONTEXT_TEMPDIR_KEY = "javax.servlet.context.tempdir";
    protected static final String JAVA_IO_TMPDIR_KEY = "java.io.tmpdir";

    // ===================================================================================
    //                                                                           Attribute
    //                                                                           =========
    protected Map<String, Object> elementsAll;
    protected Map<String, MultipartFormFile> elementsFile;
    protected Map<String, String[]> elementsText;

    // ===================================================================================
    //                                                                      Handle Request
    //                                                                      ==============
    @Override
    public void handleRequest(HttpServletRequest request) throws ServletException {
        // /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        // copied from super's method and extends it
        // basically for JVN#14876762
        // thought not all problems are resolved however the main case is safety
        // - - - - - - - - - -/
        final ServletFileUpload upload = createServletFileUpload(request);
        prepareElementsHash();
        try {
            final List<FileItem> items = parseRequest(request, upload);
            mappingParameter(request, items);
        } catch (SizeLimitExceededException e) {
            handleSizeLimitExceededException(request, e);
        } catch (FileUploadException e) {
            handleFileUploadException(e);
        }
    }

    protected ModuleConfig getModuleConfig(HttpServletRequest request) {
        return (ModuleConfig) request.getAttribute(LastaWebKey.MODULE_CONFIG_KEY);
    }

    // ===================================================================================
    //                                                            Create ServletFileUpload
    //                                                            ========================
    protected ServletFileUpload createServletFileUpload(HttpServletRequest request) {
        final DiskFileItemFactory fileItemFactory = createDiskFileItemFactory();
        final ServletFileUpload upload = newServletFileUpload(fileItemFactory);
        upload.setHeaderEncoding(request.getCharacterEncoding());
        upload.setSizeMax(getSizeMax());
        return upload;
    }

    protected ServletFileUpload newServletFileUpload(DiskFileItemFactory fileItemFactory) {
        return new ServletFileUpload(fileItemFactory) {
            @Override
            protected byte[] getBoundary(String contentType) { // for security
                final byte[] boundary = super.getBoundary(contentType);
                checkBoundarySize(contentType, boundary);
                return boundary;
            }
        };
    }

    protected void checkBoundarySize(String contentType, byte[] boundary) {
        final int boundarySize = boundary.length;
        final int limitSize = getBoundaryLimitSize();
        if (boundarySize > getBoundaryLimitSize()) {
            throwTooLongBoundarySizeException(contentType, boundarySize, limitSize);
        }
    }

    protected int getBoundaryLimitSize() {
        // one HTTP proxy tool already limits the size (e.g. 3450 bytes)
        // so specify this size for test
        return 2000; // you can override as you like it
    }

    protected void throwTooLongBoundarySizeException(String contentType, int boundarySize, int limitSize) {
        final ExceptionMessageBuilder br = new ExceptionMessageBuilder();
        br.addNotice("Too long boundary size so treats it as 404.");
        br.addItem("Advice");
        br.addElement("Against for JVN14876762.");
        br.addElement("Boundary size is limited by Framework.");
        br.addElement("Too long boundary is treated as 404 because it's thought of as attack.");
        br.addElement("");
        br.addElement("While, you can override the boundary limit size");
        br.addElement(" in " + NiconicoMultipartRequestHandler.class.getSimpleName() + ".");
        br.addItem("Content Type");
        br.addElement(contentType);
        br.addItem("Boundary Size");
        br.addElement(boundarySize);
        br.addItem("Limit Size");
        br.addElement(limitSize);
        final String msg = br.buildExceptionMessage();
        throw new Forced404NotFoundException(msg, UserMessages.empty()); // heavy attack!? so give no page to tell wasted action
    }

    protected DiskFileItemFactory createDiskFileItemFactory() {
        final File repository = createRepositoryFile();
        return new DiskFileItemFactory((int) getSizeThreshold(), repository);
    }

    protected File createRepositoryFile() {
        return new File(getRepositoryPath());
    }

    // ===================================================================================
    //                                                                      Handling Parts
    //                                                                      ==============
    protected void prepareElementsHash() {
        elementsText = new Hashtable<String, String[]>();
        elementsFile = new Hashtable<String, MultipartFormFile>();
        elementsAll = new Hashtable<String, Object>();
    }

    protected List<FileItem> parseRequest(HttpServletRequest request, ServletFileUpload upload)
            throws FileUploadException {
        return upload.parseRequest(request);
    }

    protected void mappingParameter(HttpServletRequest request, List<FileItem> items) {
        showFieldLoggingTitle();
        final Iterator<FileItem> iter = items.iterator();
        while (iter.hasNext()) {
            final FileItem item = iter.next();
            if (item.isFormField()) {
                showFormFieldParameter(item);
                addTextParameter(request, item);
            } else {
                showFileFieldParameter(item);
                final String itemName = item.getName();
                if (itemName != null && !itemName.isEmpty()) {
                    addFileParameter(item);
                }
            }
        }
    }

    protected void showFieldLoggingTitle() {
        // logging filter cannot show the parameters when multi-part so logging here
        if (logger.isDebugEnabled()) {
            logger.debug("[Multipart Request Parameter]");
        }
    }

    protected void showFormFieldParameter(FileItem item) {
        if (logger.isDebugEnabled()) {
            logger.debug("[param] {}={}", item.getFieldName(), item.getString());
        }
    }

    protected void showFileFieldParameter(FileItem item) {
        if (logger.isDebugEnabled()) {
            logger.debug("[param] {}:{name={}, size={}}", item.getFieldName(), item.getName(), item.getSize());
        }
    }

    protected void handleSizeLimitExceededException(HttpServletRequest request, SizeLimitExceededException e) {
        final long actual = e.getActualSize();
        final long permitted = e.getPermittedSize();
        String msg = "Exceeded size of the multipart request: actual=" + actual + " permitted=" + permitted;
        request.setAttribute(MAX_LENGTH_EXCEEDED_KEY, new MultipartExceededException(msg, actual, permitted, e));
        try {
            final InputStream is = request.getInputStream();
            try {
                final byte[] buf = new byte[1024];
                @SuppressWarnings("unused")
                int len = 0;
                while ((len = is.read(buf)) != -1) {
                }
            } catch (Exception ignored) {
            } finally {
                try {
                    is.close();
                } catch (Exception ignored) {
                }
            }
        } catch (Exception ignored) {
        }
    }

    protected void handleFileUploadException(FileUploadException e) throws ServletException {
        // suppress logging because it can be caught by logging filter 
        //log.error("Failed to parse multipart request", e);
        throw new ServletException("Failed to upload the file.", e);
    }

    // ===================================================================================
    //                                                                           Roll-back
    //                                                                           =========
    @Override
    public void rollback() {
        final Iterator<MultipartFormFile> iter = elementsFile.values().iterator();
        while (iter.hasNext()) {
            final MultipartFormFile formFile = iter.next();
            formFile.destroy();
        }
    }

    // ===================================================================================
    //                                                                            Add Text
    //                                                                            ========
    protected void addTextParameter(HttpServletRequest request, FileItem item) {
        final String name = item.getFieldName();
        final String encoding = request.getCharacterEncoding();
        String value = null;
        boolean haveValue = false;
        if (encoding != null) {
            try {
                value = item.getString(encoding);
                haveValue = true;
            } catch (Exception e) {
            }
        }
        if (!haveValue) {
            try {
                value = item.getString("ISO-8859-1");
            } catch (java.io.UnsupportedEncodingException uee) {
                value = item.getString();
            }
            haveValue = true;
        }
        if (request instanceof MultipartRequestWrapper) {
            final MultipartRequestWrapper wrapper = (MultipartRequestWrapper) request;
            wrapper.setParameter(name, value);
        }
        final String[] oldArray = elementsText.get(name);
        final String[] newArray;
        if (oldArray != null) {
            newArray = new String[oldArray.length + 1];
            System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
            newArray[oldArray.length] = value;
        } else {
            newArray = new String[] { value };
        }
        elementsText.put(name, newArray);
        elementsAll.put(name, newArray);
    }

    protected void addFileParameter(FileItem item) {
        final MultipartFormFile formFile = newActionMultipartFormFile(item);
        elementsFile.put(item.getFieldName(), formFile);
        elementsAll.put(item.getFieldName(), formFile);
    }

    protected ActionMultipartFormFile newActionMultipartFormFile(FileItem item) {
        return new ActionMultipartFormFile(item);
    }

    // ===================================================================================
    //                                                                              Finish
    //                                                                              ======
    @Override
    public void finish() {
        rollback();
    }

    // ===================================================================================
    //                                                                        Small Helper
    //                                                                        ============
    protected long getSizeMax() {
        return DEFAULT_SIZE_MAX;
    }

    protected long getSizeThreshold() {
        return DEFAULT_SIZE_THRESHOLD;
    }

    protected String getRepositoryPath() {
        final File tempDirFile = (File) LaServletContextUtil.getServletContext().getAttribute(CONTEXT_TEMPDIR_KEY);
        String tempDir = tempDirFile.getAbsolutePath();
        if (tempDir == null || tempDir.length() == 0) {
            tempDir = System.getProperty(JAVA_IO_TMPDIR_KEY);
        }
        return tempDir;
    }

    // ===================================================================================
    //                                                                           Form File
    //                                                                           =========
    protected static class ActionMultipartFormFile implements MultipartFormFile, Serializable {

        private static final long serialVersionUID = 1L;

        protected final FileItem fileItem;

        public ActionMultipartFormFile(FileItem fileItem) {
            this.fileItem = fileItem;
        }

        public byte[] getFileData() throws IOException {
            return fileItem.get();
        }

        public InputStream getInputStream() throws IOException {
            return fileItem.getInputStream();
        }

        public String getContentType() {
            return fileItem.getContentType();
        }

        public int getFileSize() {
            return (int) fileItem.getSize();
        }

        public String getFileName() {
            return getBaseFileName(fileItem.getName());
        }

        protected String getBaseFileName(String filePath) {
            final String fileName = new File(filePath).getName();
            int colonIndex = fileName.indexOf(":");
            if (colonIndex == -1) {
                colonIndex = fileName.indexOf("\\\\"); // Windows SMB
            }
            final int backslashIndex = fileName.lastIndexOf("\\");
            if (colonIndex > -1 && backslashIndex > -1) {
                return fileName.substring(backslashIndex + 1);
            } else {
                return fileName;
            }
        }

        public void destroy() {
            fileItem.delete();
        }

        @Override
        public String toString() {
            return "formFile:{" + getFileName() + "}";
        }
    }

    // ===================================================================================
    //                                                                            Accessor
    //                                                                            ========
    @Override
    public Map<String, Object> getAllElements() {
        return elementsAll;
    }

    @Override
    public Map<String, String[]> getTextElements() {
        return elementsText;
    }

    @Override
    public Map<String, MultipartFormFile> getFileElements() {
        return elementsFile;
    }
}