/* * 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.io.InputStream; import java.net.Socket; import java.nio.charset.Charset; 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.http.parser.HttpParser; import org.apache.tomcat.util.net.AbstractEndpoint; import org.apache.tomcat.util.net.SocketWrapper; /** * Implementation of InputBuffer which provides HTTP request header parsing as * well as transfer decoding. * * @author <a href="mailto:remm@apache.org">Remy Maucherat</a> */ public class InternalInputBuffer extends AbstractInputBuffer<Socket> { private static final Log log = LogFactory.getLog(InternalInputBuffer.class); /** * Underlying input stream. */ private InputStream inputStream; /** * Default constructor. */ public InternalInputBuffer(Request request, int headerBufferSize) { this.request = request; headers = request.getMimeHeaders(); buf = new byte[headerBufferSize]; inputStreamInputBuffer = new InputStreamInputBuffer(); filterLibrary = new InputFilter[0]; activeFilters = new InputFilter[0]; lastActiveFilter = -1; parsingHeader = true; swallowInput = true; } /** * 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. */ @Override public boolean parseRequestLine(boolean useAvailableDataOnly) throws IOException { int start = 0; // // Skipping blank lines // byte chr = 0; do { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } // Set the start time once we start reading data (even if it is // just skipping blank lines) if (request.getStartTime() < 0) { request.setStartTime(System.currentTimeMillis()); } chr = buf[pos++]; } while ((chr == Constants.CR) || (chr == Constants.LF)); pos--; // Mark the current buffer position start = pos; // // Reading the method name // Method name is a token // boolean space = false; while (!space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } // Spec says method name is a token followed by a single SP but // also be tolerant of multiple SP and/or HT. if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { space = true; request.method().setBytes(buf, start, pos - start); } else if (!HttpParser.isToken(buf[pos])) { throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); } pos++; } // Spec says single SP but also be tolerant of multiple SP and/or HT while (space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { pos++; } else { space = false; } } // Mark the current buffer position start = pos; int end = 0; int questionPos = -1; // // Reading the URI // boolean eol = false; while (!space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } // Spec says single SP but it also says be tolerant of HT 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 eol = true; space = true; end = pos; } else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) { questionPos = pos; } else if (HttpParser.isNotRequestTarget(buf[pos])) { throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget")); } pos++; } request.unparsedURI().setBytes(buf, start, end - start); if (questionPos >= 0) { request.queryString().setBytes(buf, questionPos + 1, end - questionPos - 1); request.requestURI().setBytes(buf, start, questionPos - start); } else { request.requestURI().setBytes(buf, start, end - start); } // Spec says single SP but also says be tolerant of multiple SP and/or HT while (space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) { pos++; } else { space = false; } } // Mark the current buffer position start = pos; end = 0; // // Reading the protocol // Protocol is always "HTTP/" DIGIT "." DIGIT // while (!eol) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } if (buf[pos] == Constants.CR) { end = pos; } else if (buf[pos] == Constants.LF) { if (end == 0) end = pos; eol = true; } else if (!HttpParser.isHttpProtocol(buf[pos])) { throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol")); } pos++; } if ((end - start) > 0) { request.protocol().setBytes(buf, start, end - start); } else { request.protocol().setString(""); } return true; } /** * Parse the HTTP headers. */ @Override public boolean parseHeaders() throws IOException { if (!parsingHeader) { throw new IllegalStateException( sm.getString("iib.parseheaders.ise.error")); } while (parseHeader()) { // Loop until we run out of headers } parsingHeader = false; end = pos; return true; } /** * Parse an HTTP header. * * @return false after reading a blank line (which indicates that the * HTTP header parsing is done */ @SuppressWarnings("null") // headerValue cannot be null private boolean parseHeader() throws IOException { // // Check for blank line // byte chr = 0; while (true) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } chr = buf[pos]; if (chr == Constants.CR) { // Skip } else if (chr == Constants.LF) { pos++; return false; } else { break; } pos++; } // Mark the current buffer position int start = pos; // // Reading the header name // Header name is always US-ASCII // boolean colon = false; MessageBytes headerValue = null; while (!colon) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } if (buf[pos] == Constants.COLON) { colon = true; headerValue = headers.addValue(buf, start, pos - start); } else if (!HttpParser.isToken(buf[pos])) { // If a non-token header is detected, skip the line and // ignore the header skipLine(start); return true; } chr = buf[pos]; if ((chr >= Constants.A) && (chr <= Constants.Z)) { buf[pos] = (byte) (chr - Constants.LC_OFFSET); } pos++; } // Mark the current buffer position start = pos; int realPos = pos; // // Reading the header value (which can be spanned over multiple lines) // boolean eol = false; boolean validLine = true; while (validLine) { boolean space = true; // Skipping spaces while (space) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } if ((buf[pos] == Constants.SP) || (buf[pos] == Constants.HT)) { pos++; } else { space = false; } } int lastSignificantChar = realPos; // Reading bytes until the end of the line while (!eol) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } if (buf[pos] == Constants.CR) { // Skip } else if (buf[pos] == Constants.LF) { eol = true; } else if (buf[pos] == Constants.SP) { buf[realPos] = buf[pos]; realPos++; } else { buf[realPos] = buf[pos]; realPos++; lastSignificantChar = realPos; } pos++; } realPos = lastSignificantChar; // Checking the first character of the new line. If the character // is a LWS, then it's a multiline header // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } chr = buf[pos]; if ((chr != Constants.SP) && (chr != Constants.HT)) { validLine = false; } else { eol = false; // Copying one extra space in the buffer (since there must // be at least one space inserted between the lines) buf[realPos] = chr; realPos++; } } // Set the header value headerValue.setBytes(buf, start, realPos - start); return true; } @Override public void recycle() { super.recycle(); inputStream = null; } // ------------------------------------------------------ Protected Methods @Override protected void init(SocketWrapper<Socket> socketWrapper, AbstractEndpoint<Socket> endpoint) throws IOException { inputStream = socketWrapper.getSocket().getInputStream(); } private void skipLine(int start) throws IOException { boolean eol = false; int lastRealByte = start; if (pos - 1 > start) { lastRealByte = pos - 1; } while (!eol) { // Read new bytes if needed if (pos >= lastValid) { if (!fill()) throw new EOFException(sm.getString("iib.eof.error")); } if (buf[pos] == Constants.CR) { // Skip } else if (buf[pos] == Constants.LF) { eol = true; } else { lastRealByte = pos; } pos++; } if (log.isDebugEnabled()) { log.debug(sm.getString("iib.invalidheader", new String(buf, start, lastRealByte - start + 1, Charset.forName("ISO-8859-1")))); } } /** * Fill the internal buffer using data from the underlying input stream. * * @return false if at end of stream */ protected boolean fill() throws IOException { return fill(true); } @Override protected boolean fill(boolean block) throws IOException { int nRead = 0; if (parsingHeader) { if (lastValid == buf.length) { throw new IllegalArgumentException (sm.getString("iib.requestheadertoolarge.error")); } nRead = inputStream.read(buf, pos, buf.length - lastValid); if (nRead > 0) { lastValid = pos + nRead; } } else { if (buf.length - end < 4500) { // In this case, the request header was really large, so we allocate a // brand new one; the old one will get GCed when subsequent requests // clear all references buf = new byte[buf.length]; end = 0; } pos = end; lastValid = pos; nRead = inputStream.read(buf, pos, buf.length - lastValid); if (nRead > 0) { lastValid = pos + nRead; } } return (nRead > 0); } // ------------------------------------- InputStreamInputBuffer Inner Class /** * This class is an input buffer which will read its data from an input * stream. */ protected class InputStreamInputBuffer implements InputBuffer { /** * Read bytes into the specified chunk. */ @Override public int doRead(ByteChunk chunk, Request req ) throws IOException { if (pos >= lastValid) { if (!fill()) return -1; } int length = lastValid - pos; chunk.setBytes(buf, pos, length); pos = lastValid; return (length); } } }