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

org.eclipse.jetty.http.MultiPartParser Maven / Gradle / Ivy

Go to download

Ehcache is an open source, standards-based cache used to boost performance, offload the database and simplify scalability. Ehcache is robust, proven and full-featured and this has made it the most widely-used Java-based cache.

There is a newer version: 2.10.9.2
Show newest version
//
//  ========================================================================
//  Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.http;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.EnumSet;

import org.eclipse.jetty.http.HttpParser.RequestHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.SearchPattern;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

/* ------------------------------------------------------------ */

/**
 * A parser for MultiPart content type.
 *
 * @see https://tools.ietf.org/html/rfc2046#section-5.1
 * @see https://tools.ietf.org/html/rfc2045
 */
public class MultiPartParser
{
    public static final Logger LOG = Log.getLogger(MultiPartParser.class);
        
    // States
    public enum FieldState
    {
        FIELD,
        IN_NAME,
        AFTER_NAME,
        VALUE,
        IN_VALUE
    }
    
    // States
    public enum State
    {
        PREAMBLE,
        DELIMITER,
        DELIMITER_PADDING,
        DELIMITER_CLOSE,
        BODY_PART,
        FIRST_OCTETS,
        OCTETS,
        EPILOGUE,
        END
    }
    
    private final static EnumSet __delimiterStates = EnumSet.of(State.DELIMITER, State.DELIMITER_CLOSE, State.DELIMITER_PADDING);
    private final static int MAX_HEADER_LINE_LENGTH = 998;
    
    private final boolean DEBUG = LOG.isDebugEnabled();
    private final Handler _handler;
    private final SearchPattern _delimiterSearch;
    
    private String _fieldName;
    private String _fieldValue;
    
    private State _state = State.PREAMBLE;
    private FieldState _fieldState = FieldState.FIELD;
    private int _partialBoundary = 2; // No CRLF if no preamble
    private boolean _cr;
    private ByteBuffer _patternBuffer;
    
    private final Utf8StringBuilder _string = new Utf8StringBuilder();
    private int _length;
    
    private int _totalHeaderLineLength = -1;
    
    /* ------------------------------------------------------------------------------- */
    public MultiPartParser(Handler handler, String boundary)
    {
        _handler = handler;
        
        String delimiter = "\r\n--" + boundary;
        _patternBuffer = ByteBuffer.wrap(delimiter.getBytes(StandardCharsets.US_ASCII));
        _delimiterSearch = SearchPattern.compile(_patternBuffer.array());
    }
    
    public void reset()
    {
        _state = State.PREAMBLE;
        _fieldState = FieldState.FIELD;
        _partialBoundary = 2; // No CRLF if no preamble
    }
    
    /* ------------------------------------------------------------------------------- */
    public Handler getHandler()
    {
        return _handler;
    }
    
    /* ------------------------------------------------------------------------------- */
    public State getState()
    {
        return _state;
    }
    
    /* ------------------------------------------------------------------------------- */
    public boolean isState(State state)
    {
        return _state == state;
    }
    
    /* ------------------------------------------------------------------------------- */
    private static boolean hasNextByte(ByteBuffer buffer)
    {
        return BufferUtil.hasContent(buffer);
    }
    
    /* ------------------------------------------------------------------------------- */
    private HttpTokens.Token next(ByteBuffer buffer)
    {
        byte ch = buffer.get();

        HttpTokens.Token t = HttpTokens.TOKENS[0xff & ch];
        
        if (DEBUG)
            LOG.debug("token={}",t);
        
        switch(t.getType())
        {
            case CNTL:
                throw new IllegalCharacterException(_state,t,buffer);

            case LF:
                _cr=false;
                break;

            case CR:
                if (_cr)
                    throw new BadMessageException("Bad EOL");

                _cr=true;
                return null;

            case ALPHA:
            case DIGIT:
            case TCHAR:
            case VCHAR:
            case HTAB:
            case SPACE:
            case OTEXT:
            case COLON:
                if (_cr)
                    throw new BadMessageException("Bad EOL");
                break;
                
            default:
                break;
        }

        return t;
    }    
    
    /* ------------------------------------------------------------------------------- */
    private void setString(String s)
    {
        _string.reset();
        _string.append(s);
        _length = s.length();
    }
    
    /* ------------------------------------------------------------------------------- */
    /*
     * Mime Field strings are treated as UTF-8 as per https://tools.ietf.org/html/rfc7578#section-5.1
     */
    private String takeString()
    {
        String s = _string.toString();
        // trim trailing whitespace.
        if (s.length() > _length)
            s = s.substring(0, _length);
        _string.reset();
        _length = -1;
        return s;
    }
    
    /* ------------------------------------------------------------------------------- */
    
    /**
     * Parse until next Event.
     *
     * @param buffer the buffer to parse
     * @param last   whether this buffer contains last bit of content
     * @return True if an {@link RequestHandler} method was called and it returned true;
     */
    public boolean parse(ByteBuffer buffer, boolean last)
    {
        boolean handle = false;
        while (!handle && BufferUtil.hasContent(buffer))
        {
            switch (_state)
            {
                case PREAMBLE:
                    parsePreamble(buffer);
                    continue;
                
                case DELIMITER:
                case DELIMITER_PADDING:
                case DELIMITER_CLOSE:
                    parseDelimiter(buffer);
                    continue;
                
                case BODY_PART:
                    handle = parseMimePartHeaders(buffer);
                    break;
                
                case FIRST_OCTETS:
                case OCTETS:
                    handle = parseOctetContent(buffer);
                    break;
                
                case EPILOGUE:
                    BufferUtil.clear(buffer);
                    break;
                
                case END:
                    handle = true;
                    break;
                
                default:
                    throw new IllegalStateException();
                
            }
        }
        
        if (last && BufferUtil.isEmpty(buffer))
        {
            if (_state == State.EPILOGUE)
            {
                _state = State.END;
                
                if (LOG.isDebugEnabled())
                    LOG.debug("messageComplete {}", this);
                
                return _handler.messageComplete();
            }
            else
            {
                if (LOG.isDebugEnabled())
                    LOG.debug("earlyEOF {}", this);
                
                _handler.earlyEOF();
                return true;
            }
        }
        
        return handle;
    }
    
    /* ------------------------------------------------------------------------------- */
    private void parsePreamble(ByteBuffer buffer)
    {
        if (_partialBoundary > 0)
        {
            int partial = _delimiterSearch.startsWith(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining(), _partialBoundary);
            if (partial > 0)
            {
                if (partial == _delimiterSearch.getLength())
                {
                    buffer.position(buffer.position() + partial - _partialBoundary);
                    _partialBoundary = 0;
                    setState(State.DELIMITER);
                    return;
                }
                
                _partialBoundary = partial;
                BufferUtil.clear(buffer);
                return;
            }
            
            _partialBoundary = 0;
        }
        
        int delimiter = _delimiterSearch.match(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
        if (delimiter >= 0)
        {
            buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength());
            setState(State.DELIMITER);
            return;
        }
        
        _partialBoundary = _delimiterSearch.endsWith(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
        BufferUtil.clear(buffer);
    }
    
    /* ------------------------------------------------------------------------------- */
    private void parseDelimiter(ByteBuffer buffer)
    {
        while (__delimiterStates.contains(_state) && hasNextByte(buffer))
        {
            HttpTokens.Token t = next(buffer);
            if (t == null)
                return;
            
            if (t.getType()==HttpTokens.Type.LF)
            {
                setState(State.BODY_PART);
                
                if (LOG.isDebugEnabled())
                    LOG.debug("startPart {}", this);
                
                _handler.startPart();
                return;
            }
            
            switch (_state)
            {
                case DELIMITER:
                    if (t.getChar() == '-')
                        setState(State.DELIMITER_CLOSE);
                    else
                        setState(State.DELIMITER_PADDING);
                    continue;
                
                case DELIMITER_CLOSE:
                    if (t.getChar() == '-')
                    {
                        setState(State.EPILOGUE);
                        return;
                    }
                    setState(State.DELIMITER_PADDING);
                    continue;
                
                case DELIMITER_PADDING:
                default:
            }
        }
    }
    
    /* ------------------------------------------------------------------------------- */
    /*
     * Parse the message headers and return true if the handler has signaled for a return
     */
    protected boolean parseMimePartHeaders(ByteBuffer buffer)
    {
        // Process headers
        while (_state == State.BODY_PART && hasNextByte(buffer))
        {
            // process each character
            HttpTokens.Token t = next(buffer);
            if (t == null)
                break;
                        
            if (t.getType() != HttpTokens.Type.LF)
                _totalHeaderLineLength++;
            
            if (_totalHeaderLineLength > MAX_HEADER_LINE_LENGTH)
                throw new IllegalStateException("Header Line Exceeded Max Length");
            
            switch (_fieldState)
            {
                case FIELD:
                    switch (t.getType())
                    {
                        case SPACE:
                        case HTAB:
                        {
                            // Folded field value!
                            
                            if (_fieldName == null)
                                throw new IllegalStateException("First field folded");
                            
                            if (_fieldValue == null)
                            {
                                _string.reset();
                                _length = 0;
                            }
                            else
                            {
                                setString(_fieldValue);
                                _string.append(' ');
                                _length++;
                                _fieldValue = null;
                            }
                            setState(FieldState.VALUE);
                            break;
                        }
                        
                        case LF:
                            handleField();
                            setState(State.FIRST_OCTETS);
                            _partialBoundary = 2; // CRLF is option for empty parts
                            
                            if (LOG.isDebugEnabled())
                                LOG.debug("headerComplete {}", this);
                            
                            if (_handler.headerComplete())
                                return true;
                            break;

                        case ALPHA:
                        case DIGIT:
                        case TCHAR:
                            // process previous header
                            handleField();
                            
                            // New header
                            setState(FieldState.IN_NAME);
                            _string.reset();
                            _string.append(t.getChar());
                            _length = 1;
                        
                            break;

                        default:
                            throw new IllegalCharacterException(_state,t,buffer);
                    }
                    break;
                
                case IN_NAME:
                    switch(t.getType())
                    {
                        case COLON:
                            _fieldName = takeString();
                            _length = -1;
                            setState(FieldState.VALUE);
                            break;
                        
                        case SPACE:
                            // Ignore trailing whitespaces
                            setState(FieldState.AFTER_NAME);
                            break;
                        
                        case LF:
                        {
                            if (LOG.isDebugEnabled())
                                LOG.debug("Line Feed in Name {}", this);
                            
                            handleField();
                            setState(FieldState.FIELD);
                            break;
                        }

                        case ALPHA:
                        case DIGIT:
                        case TCHAR:
                            _string.append(t.getChar());
                            _length = _string.length();
                            break;
                            
                        default:
                            throw new IllegalCharacterException(_state,t,buffer);
                    }
                    break;
                
                case AFTER_NAME:
                    switch(t.getType())
                    {
                        case COLON:
                            _fieldName = takeString();
                            _length = -1;
                            setState(FieldState.VALUE);
                            break;
                        
                        case LF:
                            _fieldName = takeString();
                            _string.reset();
                            _fieldValue = "";
                            _length = -1;
                            break;
                        
                        case SPACE:
                            break;
                        
                        default:
                            throw new IllegalCharacterException(_state, t, buffer);
                    }
                    break;
                
                case VALUE:
                    switch(t.getType())
                    {
                        case LF:
                            _string.reset();
                            _fieldValue = "";
                            _length = -1;
                            
                            setState(FieldState.FIELD);
                            break;
                        
                        case SPACE:
                        case HTAB:
                            break;

                        case ALPHA:
                        case DIGIT:
                        case TCHAR:
                        case VCHAR:
                        case COLON:
                        case OTEXT:
                            _string.append(t.getByte());
                            _length = _string.length();
                            setState(FieldState.IN_VALUE);
                            break;

                        default:
                            throw new IllegalCharacterException(_state,t,buffer);
                    }
                    break;
                
                case IN_VALUE:
                    switch(t.getType())
                    {
                        case SPACE:
                        case HTAB:
                            _string.append(' ');
                            break;
                        
                        case LF:
                            if (_length > 0)
                            {
                                _fieldValue = takeString();
                                _length = -1;
                                _totalHeaderLineLength = -1;
                            }
                            setState(FieldState.FIELD);
                            break;
                            
                        case ALPHA:
                        case DIGIT:
                        case TCHAR:
                        case VCHAR:
                        case COLON:
                        case OTEXT:
                            _string.append(t.getByte());
                            _length=_string.length();
                            break;

                        default:
                            throw new IllegalCharacterException(_state,t,buffer);
                    }
                    break;
                
                default:
                    throw new IllegalStateException(_state.toString());
                
            }
        }
        return false;
    }
    
    /* ------------------------------------------------------------------------------- */
    private void handleField()
    {
        if (LOG.isDebugEnabled())
            LOG.debug("parsedField:  _fieldName={} _fieldValue={} {}", _fieldName, _fieldValue, this);
        
        if (_fieldName != null && _fieldValue != null)
            _handler.parsedField(_fieldName, _fieldValue);
        _fieldName = _fieldValue = null;
    }
    
    /* ------------------------------------------------------------------------------- */
    
    protected boolean parseOctetContent(ByteBuffer buffer)
    {
        
        // Starts With
        if (_partialBoundary > 0)
        {
            int partial = _delimiterSearch.startsWith(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining(), _partialBoundary);
            if (partial > 0)
            {
                if (partial == _delimiterSearch.getLength())
                {
                    buffer.position(buffer.position() + _delimiterSearch.getLength() - _partialBoundary);
                    setState(State.DELIMITER);
                    _partialBoundary = 0;
                    
                    if (LOG.isDebugEnabled())
                        LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(BufferUtil.EMPTY_BUFFER), true, this);
                    
                    return _handler.content(BufferUtil.EMPTY_BUFFER, true);
                }
                
                _partialBoundary = partial;
                BufferUtil.clear(buffer);
                return false;
            }
            else
            {
                // output up to _partialBoundary of the search pattern
                ByteBuffer content = _patternBuffer.slice();
                if (_state == State.FIRST_OCTETS)
                {
                    setState(State.OCTETS);
                    content.position(2);
                }
                content.limit(_partialBoundary);
                _partialBoundary = 0;
                
                if (LOG.isDebugEnabled())
                    LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), false, this);
                
                if (_handler.content(content, false))
                    return true;
            }
        }
        
        // Contains
        int delimiter = _delimiterSearch.match(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
        if (delimiter >= 0)
        {
            ByteBuffer content = buffer.slice();
            content.limit(delimiter - buffer.arrayOffset() - buffer.position());
            
            buffer.position(delimiter - buffer.arrayOffset() + _delimiterSearch.getLength());
            setState(State.DELIMITER);
            
            if (LOG.isDebugEnabled())
                LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), true, this);
            
            return _handler.content(content, true);
        }
        
        // Ends With
        _partialBoundary = _delimiterSearch.endsWith(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
        if (_partialBoundary > 0)
        {
            ByteBuffer content = buffer.slice();
            content.limit(content.limit() - _partialBoundary);
            
            if (LOG.isDebugEnabled())
                LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), false, this);
            
            BufferUtil.clear(buffer);
            return _handler.content(content, false);
        }
        
        // There is normal content with no delimiter
        ByteBuffer content = buffer.slice();
        
        if (LOG.isDebugEnabled())
            LOG.debug("Content={}, Last={} {}", BufferUtil.toDetailString(content), false, this);
        
        BufferUtil.clear(buffer);
        return _handler.content(content, false);
    }
    
    /* ------------------------------------------------------------------------------- */
    private void setState(State state)
    {
        if (DEBUG)
            LOG.debug("{} --> {}", _state, state);
        _state = state;
    }
    
    /* ------------------------------------------------------------------------------- */
    private void setState(FieldState state)
    {
        if (DEBUG)
            LOG.debug("{}:{} --> {}", _state, _fieldState, state);
        _fieldState = state;
    }
    
    /* ------------------------------------------------------------------------------- */
    @Override
    public String toString()
    {
        return String.format("%s{s=%s}", getClass().getSimpleName(), _state);
    }
    
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /*
     * Event Handler interface These methods return true if the caller should process the events so far received (eg return from parseNext and call
     * HttpChannel.handle). If multiple callbacks are called in sequence (eg headerComplete then messageComplete) from the same point in the parsing then it is
     * sufficient for the caller to process the events only once.
     */
    public interface Handler
    {
        default void startPart()
        {
        }
        
        @SuppressWarnings("unused")
        default void parsedField(String name, String value)
        {
        }
        
        default boolean headerComplete()
        {
            return false;
        }
        
        @SuppressWarnings("unused")
        default boolean content(ByteBuffer item, boolean last)
        {
            return false;
        }
        
        default boolean messageComplete()
        {
            return false;
        }
        
        default void earlyEOF()
        {
        }
    }

    /* ------------------------------------------------------------------------------- */
    @SuppressWarnings("serial")
    private static class IllegalCharacterException extends BadMessageException
    {
        private IllegalCharacterException(State state,HttpTokens.Token token,ByteBuffer buffer)
        {
            super(400,String.format("Illegal character %s",token));
            if (LOG.isDebugEnabled())
                LOG.debug(String.format("Illegal character %s in state=%s for buffer %s",token,state,BufferUtil.toDetailString(buffer)));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy