All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder Maven / Gradle / Ivy

Go to download

The Netty project is an effort to provide an asynchronous event-driven network application framework and tools for rapid development of maintainable high performance and high scalability protocol servers and clients. In other words, Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.

There is a newer version: 4.0.0.Alpha8
Show newest version
/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you 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 org.jboss.netty.handler.codec.http.multipart;

import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpConstants;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.util.internal.StringUtil;

import java.nio.charset.Charset;
import java.util.List;

/**
 * This decoder will decode Body and can handle POST BODY (both multipart and standard).
 */
public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder {
    /**
     * Does this request is a Multipart request
     */
    private final InterfaceHttpPostRequestDecoder decoder;

    /**
    *
    * @param request the request to decode
    * @throws NullPointerException for request
    * @throws ErrorDataDecoderException if the default charset was wrong when decoding or other errors
    */
    public HttpPostRequestDecoder(HttpRequest request) throws ErrorDataDecoderException {
        this(new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE),
                request, HttpConstants.DEFAULT_CHARSET);
    }

    /**
     *
     * @param factory the factory used to create InterfaceHttpData
     * @param request the request to decode
     * @throws NullPointerException for request or factory
     * @throws ErrorDataDecoderException if the default charset was wrong when decoding or other errors
     */
    public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request)
            throws ErrorDataDecoderException {
        this(factory, request, HttpConstants.DEFAULT_CHARSET);
    }

    /**
     *
     * @param factory the factory used to create InterfaceHttpData
     * @param request the request to decode
     * @param charset the charset to use as default
     * @throws NullPointerException for request or charset or factory
     * @throws ErrorDataDecoderException if the default charset was wrong when decoding or other errors
     */
    public HttpPostRequestDecoder(HttpDataFactory factory, HttpRequest request,
            Charset charset) throws ErrorDataDecoderException {
        if (factory == null) {
            throw new NullPointerException("factory");
        }
        if (request == null) {
            throw new NullPointerException("request");
        }
        if (charset == null) {
            throw new NullPointerException("charset");
        }
        // Fill default values
        if (isMultipart(request)) {
            decoder = new HttpPostMultipartRequestDecoder(factory, request, charset);
        } else {
            decoder = new HttpPostStandardRequestDecoder(factory, request, charset);
        }
    }

    /**
     * states follow
     * NOTSTARTED PREAMBLE (
     *  (HEADERDELIMITER DISPOSITION (FIELD | FILEUPLOAD))*
     *  (HEADERDELIMITER DISPOSITION MIXEDPREAMBLE
     *     (MIXEDDELIMITER MIXEDDISPOSITION MIXEDFILEUPLOAD)+
     *   MIXEDCLOSEDELIMITER)*
     * CLOSEDELIMITER)+ EPILOGUE
     *
     *  First status is: NOSTARTED

        Content-type: multipart/form-data, boundary=AaB03x     => PREAMBLE in Header

        --AaB03x                                               => HEADERDELIMITER
        content-disposition: form-data; name="field1"          => DISPOSITION

        Joe Blow                                               => FIELD
        --AaB03x                                               => HEADERDELIMITER
        content-disposition: form-data; name="pics"            => DISPOSITION
        Content-type: multipart/mixed, boundary=BbC04y

        --BbC04y                                               => MIXEDDELIMITER
        Content-disposition: attachment; filename="file1.txt"  => MIXEDDISPOSITION
        Content-Type: text/plain

        ... contents of file1.txt ...                          => MIXEDFILEUPLOAD
        --BbC04y                                               => MIXEDDELIMITER
        Content-disposition: file; filename="file2.gif"  => MIXEDDISPOSITION
        Content-type: image/gif
        Content-Transfer-Encoding: binary

          ...contents of file2.gif...                          => MIXEDFILEUPLOAD
        --BbC04y--                                             => MIXEDCLOSEDELIMITER
        --AaB03x--                                             => CLOSEDELIMITER

       Once CLOSEDELIMITER is found, last status is EPILOGUE
     */
    protected enum MultiPartStatus {
        NOTSTARTED,
        PREAMBLE,
        HEADERDELIMITER,
        DISPOSITION,
        FIELD,
        FILEUPLOAD,
        MIXEDPREAMBLE,
        MIXEDDELIMITER,
        MIXEDDISPOSITION,
        MIXEDFILEUPLOAD,
        MIXEDCLOSEDELIMITER,
        CLOSEDELIMITER,
        PREEPILOGUE,
        EPILOGUE
    }

    /**
     * Check if the given request is a multipart request
     *
     * @return True if the request is a Multipart request
     */
    public static boolean isMultipart(HttpRequest request) throws ErrorDataDecoderException {
        if (request.headers().contains(HttpHeaders.Names.CONTENT_TYPE)) {
            return getMultipartDataBoundary(request.headers().get(HttpHeaders.Names.CONTENT_TYPE)) != null;
        } else {
            return false;
        }
    }

    /**
     * Check from the request ContentType if this request is a Multipart request.
     * @return an array of String if multipartDataBoundary exists with the multipartDataBoundary
     * as first element, charset if any as second (missing if not set), else null
     */
    protected static String[] getMultipartDataBoundary(String contentType)
            throws ErrorDataDecoderException {
        // Check if Post using "multipart/form-data; boundary=--89421926422648 [; charset=xxx]"
        String[] headerContentType = splitHeaderContentType(contentType);
        if (headerContentType[0].toLowerCase().startsWith(
                HttpHeaders.Values.MULTIPART_FORM_DATA)) {
            int mrank = 1, crank = 2;
            if (headerContentType[1].toLowerCase().startsWith(
                    HttpHeaders.Values.BOUNDARY.toString())) {
                mrank = 1;
                crank = 2;
            } else if (headerContentType[2].toLowerCase().startsWith(
                    HttpHeaders.Values.BOUNDARY.toString())) {
                mrank = 2;
                crank = 1;
            } else {
                return null;
            }
            String boundary = StringUtil.substringAfter(headerContentType[mrank], '=');
            if (boundary == null) {
                throw new ErrorDataDecoderException("Needs a boundary value");
            }
            if (boundary.charAt(0) == '"') {
                String bound = boundary.trim();
                int index = bound.length() - 1;
                if (bound.charAt(index) == '"') {
                    boundary = bound.substring(1, index);
                }
            }
            if (headerContentType[crank].toLowerCase().startsWith(
                    HttpHeaders.Values.CHARSET.toString())) {
                String charset = StringUtil.substringAfter(headerContentType[crank], '=');
                if (charset != null) {
                    return new String[] {"--" + boundary, charset};
                }
            }
            return new String[] {"--" + boundary};
         }
        return null;
    }

    /**
     * True if this request is a Multipart request
     * @return True if this request is a Multipart request
     */
    public boolean isMultipart() {
        return decoder.isMultipart();
    }

    /**
     * This method returns a List of all HttpDatas from body.
* * If chunked, all chunks must have been offered using offer() method. * If not, NotEnoughDataDecoderException will be raised. * * @return the list of HttpDatas from Body part for POST method * @throws NotEnoughDataDecoderException Need more chunks */ public List getBodyHttpDatas() throws NotEnoughDataDecoderException { return decoder.getBodyHttpDatas(); } /** * This method returns a List of all HttpDatas with the given name from body.
* * If chunked, all chunks must have been offered using offer() method. * If not, NotEnoughDataDecoderException will be raised. * @return All Body HttpDatas with the given name (ignore case) * @throws NotEnoughDataDecoderException need more chunks */ public List getBodyHttpDatas(String name) throws NotEnoughDataDecoderException { return decoder.getBodyHttpDatas(name); } /** * This method returns the first InterfaceHttpData with the given name from body.
* * If chunked, all chunks must have been offered using offer() method. * If not, NotEnoughDataDecoderException will be raised. * * @return The first Body InterfaceHttpData with the given name (ignore case) * @throws NotEnoughDataDecoderException need more chunks */ public InterfaceHttpData getBodyHttpData(String name) throws NotEnoughDataDecoderException { return decoder.getBodyHttpData(name); } /** * Initialized the internals from a new chunk * @param chunk the new received chunk * @throws ErrorDataDecoderException if there is a problem with the charset decoding or * other errors */ public void offer(HttpChunk chunk) throws ErrorDataDecoderException { decoder.offer(chunk); } /** * True if at current status, there is an available decoded InterfaceHttpData from the Body. * * This method works for chunked and not chunked request. * * @return True if at current status, there is a decoded InterfaceHttpData * @throws EndOfDataDecoderException No more data will be available */ public boolean hasNext() throws EndOfDataDecoderException { return decoder.hasNext(); } /** * Returns the next available InterfaceHttpData or null if, at the time it is called, there is no more * available InterfaceHttpData. A subsequent call to offer(httpChunk) could enable more data. * * @return the next available InterfaceHttpData or null if none * @throws EndOfDataDecoderException No more data will be available */ public InterfaceHttpData next() throws EndOfDataDecoderException { return decoder.next(); } /** * Clean all HttpDatas (on Disk) for the current request. */ public void cleanFiles() { decoder.cleanFiles(); } /** * Remove the given FileUpload from the list of FileUploads to clean */ public void removeHttpDataFromClean(InterfaceHttpData data) { decoder.removeHttpDataFromClean(data); } /** * Split the very first line (Content-Type value) in 3 Strings * @return the array of 3 Strings */ private static String[] splitHeaderContentType(String sb) { int aStart; int aEnd; int bStart; int bEnd; int cStart; int cEnd; aStart = HttpPostBodyUtil.findNonWhitespace(sb, 0); aEnd = sb.indexOf(';'); if (aEnd == -1) { return new String[] { sb, "", "" }; } bStart = HttpPostBodyUtil.findNonWhitespace(sb, aEnd + 1); if (sb.charAt(aEnd - 1) == ' ') { aEnd--; } bEnd = sb.indexOf(';', bStart); if (bEnd == -1) { bEnd = HttpPostBodyUtil.findEndOfString(sb); return new String[] { sb.substring(aStart, aEnd), sb.substring(bStart, bEnd), "" }; } cStart = HttpPostBodyUtil.findNonWhitespace(sb, bEnd + 1); if (sb.charAt(bEnd - 1) == ' ') { bEnd--; } cEnd = HttpPostBodyUtil.findEndOfString(sb); return new String[] { sb.substring(aStart, aEnd), sb.substring(bStart, bEnd), sb.substring(cStart, cEnd) }; } /** * Exception when try reading data from request in chunked format, and not enough * data are available (need more chunks) */ public static class NotEnoughDataDecoderException extends Exception { private static final long serialVersionUID = -7846841864603865638L; public NotEnoughDataDecoderException() { } public NotEnoughDataDecoderException(String msg) { super(msg); } public NotEnoughDataDecoderException(Throwable cause) { super(cause); } public NotEnoughDataDecoderException(String msg, Throwable cause) { super(msg, cause); } } /** * Exception when the body is fully decoded, even if there is still data */ public static class EndOfDataDecoderException extends Exception { private static final long serialVersionUID = 1336267941020800769L; } /** * Exception when an error occurs while decoding */ public static class ErrorDataDecoderException extends Exception { private static final long serialVersionUID = 5020247425493164465L; public ErrorDataDecoderException() { } public ErrorDataDecoderException(String msg) { super(msg); } public ErrorDataDecoderException(Throwable cause) { super(cause); } public ErrorDataDecoderException(String msg, Throwable cause) { super(msg, cause); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy