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

org.apache.coyote.http11.InternalNioInputBuffer Maven / Gradle / Ivy

There is a newer version: 11.0.0-M26
Show newest version
/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF 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.apache.coyote.http11;

import java.io.EOFException;
import java.io.IOException;
import java.nio.channels.Selector;
import java.nio.charset.StandardCharsets;

import org.apache.coyote.InputBuffer;
import org.apache.coyote.Request;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.net.AbstractEndpoint;
import org.apache.tomcat.util.net.NioChannel;
import org.apache.tomcat.util.net.NioEndpoint;
import org.apache.tomcat.util.net.NioSelectorPool;
import org.apache.tomcat.util.net.SocketWrapper;

/**
 * Implementation of InputBuffer which provides HTTP request header parsing as
 * well as transfer decoding.
 *
 * @author Remy Maucherat
 * @author Filip Hanik
 */
public class InternalNioInputBuffer extends AbstractInputBuffer {

    private static final Log log =
            LogFactory.getLog(InternalNioInputBuffer.class);

    // -------------------------------------------------------------- Constants

    enum HeaderParseStatus {
        DONE, HAVE_MORE_HEADERS, NEED_MORE_DATA
    }

    enum HeaderParsePosition {
        /**
         * Start of a new header. A CRLF here means that there are no more
         * headers. Any other character starts a header name.
         */
        HEADER_START,
        /**
         * Reading a header name. All characters of header are HTTP_TOKEN_CHAR.
         * Header name is followed by ':'. No whitespace is allowed.
* Any non-HTTP_TOKEN_CHAR (this includes any whitespace) encountered * before ':' will result in the whole line being ignored. */ HEADER_NAME, /** * Skipping whitespace before text of header value starts, either on the * first line of header value (just after ':') or on subsequent lines * when it is known that subsequent line starts with SP or HT. */ HEADER_VALUE_START, /** * Reading the header value. We are inside the value. Either on the * first line or on any subsequent line. We come into this state from * HEADER_VALUE_START after the first non-SP/non-HT byte is encountered * on the line. */ HEADER_VALUE, /** * Before reading a new line of a header. Once the next byte is peeked, * the state changes without advancing our position. The state becomes * either HEADER_VALUE_START (if that first byte is SP or HT), or * HEADER_START (otherwise). */ HEADER_MULTI_LINE, /** * Reading all bytes until the next CRLF. The line is being ignored. */ HEADER_SKIPLINE } // ----------------------------------------------------------- Constructors /** * Alternate constructor. */ public InternalNioInputBuffer(Request request, int headerBufferSize) { this.request = request; headers = request.getMimeHeaders(); this.headerBufferSize = headerBufferSize; inputStreamInputBuffer = new SocketInputBuffer(); filterLibrary = new InputFilter[0]; activeFilters = new InputFilter[0]; lastActiveFilter = -1; parsingHeader = true; parsingRequestLine = true; parsingRequestLinePhase = 0; parsingRequestLineEol = false; parsingRequestLineStart = 0; parsingRequestLineQPos = -1; headerParsePos = HeaderParsePosition.HEADER_START; headerData.recycle(); swallowInput = true; } /** * Parsing state - used for non blocking parsing so that * when more data arrives, we can pick up where we left off. */ private boolean parsingRequestLine; private int parsingRequestLinePhase = 0; private boolean parsingRequestLineEol = false; private int parsingRequestLineStart = 0; private int parsingRequestLineQPos = -1; private HeaderParsePosition headerParsePos; /** * Underlying socket. */ private NioChannel socket; /** * Selector pool, for blocking reads and blocking writes */ private NioSelectorPool pool; /** * Maximum allowed size of the HTTP request line plus headers plus any * leading blank lines. */ private final int headerBufferSize; /** * Known size of the NioChannel read buffer. */ private int socketReadBufferSize; // --------------------------------------------------------- Public Methods @Override protected final Log getLog() { return log; } /** * Recycle the input buffer. This should be called when closing the * connection. */ @Override public void recycle() { super.recycle(); socket = null; headerParsePos = HeaderParsePosition.HEADER_START; parsingRequestLine = true; parsingRequestLinePhase = 0; parsingRequestLineEol = false; parsingRequestLineStart = 0; parsingRequestLineQPos = -1; headerData.recycle(); } /** * End processing of current HTTP request. * Note: All bytes of the current request should have been already * consumed. This method only resets all the pointers so that we are ready * to parse the next HTTP request. */ @Override public void nextRequest() { super.nextRequest(); headerParsePos = HeaderParsePosition.HEADER_START; parsingRequestLine = true; parsingRequestLinePhase = 0; parsingRequestLineEol = false; parsingRequestLineStart = 0; parsingRequestLineQPos = -1; headerData.recycle(); } /** * Read the request line. This function is meant to be used during the * HTTP request header parsing. Do NOT attempt to read the request body * using it. * * @throws IOException If an exception occurs during the underlying socket * read operations, or if the given buffer is not big enough to accommodate * the whole line. * @return true if data is properly fed; false if no data is available * immediately and thread should be freed */ @Override public boolean parseRequestLine(boolean useAvailableDataOnly) throws IOException { //check state if ( !parsingRequestLine ) return true; // // Skipping blank lines // if ( parsingRequestLinePhase == 0 ) { byte chr = 0; do { // Read new bytes if needed if (pos >= lastValid) { if (useAvailableDataOnly) { return false; } // Do a simple read with a short timeout if (!fill(true, false)) { return false; } } chr = buf[pos++]; } while ((chr == Constants.CR) || (chr == Constants.LF)); pos--; parsingRequestLineStart = pos; parsingRequestLinePhase = 2; if (log.isDebugEnabled()) { log.debug("Received [" + new String(buf, pos, lastValid - pos, StandardCharsets.ISO_8859_1) + "]"); } } if ( parsingRequestLinePhase == 2 ) { // // Reading the method name // Method name is always US-ASCII // boolean space = false; while (!space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill(true, false)) //request line parsing return false; } // Spec says no CR or LF in method name if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) { throw new IllegalArgumentException( sm.getString("iib.invalidmethod")); } if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true; request.method().setBytes(buf, parsingRequestLineStart, pos - parsingRequestLineStart); } pos++; } parsingRequestLinePhase = 3; } if ( parsingRequestLinePhase == 3 ) { // Spec says single SP but also be tolerant of multiple and/or HT boolean space = true; while (space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill(true, false)) //request line parsing return false; } if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { pos++; } else { space = false; } } parsingRequestLineStart = pos; parsingRequestLinePhase = 4; } if (parsingRequestLinePhase == 4) { // Mark the current buffer position int end = 0; // // Reading the URI // boolean space = false; while (!space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill(true,false)) //request line parsing return false; } if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true; end = pos; } else if ((buf[pos] == Constants.CR) || (buf[pos] == Constants.LF)) { // HTTP/0.9 style request parsingRequestLineEol = true; space = true; end = pos; } else if ((buf[pos] == Constants.QUESTION) && (parsingRequestLineQPos == -1)) { parsingRequestLineQPos = pos; } pos++; } request.unparsedURI().setBytes(buf, parsingRequestLineStart, end - parsingRequestLineStart); if (parsingRequestLineQPos >= 0) { request.queryString().setBytes(buf, parsingRequestLineQPos + 1, end - parsingRequestLineQPos - 1); request.requestURI().setBytes(buf, parsingRequestLineStart, parsingRequestLineQPos - parsingRequestLineStart); } else { request.requestURI().setBytes(buf, parsingRequestLineStart, end - parsingRequestLineStart); } parsingRequestLinePhase = 5; } if ( parsingRequestLinePhase == 5 ) { // Spec says single SP but also be tolerant of multiple and/or HT boolean space = true; while (space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill(true, false)) //request line parsing return false; } if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { pos++; } else { space = false; } } parsingRequestLineStart = pos; parsingRequestLinePhase = 6; // Mark the current buffer position end = 0; } if (parsingRequestLinePhase == 6) { // // Reading the protocol // Protocol is always US-ASCII // while (!parsingRequestLineEol) { // Read new bytes if needed if (pos >= lastValid) { if (!fill(true, false)) //request line parsing return false; } if (buf[pos] == Constants.CR) { end = pos; } else if (buf[pos] == Constants.LF) { if (end == 0) end = pos; parsingRequestLineEol = true; } pos++; } if ( (end - parsingRequestLineStart) > 0) { request.protocol().setBytes(buf, parsingRequestLineStart, end - parsingRequestLineStart); } else { request.protocol().setString(""); } parsingRequestLine = false; parsingRequestLinePhase = 0; parsingRequestLineEol = false; parsingRequestLineStart = 0; return true; } throw new IllegalStateException("Invalid request line parse phase:"+parsingRequestLinePhase); } private void expand(int newsize) { if ( newsize > buf.length ) { if (parsingHeader) { throw new IllegalArgumentException( sm.getString("iib.requestheadertoolarge.error")); } // Should not happen log.warn("Expanding buffer size. Old size: " + buf.length + ", new size: " + newsize, new Exception()); byte[] tmp = new byte[newsize]; System.arraycopy(buf,0,tmp,0,buf.length); buf = tmp; } } /** * Perform blocking read with a timeout if desired * @param timeout boolean - if we want to use the timeout data * @param block - true if the system should perform a blocking read, false otherwise * @return boolean - true if data was read, false is no data read, EOFException if EOF is reached * @throws IOException if a socket exception occurs * @throws EOFException if end of stream is reached */ private int readSocket(boolean timeout, boolean block) throws IOException { int nRead = 0; socket.getBufHandler().getReadBuffer().clear(); if ( block ) { Selector selector = null; try { selector = pool.get(); } catch ( IOException x ) { // Ignore } try { NioEndpoint.KeyAttachment att = (NioEndpoint.KeyAttachment) socket.getAttachment(false); if (att == null) { throw new IOException("Key must be cancelled."); } nRead = pool.read(socket.getBufHandler().getReadBuffer(), socket, selector, socket.getIOChannel().socket().getSoTimeout()); } catch ( EOFException eof ) { nRead = -1; } finally { if ( selector != null ) pool.put(selector); } } else { nRead = socket.read(socket.getBufHandler().getReadBuffer()); } if (nRead > 0) { socket.getBufHandler().getReadBuffer().flip(); socket.getBufHandler().getReadBuffer().limit(nRead); expand(nRead + pos); socket.getBufHandler().getReadBuffer().get(buf, pos, nRead); lastValid = pos + nRead; return nRead; } else if (nRead == -1) { //return false; throw new EOFException(sm.getString("iib.eof.error")); } else { return 0; } } /** * Parse the HTTP headers. */ @Override public boolean parseHeaders() throws IOException { if (!parsingHeader) { throw new IllegalStateException( sm.getString("iib.parseheaders.ise.error")); } HeaderParseStatus status = HeaderParseStatus.HAVE_MORE_HEADERS; do { status = parseHeader(); // Checking that // (1) Headers plus request line size does not exceed its limit // (2) There are enough bytes to avoid expanding the buffer when // reading body // Technically, (2) is technical limitation, (1) is logical // limitation to enforce the meaning of headerBufferSize // From the way how buf is allocated and how blank lines are being // read, it should be enough to check (1) only. if (pos > headerBufferSize || buf.length - pos < socketReadBufferSize) { throw new IllegalArgumentException( sm.getString("iib.requestheadertoolarge.error")); } } while ( status == HeaderParseStatus.HAVE_MORE_HEADERS ); if (status == HeaderParseStatus.DONE) { parsingHeader = false; end = pos; return true; } else { return false; } } /** * Parse an HTTP header. * * @return false after reading a blank line (which indicates that the * HTTP header parsing is done */ private HeaderParseStatus parseHeader() throws IOException { // // Check for blank line // byte chr = 0; while (headerParsePos == HeaderParsePosition.HEADER_START) { // Read new bytes if needed if (pos >= lastValid) { if (!fill(true,false)) {//parse header headerParsePos = HeaderParsePosition.HEADER_START; return HeaderParseStatus.NEED_MORE_DATA; } } chr = buf[pos]; if (chr == Constants.CR) { // Skip } else if (chr == Constants.LF) { pos++; return HeaderParseStatus.DONE; } else { break; } pos++; } if ( headerParsePos == HeaderParsePosition.HEADER_START ) { // Mark the current buffer position headerData.start = pos; headerParsePos = HeaderParsePosition.HEADER_NAME; } // // Reading the header name // Header name is always US-ASCII // while (headerParsePos == HeaderParsePosition.HEADER_NAME) { // Read new bytes if needed if (pos >= lastValid) { if (!fill(true,false)) { //parse header return HeaderParseStatus.NEED_MORE_DATA; } } chr = buf[pos]; if (chr == Constants.COLON) { headerParsePos = HeaderParsePosition.HEADER_VALUE_START; headerData.headerValue = headers.addValue(buf, headerData.start, pos - headerData.start); pos++; // Mark the current buffer position headerData.start = pos; headerData.realPos = pos; headerData.lastSignificantChar = pos; break; } else if (!HTTP_TOKEN_CHAR[chr]) { // If a non-token header is detected, skip the line and // ignore the header headerData.lastSignificantChar = pos; return skipLine(); } // chr is next byte of header name. Convert to lowercase. if ((chr >= Constants.A) && (chr <= Constants.Z)) { buf[pos] = (byte) (chr - Constants.LC_OFFSET); } pos++; } // Skip the line and ignore the header if (headerParsePos == HeaderParsePosition.HEADER_SKIPLINE) { return skipLine(); } // // Reading the header value (which can be spanned over multiple lines) // while (headerParsePos == HeaderParsePosition.HEADER_VALUE_START || headerParsePos == HeaderParsePosition.HEADER_VALUE || headerParsePos == HeaderParsePosition.HEADER_MULTI_LINE) { if ( headerParsePos == HeaderParsePosition.HEADER_VALUE_START ) { // Skipping spaces while (true) { // Read new bytes if needed if (pos >= lastValid) { if (!fill(true,false)) {//parse header //HEADER_VALUE_START return HeaderParseStatus.NEED_MORE_DATA; } } chr = buf[pos]; if (chr == Constants.SP || chr == Constants.HT) { pos++; } else { headerParsePos = HeaderParsePosition.HEADER_VALUE; break; } } } if ( headerParsePos == HeaderParsePosition.HEADER_VALUE ) { // Reading bytes until the end of the line boolean eol = false; while (!eol) { // Read new bytes if needed if (pos >= lastValid) { if (!fill(true,false)) {//parse header //HEADER_VALUE return HeaderParseStatus.NEED_MORE_DATA; } } chr = buf[pos]; if (chr == Constants.CR) { // Skip } else if (chr == Constants.LF) { eol = true; } else if (chr == Constants.SP || chr == Constants.HT) { buf[headerData.realPos] = chr; headerData.realPos++; } else { buf[headerData.realPos] = chr; headerData.realPos++; headerData.lastSignificantChar = headerData.realPos; } pos++; } // Ignore whitespaces at the end of the line headerData.realPos = headerData.lastSignificantChar; // Checking the first character of the new line. If the character // is a LWS, then it's a multiline header headerParsePos = HeaderParsePosition.HEADER_MULTI_LINE; } // Read new bytes if needed if (pos >= lastValid) { if (!fill(true,false)) {//parse header //HEADER_MULTI_LINE return HeaderParseStatus.NEED_MORE_DATA; } } chr = buf[pos]; if ( headerParsePos == HeaderParsePosition.HEADER_MULTI_LINE ) { if ( (chr != Constants.SP) && (chr != Constants.HT)) { headerParsePos = HeaderParsePosition.HEADER_START; break; } else { // Copying one extra space in the buffer (since there must // be at least one space inserted between the lines) buf[headerData.realPos] = chr; headerData.realPos++; headerParsePos = HeaderParsePosition.HEADER_VALUE_START; } } } // Set the header value headerData.headerValue.setBytes(buf, headerData.start, headerData.lastSignificantChar - headerData.start); headerData.recycle(); return HeaderParseStatus.HAVE_MORE_HEADERS; } public int getParsingRequestLinePhase() { return parsingRequestLinePhase; } private HeaderParseStatus skipLine() throws IOException { headerParsePos = HeaderParsePosition.HEADER_SKIPLINE; boolean eol = false; // Reading bytes until the end of the line while (!eol) { // Read new bytes if needed if (pos >= lastValid) { if (!fill(true,false)) { return HeaderParseStatus.NEED_MORE_DATA; } } if (buf[pos] == Constants.CR) { // Skip } else if (buf[pos] == Constants.LF) { eol = true; } else { headerData.lastSignificantChar = pos; } pos++; } if (log.isDebugEnabled()) { log.debug(sm.getString("iib.invalidheader", new String(buf, headerData.start, headerData.lastSignificantChar - headerData.start + 1, StandardCharsets.ISO_8859_1))); } headerParsePos = HeaderParsePosition.HEADER_START; return HeaderParseStatus.HAVE_MORE_HEADERS; } private final HeaderParseData headerData = new HeaderParseData(); public static class HeaderParseData { /** * When parsing header name: first character of the header.
* When skipping broken header line: first character of the header.
* When parsing header value: first character after ':'. */ int start = 0; /** * When parsing header name: not used (stays as 0).
* When skipping broken header line: not used (stays as 0).
* When parsing header value: starts as the first character after ':'. * Then is increased as far as more bytes of the header are harvested. * Bytes from buf[pos] are copied to buf[realPos]. Thus the string from * [start] to [realPos-1] is the prepared value of the header, with * whitespaces removed as needed.
*/ int realPos = 0; /** * When parsing header name: not used (stays as 0).
* When skipping broken header line: last non-CR/non-LF character.
* When parsing header value: position after the last not-LWS character.
*/ int lastSignificantChar = 0; /** * MB that will store the value of the header. It is null while parsing * header name and is created after the name has been parsed. */ MessageBytes headerValue = null; public void recycle() { start = 0; realPos = 0; lastSignificantChar = 0; headerValue = null; } } // ------------------------------------------------------ Protected Methods @Override protected void init(SocketWrapper socketWrapper, AbstractEndpoint endpoint) throws IOException { socket = socketWrapper.getSocket(); if (socket == null) { // Socket has been closed in another thread throw new IOException(sm.getString("iib.socketClosed")); } socketReadBufferSize = socket.getBufHandler().getReadBuffer().capacity(); int bufLength = headerBufferSize + socketReadBufferSize; if (buf == null || buf.length < bufLength) { buf = new byte[bufLength]; } pool = ((NioEndpoint)endpoint).getSelectorPool(); } @Override protected boolean fill(boolean block) throws IOException, EOFException { return fill(true,block); } protected boolean fill(boolean timeout, boolean block) throws IOException, EOFException { boolean read = false; if (parsingHeader) { if (lastValid > headerBufferSize) { throw new IllegalArgumentException (sm.getString("iib.requestheadertoolarge.error")); } // Do a simple read with a short timeout read = readSocket(timeout,block)>0; } else { lastValid = pos = end; // Do a simple read with a short timeout read = readSocket(timeout, block)>0; } return read; } // ------------------------------------- InputStreamInputBuffer Inner Class /** * This class is an input buffer which will read its data from an input * stream. */ protected class SocketInputBuffer implements InputBuffer { /** * Read bytes into the specified chunk. */ @Override public int doRead(ByteChunk chunk, Request req ) throws IOException { if (pos >= lastValid) { if (!fill(true,true)) //read body, must be blocking, as the thread is inside the app return -1; } int length = lastValid - pos; chunk.setBytes(buf, pos, length); pos = lastValid; return (length); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy