| /* |
| * 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.ByteBuffer; |
| import java.nio.charset.StandardCharsets; |
| import java.util.Arrays; |
| |
| import org.apache.coyote.CloseNowException; |
| 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.http.HeaderUtil; |
| import org.apache.tomcat.util.http.parser.HttpHeaderParser; |
| import org.apache.tomcat.util.http.parser.HttpHeaderParser.HeaderDataSource; |
| import org.apache.tomcat.util.http.parser.HttpHeaderParser.HeaderParseStatus; |
| import org.apache.tomcat.util.http.parser.HttpParser; |
| import org.apache.tomcat.util.net.ApplicationBufferHandler; |
| import org.apache.tomcat.util.net.SocketWrapperBase; |
| import org.apache.tomcat.util.res.StringManager; |
| |
| /** |
| * InputBuffer for HTTP that provides request header parsing as well as transfer encoding. |
| */ |
| public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler, HeaderDataSource { |
| |
| // -------------------------------------------------------------- Constants |
| |
| private static final Log log = LogFactory.getLog(Http11InputBuffer.class); |
| |
| /** |
| * The string manager for this package. |
| */ |
| private static final StringManager sm = StringManager.getManager(Http11InputBuffer.class); |
| |
| |
| private static final byte[] CLIENT_PREFACE_START = |
| "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1); |
| |
| /** |
| * Associated Coyote request. |
| */ |
| private final Request request; |
| |
| |
| /** |
| * State. |
| */ |
| private volatile boolean parsingHeader; |
| |
| |
| /** |
| * Swallow input ? (in the case of an expectation) |
| */ |
| private boolean swallowInput; |
| |
| |
| /** |
| * The read buffer. |
| */ |
| private ByteBuffer byteBuffer; |
| |
| |
| /** |
| * Pos of the end of the header in the buffer, which is also the start of the body. |
| */ |
| private int end; |
| |
| |
| /** |
| * Wrapper that provides access to the underlying socket. |
| */ |
| private SocketWrapperBase<?> wrapper; |
| |
| |
| /** |
| * Underlying input buffer. |
| */ |
| private final InputBuffer inputStreamInputBuffer; |
| |
| |
| /** |
| * Filter library. Note: Filter[Constants.CHUNKED_FILTER] is always the "chunked" filter. |
| */ |
| private InputFilter[] filterLibrary; |
| |
| |
| /** |
| * Active filters (in order). |
| */ |
| private InputFilter[] activeFilters; |
| |
| |
| /** |
| * Index of the last active filter. |
| */ |
| private int lastActiveFilter; |
| |
| |
| /** |
| * Parsing state - used for non-blocking parsing so that when more data arrives, we can pick up where we left off. |
| */ |
| private byte prevChr = 0; |
| private byte chr = 0; |
| private volatile boolean parsingRequestLine; |
| private int parsingRequestLinePhase; |
| private boolean parsingRequestLineEol; |
| private int parsingRequestLineStart; |
| private int parsingRequestLineQPos; |
| private final HttpParser httpParser; |
| private final HttpHeaderParser httpHeaderParser; |
| |
| /** |
| * Maximum allowed size of the HTTP request line plus headers plus any leading blank lines. |
| */ |
| private final int headerBufferSize; |
| |
| // ----------------------------------------------------------- Constructors |
| |
| public Http11InputBuffer(Request request, int headerBufferSize, HttpParser httpParser) { |
| |
| this.request = request; |
| |
| this.headerBufferSize = headerBufferSize; |
| this.httpParser = httpParser; |
| |
| filterLibrary = new InputFilter[0]; |
| activeFilters = new InputFilter[0]; |
| lastActiveFilter = -1; |
| |
| parsingRequestLine = true; |
| parsingRequestLinePhase = 0; |
| parsingRequestLineEol = false; |
| parsingRequestLineStart = 0; |
| parsingRequestLineQPos = -1; |
| |
| parsingHeader = true; |
| httpHeaderParser = new HttpHeaderParser(this, request.getMimeHeaders(), true); |
| |
| swallowInput = true; |
| |
| inputStreamInputBuffer = new SocketInputBuffer(); |
| } |
| |
| |
| // ------------------------------------------------------------- Properties |
| |
| /** |
| * Add an input filter to the filter library. |
| * |
| * @throws NullPointerException if the supplied filter is null |
| */ |
| void addFilter(InputFilter filter) { |
| |
| if (filter == null) { |
| throw new NullPointerException(sm.getString("iib.filter.npe")); |
| } |
| |
| InputFilter[] newFilterLibrary = Arrays.copyOf(filterLibrary, filterLibrary.length + 1); |
| newFilterLibrary[filterLibrary.length] = filter; |
| filterLibrary = newFilterLibrary; |
| |
| activeFilters = new InputFilter[filterLibrary.length]; |
| } |
| |
| |
| /** |
| * Get filters. |
| */ |
| InputFilter[] getFilters() { |
| return filterLibrary; |
| } |
| |
| |
| /** |
| * Add an input filter to the filter library. |
| */ |
| void addActiveFilter(InputFilter filter) { |
| |
| if (lastActiveFilter == -1) { |
| filter.setBuffer(inputStreamInputBuffer); |
| } else { |
| for (int i = 0; i <= lastActiveFilter; i++) { |
| if (activeFilters[i] == filter) { |
| return; |
| } |
| } |
| filter.setBuffer(activeFilters[lastActiveFilter]); |
| } |
| |
| activeFilters[++lastActiveFilter] = filter; |
| |
| filter.setRequest(request); |
| } |
| |
| |
| /** |
| * Set the swallow input flag. |
| */ |
| void setSwallowInput(boolean swallowInput) { |
| this.swallowInput = swallowInput; |
| } |
| |
| |
| // ---------------------------------------------------- InputBuffer Methods |
| |
| @Override |
| public int doRead(ApplicationBufferHandler handler) throws IOException { |
| if (lastActiveFilter == -1) { |
| return inputStreamInputBuffer.doRead(handler); |
| } else { |
| return activeFilters[lastActiveFilter].doRead(handler); |
| } |
| } |
| |
| |
| // ------------------------------------------------------- Protected Methods |
| |
| /** |
| * Recycle the input buffer. This should be called when closing the connection. |
| */ |
| void recycle() { |
| wrapper = null; |
| request.recycle(); |
| |
| for (int i = 0; i <= lastActiveFilter; i++) { |
| activeFilters[i].recycle(); |
| } |
| |
| // Avoid rare NPE reported on users@ list |
| if (byteBuffer != null) { |
| byteBuffer.limit(0).position(0); |
| } |
| lastActiveFilter = -1; |
| swallowInput = true; |
| |
| chr = 0; |
| prevChr = 0; |
| parsingRequestLinePhase = 0; |
| parsingRequestLineEol = false; |
| parsingRequestLineStart = 0; |
| parsingRequestLineQPos = -1; |
| httpHeaderParser.recycle(); |
| // Recycled last because they are volatile |
| // All variables visible to this thread are guaranteed to be visible to |
| // any other thread once that thread reads the same volatile. The first |
| // action when parsing input data is to read one of these volatiles. |
| parsingRequestLine = true; |
| parsingHeader = true; |
| } |
| |
| |
| /** |
| * 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. |
| */ |
| void nextRequest() { |
| request.recycle(); |
| |
| if (byteBuffer.position() > 0) { |
| if (byteBuffer.remaining() > 0) { |
| // Copy leftover bytes to the beginning of the buffer |
| byteBuffer.compact(); |
| byteBuffer.flip(); |
| } else { |
| // Reset position and limit to 0 |
| byteBuffer.position(0).limit(0); |
| } |
| } |
| |
| // Recycle filters |
| for (int i = 0; i <= lastActiveFilter; i++) { |
| activeFilters[i].recycle(); |
| } |
| |
| // Reset pointers |
| lastActiveFilter = -1; |
| parsingHeader = true; |
| swallowInput = true; |
| |
| parsingRequestLine = true; |
| parsingRequestLinePhase = 0; |
| parsingRequestLineEol = false; |
| parsingRequestLineStart = 0; |
| parsingRequestLineQPos = -1; |
| httpHeaderParser.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 |
| */ |
| boolean parseRequestLine(boolean keptAlive, int connectionTimeout, int keepAliveTimeout) throws IOException { |
| |
| // check state |
| if (!parsingRequestLine) { |
| return true; |
| } |
| // |
| // Skipping blank lines |
| // |
| if (parsingRequestLinePhase < 2) { |
| do { |
| // Read new bytes if needed |
| if (byteBuffer.position() >= byteBuffer.limit()) { |
| if (keptAlive) { |
| // Haven't read any request data yet so use the keep-alive |
| // timeout. |
| wrapper.setReadTimeout(keepAliveTimeout); |
| } |
| if (!fill(false)) { |
| return false; |
| } |
| // At least one byte of the request has been received. |
| // Switch to the socket timeout. |
| wrapper.setReadTimeout(connectionTimeout); |
| } |
| if (!keptAlive && byteBuffer.position() == 0 && byteBuffer.limit() >= CLIENT_PREFACE_START.length) { |
| boolean prefaceMatch = true; |
| for (int i = 0; i < CLIENT_PREFACE_START.length && prefaceMatch; i++) { |
| if (CLIENT_PREFACE_START[i] != byteBuffer.get(i)) { |
| prefaceMatch = false; |
| } |
| } |
| if (prefaceMatch) { |
| // HTTP/2 preface matched |
| parsingRequestLinePhase = -1; |
| return false; |
| } |
| } |
| // Set the start time once we start reading data (even if it is |
| // just skipping blank lines) |
| if (parsingRequestLinePhase == 0) { |
| parsingRequestLinePhase = 1; |
| request.markStartTime(); |
| } |
| chr = byteBuffer.get(); |
| } while (chr == Constants.CR || chr == Constants.LF); |
| byteBuffer.position(byteBuffer.position() - 1); |
| |
| parsingRequestLineStart = byteBuffer.position(); |
| parsingRequestLinePhase = 2; |
| } |
| if (parsingRequestLinePhase == 2) { |
| // |
| // Reading the method name |
| // Method name is a token |
| // |
| boolean space = false; |
| while (!space) { |
| // Read new bytes if needed |
| if (byteBuffer.position() >= byteBuffer.limit()) { |
| if (!fill(false)) { |
| return false; |
| } |
| } |
| // Spec says method name is a token followed by a single SP but |
| // also be tolerant of multiple SP and/or HT. |
| int pos = byteBuffer.position(); |
| chr = byteBuffer.get(); |
| if (chr == Constants.SP || chr == Constants.HT) { |
| space = true; |
| request.setMethod(byteBuffer.array(), parsingRequestLineStart, pos - parsingRequestLineStart); |
| } else if (!HttpParser.isToken(chr)) { |
| // Avoid unknown protocol triggering an additional error |
| request.protocol().setString(Constants.HTTP_11); |
| String invalidMethodValue = parseInvalid(parsingRequestLineStart, byteBuffer); |
| throw new IllegalArgumentException(sm.getString("iib.invalidmethod", invalidMethodValue)); |
| } |
| } |
| parsingRequestLinePhase = 3; |
| } |
| if (parsingRequestLinePhase == 3) { |
| // Spec says single SP but also be tolerant of multiple SP and/or HT |
| boolean space = true; |
| while (space) { |
| // Read new bytes if needed |
| if (byteBuffer.position() >= byteBuffer.limit()) { |
| if (!fill(false)) { |
| return false; |
| } |
| } |
| chr = byteBuffer.get(); |
| if (chr != Constants.SP && chr != Constants.HT) { |
| space = false; |
| byteBuffer.position(byteBuffer.position() - 1); |
| } |
| } |
| parsingRequestLineStart = byteBuffer.position(); |
| 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 (byteBuffer.position() >= byteBuffer.limit()) { |
| if (!fill(false)) { |
| return false; |
| } |
| } |
| int pos = byteBuffer.position(); |
| prevChr = chr; |
| chr = byteBuffer.get(); |
| if (prevChr == Constants.CR && chr != Constants.LF) { |
| // CR not followed by LF so not an HTTP/0.9 request and |
| // therefore invalid. Trigger error handling. |
| // Avoid unknown protocol triggering an additional error |
| request.protocol().setString(Constants.HTTP_11); |
| String invalidRequestTarget = parseInvalid(parsingRequestLineStart, byteBuffer); |
| throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", invalidRequestTarget)); |
| } |
| if (chr == Constants.SP || chr == Constants.HT) { |
| space = true; |
| end = pos; |
| } else if (chr == Constants.CR) { |
| // HTTP/0.9 style request. CR is optional. LF is not. |
| } else if (chr == Constants.LF) { |
| // HTTP/0.9 style request |
| // Stop this processing loop |
| space = true; |
| // Set blank protocol (indicates HTTP/0.9) |
| request.protocol().setString(""); |
| // Skip the protocol processing |
| parsingRequestLinePhase = 7; |
| if (prevChr == Constants.CR) { |
| end = pos - 1; |
| } else { |
| end = pos; |
| } |
| } else if (chr == Constants.QUESTION && parsingRequestLineQPos == -1) { |
| parsingRequestLineQPos = pos; |
| } else if (parsingRequestLineQPos != -1 && !httpParser.isQueryRelaxed(chr)) { |
| // Avoid unknown protocol triggering an additional error |
| request.protocol().setString(Constants.HTTP_11); |
| // %nn decoding will be checked at the point of decoding |
| String invalidRequestTarget = parseInvalid(parsingRequestLineStart, byteBuffer); |
| throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", invalidRequestTarget)); |
| } else if (httpParser.isNotRequestTargetRelaxed(chr)) { |
| // Avoid unknown protocol triggering an additional error |
| request.protocol().setString(Constants.HTTP_11); |
| // This is a general check that aims to catch problems early |
| // Detailed checking of each part of the request target will |
| // happen in Http11Processor#prepareRequest() |
| String invalidRequestTarget = parseInvalid(parsingRequestLineStart, byteBuffer); |
| throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget", invalidRequestTarget)); |
| } |
| } |
| if (parsingRequestLineQPos >= 0) { |
| request.queryString().setBytes(byteBuffer.array(), parsingRequestLineQPos + 1, |
| end - parsingRequestLineQPos - 1); |
| request.requestURI().setBytes(byteBuffer.array(), parsingRequestLineStart, |
| parsingRequestLineQPos - parsingRequestLineStart); |
| } else { |
| request.requestURI().setBytes(byteBuffer.array(), parsingRequestLineStart, |
| end - parsingRequestLineStart); |
| } |
| // HTTP/0.9 processing jumps to stage 7. |
| // Don't want to overwrite that here. |
| if (parsingRequestLinePhase == 4) { |
| 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 (byteBuffer.position() >= byteBuffer.limit()) { |
| if (!fill(false)) { |
| return false; |
| } |
| } |
| byte chr = byteBuffer.get(); |
| if (chr != Constants.SP && chr != Constants.HT) { |
| space = false; |
| byteBuffer.position(byteBuffer.position() - 1); |
| } |
| } |
| parsingRequestLineStart = byteBuffer.position(); |
| parsingRequestLinePhase = 6; |
| |
| // Mark the current buffer position |
| end = 0; |
| } |
| if (parsingRequestLinePhase == 6) { |
| // |
| // Reading the protocol |
| // Protocol is always "HTTP/" DIGIT "." DIGIT |
| // |
| while (!parsingRequestLineEol) { |
| // Read new bytes if needed |
| if (byteBuffer.position() >= byteBuffer.limit()) { |
| if (!fill(false)) { |
| return false; |
| } |
| } |
| |
| int pos = byteBuffer.position(); |
| prevChr = chr; |
| chr = byteBuffer.get(); |
| if (chr == Constants.CR) { |
| // Possible end of request line. Need LF next else invalid. |
| } else if (prevChr == Constants.CR && chr == Constants.LF) { |
| // CRLF is the standard line terminator |
| end = pos - 1; |
| parsingRequestLineEol = true; |
| } else if (chr == Constants.LF) { |
| // LF is an optional line terminator |
| end = pos; |
| parsingRequestLineEol = true; |
| } else if (prevChr == Constants.CR || !HttpParser.isHttpProtocol(chr)) { |
| String invalidProtocol = parseInvalid(parsingRequestLineStart, byteBuffer); |
| throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol", invalidProtocol)); |
| } |
| } |
| |
| if (end - parsingRequestLineStart > 0) { |
| request.protocol().setBytes(byteBuffer.array(), parsingRequestLineStart, end - parsingRequestLineStart); |
| parsingRequestLinePhase = 7; |
| } |
| // If no protocol is found, the ISE below will be triggered. |
| } |
| if (parsingRequestLinePhase == 7) { |
| // Parsing is complete. Return and clean-up. |
| parsingRequestLine = false; |
| parsingRequestLinePhase = 0; |
| parsingRequestLineEol = false; |
| parsingRequestLineStart = 0; |
| return true; |
| } |
| throw new IllegalStateException(sm.getString("iib.invalidPhase", Integer.valueOf(parsingRequestLinePhase))); |
| } |
| |
| |
| /** |
| * Parse the HTTP headers. |
| * |
| * @throws IOException an underlying I/O error occurred |
| */ |
| boolean parseHeaders() throws IOException { |
| if (!parsingHeader) { |
| throw new IllegalStateException(sm.getString("iib.parseheaders.ise.error")); |
| } |
| |
| HeaderParseStatus status; |
| |
| do { |
| status = httpHeaderParser.parseHeader(); |
| // Checking that headers plus request line size does not exceed its limit |
| if (byteBuffer.position() > headerBufferSize) { |
| throw new IllegalArgumentException(sm.getString("iib.requestheadertoolarge.error")); |
| } |
| } while (status == HeaderParseStatus.HAVE_MORE_HEADERS); |
| if (status == HeaderParseStatus.DONE) { |
| parsingHeader = false; |
| end = byteBuffer.position(); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| |
| int getParsingRequestLinePhase() { |
| return parsingRequestLinePhase; |
| } |
| |
| |
| private String parseInvalid(int startPos, ByteBuffer buffer) { |
| // Look for the next space |
| byte b = 0; |
| while (buffer.hasRemaining() && b != 0x20) { |
| b = buffer.get(); |
| } |
| String result = HeaderUtil.toPrintableString(buffer.array(), buffer.arrayOffset() + startPos, |
| buffer.position() - startPos); |
| if (b != 0x20) { |
| // Ran out of buffer rather than found a space |
| result = result + "..."; |
| } |
| return result; |
| } |
| |
| |
| /** |
| * End request (consumes leftover bytes). |
| * |
| * @throws IOException an underlying I/O error occurred |
| */ |
| void endRequest() throws IOException { |
| |
| if (swallowInput && (lastActiveFilter != -1)) { |
| int extraBytes = (int) activeFilters[lastActiveFilter].end(); |
| byteBuffer.position(byteBuffer.position() - extraBytes); |
| } |
| } |
| |
| |
| @Override |
| public int available() { |
| return available(false); |
| } |
| |
| |
| /** |
| * Available bytes in the buffers for the current request. Note that when requests are pipelined, the data in |
| * byteBuffer may relate to the next request rather than this one. |
| * |
| * @return the amount of bytes available, 0 if none, and 1 if there was an IO error to trigger a read |
| */ |
| int available(boolean read) { |
| int available; |
| |
| if (lastActiveFilter == -1) { |
| available = inputStreamInputBuffer.available(); |
| } else { |
| available = activeFilters[lastActiveFilter].available(); |
| } |
| |
| // Only try a non-blocking read if: |
| // - there is no data in the filters |
| // - the caller requested a read |
| // - there is no data in byteBuffer |
| // - the socket wrapper indicates a read is allowed |
| // |
| // Notes: 1. When pipelined requests are being used available may be |
| // zero even when byteBuffer has data. This is because the data |
| // in byteBuffer is for the next request. We don't want to |
| // attempt a read in this case. |
| // 2. wrapper.hasDataToRead() is present to handle the NIO2 case |
| try { |
| if (available == 0 && read && !byteBuffer.hasRemaining() && wrapper.hasDataToRead()) { |
| fill(false); |
| available = byteBuffer.remaining(); |
| } |
| } catch (IOException ioe) { |
| if (log.isDebugEnabled()) { |
| log.debug(sm.getString("iib.available.readFail"), ioe); |
| } |
| // Not ideal. This will indicate that data is available which should |
| // trigger a read which in turn will trigger another IOException and |
| // that one can be thrown. |
| available = 1; |
| } |
| return available; |
| } |
| |
| |
| /** |
| * Has all of the request body been read? There are subtle differences between this and available() > 0 primarily |
| * because of having to handle faking non-blocking reads with the blocking IO connector. |
| * |
| * @return {@code true} if the request has been fully read |
| */ |
| boolean isFinished() { |
| // The active filters have the definitive information on whether or not |
| // the current request body has been read. Note that byteBuffer may |
| // contain pipelined data so is not a good indicator. |
| if (lastActiveFilter >= 0) { |
| return activeFilters[lastActiveFilter].isFinished(); |
| } else { |
| // No filters. Assume request is not finished. EOF will signal end of |
| // request. |
| return false; |
| } |
| } |
| |
| ByteBuffer getLeftover() { |
| int available = byteBuffer.remaining(); |
| if (available > 0) { |
| return ByteBuffer.wrap(byteBuffer.array(), byteBuffer.position(), available); |
| } else { |
| return null; |
| } |
| } |
| |
| |
| boolean isChunking() { |
| for (int i = 0; i < lastActiveFilter; i++) { |
| if (activeFilters[i] == filterLibrary[Constants.CHUNKED_FILTER]) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| void init(SocketWrapperBase<?> socketWrapper) { |
| |
| wrapper = socketWrapper; |
| wrapper.setAppReadBufHandler(this); |
| |
| int bufLength = headerBufferSize + wrapper.getSocketBufferHandler().getReadBuffer().capacity(); |
| if (byteBuffer == null || byteBuffer.capacity() < bufLength) { |
| byteBuffer = ByteBuffer.allocate(bufLength); |
| byteBuffer.position(0).limit(0); |
| } |
| } |
| |
| |
| @Override |
| public boolean fillHeaderBuffer() throws IOException { |
| // HTTP headers are always read using non-blocking IO. |
| return fill(false); |
| } |
| |
| |
| /** |
| * Attempts to read some data into the input buffer. |
| * |
| * @param block Should blocking IO be used when filling the input buffer |
| * |
| * @return <code>true</code> if more data was added to the input buffer otherwise <code>false</code> |
| * |
| * @throws IOException if an IO error occurs while filling the input buffer |
| */ |
| private boolean fill(boolean block) throws IOException { |
| |
| if (log.isTraceEnabled()) { |
| log.trace("Before fill(): parsingHeader: [" + parsingHeader + "], parsingRequestLine: [" + |
| parsingRequestLine + "], parsingRequestLinePhase: [" + parsingRequestLinePhase + |
| "], parsingRequestLineStart: [" + parsingRequestLineStart + "], byteBuffer.position(): [" + |
| byteBuffer.position() + "], byteBuffer.limit(): [" + byteBuffer.limit() + "], end: [" + end + "]"); |
| } |
| |
| if (parsingHeader) { |
| if (byteBuffer.limit() >= headerBufferSize) { |
| if (parsingRequestLine) { |
| // Avoid unknown protocol triggering an additional error |
| request.protocol().setString(Constants.HTTP_11); |
| } |
| throw new IllegalArgumentException(sm.getString("iib.requestheadertoolarge.error")); |
| } |
| } else { |
| byteBuffer.limit(end).position(end); |
| } |
| |
| int nRead; |
| int mark = byteBuffer.position(); |
| try { |
| if (byteBuffer.position() < byteBuffer.limit()) { |
| byteBuffer.position(byteBuffer.limit()); |
| } |
| byteBuffer.limit(byteBuffer.capacity()); |
| SocketWrapperBase<?> socketWrapper = this.wrapper; |
| if (socketWrapper != null) { |
| nRead = socketWrapper.read(block, byteBuffer); |
| } else { |
| throw new CloseNowException(sm.getString("iib.eof.error")); |
| } |
| } finally { |
| // Ensure that the buffer limit and position are returned to a |
| // consistent "ready for read" state if an error occurs during in |
| // the above code block. |
| // Some error conditions can result in the position being reset to |
| // zero which also invalidates the mark. |
| // https://bz.apache.org/bugzilla/show_bug.cgi?id=65677 |
| if (byteBuffer.position() >= mark) { |
| // // Position and mark are consistent. Assume a read (possibly |
| // of zero bytes) has occurred. |
| byteBuffer.limit(byteBuffer.position()); |
| byteBuffer.position(mark); |
| } else { |
| // Position and mark are inconsistent. Set position and limit to |
| // zero so effectively no data is reported as read. |
| byteBuffer.position(0); |
| byteBuffer.limit(0); |
| } |
| } |
| |
| if (log.isTraceEnabled()) { |
| log.trace("Received [" + new String(byteBuffer.array(), byteBuffer.position(), byteBuffer.remaining(), |
| StandardCharsets.ISO_8859_1) + "]"); |
| } |
| |
| if (nRead > 0) { |
| return true; |
| } else if (nRead == -1) { |
| throw new EOFException(sm.getString("iib.eof.error")); |
| } else { |
| return false; |
| } |
| |
| } |
| |
| |
| // ------------------------------------- InputStreamInputBuffer Inner Class |
| |
| /** |
| * This class is an input buffer which will read its data from an input stream. |
| */ |
| private class SocketInputBuffer implements InputBuffer { |
| |
| @Override |
| public int doRead(ApplicationBufferHandler handler) throws IOException { |
| |
| if (byteBuffer.position() >= byteBuffer.limit()) { |
| // The application is reading the HTTP request body |
| boolean block = (request.getReadListener() == null); |
| if (!fill(block)) { |
| if (block) { |
| return -1; |
| } else { |
| return 0; |
| } |
| } |
| } |
| |
| int length = byteBuffer.remaining(); |
| handler.setByteBuffer(byteBuffer.duplicate()); |
| byteBuffer.position(byteBuffer.limit()); |
| |
| return length; |
| } |
| |
| @Override |
| public int available() { |
| return byteBuffer.remaining(); |
| } |
| } |
| |
| |
| @Override |
| public void setByteBuffer(ByteBuffer buffer) { |
| byteBuffer = buffer; |
| } |
| |
| |
| @Override |
| public ByteBuffer getByteBuffer() { |
| return byteBuffer; |
| } |
| |
| |
| @Override |
| public ByteBuffer getHeaderByteBuffer() { |
| return getByteBuffer(); |
| } |
| |
| |
| @Override |
| public void expand(int size) { |
| if (byteBuffer.capacity() >= size) { |
| byteBuffer.limit(size); |
| } |
| ByteBuffer temp = ByteBuffer.allocate(size); |
| temp.put(byteBuffer); |
| byteBuffer = temp; |
| byteBuffer.mark(); |
| } |
| } |