| /* |
| * 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.IOException; |
| import java.nio.ByteBuffer; |
| |
| import org.apache.coyote.InputBuffer; |
| import org.apache.coyote.Request; |
| import org.apache.juli.logging.Log; |
| import org.apache.tomcat.util.buf.ByteChunk; |
| import org.apache.tomcat.util.http.MimeHeaders; |
| import org.apache.tomcat.util.http.parser.HttpParser; |
| import org.apache.tomcat.util.net.AbstractEndpoint; |
| import org.apache.tomcat.util.net.SocketWrapper; |
| import org.apache.tomcat.util.res.StringManager; |
| |
| public abstract class AbstractInputBuffer<S> implements InputBuffer{ |
| |
| /** |
| * The string manager for this package. |
| */ |
| protected static final StringManager sm = StringManager.getManager(Constants.Package); |
| |
| /** |
| * Associated Coyote request. |
| */ |
| protected Request request; |
| |
| |
| /** |
| * Headers of the associated request. |
| */ |
| protected MimeHeaders headers; |
| |
| |
| /** |
| * State. |
| */ |
| protected boolean parsingHeader; |
| |
| |
| /** |
| * Swallow input ? (in the case of an expectation) |
| */ |
| protected boolean swallowInput; |
| |
| |
| /** |
| * Pointer to the current read buffer. |
| */ |
| protected byte[] buf; |
| |
| |
| /** |
| * Last valid byte. |
| */ |
| protected int lastValid; |
| |
| |
| /** |
| * Position in the buffer. |
| */ |
| protected int pos; |
| |
| |
| /** |
| * Pos of the end of the header in the buffer, which is also the |
| * start of the body. |
| */ |
| protected int end; |
| |
| |
| /** |
| * Underlying input buffer. |
| */ |
| protected InputBuffer inputStreamInputBuffer; |
| |
| |
| /** |
| * Filter library. |
| * Note: Filter[Constants.CHUNKED_FILTER] is always the "chunked" filter. |
| */ |
| protected InputFilter[] filterLibrary; |
| |
| |
| /** |
| * Active filters (in order). |
| */ |
| protected InputFilter[] activeFilters; |
| |
| |
| /** |
| * Index of the last active filter. |
| */ |
| protected int lastActiveFilter; |
| |
| |
| protected boolean rejectIllegalHeaderName; |
| |
| |
| protected HttpParser httpParser; |
| |
| |
| // ------------------------------------------------------------- Properties |
| |
| /** |
| * Add an input filter to the filter library. |
| * |
| * @throws NullPointerException if the supplied filter is null |
| */ |
| public void addFilter(InputFilter filter) { |
| |
| if (filter == null) { |
| throw new NullPointerException(sm.getString("iib.filter.npe")); |
| } |
| |
| InputFilter[] newFilterLibrary = |
| new InputFilter[filterLibrary.length + 1]; |
| for (int i = 0; i < filterLibrary.length; i++) { |
| newFilterLibrary[i] = filterLibrary[i]; |
| } |
| newFilterLibrary[filterLibrary.length] = filter; |
| filterLibrary = newFilterLibrary; |
| |
| activeFilters = new InputFilter[filterLibrary.length]; |
| } |
| |
| |
| /** |
| * Get filters. |
| */ |
| public InputFilter[] getFilters() { |
| |
| return filterLibrary; |
| |
| } |
| |
| |
| /** |
| * Add an input filter to the filter library. |
| */ |
| public 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. |
| */ |
| public void setSwallowInput(boolean swallowInput) { |
| this.swallowInput = swallowInput; |
| } |
| |
| |
| /** |
| * Implementations are expected to call {@link Request#setStartTime(long)} |
| * as soon as the first byte is read from the request. |
| */ |
| public abstract boolean parseRequestLine(boolean useAvailableDataOnly) |
| throws IOException; |
| |
| public abstract boolean parseHeaders() throws IOException; |
| |
| /** |
| * Attempts to read some data into the input buffer. |
| * |
| * @return <code>true</code> if more data was added to the input buffer |
| * otherwise <code>false</code> |
| */ |
| protected abstract boolean fill(boolean block) throws IOException; |
| |
| protected abstract void init(SocketWrapper<S> socketWrapper, |
| AbstractEndpoint<S> endpoint) throws IOException; |
| |
| protected abstract Log getLog(); |
| |
| |
| // --------------------------------------------------------- Public Methods |
| |
| /** |
| * Recycle the input buffer. This should be called when closing the |
| * connection. |
| */ |
| public void recycle() { |
| |
| // Recycle Request object |
| request.recycle(); |
| |
| // Recycle filters |
| for (int i = 0; i <= lastActiveFilter; i++) { |
| activeFilters[i].recycle(); |
| } |
| |
| lastValid = 0; |
| pos = 0; |
| lastActiveFilter = -1; |
| parsingHeader = true; |
| swallowInput = 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. |
| */ |
| public void nextRequest() { |
| |
| // Recycle Request object |
| request.recycle(); |
| |
| // Copy leftover bytes to the beginning of the buffer |
| if (lastValid - pos > 0 && pos > 0) { |
| System.arraycopy(buf, pos, buf, 0, lastValid - pos); |
| } |
| // Always reset pos to zero |
| lastValid = lastValid - pos; |
| pos = 0; |
| |
| // Recycle filters |
| for (int i = 0; i <= lastActiveFilter; i++) { |
| activeFilters[i].recycle(); |
| } |
| |
| // Reset pointers |
| lastActiveFilter = -1; |
| parsingHeader = true; |
| swallowInput = true; |
| } |
| |
| |
| /** |
| * End request (consumes leftover bytes). |
| * |
| * @throws IOException an underlying I/O error occurred |
| */ |
| public void endRequest() throws IOException { |
| |
| if (swallowInput && (lastActiveFilter != -1)) { |
| int extraBytes = (int) activeFilters[lastActiveFilter].end(); |
| pos = pos - extraBytes; |
| } |
| } |
| |
| |
| /** |
| * Available bytes in the buffers (note that due to encoding, this may not |
| * correspond). |
| */ |
| public int available(boolean read) { |
| int available = lastValid - pos; |
| if ((available == 0) && (lastActiveFilter >= 0)) { |
| for (int i = 0; (available == 0) && (i <= lastActiveFilter); i++) { |
| available = activeFilters[i].available(); |
| } |
| } |
| if (available > 0 || !read) { |
| return available; |
| } |
| |
| try { |
| fill(false); |
| available = lastValid - pos; |
| } catch (IOException ioe) { |
| if (getLog().isDebugEnabled()) { |
| getLog().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. |
| */ |
| public boolean isFinished() { |
| if (lastValid > pos) { |
| // Data to read in the buffer so not finished |
| return false; |
| } |
| |
| /* |
| * Don't use fill(false) here because in the following circumstances |
| * BIO will block - possibly indefinitely |
| * - client is using keep-alive and connection is still open |
| * - client has sent the complete request |
| * - client has not sent any of the next request (i.e. no pipelining) |
| * - application has read the complete request |
| */ |
| |
| // Check the InputFilters |
| |
| 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 = lastValid - pos; |
| if (available > 0) { |
| return ByteBuffer.wrap(buf, pos, available); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Is standard Servlet blocking IO being used for input? |
| */ |
| protected final boolean isBlocking() { |
| return request.getReadListener() == null; |
| } |
| |
| |
| // ---------------------------------------------------- InputBuffer Methods |
| |
| /** |
| * Read some bytes. |
| */ |
| @Override |
| public int doRead(ByteChunk chunk, Request req) |
| throws IOException { |
| |
| if (lastActiveFilter == -1) |
| return inputStreamInputBuffer.doRead(chunk, req); |
| else |
| return activeFilters[lastActiveFilter].doRead(chunk,req); |
| |
| } |
| } |