Here are the examples of the java api org.apache.coyote.Request taken from open source projects. By voting up you can indicate which examples are most useful and appropriate.
54 Examples
19
View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : how2j
Copyright Apache License 2.0
Author : how2j
// ------------------------------------------------------------- Properties
/**
* replacedociated Coyote request.
*
* @param coyoteRequest
* replacedociated Coyote request
*/
public void setRequest(Request coyoteRequest) {
this.coyoteRequest = coyoteRequest;
}
19
View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
// ------------------------------------------------------------- Properties
/**
* replacedociated Coyote request.
*
* @param coyoteRequest replacedociated Coyote request
*/
public void setRequest(Request coyoteRequest) {
this.coyoteRequest = coyoteRequest;
}
18
View Complete Implementation : VoidInputFilter.java
Copyright Apache License 2.0
Author : how2j
Copyright Apache License 2.0
Author : how2j
// --------------------------------------------------- OutputFilter Methods
/**
* Set the replacedociated request.
*/
@Override
public void setRequest(Request request) {
// NOOP: Request isn't used so ignore it
}
18
View Complete Implementation : VoidInputFilter.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputFilter Methods
/**
* Set the replacedociated request.
*/
@Override
public void setRequest(Request request) {
// NOOP: Request isn't used so ignore it
}
18
View Complete Implementation : StreamProcessor.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
// Static so it can be used by Stream to build the MimeHeaders required for
// an ACK. For that use case coyoteRequest, protocol and stream will be null.
static void prepareHeaders(Request coyoteRequest, Response coyoteResponse, boolean noSendfile, Http2Protocol protocol, Stream stream) {
MimeHeaders headers = coyoteResponse.getMimeHeaders();
int statusCode = coyoteResponse.getStatus();
// Add the pseudo header for status
headers.addValue(":status").setString(Integer.toString(statusCode));
// Check to see if a response body is present
if (!(statusCode < 200 || statusCode == 204 || statusCode == 205 || statusCode == 304)) {
String contentType = coyoteResponse.getContentType();
if (contentType != null) {
headers.setValue("content-type").setString(contentType);
}
String contentLanguage = coyoteResponse.getContentLanguage();
if (contentLanguage != null) {
headers.setValue("content-language").setString(contentLanguage);
}
// Add a content-length header if a content length has been set unless
// the application has already added one
long contentLength = coyoteResponse.getContentLengthLong();
if (contentLength != -1 && headers.getValue("content-length") == null) {
headers.addValue("content-length").setLong(contentLength);
}
} else {
if (statusCode == 205) {
// RFC 7231 requires the server to explicitly signal an empty
// response in this case
coyoteResponse.setContentLength(0);
} else {
coyoteResponse.setContentLength(-1);
}
}
// Add date header unless it is an informational response or the
// application has already set one
if (statusCode >= 200 && headers.getValue("date") == null) {
headers.addValue("date").setString(FastHttpDateFormat.getCurrentDate());
}
// Compression can't be used with sendfile
if (noSendfile && protocol != null && protocol.useCompression(coyoteRequest, coyoteResponse)) {
// Enable compression. Headers will have been set. Need to configure
// output filter at this point.
stream.addOutputFilter(new GzipOutputFilter());
}
}
18
View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : codefollower
Copyright Apache License 2.0
Author : codefollower
/**
* Chunked input filter. Parses chunked data according to
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
*
* @author Remy Maucherat
* @author Filip Hanik
*/
public clreplaced ChunkedInputFilter implements InputFilter {
// -------------------------------------------------------------- Constants
protected static final String ENCODING_NAME = "chunked";
protected static final ByteChunk ENCODING = new ByteChunk();
// ----------------------------------------------------- Static Initializer
static {
ENCODING.setBytes(ENCODING_NAME.getBytes(StandardCharsets.ISO_8859_1), 0, ENCODING_NAME.length());
}
// ----------------------------------------------------- Instance Variables
/**
* Next buffer in the pipeline.
*/
protected InputBuffer buffer;
/**
* Number of bytes remaining in the current chunk.
*/
protected int remaining = 0;
/**
* Position in the buffer.
*/
protected int pos = 0;
/**
* Last valid byte in the buffer.
*/
protected int lastValid = 0;
/**
* Read bytes buffer.
*/
protected byte[] buf = null;
/**
* Byte chunk used to read bytes.
*/
protected final ByteChunk readChunk = new ByteChunk();
/**
* Flag set to true when the end chunk has been read.
*/
protected boolean endChunk = false;
/**
* Byte chunk used to store trailing headers.
*/
protected final ByteChunk trailingHeaders = new ByteChunk();
/**
* Flag set to true if the next call to doRead() must parse a CRLF pair
* before doing anything else.
*/
protected boolean needCRLFParse = false;
/**
* Request being parsed.
*/
private Request request;
/**
* Limit for extension size.
*/
private final long maxExtensionSize;
/**
* Limit for trailer size.
*/
private final int maxTrailerSize;
/**
* Size of extensions processed for this request.
*/
private long extensionSize;
// ----------------------------------------------------------- Constructors
public ChunkedInputFilter(int maxTrailerSize, int maxExtensionSize) {
this.trailingHeaders.setLimit(maxTrailerSize);
this.maxExtensionSize = maxExtensionSize;
this.maxTrailerSize = maxTrailerSize;
}
// ---------------------------------------------------- InputBuffer Methods
/**
* Read bytes.
*
* @return If the filter does request length control, this value is
* significant; it should be the number of bytes consumed from the buffer,
* up until the end of the current request body, or the buffer length,
* whichever is greater. If the filter does not do request body length
* control, the returned value should be -1.
*/
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
if (endChunk)
return -1;
if (needCRLFParse) {
needCRLFParse = false;
parseCRLF(false);
}
if (remaining <= 0) {
if (!parseChunkHeader()) {
throw new IOException("Invalid chunk header");
}
if (endChunk) {
parseEndChunk();
return -1;
}
}
int result = 0;
if (pos >= lastValid) {
if (readBytes() < 0) {
throw new IOException("Unexpected end of stream whilst reading request body");
}
}
if (remaining > (lastValid - pos)) {
result = lastValid - pos;
remaining = remaining - result;
chunk.setBytes(buf, pos, result);
pos = lastValid;
} else {
result = remaining;
chunk.setBytes(buf, pos, remaining);
pos = pos + remaining;
remaining = 0;
// we need a CRLF
if ((pos + 1) >= lastValid) {
// if we call parseCRLF we overrun the buffer here
// so we defer it to the next call BZ 11117
needCRLFParse = true;
} else {
// parse the CRLF immediately
parseCRLF(false);
}
}
return result;
}
// ---------------------------------------------------- InputFilter Methods
/**
* Read the content length from the request.
*/
@Override
public void setRequest(Request request) {
this.request = request;
}
/**
* End the current request.
*/
@Override
public long end() throws IOException {
// Consume extra bytes : parse the stream until the end chunk is found
while (doRead(readChunk, null) >= 0) {
// NOOP: Just consume the input
}
// Return the number of extra bytes which were consumed
return (lastValid - pos);
}
/**
* Amount of bytes still available in a buffer.
*/
@Override
public int available() {
return (lastValid - pos);
}
/**
* Set the next buffer in the filter pipeline.
*/
@Override
public void setBuffer(InputBuffer buffer) {
this.buffer = buffer;
}
/**
* Make the filter ready to process the next request.
*/
@Override
public void recycle() {
remaining = 0;
pos = 0;
lastValid = 0;
endChunk = false;
needCRLFParse = false;
trailingHeaders.recycle();
trailingHeaders.setLimit(maxTrailerSize);
extensionSize = 0;
}
/**
* Return the name of the replacedociated encoding; Here, the value is
* "idenreplacedy".
*/
@Override
public ByteChunk getEncodingName() {
return ENCODING;
}
// ------------------------------------------------------ Protected Methods
/**
* Read bytes from the previous buffer.
*/
protected int readBytes() throws IOException {
int nRead = buffer.doRead(readChunk, null);
pos = readChunk.getStart();
lastValid = pos + nRead;
buf = readChunk.getBytes();
return nRead;
}
/**
* Parse the header of a chunk.
* A chunk header can look like one of the following:<br />
* A10CRLF<br />
* F23;chunk-extension to be ignoredCRLF
*
* <p>
* The letters before CRLF or ';' (whatever comes first) must be valid hex
* digits. We should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid
* header according to the spec.
*/
protected boolean parseChunkHeader() throws IOException {
int result = 0;
boolean eol = false;
boolean readDigit = false;
boolean extension = false;
while (!eol) {
if (pos >= lastValid) {
if (readBytes() <= 0)
return false;
}
if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
parseCRLF(false);
eol = true;
} else if (buf[pos] == Constants.SEMI_COLON && !extension) {
// First semi-colon marks the start of the extension. Further
// semi-colons may appear to separate multiple chunk-extensions.
// These need to be processed as part of parsing the extensions.
extension = true;
extensionSize++;
} else if (!extension) {
// don't read data after the trailer
int charValue = HexUtils.getDec(buf[pos]);
if (charValue != -1) {
readDigit = true;
result *= 16;
result += charValue;
} else {
// we shouldn't allow invalid, non hex characters
// in the chunked header
return false;
}
} else {
// Extension 'parsing'
// Note that the chunk-extension is neither parsed nor
// validated. Currently it is simply ignored.
extensionSize++;
if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
throw new IOException("maxExtensionSize exceeded");
}
}
// Parsing the CRLF increments pos
if (!eol) {
pos++;
}
}
if (!readDigit)
return false;
if (result == 0)
endChunk = true;
remaining = result;
if (remaining < 0)
return false;
return true;
}
/**
* Parse CRLF at end of chunk.
*
* @param tolerant Should tolerant parsing (LF and CRLF) be used? This
* is recommended (RFC2616, section 19.3) for message
* headers.
*/
protected void parseCRLF(boolean tolerant) throws IOException {
boolean eol = false;
boolean crfound = false;
while (!eol) {
if (pos >= lastValid) {
if (readBytes() <= 0)
throw new IOException("Invalid CRLF");
}
if (buf[pos] == Constants.CR) {
if (crfound)
throw new IOException("Invalid CRLF, two CR characters encountered.");
crfound = true;
} else if (buf[pos] == Constants.LF) {
if (!tolerant && !crfound) {
throw new IOException("Invalid CRLF, no CR character encountered.");
}
eol = true;
} else {
throw new IOException("Invalid CRLF");
}
pos++;
}
}
/**
* Parse end chunk data.
*/
protected void parseEndChunk() throws IOException {
// Handle optional trailer headers
while (parseHeader()) {
// Loop until we run out of headers
}
}
private boolean parseHeader() throws IOException {
MimeHeaders headers = request.getMimeHeaders();
byte chr = 0;
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0)
throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
}
chr = buf[pos];
// CRLF terminates the request
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(false);
return false;
}
// Mark the current buffer position
int start = trailingHeaders.getEnd();
//
// Reading the header name
// Header name is always US-ASCII
//
boolean colon = false;
while (!colon) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0)
throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
}
chr = buf[pos];
if ((chr >= Constants.A) && (chr <= Constants.Z)) {
chr = (byte) (chr - Constants.LC_OFFSET);
}
if (chr == Constants.COLON) {
colon = true;
} else {
trailingHeaders.append(chr);
}
pos++;
}
MessageBytes headerValue = headers.addValue(trailingHeaders.getBytes(), start, trailingHeaders.getEnd() - start);
// Mark the current buffer position
start = trailingHeaders.getEnd();
//
// Reading the header value (which can be spanned over multiple lines)
//
boolean eol = false;
boolean validLine = true;
int lastSignificantChar = 0;
while (validLine) {
boolean space = true;
// Skipping spaces
while (space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0)
throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
}
chr = buf[pos];
if ((chr == Constants.SP) || (chr == Constants.HT)) {
pos++;
// If we swallow whitespace, make sure it counts towards the
// limit placed on trailing header size
int newlimit = trailingHeaders.getLimit() - 1;
if (trailingHeaders.getEnd() > newlimit) {
throw new IOException("Exceeded maxTrailerSize");
}
trailingHeaders.setLimit(newlimit);
} else {
space = false;
}
}
// Reading bytes until the end of the line
while (!eol) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0)
throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
}
chr = buf[pos];
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(true);
eol = true;
} else if (chr == Constants.SP) {
trailingHeaders.append(chr);
} else {
trailingHeaders.append(chr);
lastSignificantChar = trailingHeaders.getEnd();
}
if (!eol) {
pos++;
}
}
// 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 (readBytes() < 0)
throw new EOFException("Unexpected end of stream whilst reading trailer headers for chunked request");
}
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)
trailingHeaders.append(chr);
}
}
// Set the header value
headerValue.setBytes(trailingHeaders.getBytes(), start, lastSignificantChar - start);
return true;
}
@Override
public boolean isFinished() {
return endChunk;
}
}
18
View Complete Implementation : IdentityInputFilter.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputFilter Methods
/**
* Read the content length from the request.
*/
@Override
public void setRequest(Request request) {
contentLength = request.getContentLengthLong();
remaining = contentLength;
}
18
View Complete Implementation : Stream.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
final void push(Request request) throws IOException {
// Can only push when supported and from a peer initiated stream
if (!isPushSupported() || getIdAsInt() % 2 == 0) {
return;
}
// Set the special HTTP/2 headers
request.getMimeHeaders().addValue(":method").duplicate(request.method());
request.getMimeHeaders().addValue(":scheme").duplicate(request.scheme());
StringBuilder path = new StringBuilder(request.requestURI().toString());
if (!request.queryString().isNull()) {
path.append('?');
path.append(request.queryString().toString());
}
request.getMimeHeaders().addValue(":path").setString(path.toString());
// Authority needs to include the port only if a non-standard port is
// being used.
if (!(request.scheme().equals("http") && request.getServerPort() == 80) && !(request.scheme().equals("https") && request.getServerPort() == 443)) {
request.getMimeHeaders().addValue(":authority").setString(request.serverName().getString() + ":" + request.getServerPort());
} else {
request.getMimeHeaders().addValue(":authority").duplicate(request.serverName());
}
push(handler, request, this);
}
18
View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputFilter Methods
/**
* Read the content length from the request.
*/
@Override
public void setRequest(Request request) {
this.request = request;
}
18
View Complete Implementation : AbstractInputBuffer.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- 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);
}
18
View Complete Implementation : BufferedInputFilter.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputBuffer Methods
/**
* Reads the request body and buffers it.
*/
@Override
public void setRequest(Request request) {
// save off the Request body
try {
while (buffer.doRead(tempRead, request) >= 0) {
buffered.append(tempRead);
tempRead.recycle();
}
} catch (IOException ioe) {
// No need for i18n - this isn't going to get logged anywhere
throw new IllegalStateException("Request body too large for buffer");
}
}
18
View Complete Implementation : VoidInputFilter.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputBuffer Methods
/**
* Write some bytes.
*
* @return number of bytes written by the filter
*/
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
return -1;
}
18
View Complete Implementation : VoidInputFilter.java
Copyright Apache License 2.0
Author : codefollower
Copyright Apache License 2.0
Author : codefollower
// ---------------------------------------------------- InputBuffer Methods
/**
* Write some bytes.
*
* @return number of bytes written by the filter
*/
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
read = true;
return -1;
}
18
View Complete Implementation : StreamProcessor.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
@Override
protected final void doPush(Request pushTarget) {
try {
stream.push(pushTarget);
} catch (IOException ioe) {
setErrorState(ErrorState.CLOSE_CONNECTION_NOW, ioe);
response.setErrorException(ioe);
}
}
18
View Complete Implementation : Stream.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
private static void push(final Http2UpgradeHandler handler, final Request request, final Stream stream) throws IOException {
if (org.apache.coyote.Constants.IS_SECURITY_ENABLED) {
try {
AccessController.doPrivileged(new PrivilegedPush(handler, request, stream));
} catch (PrivilegedActionException ex) {
Exception e = ex.getException();
if (e instanceof IOException) {
throw (IOException) e;
} else {
throw new IOException(ex);
}
}
} else {
handler.push(request, stream);
}
}
18
View Complete Implementation : StreamProcessor.java
Copyright MIT License
Author : chenmudu
Copyright MIT License
Author : chenmudu
// Static so it can be used by Stream to build the MimeHeaders required for
// an ACK. For that use case coyoteRequest, protocol and stream will be null.
static void prepareHeaders(Request coyoteRequest, Response coyoteResponse, Http2Protocol protocol, Stream stream) {
MimeHeaders headers = coyoteResponse.getMimeHeaders();
int statusCode = coyoteResponse.getStatus();
// Add the pseudo header for status
headers.addValue(":status").setString(Integer.toString(statusCode));
// Check to see if a response body is present
if (!(statusCode < 200 || statusCode == 204 || statusCode == 205 || statusCode == 304)) {
String contentType = coyoteResponse.getContentType();
if (contentType != null) {
headers.setValue("content-type").setString(contentType);
}
String contentLanguage = coyoteResponse.getContentLanguage();
if (contentLanguage != null) {
headers.setValue("content-language").setString(contentLanguage);
}
// Add a content-length header if a content length has been set unless
// the application has already added one
long contentLength = coyoteResponse.getContentLengthLong();
if (contentLength != -1 && headers.getValue("content-length") == null) {
headers.addValue("content-length").setLong(contentLength);
}
} else {
if (statusCode == 205) {
// RFC 7231 requires the server to explicitly signal an empty
// response in this case
coyoteResponse.setContentLength(0);
} else {
coyoteResponse.setContentLength(-1);
}
}
// Add date header unless it is an informational response or the
// application has already set one
if (statusCode >= 200 && headers.getValue("date") == null) {
headers.addValue("date").setString(FastHttpDateFormat.getCurrentDate());
}
if (protocol != null && protocol.useCompression(coyoteRequest, coyoteResponse)) {
// Enable compression. Headers will have been set. Need to configure
// output filter at this point.
stream.addOutputFilter(new GzipOutputFilter());
}
}
18
View Complete Implementation : Http2Protocol.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
@Override
public InternalHttpUpgradeHandler getInternalUpgradeHandler(SocketWrapperBase<?> socketWrapper, Adapter adapter, Request coyoteRequest) {
return socketWrapper.hasAsyncIO() ? new Http2AsyncUpgradeHandler(this, adapter, coyoteRequest) : new Http2UpgradeHandler(this, adapter, coyoteRequest);
}
18
View Complete Implementation : VoidInputFilter.java
Copyright Apache License 2.0
Author : how2j
Copyright Apache License 2.0
Author : how2j
// ----------------------------------------------------- Instance Variables
// --------------------------------------------------- OutputBuffer Methods
/**
* Write some bytes.
*
* @return number of bytes written by the filter
*/
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
return -1;
}
18
View Complete Implementation : AbstractHttp11Protocol.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
public boolean useCompression(Request request, Response response) {
return compressionConfig.useCompression(request, response);
}
18
View Complete Implementation : Http2Protocol.java
Copyright MIT License
Author : chenmudu
Copyright MIT License
Author : chenmudu
@Override
public InternalHttpUpgradeHandler getInternalUpgradeHandler(Adapter adapter, Request coyoteRequest) {
Http2UpgradeHandler result = new Http2UpgradeHandler(this, adapter, coyoteRequest);
result.setReadTimeout(getReadTimeout());
result.setKeepAliveTimeout(getKeepAliveTimeout());
result.setWriteTimeout(getWriteTimeout());
result.setMaxConcurrentStreams(getMaxConcurrentStreams());
result.setMaxConcurrentStreamExecution(getMaxConcurrentStreamExecution());
result.setInitialWindowSize(getInitialWindowSize());
result.setAllowedTrailerHeaders(allowedTrailerHeaders);
result.setMaxHeaderCount(getMaxHeaderCount());
result.setMaxHeaderSize(getMaxHeaderSize());
result.setMaxTrailerCount(getMaxTrailerCount());
result.setMaxTrailerSize(getMaxTrailerSize());
result.setInitiatePingDisabled(initiatePingDisabled);
return result;
}
17
View Complete Implementation : InputBuffer.java
Copyright MIT License
Author : chenmudu
Copyright MIT License
Author : chenmudu
/**
* The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
* OutputBuffer, adapted to handle input instead of output. This allows
* complete recycling of the facade objects (the ServletInputStream and the
* BufferedReader).
*
* @author Remy Maucherat
*/
public clreplaced InputBuffer extends Reader implements ByteChunk.ByteInputChannel, ApplicationBufferHandler {
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(InputBuffer.clreplaced);
private static final Log log = LogFactory.getLog(InputBuffer.clreplaced);
public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
// The buffer can be used for byte[] and char[] reading
// ( this is needed to support ServletInputStream and BufferedReader )
public final int INITIAL_STATE = 0;
public final int CHAR_STATE = 1;
public final int BYTE_STATE = 2;
/**
* Encoder cache.
*/
private static final ConcurrentMap<Charset, SynchronizedStack<B2CConverter>> encoders = new ConcurrentHashMap<>();
// ----------------------------------------------------- Instance Variables
/**
* The byte buffer.
*/
private ByteBuffer bb;
/**
* The char buffer.
*/
private CharBuffer cb;
/**
* State of the output buffer.
*/
private int state = 0;
/**
* Flag which indicates if the input buffer is closed.
*/
private boolean closed = false;
/**
* Encoding to use.
*/
private String enc;
/**
* Current byte to char converter.
*/
protected B2CConverter conv;
/**
* replacedociated Coyote request.
*/
private Request coyoteRequest;
/**
* Buffer position.
*/
private int markPos = -1;
/**
* Char buffer limit.
*/
private int readLimit;
/**
* Buffer size.
*/
private final int size;
// ----------------------------------------------------------- Constructors
/**
* Default constructor. Allocate the buffer with the default buffer size.
*/
public InputBuffer() {
this(DEFAULT_BUFFER_SIZE);
}
/**
* Alternate constructor which allows specifying the initial buffer size.
*
* @param size Buffer size to use
*/
public InputBuffer(int size) {
this.size = size;
bb = ByteBuffer.allocate(size);
clear(bb);
cb = CharBuffer.allocate(size);
clear(cb);
readLimit = size;
}
// ------------------------------------------------------------- Properties
/**
* replacedociated Coyote request.
*
* @param coyoteRequest replacedociated Coyote request
*/
public void setRequest(Request coyoteRequest) {
this.coyoteRequest = coyoteRequest;
}
// --------------------------------------------------------- Public Methods
/**
* Recycle the output buffer.
*/
public void recycle() {
state = INITIAL_STATE;
// If usage of mark made the buffer too big, reallocate it
if (cb.capacity() > size) {
cb = CharBuffer.allocate(size);
clear(cb);
} else {
clear(cb);
}
readLimit = size;
markPos = -1;
clear(bb);
closed = false;
if (conv != null) {
conv.recycle();
encoders.get(conv.getCharset()).push(conv);
conv = null;
}
enc = null;
}
/**
* Close the input buffer.
*
* @throws IOException An underlying IOException occurred
*/
@Override
public void close() throws IOException {
closed = true;
}
public int available() {
int available = availableInThisBuffer();
if (available == 0) {
coyoteRequest.action(ActionCode.AVAILABLE, Boolean.valueOf(coyoteRequest.getReadListener() != null));
available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
}
return available;
}
private int availableInThisBuffer() {
int available = 0;
if (state == BYTE_STATE) {
available = bb.remaining();
} else if (state == CHAR_STATE) {
available = cb.remaining();
}
return available;
}
public void setReadListener(ReadListener listener) {
coyoteRequest.setReadListener(listener);
// The container is responsible for the first call to
// listener.onDataAvailable(). If isReady() returns true, the container
// needs to call listener.onDataAvailable() from a new thread. If
// isReady() returns false, the socket will be registered for read and
// the container will call listener.onDataAvailable() once data arrives.
// Must call isFinished() first as a call to isReady() if the request
// has been finished will register the socket for read interest and that
// is not required.
if (!coyoteRequest.isFinished() && isReady()) {
coyoteRequest.action(ActionCode.DISPATCH_READ, null);
if (!ContainerThreadMarker.isContainerThread()) {
// Not on a container thread so need to execute the dispatch
coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
}
}
}
public boolean isFinished() {
int available = 0;
if (state == BYTE_STATE) {
available = bb.remaining();
} else if (state == CHAR_STATE) {
available = cb.remaining();
}
if (available > 0) {
return false;
} else {
return coyoteRequest.isFinished();
}
}
public boolean isReady() {
if (coyoteRequest.getReadListener() == null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("inputBuffer.requiresNonBlocking"));
}
return false;
}
if (isFinished()) {
// If this is a non-container thread, need to trigger a read
// which will eventually lead to a call to onAllDataRead() via a
// container thread.
if (!ContainerThreadMarker.isContainerThread()) {
coyoteRequest.action(ActionCode.DISPATCH_READ, null);
coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
}
return false;
}
// Checking for available data at the network level and registering for
// read can be done sequentially for HTTP/1.x and AJP as there is only
// ever a single thread processing the socket at any one time. However,
// for HTTP/2 there is one thread processing the connection and separate
// threads for each stream. For HTTP/2 the two operations have to be
// performed atomically else it is possible for the connection thread to
// read more data in to the buffer after the stream thread checks for
// available network data but before it registers for read.
if (availableInThisBuffer() > 0) {
return true;
}
AtomicBoolean result = new AtomicBoolean();
coyoteRequest.action(ActionCode.NB_READ_INTEREST, result);
return result.get();
}
boolean isBlocking() {
return coyoteRequest.getReadListener() == null;
}
// ------------------------------------------------- Bytes Handling Methods
/**
* Reads new bytes in the byte chunk.
*
* @throws IOException An underlying IOException occurred
*/
@Override
public int realReadBytes() throws IOException {
if (closed) {
return -1;
}
if (coyoteRequest == null) {
return -1;
}
if (state == INITIAL_STATE) {
state = BYTE_STATE;
}
try {
return coyoteRequest.doRead(this);
} catch (IOException ioe) {
// An IOException on a read is almost always due to
// the remote client aborting the request.
throw new ClientAbortException(ioe);
}
}
public int readByte() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (checkByteBufferEof()) {
return -1;
}
return bb.get() & 0xFF;
}
public int read(byte[] b, int off, int len) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (checkByteBufferEof()) {
return -1;
}
int n = Math.min(len, bb.remaining());
bb.get(b, off, n);
return n;
}
/**
* Transfers bytes from the buffer to the specified ByteBuffer. After the
* operation the position of the ByteBuffer will be returned to the one
* before the operation, the limit will be the position incremented by
* the number of the transferred bytes.
*
* @param to the ByteBuffer into which bytes are to be written.
* @return an integer specifying the actual number of bytes read, or -1 if
* the end of the stream is reached
* @throws IOException if an input or output exception has occurred
*/
public int read(ByteBuffer to) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (checkByteBufferEof()) {
return -1;
}
int n = Math.min(to.remaining(), bb.remaining());
int orgLimit = bb.limit();
bb.limit(bb.position() + n);
to.put(bb);
bb.limit(orgLimit);
to.limit(to.position()).position(to.position() - n);
return n;
}
// ------------------------------------------------- Chars Handling Methods
/**
* @param s New encoding value
*
* @deprecated This method will be removed in Tomcat 9.0.x
*/
@Deprecated
public void setEncoding(String s) {
enc = s;
}
public int realReadChars() throws IOException {
checkConverter();
boolean eof = false;
if (bb.remaining() <= 0) {
int nRead = realReadBytes();
if (nRead < 0) {
eof = true;
}
}
if (markPos == -1) {
clear(cb);
} else {
// Make sure there's enough space in the worst case
makeSpace(bb.remaining());
if ((cb.capacity() - cb.limit()) == 0 && bb.remaining() != 0) {
// We went over the limit
clear(cb);
markPos = -1;
}
}
state = CHAR_STATE;
conv.convert(bb, cb, this, eof);
if (cb.remaining() == 0 && eof) {
return -1;
} else {
return cb.remaining();
}
}
@Override
public int read() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (checkCharBufferEof()) {
return -1;
}
return cb.get();
}
@Override
public int read(char[] cbuf) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return read(cbuf, 0, cbuf.length);
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (checkCharBufferEof()) {
return -1;
}
int n = Math.min(len, cb.remaining());
cb.get(cbuf, off, n);
return n;
}
@Override
public long skip(long n) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (n < 0) {
throw new IllegalArgumentException();
}
long nRead = 0;
while (nRead < n) {
if (cb.remaining() >= n) {
cb.position(cb.position() + (int) n);
nRead = n;
} else {
nRead += cb.remaining();
cb.position(cb.limit());
int nb = realReadChars();
if (nb < 0) {
break;
}
}
}
return nRead;
}
@Override
public boolean ready() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (state == INITIAL_STATE) {
state = CHAR_STATE;
}
return (available() > 0);
}
@Override
public boolean markSupported() {
return true;
}
@Override
public void mark(int readAheadLimit) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (cb.remaining() <= 0) {
clear(cb);
} else {
if ((cb.capacity() > (2 * size)) && (cb.remaining()) < (cb.position())) {
cb.compact();
cb.flip();
}
}
readLimit = cb.position() + readAheadLimit + size;
markPos = cb.position();
}
@Override
public void reset() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (state == CHAR_STATE) {
if (markPos < 0) {
clear(cb);
markPos = -1;
throw new IOException();
} else {
cb.position(markPos);
}
} else {
clear(bb);
}
}
public void checkConverter() throws IOException {
if (conv != null) {
return;
}
Charset charset = null;
if (coyoteRequest != null) {
charset = coyoteRequest.getCharset();
}
if (charset == null) {
if (enc == null) {
charset = org.apache.coyote.Constants.DEFAULT_BODY_CHARSET;
} else {
charset = B2CConverter.getCharset(enc);
}
}
SynchronizedStack<B2CConverter> stack = encoders.get(charset);
if (stack == null) {
stack = new SynchronizedStack<>();
encoders.putIfAbsent(charset, stack);
stack = encoders.get(charset);
}
conv = stack.pop();
if (conv == null) {
conv = createConverter(charset);
}
}
private static B2CConverter createConverter(final Charset charset) throws IOException {
if (SecurityUtil.isPackageProtectionEnabled()) {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<B2CConverter>() {
@Override
public B2CConverter run() throws IOException {
return new B2CConverter(charset);
}
});
} catch (PrivilegedActionException ex) {
Exception e = ex.getException();
if (e instanceof IOException) {
throw (IOException) e;
} else {
throw new IOException(e);
}
}
} else {
return new B2CConverter(charset);
}
}
@Override
public void setByteBuffer(ByteBuffer buffer) {
bb = buffer;
}
@Override
public ByteBuffer getByteBuffer() {
return bb;
}
@Override
public void expand(int size) {
// no-op
}
private boolean checkByteBufferEof() throws IOException {
if (bb.remaining() == 0) {
int n = realReadBytes();
if (n < 0) {
return true;
}
}
return false;
}
private boolean checkCharBufferEof() throws IOException {
if (cb.remaining() == 0) {
int n = realReadChars();
if (n < 0) {
return true;
}
}
return false;
}
private void clear(Buffer buffer) {
buffer.rewind().limit(0);
}
private void makeSpace(int count) {
int desiredSize = cb.limit() + count;
if (desiredSize > readLimit) {
desiredSize = readLimit;
}
if (desiredSize <= cb.capacity()) {
return;
}
int newSize = 2 * cb.capacity();
if (desiredSize >= newSize) {
newSize = 2 * cb.capacity() + count;
}
if (newSize > readLimit) {
newSize = readLimit;
}
CharBuffer tmp = CharBuffer.allocate(newSize);
int oldPosition = cb.position();
cb.position(0);
tmp.put(cb);
tmp.flip();
tmp.position(oldPosition);
cb = tmp;
tmp = null;
}
}
16
View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : how2j
Copyright Apache License 2.0
Author : how2j
/**
* The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
* OutputBuffer, adapted to handle input instead of output. This allows complete
* recycling of the facade objects (the ServletInputStream and the
* BufferedReader).
*
* @author Remy Maucherat
*/
public clreplaced InputBuffer extends Reader implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel, CharChunk.CharOutputChannel {
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(Constants.Package);
// -------------------------------------------------------------- Constants
public static final String DEFAULT_ENCODING = org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
// The buffer can be used for byte[] and char[] reading
// ( this is needed to support ServletInputStream and BufferedReader )
public final int INITIAL_STATE = 0;
public final int CHAR_STATE = 1;
public final int BYTE_STATE = 2;
// ----------------------------------------------------- Instance Variables
/**
* The byte buffer.
*/
private final ByteChunk bb;
/**
* The chunk buffer.
*/
private CharChunk cb;
/**
* State of the output buffer.
*/
private int state = 0;
/**
* Flag which indicates if the input buffer is closed.
*/
private boolean closed = false;
/**
* Encoding to use.
*/
private String enc;
/**
* Encoder is set.
*/
private boolean gotEnc = false;
/**
* List of encoders.
*/
private final Map<String, B2CConverter> encoders = new ConcurrentHashMap<String, B2CConverter>();
/**
* Current byte to char converter.
*/
protected B2CConverter conv;
/**
* replacedociated Coyote request.
*/
private Request coyoteRequest;
/**
* Buffer position.
*/
private int markPos = -1;
/**
* Buffer size.
*/
private int size = -1;
// ----------------------------------------------------------- Constructors
/**
* Default constructor. Allocate the buffer with the default buffer size.
*/
public InputBuffer() {
this(DEFAULT_BUFFER_SIZE);
}
/**
* Alternate constructor which allows specifying the initial buffer size.
*
* @param size
* Buffer size to use
*/
public InputBuffer(int size) {
this.size = size;
bb = new ByteChunk(size);
bb.setLimit(size);
bb.setByteInputChannel(this);
cb = new CharChunk(size);
cb.setLimit(size);
cb.setOptimizedWrite(false);
cb.setCharInputChannel(this);
cb.setCharOutputChannel(this);
}
// ------------------------------------------------------------- Properties
/**
* replacedociated Coyote request.
*
* @param coyoteRequest
* replacedociated Coyote request
*/
public void setRequest(Request coyoteRequest) {
this.coyoteRequest = coyoteRequest;
}
/**
* Get replacedociated Coyote request.
*
* @return the replacedociated Coyote request
*/
@Deprecated
public Request getRequest() {
return this.coyoteRequest;
}
// --------------------------------------------------------- Public Methods
/**
* Recycle the output buffer.
*/
public void recycle() {
state = INITIAL_STATE;
// If usage of mark made the buffer too big, reallocate it
if (cb.getChars().length > size) {
cb = new CharChunk(size);
cb.setLimit(size);
cb.setOptimizedWrite(false);
cb.setCharInputChannel(this);
cb.setCharOutputChannel(this);
} else {
cb.recycle();
}
markPos = -1;
bb.recycle();
closed = false;
if (conv != null) {
conv.recycle();
}
gotEnc = false;
enc = null;
}
/**
* Clear cached encoders (to save memory for Comet requests).
*/
public void clearEncoders() {
encoders.clear();
}
/**
* Close the input buffer.
*
* @throws IOException
* An underlying IOException occurred
*/
@Override
public void close() throws IOException {
closed = true;
}
public int available() {
int available = 0;
if (state == BYTE_STATE) {
available = bb.getLength();
} else if (state == CHAR_STATE) {
available = cb.getLength();
}
if (available == 0) {
coyoteRequest.action(ActionCode.AVAILABLE, null);
available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
}
return available;
}
// ------------------------------------------------- Bytes Handling Methods
/**
* Reads new bytes in the byte chunk.
*
* @param cbuf
* Byte buffer to be written to the response
* @param off
* Offset
* @param len
* Length
*
* @throws IOException
* An underlying IOException occurred
*/
@Override
public int realReadBytes(byte[] cbuf, int off, int len) throws IOException {
if (closed) {
return -1;
}
if (coyoteRequest == null) {
return -1;
}
if (state == INITIAL_STATE) {
state = BYTE_STATE;
}
int result = coyoteRequest.doRead(bb);
return result;
}
public int readByte() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return bb.substract();
}
public int read(byte[] b, int off, int len) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return bb.substract(b, off, len);
}
// ------------------------------------------------- Chars Handling Methods
/**
* Since the converter will use append, it is possible to get chars to be
* removed from the buffer for "writing". Since the chars have already been
* read before, they are ignored. If a mark was set, then the mark is lost.
*/
@Override
public void realWriteChars(char[] c, int off, int len) throws IOException {
markPos = -1;
cb.setOffset(0);
cb.setEnd(0);
}
public void setEncoding(String s) {
enc = s;
}
@Override
public int realReadChars(char[] cbuf, int off, int len) throws IOException {
if (!gotEnc) {
setConverter();
}
boolean eof = false;
if (bb.getLength() <= 0) {
int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length);
if (nRead < 0) {
eof = true;
}
}
if (markPos == -1) {
cb.setOffset(0);
cb.setEnd(0);
} else {
// Make sure there's enough space in the worst case
cb.makeSpace(bb.getLength());
if ((cb.getBuffer().length - cb.getEnd()) == 0 && bb.getLength() != 0) {
// We went over the limit
cb.setOffset(0);
cb.setEnd(0);
markPos = -1;
}
}
state = CHAR_STATE;
conv.convert(bb, cb, eof);
if (cb.getLength() == 0 && eof) {
return -1;
} else {
return cb.getLength();
}
}
@Override
public int read() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return cb.substract();
}
@Override
public int read(char[] cbuf) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return read(cbuf, 0, cbuf.length);
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return cb.substract(cbuf, off, len);
}
@Override
public long skip(long n) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (n < 0) {
throw new IllegalArgumentException();
}
long nRead = 0;
while (nRead < n) {
if (cb.getLength() >= n) {
cb.setOffset(cb.getStart() + (int) n);
nRead = n;
} else {
nRead += cb.getLength();
cb.setOffset(cb.getEnd());
int toRead = 0;
if (cb.getChars().length < (n - nRead)) {
toRead = cb.getChars().length;
} else {
toRead = (int) (n - nRead);
}
int nb = realReadChars(cb.getChars(), 0, toRead);
if (nb < 0) {
break;
}
}
}
return nRead;
}
@Override
public boolean ready() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return (available() > 0);
}
@Override
public boolean markSupported() {
return true;
}
@Override
public void mark(int readAheadLimit) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (cb.getLength() <= 0) {
cb.setOffset(0);
cb.setEnd(0);
} else {
if ((cb.getBuffer().length > (2 * size)) && (cb.getLength()) < (cb.getStart())) {
System.arraycopy(cb.getBuffer(), cb.getStart(), cb.getBuffer(), 0, cb.getLength());
cb.setEnd(cb.getLength());
cb.setOffset(0);
}
}
cb.setLimit(cb.getStart() + readAheadLimit + size);
markPos = cb.getStart();
}
@Override
public void reset() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (state == CHAR_STATE) {
if (markPos < 0) {
cb.recycle();
markPos = -1;
throw new IOException();
} else {
cb.setOffset(markPos);
}
} else {
bb.recycle();
}
}
public void checkConverter() throws IOException {
if (!gotEnc) {
setConverter();
}
}
protected void setConverter() throws IOException {
if (coyoteRequest != null) {
enc = coyoteRequest.getCharacterEncoding();
}
gotEnc = true;
if (enc == null) {
enc = DEFAULT_ENCODING;
}
conv = encoders.get(enc);
if (conv == null) {
if (SecurityUtil.isPackageProtectionEnabled()) {
try {
conv = AccessController.doPrivileged(new PrivilegedExceptionAction<B2CConverter>() {
@Override
public B2CConverter run() throws IOException {
return new B2CConverter(enc);
}
});
} catch (PrivilegedActionException ex) {
Exception e = ex.getException();
if (e instanceof IOException) {
throw (IOException) e;
}
}
} else {
conv = new B2CConverter(enc);
}
encoders.put(enc, conv);
}
}
}
16
View Complete Implementation : AbstractInputBuffer.java
Copyright Apache License 2.0
Author : how2j
Copyright Apache License 2.0
Author : how2j
public abstract clreplaced AbstractInputBuffer<S> implements InputBuffer {
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(Constants.Package);
/**
* replacedociated Coyote request.
*/
protected Request request;
/**
* Headers of the replacedociated 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[0] is always the "chunked" filter.
*/
protected InputFilter[] filterLibrary;
/**
* Active filters (in order).
*/
protected InputFilter[] activeFilters;
/**
* Index of the last active filter.
*/
protected int lastActiveFilter;
// ------------------------------------------------------------- Properties
/**
* Add an input filter to the filter library.
*/
public void addFilter(InputFilter filter) {
// FIXME: Check for null ?
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;
protected abstract boolean fill(boolean block) throws IOException;
protected abstract void init(SocketWrapper<S> socketWrapper, AbstractEndpoint<S> endpoint) throws IOException;
// --------------------------------------------------------- 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() {
int result = (lastValid - pos);
if ((result == 0) && (lastActiveFilter >= 0)) {
for (int i = 0; (result == 0) && (i <= lastActiveFilter); i++) {
result = activeFilters[i].available();
}
}
return result;
}
// ---------------------------------------------------- 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);
}
}
16
View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : how2j
Copyright Apache License 2.0
Author : how2j
// ---------------------------------------------------- InputBuffer Methods
/**
* Read bytes.
*
* @return If the filter does request length control, this value is
* significant; it should be the number of bytes consumed from the
* buffer, up until the end of the current request body, or the
* buffer length, whichever is greater. If the filter does not do
* request body length control, the returned value should be -1.
*/
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
if (endChunk) {
return -1;
}
checkError();
if (needCRLFParse) {
needCRLFParse = false;
parseCRLF(false);
}
if (remaining <= 0) {
if (!parseChunkHeader()) {
throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
}
if (endChunk) {
parseEndChunk();
return -1;
}
}
int result = 0;
if (pos >= lastValid) {
if (readBytes() < 0) {
throwIOException(sm.getString("chunkedInputFilter.eos"));
}
}
if (remaining > (lastValid - pos)) {
result = lastValid - pos;
remaining = remaining - result;
chunk.setBytes(buf, pos, result);
pos = lastValid;
} else {
result = remaining;
chunk.setBytes(buf, pos, remaining);
pos = pos + remaining;
remaining = 0;
// we need a CRLF
if ((pos + 1) >= lastValid) {
// if we call parseCRLF we overrun the buffer here
// so we defer it to the next call BZ 11117
needCRLFParse = true;
} else {
// parse the CRLF immediately
parseCRLF(false);
}
}
return result;
}
16
View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : tryandcatch
Copyright Apache License 2.0
Author : tryandcatch
/**
* The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
* OutputBuffer, adapted to handle input instead of output. This allows
* complete recycling of the facade objects (the ServletInputStream and the
* BufferedReader).
*
* @author Remy Maucherat
*/
public clreplaced InputBuffer extends Reader implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel, CharChunk.CharOutputChannel {
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(Constants.Package);
// -------------------------------------------------------------- Constants
public static final String DEFAULT_ENCODING = org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
// The buffer can be used for byte[] and char[] reading
// ( this is needed to support ServletInputStream and BufferedReader )
public final int INITIAL_STATE = 0;
public final int CHAR_STATE = 1;
public final int BYTE_STATE = 2;
// ----------------------------------------------------- Instance Variables
/**
* The byte buffer.
*/
private final ByteChunk bb;
/**
* The chunk buffer.
*/
private CharChunk cb;
/**
* State of the output buffer.
*/
private int state = 0;
/**
* Flag which indicates if the input buffer is closed.
*/
private boolean closed = false;
/**
* Encoding to use.
*/
private String enc;
/**
* Encoder is set.
*/
private boolean gotEnc = false;
/**
* List of encoders.
*/
protected final ConcurrentHashMap<String, B2CConverter> encoders = new ConcurrentHashMap<String, B2CConverter>();
/**
* Current byte to char converter.
*/
protected B2CConverter conv;
/**
* replacedociated Coyote request.
*/
private Request coyoteRequest;
/**
* Buffer position.
*/
private int markPos = -1;
/**
* Buffer size.
*/
private int size = -1;
// ----------------------------------------------------------- Constructors
/**
* Default constructor. Allocate the buffer with the default buffer size.
*/
public InputBuffer() {
this(DEFAULT_BUFFER_SIZE);
}
/**
* Alternate constructor which allows specifying the initial buffer size.
*
* @param size Buffer size to use
*/
public InputBuffer(int size) {
this.size = size;
bb = new ByteChunk(size);
bb.setLimit(size);
bb.setByteInputChannel(this);
cb = new CharChunk(size);
cb.setLimit(size);
cb.setOptimizedWrite(false);
cb.setCharInputChannel(this);
cb.setCharOutputChannel(this);
}
// ------------------------------------------------------------- Properties
/**
* replacedociated Coyote request.
*
* @param coyoteRequest replacedociated Coyote request
*/
public void setRequest(Request coyoteRequest) {
this.coyoteRequest = coyoteRequest;
}
/**
* Get replacedociated Coyote request.
*
* @return the replacedociated Coyote request
*/
@Deprecated
public Request getRequest() {
return this.coyoteRequest;
}
// --------------------------------------------------------- Public Methods
/**
* Recycle the output buffer.
*/
public void recycle() {
state = INITIAL_STATE;
// If usage of mark made the buffer too big, reallocate it
if (cb.getChars().length > size) {
cb = new CharChunk(size);
cb.setLimit(size);
cb.setOptimizedWrite(false);
cb.setCharInputChannel(this);
cb.setCharOutputChannel(this);
} else {
cb.recycle();
}
markPos = -1;
bb.recycle();
closed = false;
if (conv != null) {
conv.recycle();
}
gotEnc = false;
enc = null;
}
/**
* Clear cached encoders (to save memory for Comet requests).
*/
public void clearEncoders() {
encoders.clear();
}
/**
* Close the input buffer.
*
* @throws IOException An underlying IOException occurred
*/
@Override
public void close() throws IOException {
closed = true;
}
public int available() {
int available = 0;
if (state == BYTE_STATE) {
available = bb.getLength();
} else if (state == CHAR_STATE) {
available = cb.getLength();
}
if (available == 0) {
coyoteRequest.action(ActionCode.AVAILABLE, null);
available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
}
return available;
}
// ------------------------------------------------- Bytes Handling Methods
/**
* Reads new bytes in the byte chunk.
*
* @param cbuf Byte buffer to be written to the response
* @param off Offset
* @param len Length
*
* @throws IOException An underlying IOException occurred
*/
@Override
public int realReadBytes(byte[] cbuf, int off, int len) throws IOException {
if (closed) {
return -1;
}
if (coyoteRequest == null) {
return -1;
}
if (state == INITIAL_STATE) {
state = BYTE_STATE;
}
int result = coyoteRequest.doRead(bb);
return result;
}
public int readByte() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return bb.substract();
}
public int read(byte[] b, int off, int len) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return bb.substract(b, off, len);
}
// ------------------------------------------------- Chars Handling Methods
/**
* Since the converter will use append, it is possible to get chars to
* be removed from the buffer for "writing". Since the chars have already
* been read before, they are ignored. If a mark was set, then the
* mark is lost.
*/
@Override
public void realWriteChars(char[] c, int off, int len) throws IOException {
markPos = -1;
cb.setOffset(0);
cb.setEnd(0);
}
public void setEncoding(String s) {
enc = s;
}
@Override
public int realReadChars(char[] cbuf, int off, int len) throws IOException {
if (!gotEnc) {
setConverter();
}
boolean eof = false;
if (bb.getLength() <= 0) {
int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length);
if (nRead < 0) {
eof = true;
}
}
if (markPos == -1) {
cb.setOffset(0);
cb.setEnd(0);
} else {
// Make sure there's enough space in the worst case
cb.makeSpace(bb.getLength());
if ((cb.getBuffer().length - cb.getEnd()) == 0) {
// We went over the limit
cb.setOffset(0);
cb.setEnd(0);
markPos = -1;
}
}
state = CHAR_STATE;
conv.convert(bb, cb, eof);
if (cb.getLength() == 0 && eof) {
return -1;
} else {
return cb.getLength();
}
}
@Override
public int read() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return cb.substract();
}
@Override
public int read(char[] cbuf) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return read(cbuf, 0, cbuf.length);
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return cb.substract(cbuf, off, len);
}
@Override
public long skip(long n) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (n < 0) {
throw new IllegalArgumentException();
}
long nRead = 0;
while (nRead < n) {
if (cb.getLength() >= n) {
cb.setOffset(cb.getStart() + (int) n);
nRead = n;
} else {
nRead += cb.getLength();
cb.setOffset(cb.getEnd());
int toRead = 0;
if (cb.getChars().length < (n - nRead)) {
toRead = cb.getChars().length;
} else {
toRead = (int) (n - nRead);
}
int nb = realReadChars(cb.getChars(), 0, toRead);
if (nb < 0) {
break;
}
}
}
return nRead;
}
@Override
public boolean ready() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return (available() > 0);
}
@Override
public boolean markSupported() {
return true;
}
@Override
public void mark(int readAheadLimit) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (cb.getLength() <= 0) {
cb.setOffset(0);
cb.setEnd(0);
} else {
if ((cb.getBuffer().length > (2 * size)) && (cb.getLength()) < (cb.getStart())) {
System.arraycopy(cb.getBuffer(), cb.getStart(), cb.getBuffer(), 0, cb.getLength());
cb.setEnd(cb.getLength());
cb.setOffset(0);
}
}
cb.setLimit(cb.getStart() + readAheadLimit + size);
markPos = cb.getStart();
}
@Override
public void reset() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (state == CHAR_STATE) {
if (markPos < 0) {
cb.recycle();
markPos = -1;
throw new IOException();
} else {
cb.setOffset(markPos);
}
} else {
bb.recycle();
}
}
public void checkConverter() throws IOException {
if (!gotEnc) {
setConverter();
}
}
protected void setConverter() throws IOException {
if (coyoteRequest != null) {
enc = coyoteRequest.getCharacterEncoding();
}
gotEnc = true;
if (enc == null) {
enc = DEFAULT_ENCODING;
}
conv = encoders.get(enc);
if (conv == null) {
if (SecurityUtil.isPackageProtectionEnabled()) {
try {
conv = AccessController.doPrivileged(new PrivilegedExceptionAction<B2CConverter>() {
@Override
public B2CConverter run() throws IOException {
return new B2CConverter(enc);
}
});
} catch (PrivilegedActionException ex) {
Exception e = ex.getException();
if (e instanceof IOException) {
throw (IOException) e;
}
}
} else {
conv = new B2CConverter(enc);
}
encoders.put(enc, conv);
}
}
}
16
View Complete Implementation : AbstractInputBuffer.java
Copyright Apache License 2.0
Author : tryandcatch
Copyright Apache License 2.0
Author : tryandcatch
public abstract clreplaced AbstractInputBuffer<S> implements InputBuffer {
protected static final boolean[] HTTP_TOKEN_CHAR = new boolean[128];
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(Constants.Package);
static {
for (int i = 0; i < 128; i++) {
if (i < 32) {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == 127) {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '(') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == ')') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '<') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '>') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '@') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == ',') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == ';') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == ':') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '\\') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '\"') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '/') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '[') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == ']') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '?') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '=') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '{') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '}') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == ' ') {
HTTP_TOKEN_CHAR[i] = false;
} else {
HTTP_TOKEN_CHAR[i] = true;
}
}
}
/**
* replacedociated Coyote request.
*/
protected Request request;
/**
* Headers of the replacedociated 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[0] is always the "chunked" filter.
*/
protected InputFilter[] filterLibrary;
/**
* Active filters (in order).
*/
protected InputFilter[] activeFilters;
/**
* Index of the last active filter.
*/
protected int lastActiveFilter;
// ------------------------------------------------------------- Properties
/**
* Add an input filter to the filter library.
*/
public void addFilter(InputFilter filter) {
// FIXME: Check for null ?
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;
protected abstract boolean fill(boolean block) throws IOException;
protected abstract void init(SocketWrapper<S> socketWrapper, AbstractEndpoint<S> endpoint) throws IOException;
// --------------------------------------------------------- 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() {
int result = (lastValid - pos);
if ((result == 0) && (lastActiveFilter >= 0)) {
for (int i = 0; (result == 0) && (i <= lastActiveFilter); i++) {
result = activeFilters[i].available();
}
}
return result;
}
// ---------------------------------------------------- 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);
}
}
16
View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : tryandcatch
Copyright Apache License 2.0
Author : tryandcatch
/**
* Chunked input filter. Parses chunked data according to
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
*
* @author Remy Maucherat
* @author Filip Hanik
*/
public clreplaced ChunkedInputFilter implements InputFilter {
private static final StringManager sm = StringManager.getManager(ChunkedInputFilter.clreplaced.getPackage().getName());
// -------------------------------------------------------------- Constants
protected static final String ENCODING_NAME = "chunked";
protected static final ByteChunk ENCODING = new ByteChunk();
// ----------------------------------------------------- Static Initializer
static {
ENCODING.setBytes(ENCODING_NAME.getBytes(Charset.defaultCharset()), 0, ENCODING_NAME.length());
}
// ----------------------------------------------------- Instance Variables
/**
* Next buffer in the pipeline.
*/
protected InputBuffer buffer;
/**
* Number of bytes remaining in the current chunk.
*/
protected int remaining = 0;
/**
* Position in the buffer.
*/
protected int pos = 0;
/**
* Last valid byte in the buffer.
*/
protected int lastValid = 0;
/**
* Read bytes buffer.
*/
protected byte[] buf = null;
/**
* Byte chunk used to read bytes.
*/
protected ByteChunk readChunk = new ByteChunk();
/**
* Flag set to true when the end chunk has been read.
*/
protected boolean endChunk = false;
/**
* Byte chunk used to store trailing headers.
*/
protected ByteChunk trailingHeaders = new ByteChunk();
/**
* Flag set to true if the next call to doRead() must parse a CRLF pair
* before doing anything else.
*/
protected boolean needCRLFParse = false;
/**
* Request being parsed.
*/
private Request request;
/**
* Limit for extension size.
*/
private final long maxExtensionSize;
/**
* Limit for trailer size.
*/
private final int maxTrailerSize;
/**
* Size of extensions processed for this request.
*/
private long extensionSize;
private final int maxSwallowSize;
/**
* Flag that indicates if an error has occurred.
*/
private boolean error;
private final Set<String> allowedTrailerHeaders;
// ----------------------------------------------------------- Constructors
public ChunkedInputFilter(int maxTrailerSize, Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize) {
this.trailingHeaders.setLimit(maxTrailerSize);
this.allowedTrailerHeaders = allowedTrailerHeaders;
this.maxExtensionSize = maxExtensionSize;
this.maxTrailerSize = maxTrailerSize;
this.maxSwallowSize = maxSwallowSize;
}
// ---------------------------------------------------- InputBuffer Methods
/**
* Read bytes.
*
* @return If the filter does request length control, this value is
* significant; it should be the number of bytes consumed from the buffer,
* up until the end of the current request body, or the buffer length,
* whichever is greater. If the filter does not do request body length
* control, the returned value should be -1.
*/
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
if (endChunk) {
return -1;
}
checkError();
if (needCRLFParse) {
needCRLFParse = false;
parseCRLF(false);
}
if (remaining <= 0) {
if (!parseChunkHeader()) {
throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
}
if (endChunk) {
parseEndChunk();
return -1;
}
}
int result = 0;
if (pos >= lastValid) {
if (readBytes() < 0) {
throwIOException(sm.getString("chunkedInputFilter.eos"));
}
}
if (remaining > (lastValid - pos)) {
result = lastValid - pos;
remaining = remaining - result;
chunk.setBytes(buf, pos, result);
pos = lastValid;
} else {
result = remaining;
chunk.setBytes(buf, pos, remaining);
pos = pos + remaining;
remaining = 0;
// we need a CRLF
if ((pos + 1) >= lastValid) {
// if we call parseCRLF we overrun the buffer here
// so we defer it to the next call BZ 11117
needCRLFParse = true;
} else {
// parse the CRLF immediately
parseCRLF(false);
}
}
return result;
}
// ---------------------------------------------------- InputFilter Methods
/**
* Read the content length from the request.
*/
@Override
public void setRequest(Request request) {
this.request = request;
}
/**
* End the current request.
*/
@Override
public long end() throws IOException {
long swallowed = 0;
int read = 0;
// Consume extra bytes : parse the stream until the end chunk is found
while ((read = doRead(readChunk, null)) >= 0) {
swallowed += read;
if (maxSwallowSize > -1 && swallowed > maxSwallowSize) {
throwIOException(sm.getString("inputFilter.maxSwallow"));
}
}
// Return the number of extra bytes which were consumed
return lastValid - pos;
}
/**
* Amount of bytes still available in a buffer.
*/
@Override
public int available() {
return lastValid - pos;
}
/**
* Set the next buffer in the filter pipeline.
*/
@Override
public void setBuffer(InputBuffer buffer) {
this.buffer = buffer;
}
/**
* Make the filter ready to process the next request.
*/
@Override
public void recycle() {
remaining = 0;
pos = 0;
lastValid = 0;
endChunk = false;
needCRLFParse = false;
trailingHeaders.recycle();
trailingHeaders.setLimit(maxTrailerSize);
extensionSize = 0;
error = false;
}
/**
* Return the name of the replacedociated encoding; Here, the value is
* "idenreplacedy".
*/
@Override
public ByteChunk getEncodingName() {
return ENCODING;
}
// ------------------------------------------------------ Protected Methods
/**
* Read bytes from the previous buffer.
*/
protected int readBytes() throws IOException {
int nRead = buffer.doRead(readChunk, null);
pos = readChunk.getStart();
lastValid = pos + nRead;
buf = readChunk.getBytes();
return nRead;
}
/**
* Parse the header of a chunk.
* A chunk header can look like one of the following:<br />
* A10CRLF<br />
* F23;chunk-extension to be ignoredCRLF
*
* <p>
* The letters before CRLF or ';' (whatever comes first) must be valid hex
* digits. We should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid
* header according to the spec.
*/
protected boolean parseChunkHeader() throws IOException {
int result = 0;
boolean eol = false;
int readDigit = 0;
boolean extension = false;
while (!eol) {
if (pos >= lastValid) {
if (readBytes() <= 0)
return false;
}
if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
parseCRLF(false);
eol = true;
} else if (buf[pos] == Constants.SEMI_COLON && !extension) {
// First semi-colon marks the start of the extension. Further
// semi-colons may appear to separate multiple chunk-extensions.
// These need to be processed as part of parsing the extensions.
extension = true;
extensionSize++;
} else if (!extension) {
// don't read data after the trailer
int charValue = HexUtils.getDec(buf[pos]);
if (charValue != -1 && readDigit < 8) {
readDigit++;
result = (result << 4) | charValue;
} else {
// we shouldn't allow invalid, non hex characters
// in the chunked header
return false;
}
} else {
// Extension 'parsing'
// Note that the chunk-extension is neither parsed nor
// validated. Currently it is simply ignored.
extensionSize++;
if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
}
}
// Parsing the CRLF increments pos
if (!eol) {
pos++;
}
}
if (readDigit == 0 || result < 0) {
return false;
}
if (result == 0) {
endChunk = true;
}
remaining = result;
return true;
}
/**
* Parse CRLF at end of chunk.
* @deprecated Use {@link #parseCRLF(boolean)}
*/
@Deprecated
protected boolean parseCRLF() throws IOException {
parseCRLF(false);
return true;
}
/**
* Parse CRLF at end of chunk.
*
* @param tolerant Should tolerant parsing (LF and CRLF) be used? This
* is recommended (RFC2616, section 19.3) for message
* headers.
*/
protected void parseCRLF(boolean tolerant) throws IOException {
boolean eol = false;
boolean crfound = false;
while (!eol) {
if (pos >= lastValid) {
if (readBytes() <= 0) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
}
}
if (buf[pos] == Constants.CR) {
if (crfound) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
}
crfound = true;
} else if (buf[pos] == Constants.LF) {
if (!tolerant && !crfound) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoCR"));
}
eol = true;
} else {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlf"));
}
pos++;
}
}
/**
* Parse end chunk data.
*/
protected void parseEndChunk() throws IOException {
// Handle optional trailer headers
while (parseHeader()) {
// Loop until we run out of headers
}
}
private boolean parseHeader() throws IOException {
MimeHeaders headers = request.getMimeHeaders();
byte chr = 0;
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
// CRLF terminates the request
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(false);
return false;
}
// Mark the current buffer position
int startPos = trailingHeaders.getEnd();
//
// Reading the header name
// Header name is always US-ASCII
//
boolean colon = false;
while (!colon) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
if ((chr >= Constants.A) && (chr <= Constants.Z)) {
chr = (byte) (chr - Constants.LC_OFFSET);
}
if (chr == Constants.COLON) {
colon = true;
} else {
trailingHeaders.append(chr);
}
pos++;
}
int colonPos = trailingHeaders.getEnd();
//
// Reading the header value (which can be spanned over multiple lines)
//
boolean eol = false;
boolean validLine = true;
int lastSignificantChar = 0;
while (validLine) {
boolean space = true;
// Skipping spaces
while (space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
if ((chr == Constants.SP) || (chr == Constants.HT)) {
pos++;
// If we swallow whitespace, make sure it counts towards the
// limit placed on trailing header size
int newlimit = trailingHeaders.getLimit() - 1;
if (trailingHeaders.getEnd() > newlimit) {
throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
}
trailingHeaders.setLimit(newlimit);
} else {
space = false;
}
}
// Reading bytes until the end of the line
while (!eol) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(true);
eol = true;
} else if (chr == Constants.SP) {
trailingHeaders.append(chr);
} else {
trailingHeaders.append(chr);
lastSignificantChar = trailingHeaders.getEnd();
}
if (!eol) {
pos++;
}
}
// 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 (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
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)
trailingHeaders.append(chr);
}
}
String headerName = new String(trailingHeaders.getBytes(), startPos, colonPos - startPos, "ISO_8859_1");
if (allowedTrailerHeaders.contains(headerName.trim().toLowerCase(Locale.ENGLISH))) {
MessageBytes headerValue = headers.addValue(headerName);
// Set the header value
headerValue.setBytes(trailingHeaders.getBytes(), colonPos, lastSignificantChar - colonPos);
}
return true;
}
private void throwIOException(String msg) throws IOException {
error = true;
throw new IOException(msg);
}
private void throwEOFException(String msg) throws IOException {
error = true;
throw new EOFException(msg);
}
private void checkError() throws IOException {
if (error) {
throw new IOException(sm.getString("chunkedInputFilter.error"));
}
}
}
16
View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : codefollower
Copyright Apache License 2.0
Author : codefollower
// ---------------------------------------------------- InputBuffer Methods
/**
* Read bytes.
*
* @return If the filter does request length control, this value is
* significant; it should be the number of bytes consumed from the buffer,
* up until the end of the current request body, or the buffer length,
* whichever is greater. If the filter does not do request body length
* control, the returned value should be -1.
*/
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
if (endChunk)
return -1;
if (needCRLFParse) {
needCRLFParse = false;
parseCRLF(false);
}
if (remaining <= 0) {
if (!parseChunkHeader()) {
throw new IOException("Invalid chunk header");
}
if (endChunk) {
parseEndChunk();
return -1;
}
}
int result = 0;
if (pos >= lastValid) {
if (readBytes() < 0) {
throw new IOException("Unexpected end of stream whilst reading request body");
}
}
if (remaining > (lastValid - pos)) {
result = lastValid - pos;
remaining = remaining - result;
chunk.setBytes(buf, pos, result);
pos = lastValid;
} else {
result = remaining;
chunk.setBytes(buf, pos, remaining);
pos = pos + remaining;
remaining = 0;
// we need a CRLF
if ((pos + 1) >= lastValid) {
// if we call parseCRLF we overrun the buffer here
// so we defer it to the next call BZ 11117
needCRLFParse = true;
} else {
// parse the CRLF immediately
parseCRLF(false);
}
}
return result;
}
16
View Complete Implementation : AbstractInputBuffer.java
Copyright Apache License 2.0
Author : wangyingjie
Copyright Apache License 2.0
Author : wangyingjie
public abstract clreplaced AbstractInputBuffer<S> implements InputBuffer {
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(Constants.Package);
/**
* replacedociated Coyote request.
*/
protected Request request;
/**
* Headers of the replacedociated 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[0] is always the "chunked" filter.
*/
protected InputFilter[] filterLibrary;
/**
* Active filters (in order).
*/
protected InputFilter[] activeFilters;
/**
* Index of the last active filter.
*/
protected int lastActiveFilter;
// ------------------------------------------------------------- Properties
/**
* Add an input filter to the filter library.
*/
public void addFilter(InputFilter filter) {
// FIXME: Check for null ?
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;
protected abstract boolean fill(boolean block) throws IOException;
protected abstract void init(SocketWrapper<S> socketWrapper, AbstractEndpoint<S> endpoint) throws IOException;
// --------------------------------------------------------- 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() {
int result = (lastValid - pos);
if ((result == 0) && (lastActiveFilter >= 0)) {
for (int i = 0; (result == 0) && (i <= lastActiveFilter); i++) {
result = activeFilters[i].available();
}
}
return result;
}
// ---------------------------------------------------- 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);
}
}
16
View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : wangyingjie
Copyright Apache License 2.0
Author : wangyingjie
/**
* Chunked input filter. Parses chunked data according to
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
*
* @author Remy Maucherat
* @author Filip Hanik
*/
public clreplaced ChunkedInputFilter implements InputFilter {
private static final StringManager sm = StringManager.getManager(ChunkedInputFilter.clreplaced.getPackage().getName());
// -------------------------------------------------------------- Constants
protected static final String ENCODING_NAME = "chunked";
protected static final ByteChunk ENCODING = new ByteChunk();
// ----------------------------------------------------- Static Initializer
static {
ENCODING.setBytes(ENCODING_NAME.getBytes(Charset.defaultCharset()), 0, ENCODING_NAME.length());
}
// ----------------------------------------------------- Instance Variables
/**
* Next buffer in the pipeline.
*/
protected InputBuffer buffer;
/**
* Number of bytes remaining in the current chunk.
*/
protected int remaining = 0;
/**
* Position in the buffer.
*/
protected int pos = 0;
/**
* Last valid byte in the buffer.
*/
protected int lastValid = 0;
/**
* Read bytes buffer.
*/
protected byte[] buf = null;
/**
* Byte chunk used to read bytes.
*/
protected ByteChunk readChunk = new ByteChunk();
/**
* Flag set to true when the end chunk has been read.
*/
protected boolean endChunk = false;
/**
* Byte chunk used to store trailing headers.
*/
protected ByteChunk trailingHeaders = new ByteChunk();
/**
* Flag set to true if the next call to doRead() must parse a CRLF pair
* before doing anything else.
*/
protected boolean needCRLFParse = false;
/**
* Request being parsed.
*/
private Request request;
/**
* Limit for extension size.
*/
private final long maxExtensionSize;
/**
* Limit for trailer size.
*/
private final int maxTrailerSize;
/**
* Size of extensions processed for this request.
*/
private long extensionSize;
private final int maxSwallowSize;
/**
* Flag that indicates if an error has occurred.
*/
private boolean error;
private final Set<String> allowedTrailerHeaders;
// ----------------------------------------------------------- Constructors
public ChunkedInputFilter(int maxTrailerSize, Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize) {
this.trailingHeaders.setLimit(maxTrailerSize);
this.allowedTrailerHeaders = allowedTrailerHeaders;
this.maxExtensionSize = maxExtensionSize;
this.maxTrailerSize = maxTrailerSize;
this.maxSwallowSize = maxSwallowSize;
}
// ---------------------------------------------------- InputBuffer Methods
/**
* Read bytes.
*
* @return If the filter does request length control, this value is
* significant; it should be the number of bytes consumed from the buffer,
* up until the end of the current request body, or the buffer length,
* whichever is greater. If the filter does not do request body length
* control, the returned value should be -1.
*/
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
if (endChunk) {
return -1;
}
checkError();
if (needCRLFParse) {
needCRLFParse = false;
parseCRLF(false);
}
if (remaining <= 0) {
if (!parseChunkHeader()) {
throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
}
if (endChunk) {
parseEndChunk();
return -1;
}
}
int result = 0;
if (pos >= lastValid) {
if (readBytes() < 0) {
throwIOException(sm.getString("chunkedInputFilter.eos"));
}
}
if (remaining > (lastValid - pos)) {
result = lastValid - pos;
remaining = remaining - result;
chunk.setBytes(buf, pos, result);
pos = lastValid;
} else {
result = remaining;
chunk.setBytes(buf, pos, remaining);
pos = pos + remaining;
remaining = 0;
// we need a CRLF
if ((pos + 1) >= lastValid) {
// if we call parseCRLF we overrun the buffer here
// so we defer it to the next call BZ 11117
needCRLFParse = true;
} else {
// parse the CRLF immediately
parseCRLF(false);
}
}
return result;
}
// ---------------------------------------------------- InputFilter Methods
/**
* Read the content length from the request.
*/
@Override
public void setRequest(Request request) {
this.request = request;
}
/**
* End the current request.
*/
@Override
public long end() throws IOException {
long swallowed = 0;
int read = 0;
// Consume extra bytes : parse the stream until the end chunk is found
while ((read = doRead(readChunk, null)) >= 0) {
swallowed += read;
if (maxSwallowSize > -1 && swallowed > maxSwallowSize) {
throwIOException(sm.getString("inputFilter.maxSwallow"));
}
}
// Return the number of extra bytes which were consumed
return lastValid - pos;
}
/**
* Amount of bytes still available in a buffer.
*/
@Override
public int available() {
return lastValid - pos;
}
/**
* Set the next buffer in the filter pipeline.
*/
@Override
public void setBuffer(InputBuffer buffer) {
this.buffer = buffer;
}
/**
* Make the filter ready to process the next request.
*/
@Override
public void recycle() {
remaining = 0;
pos = 0;
lastValid = 0;
endChunk = false;
needCRLFParse = false;
trailingHeaders.recycle();
trailingHeaders.setLimit(maxTrailerSize);
extensionSize = 0;
error = false;
}
/**
* Return the name of the replacedociated encoding; Here, the value is
* "idenreplacedy".
*/
@Override
public ByteChunk getEncodingName() {
return ENCODING;
}
// ------------------------------------------------------ Protected Methods
/**
* Read bytes from the previous buffer.
*/
protected int readBytes() throws IOException {
int nRead = buffer.doRead(readChunk, null);
pos = readChunk.getStart();
lastValid = pos + nRead;
buf = readChunk.getBytes();
return nRead;
}
/**
* Parse the header of a chunk.
* A chunk header can look like one of the following:<br />
* A10CRLF<br />
* F23;chunk-extension to be ignoredCRLF
*
* <p>
* The letters before CRLF or ';' (whatever comes first) must be valid hex
* digits. We should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid
* header according to the spec.
*/
protected boolean parseChunkHeader() throws IOException {
int result = 0;
boolean eol = false;
int readDigit = 0;
boolean extension = false;
while (!eol) {
if (pos >= lastValid) {
if (readBytes() <= 0)
return false;
}
if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
parseCRLF(false);
eol = true;
} else if (buf[pos] == Constants.SEMI_COLON && !extension) {
// First semi-colon marks the start of the extension. Further
// semi-colons may appear to separate multiple chunk-extensions.
// These need to be processed as part of parsing the extensions.
extension = true;
extensionSize++;
} else if (!extension) {
// don't read data after the trailer
int charValue = HexUtils.getDec(buf[pos]);
if (charValue != -1 && readDigit < 8) {
readDigit++;
result = (result << 4) | charValue;
} else {
// we shouldn't allow invalid, non hex characters
// in the chunked header
return false;
}
} else {
// Extension 'parsing'
// Note that the chunk-extension is neither parsed nor
// validated. Currently it is simply ignored.
extensionSize++;
if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
}
}
// Parsing the CRLF increments pos
if (!eol) {
pos++;
}
}
if (readDigit == 0 || result < 0) {
return false;
}
if (result == 0) {
endChunk = true;
}
remaining = result;
return true;
}
/**
* Parse CRLF at end of chunk.
* @deprecated Use {@link #parseCRLF(boolean)}
*/
@Deprecated
protected boolean parseCRLF() throws IOException {
parseCRLF(false);
return true;
}
/**
* Parse CRLF at end of chunk.
*
* @param tolerant Should tolerant parsing (LF and CRLF) be used? This
* is recommended (RFC2616, section 19.3) for message
* headers.
*/
protected void parseCRLF(boolean tolerant) throws IOException {
boolean eol = false;
boolean crfound = false;
while (!eol) {
if (pos >= lastValid) {
if (readBytes() <= 0) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
}
}
if (buf[pos] == Constants.CR) {
if (crfound) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
}
crfound = true;
} else if (buf[pos] == Constants.LF) {
if (!tolerant && !crfound) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoCR"));
}
eol = true;
} else {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlf"));
}
pos++;
}
}
/**
* Parse end chunk data.
*/
protected void parseEndChunk() throws IOException {
// Handle optional trailer headers
while (parseHeader()) {
// Loop until we run out of headers
}
}
private boolean parseHeader() throws IOException {
MimeHeaders headers = request.getMimeHeaders();
byte chr = 0;
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
// CRLF terminates the request
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(false);
return false;
}
// Mark the current buffer position
int startPos = trailingHeaders.getEnd();
//
// Reading the header name
// Header name is always US-ASCII
//
boolean colon = false;
while (!colon) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
if ((chr >= Constants.A) && (chr <= Constants.Z)) {
chr = (byte) (chr - Constants.LC_OFFSET);
}
if (chr == Constants.COLON) {
colon = true;
} else {
trailingHeaders.append(chr);
}
pos++;
}
int colonPos = trailingHeaders.getEnd();
//
// Reading the header value (which can be spanned over multiple lines)
//
boolean eol = false;
boolean validLine = true;
int lastSignificantChar = 0;
while (validLine) {
boolean space = true;
// Skipping spaces
while (space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
if ((chr == Constants.SP) || (chr == Constants.HT)) {
pos++;
// If we swallow whitespace, make sure it counts towards the
// limit placed on trailing header size
int newlimit = trailingHeaders.getLimit() - 1;
if (trailingHeaders.getEnd() > newlimit) {
throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
}
trailingHeaders.setLimit(newlimit);
} else {
space = false;
}
}
// Reading bytes until the end of the line
while (!eol) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(true);
eol = true;
} else if (chr == Constants.SP) {
trailingHeaders.append(chr);
} else {
trailingHeaders.append(chr);
lastSignificantChar = trailingHeaders.getEnd();
}
if (!eol) {
pos++;
}
}
// 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 (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
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)
trailingHeaders.append(chr);
}
}
String headerName = new String(trailingHeaders.getBytes(), startPos, colonPos - startPos, "ISO_8859_1");
if (allowedTrailerHeaders.contains(headerName.toLowerCase(Locale.ENGLISH))) {
MessageBytes headerValue = headers.addValue(headerName);
// Set the header value
headerValue.setBytes(trailingHeaders.getBytes(), colonPos, lastSignificantChar - colonPos);
}
return true;
}
private void throwIOException(String msg) throws IOException {
error = true;
throw new IOException(msg);
}
private void throwEOFException(String msg) throws IOException {
error = true;
throw new EOFException(msg);
}
private void checkError() throws IOException {
if (error) {
throw new IOException(sm.getString("chunkedInputFilter.error"));
}
}
}
16
View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
/**
* The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
* OutputBuffer, adapted to handle input instead of output. This allows
* complete recycling of the facade objects (the ServletInputStream and the
* BufferedReader).
*
* @author Remy Maucherat
*/
public clreplaced InputBuffer extends Reader implements ByteChunk.ByteInputChannel, ApplicationBufferHandler {
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(InputBuffer.clreplaced);
private static final Log log = LogFactory.getLog(InputBuffer.clreplaced);
public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
// The buffer can be used for byte[] and char[] reading
// ( this is needed to support ServletInputStream and BufferedReader )
public final int INITIAL_STATE = 0;
public final int CHAR_STATE = 1;
public final int BYTE_STATE = 2;
/**
* Encoder cache.
*/
private static final Map<Charset, SynchronizedStack<B2CConverter>> encoders = new ConcurrentHashMap<>();
// ----------------------------------------------------- Instance Variables
/**
* The byte buffer.
*/
private ByteBuffer bb;
/**
* The char buffer.
*/
private CharBuffer cb;
/**
* State of the output buffer.
*/
private int state = 0;
/**
* Flag which indicates if the input buffer is closed.
*/
private boolean closed = false;
/**
* Current byte to char converter.
*/
protected B2CConverter conv;
/**
* replacedociated Coyote request.
*/
private Request coyoteRequest;
/**
* Buffer position.
*/
private int markPos = -1;
/**
* Char buffer limit.
*/
private int readLimit;
/**
* Buffer size.
*/
private final int size;
// ----------------------------------------------------------- Constructors
/**
* Default constructor. Allocate the buffer with the default buffer size.
*/
public InputBuffer() {
this(DEFAULT_BUFFER_SIZE);
}
/**
* Alternate constructor which allows specifying the initial buffer size.
*
* @param size Buffer size to use
*/
public InputBuffer(int size) {
this.size = size;
bb = ByteBuffer.allocate(size);
clear(bb);
cb = CharBuffer.allocate(size);
clear(cb);
readLimit = size;
}
// ------------------------------------------------------------- Properties
/**
* replacedociated Coyote request.
*
* @param coyoteRequest replacedociated Coyote request
*/
public void setRequest(Request coyoteRequest) {
this.coyoteRequest = coyoteRequest;
}
// --------------------------------------------------------- Public Methods
/**
* Recycle the output buffer.
*/
public void recycle() {
state = INITIAL_STATE;
// If usage of mark made the buffer too big, reallocate it
if (cb.capacity() > size) {
cb = CharBuffer.allocate(size);
clear(cb);
} else {
clear(cb);
}
readLimit = size;
markPos = -1;
clear(bb);
closed = false;
if (conv != null) {
conv.recycle();
encoders.get(conv.getCharset()).push(conv);
conv = null;
}
}
/**
* Close the input buffer.
*
* @throws IOException An underlying IOException occurred
*/
@Override
public void close() throws IOException {
closed = true;
}
public int available() {
int available = availableInThisBuffer();
if (available == 0) {
coyoteRequest.action(ActionCode.AVAILABLE, Boolean.valueOf(coyoteRequest.getReadListener() != null));
available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
}
return available;
}
private int availableInThisBuffer() {
int available = 0;
if (state == BYTE_STATE) {
available = bb.remaining();
} else if (state == CHAR_STATE) {
available = cb.remaining();
}
return available;
}
public void setReadListener(ReadListener listener) {
coyoteRequest.setReadListener(listener);
// The container is responsible for the first call to
// listener.onDataAvailable(). If isReady() returns true, the container
// needs to call listener.onDataAvailable() from a new thread. If
// isReady() returns false, the socket will be registered for read and
// the container will call listener.onDataAvailable() once data arrives.
// Must call isFinished() first as a call to isReady() if the request
// has been finished will register the socket for read interest and that
// is not required.
if (!coyoteRequest.isFinished() && isReady()) {
coyoteRequest.action(ActionCode.DISPATCH_READ, null);
if (!ContainerThreadMarker.isContainerThread()) {
// Not on a container thread so need to execute the dispatch
coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
}
}
}
public boolean isFinished() {
int available = 0;
if (state == BYTE_STATE) {
available = bb.remaining();
} else if (state == CHAR_STATE) {
available = cb.remaining();
}
if (available > 0) {
return false;
} else {
return coyoteRequest.isFinished();
}
}
public boolean isReady() {
if (coyoteRequest.getReadListener() == null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("inputBuffer.requiresNonBlocking"));
}
return false;
}
if (isFinished()) {
// If this is a non-container thread, need to trigger a read
// which will eventually lead to a call to onAllDataRead() via a
// container thread.
if (!ContainerThreadMarker.isContainerThread()) {
coyoteRequest.action(ActionCode.DISPATCH_READ, null);
coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
}
return false;
}
// Checking for available data at the network level and registering for
// read can be done sequentially for HTTP/1.x and AJP as there is only
// ever a single thread processing the socket at any one time. However,
// for HTTP/2 there is one thread processing the connection and separate
// threads for each stream. For HTTP/2 the two operations have to be
// performed atomically else it is possible for the connection thread to
// read more data in to the buffer after the stream thread checks for
// available network data but before it registers for read.
if (availableInThisBuffer() > 0) {
return true;
}
AtomicBoolean result = new AtomicBoolean();
coyoteRequest.action(ActionCode.NB_READ_INTEREST, result);
return result.get();
}
boolean isBlocking() {
return coyoteRequest.getReadListener() == null;
}
// ------------------------------------------------- Bytes Handling Methods
/**
* Reads new bytes in the byte chunk.
*
* @throws IOException An underlying IOException occurred
*/
@Override
public int realReadBytes() throws IOException {
if (closed) {
return -1;
}
if (coyoteRequest == null) {
return -1;
}
if (state == INITIAL_STATE) {
state = BYTE_STATE;
}
try {
return coyoteRequest.doRead(this);
} catch (IOException ioe) {
// An IOException on a read is almost always due to
// the remote client aborting the request.
throw new ClientAbortException(ioe);
}
}
public int readByte() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (checkByteBufferEof()) {
return -1;
}
return bb.get() & 0xFF;
}
public int read(byte[] b, int off, int len) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (checkByteBufferEof()) {
return -1;
}
int n = Math.min(len, bb.remaining());
bb.get(b, off, n);
return n;
}
/**
* Transfers bytes from the buffer to the specified ByteBuffer. After the
* operation the position of the ByteBuffer will be returned to the one
* before the operation, the limit will be the position incremented by
* the number of the transferred bytes.
*
* @param to the ByteBuffer into which bytes are to be written.
* @return an integer specifying the actual number of bytes read, or -1 if
* the end of the stream is reached
* @throws IOException if an input or output exception has occurred
*/
public int read(ByteBuffer to) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (checkByteBufferEof()) {
return -1;
}
int n = Math.min(to.remaining(), bb.remaining());
int orgLimit = bb.limit();
bb.limit(bb.position() + n);
to.put(bb);
bb.limit(orgLimit);
to.limit(to.position()).position(to.position() - n);
return n;
}
// ------------------------------------------------- Chars Handling Methods
public int realReadChars() throws IOException {
checkConverter();
boolean eof = false;
if (bb.remaining() <= 0) {
int nRead = realReadBytes();
if (nRead < 0) {
eof = true;
}
}
if (markPos == -1) {
clear(cb);
} else {
// Make sure there's enough space in the worst case
makeSpace(bb.remaining());
if ((cb.capacity() - cb.limit()) == 0 && bb.remaining() != 0) {
// We went over the limit
clear(cb);
markPos = -1;
}
}
state = CHAR_STATE;
conv.convert(bb, cb, this, eof);
if (cb.remaining() == 0 && eof) {
return -1;
} else {
return cb.remaining();
}
}
@Override
public int read() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (checkCharBufferEof()) {
return -1;
}
return cb.get();
}
@Override
public int read(char[] cbuf) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return read(cbuf, 0, cbuf.length);
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (checkCharBufferEof()) {
return -1;
}
int n = Math.min(len, cb.remaining());
cb.get(cbuf, off, n);
return n;
}
@Override
public long skip(long n) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (n < 0) {
throw new IllegalArgumentException();
}
long nRead = 0;
while (nRead < n) {
if (cb.remaining() >= n) {
cb.position(cb.position() + (int) n);
nRead = n;
} else {
nRead += cb.remaining();
cb.position(cb.limit());
int nb = realReadChars();
if (nb < 0) {
break;
}
}
}
return nRead;
}
@Override
public boolean ready() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (state == INITIAL_STATE) {
state = CHAR_STATE;
}
return (available() > 0);
}
@Override
public boolean markSupported() {
return true;
}
@Override
public void mark(int readAheadLimit) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (cb.remaining() <= 0) {
clear(cb);
} else {
if ((cb.capacity() > (2 * size)) && (cb.remaining()) < (cb.position())) {
cb.compact();
cb.flip();
}
}
readLimit = cb.position() + readAheadLimit + size;
markPos = cb.position();
}
@Override
public void reset() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (state == CHAR_STATE) {
if (markPos < 0) {
clear(cb);
markPos = -1;
throw new IOException();
} else {
cb.position(markPos);
}
} else {
clear(bb);
}
}
public void checkConverter() throws IOException {
if (conv != null) {
return;
}
Charset charset = null;
if (coyoteRequest != null) {
charset = coyoteRequest.getCharset();
}
if (charset == null) {
charset = org.apache.coyote.Constants.DEFAULT_BODY_CHARSET;
}
SynchronizedStack<B2CConverter> stack = encoders.get(charset);
if (stack == null) {
stack = new SynchronizedStack<>();
encoders.putIfAbsent(charset, stack);
stack = encoders.get(charset);
}
conv = stack.pop();
if (conv == null) {
conv = createConverter(charset);
}
}
private static B2CConverter createConverter(Charset charset) throws IOException {
if (SecurityUtil.isPackageProtectionEnabled()) {
try {
return AccessController.doPrivileged(new PrivilegedCreateConverter(charset));
} catch (PrivilegedActionException ex) {
Exception e = ex.getException();
if (e instanceof IOException) {
throw (IOException) e;
} else {
throw new IOException(e);
}
}
} else {
return new B2CConverter(charset);
}
}
@Override
public void setByteBuffer(ByteBuffer buffer) {
bb = buffer;
}
@Override
public ByteBuffer getByteBuffer() {
return bb;
}
@Override
public void expand(int size) {
// no-op
}
private boolean checkByteBufferEof() throws IOException {
if (bb.remaining() == 0) {
int n = realReadBytes();
if (n < 0) {
return true;
}
}
return false;
}
private boolean checkCharBufferEof() throws IOException {
if (cb.remaining() == 0) {
int n = realReadChars();
if (n < 0) {
return true;
}
}
return false;
}
private void clear(Buffer buffer) {
buffer.rewind().limit(0);
}
private void makeSpace(int count) {
int desiredSize = cb.limit() + count;
if (desiredSize > readLimit) {
desiredSize = readLimit;
}
if (desiredSize <= cb.capacity()) {
return;
}
int newSize = 2 * cb.capacity();
if (desiredSize >= newSize) {
newSize = 2 * cb.capacity() + count;
}
if (newSize > readLimit) {
newSize = readLimit;
}
CharBuffer tmp = CharBuffer.allocate(newSize);
int oldPosition = cb.position();
cb.position(0);
tmp.put(cb);
tmp.flip();
tmp.position(oldPosition);
cb = tmp;
tmp = null;
}
private static clreplaced PrivilegedCreateConverter implements PrivilegedExceptionAction<B2CConverter> {
private final Charset charset;
public PrivilegedCreateConverter(Charset charset) {
this.charset = charset;
}
@Override
public B2CConverter run() throws IOException {
return new B2CConverter(charset);
}
}
}
16
View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
/**
* Chunked input filter. Parses chunked data according to
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
*
* @author Remy Maucherat
*/
public clreplaced ChunkedInputFilter implements InputFilter, ApplicationBufferHandler {
private static final StringManager sm = StringManager.getManager(ChunkedInputFilter.clreplaced.getPackage().getName());
// -------------------------------------------------------------- Constants
protected static final String ENCODING_NAME = "chunked";
protected static final ByteChunk ENCODING = new ByteChunk();
// ----------------------------------------------------- Static Initializer
static {
ENCODING.setBytes(ENCODING_NAME.getBytes(StandardCharsets.ISO_8859_1), 0, ENCODING_NAME.length());
}
// ----------------------------------------------------- Instance Variables
/**
* Next buffer in the pipeline.
*/
protected InputBuffer buffer;
/**
* Number of bytes remaining in the current chunk.
*/
protected int remaining = 0;
/**
* Byte chunk used to read bytes.
*/
protected ByteBuffer readChunk;
/**
* Flag set to true when the end chunk has been read.
*/
protected boolean endChunk = false;
/**
* Byte chunk used to store trailing headers.
*/
protected final ByteChunk trailingHeaders = new ByteChunk();
/**
* Flag set to true if the next call to doRead() must parse a CRLF pair
* before doing anything else.
*/
protected boolean needCRLFParse = false;
/**
* Request being parsed.
*/
private Request request;
/**
* Limit for extension size.
*/
private final long maxExtensionSize;
/**
* Limit for trailer size.
*/
private final int maxTrailerSize;
/**
* Size of extensions processed for this request.
*/
private long extensionSize;
private final int maxSwallowSize;
/**
* Flag that indicates if an error has occurred.
*/
private boolean error;
private final Set<String> allowedTrailerHeaders;
// ----------------------------------------------------------- Constructors
public ChunkedInputFilter(int maxTrailerSize, Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize) {
this.trailingHeaders.setLimit(maxTrailerSize);
this.allowedTrailerHeaders = allowedTrailerHeaders;
this.maxExtensionSize = maxExtensionSize;
this.maxTrailerSize = maxTrailerSize;
this.maxSwallowSize = maxSwallowSize;
}
// ---------------------------------------------------- InputBuffer Methods
@Override
public int doRead(ApplicationBufferHandler handler) throws IOException {
if (endChunk) {
return -1;
}
checkError();
if (needCRLFParse) {
needCRLFParse = false;
parseCRLF(false);
}
if (remaining <= 0) {
if (!parseChunkHeader()) {
throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
}
if (endChunk) {
parseEndChunk();
return -1;
}
}
int result = 0;
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() < 0) {
throwIOException(sm.getString("chunkedInputFilter.eos"));
}
}
if (remaining > readChunk.remaining()) {
result = readChunk.remaining();
remaining = remaining - result;
if (readChunk != handler.getByteBuffer()) {
handler.setByteBuffer(readChunk.duplicate());
}
readChunk.position(readChunk.limit());
} else {
result = remaining;
if (readChunk != handler.getByteBuffer()) {
handler.setByteBuffer(readChunk.duplicate());
handler.getByteBuffer().limit(readChunk.position() + remaining);
}
readChunk.position(readChunk.position() + remaining);
remaining = 0;
// we need a CRLF
if ((readChunk.position() + 1) >= readChunk.limit()) {
// if we call parseCRLF we overrun the buffer here
// so we defer it to the next call BZ 11117
needCRLFParse = true;
} else {
// parse the CRLF immediately
parseCRLF(false);
}
}
return result;
}
// ---------------------------------------------------- InputFilter Methods
/**
* Read the content length from the request.
*/
@Override
public void setRequest(Request request) {
this.request = request;
}
/**
* End the current request.
*/
@Override
public long end() throws IOException {
long swallowed = 0;
int read = 0;
// Consume extra bytes : parse the stream until the end chunk is found
while ((read = doRead(this)) >= 0) {
swallowed += read;
if (maxSwallowSize > -1 && swallowed > maxSwallowSize) {
throwIOException(sm.getString("inputFilter.maxSwallow"));
}
}
// Return the number of extra bytes which were consumed
return readChunk.remaining();
}
/**
* Amount of bytes still available in a buffer.
*/
@Override
public int available() {
return readChunk != null ? readChunk.remaining() : 0;
}
/**
* Set the next buffer in the filter pipeline.
*/
@Override
public void setBuffer(InputBuffer buffer) {
this.buffer = buffer;
}
/**
* Make the filter ready to process the next request.
*/
@Override
public void recycle() {
remaining = 0;
if (readChunk != null) {
readChunk.position(0).limit(0);
}
endChunk = false;
needCRLFParse = false;
trailingHeaders.recycle();
trailingHeaders.setLimit(maxTrailerSize);
extensionSize = 0;
error = false;
}
/**
* Return the name of the replacedociated encoding; Here, the value is
* "idenreplacedy".
*/
@Override
public ByteChunk getEncodingName() {
return ENCODING;
}
@Override
public boolean isFinished() {
return endChunk;
}
// ------------------------------------------------------ Protected Methods
/**
* Read bytes from the previous buffer.
* @return The byte count which has been read
* @throws IOException Read error
*/
protected int readBytes() throws IOException {
return buffer.doRead(this);
}
/**
* Parse the header of a chunk.
* A chunk header can look like one of the following:<br>
* A10CRLF<br>
* F23;chunk-extension to be ignoredCRLF
*
* <p>
* The letters before CRLF or ';' (whatever comes first) must be valid hex
* digits. We should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid
* header according to the spec.
* @return <code>true</code> if the chunk header has been
* successfully parsed
* @throws IOException Read error
*/
protected boolean parseChunkHeader() throws IOException {
int result = 0;
boolean eol = false;
int readDigit = 0;
boolean extension = false;
while (!eol) {
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() <= 0)
return false;
}
byte chr = readChunk.get(readChunk.position());
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(false);
eol = true;
} else if (chr == Constants.SEMI_COLON && !extension) {
// First semi-colon marks the start of the extension. Further
// semi-colons may appear to separate multiple chunk-extensions.
// These need to be processed as part of parsing the extensions.
extension = true;
extensionSize++;
} else if (!extension) {
// don't read data after the trailer
int charValue = HexUtils.getDec(chr);
if (charValue != -1 && readDigit < 8) {
readDigit++;
result = (result << 4) | charValue;
} else {
// we shouldn't allow invalid, non hex characters
// in the chunked header
return false;
}
} else {
// Extension 'parsing'
// Note that the chunk-extension is neither parsed nor
// validated. Currently it is simply ignored.
extensionSize++;
if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
}
}
// Parsing the CRLF increments pos
if (!eol) {
readChunk.position(readChunk.position() + 1);
}
}
if (readDigit == 0 || result < 0) {
return false;
}
if (result == 0) {
endChunk = true;
}
remaining = result;
return true;
}
/**
* Parse CRLF at end of chunk.
*
* @param tolerant Should tolerant parsing (LF and CRLF) be used? This
* is recommended (RFC2616, section 19.3) for message
* headers.
* @throws IOException An error occurred parsing CRLF
*/
protected void parseCRLF(boolean tolerant) throws IOException {
boolean eol = false;
boolean crfound = false;
while (!eol) {
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() <= 0) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
}
}
byte chr = readChunk.get(readChunk.position());
if (chr == Constants.CR) {
if (crfound) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
}
crfound = true;
} else if (chr == Constants.LF) {
if (!tolerant && !crfound) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoCR"));
}
eol = true;
} else {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlf"));
}
readChunk.position(readChunk.position() + 1);
}
}
/**
* Parse end chunk data.
* @throws IOException Error propagation
*/
protected void parseEndChunk() throws IOException {
// Handle optional trailer headers
while (parseHeader()) {
// Loop until we run out of headers
}
}
private boolean parseHeader() throws IOException {
Map<String, String> headers = request.getTrailerFields();
byte chr = 0;
// Read new bytes if needed
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
// readBytes() above will set readChunk unless it returns a value < 0
chr = readChunk.get(readChunk.position());
// CRLF terminates the request
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(false);
return false;
}
// Mark the current buffer position
int startPos = trailingHeaders.getEnd();
//
// Reading the header name
// Header name is always US-ASCII
//
boolean colon = false;
while (!colon) {
// Read new bytes if needed
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
// readBytes() above will set readChunk unless it returns a value < 0
chr = readChunk.get(readChunk.position());
if ((chr >= Constants.A) && (chr <= Constants.Z)) {
chr = (byte) (chr - Constants.LC_OFFSET);
}
if (chr == Constants.COLON) {
colon = true;
} else {
trailingHeaders.append(chr);
}
readChunk.position(readChunk.position() + 1);
}
int colonPos = trailingHeaders.getEnd();
//
// Reading the header value (which can be spanned over multiple lines)
//
boolean eol = false;
boolean validLine = true;
int lastSignificantChar = 0;
while (validLine) {
boolean space = true;
// Skipping spaces
while (space) {
// Read new bytes if needed
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = readChunk.get(readChunk.position());
if ((chr == Constants.SP) || (chr == Constants.HT)) {
readChunk.position(readChunk.position() + 1);
// If we swallow whitespace, make sure it counts towards the
// limit placed on trailing header size
int newlimit = trailingHeaders.getLimit() - 1;
if (trailingHeaders.getEnd() > newlimit) {
throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
}
trailingHeaders.setLimit(newlimit);
} else {
space = false;
}
}
// Reading bytes until the end of the line
while (!eol) {
// Read new bytes if needed
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = readChunk.get(readChunk.position());
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(true);
eol = true;
} else if (chr == Constants.SP) {
trailingHeaders.append(chr);
} else {
trailingHeaders.append(chr);
lastSignificantChar = trailingHeaders.getEnd();
}
if (!eol) {
readChunk.position(readChunk.position() + 1);
}
}
// 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 (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = readChunk.get(readChunk.position());
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)
trailingHeaders.append(chr);
}
}
String headerName = new String(trailingHeaders.getBytes(), startPos, colonPos - startPos, StandardCharsets.ISO_8859_1);
headerName = headerName.toLowerCase(Locale.ENGLISH);
if (allowedTrailerHeaders.contains(headerName)) {
String value = new String(trailingHeaders.getBytes(), colonPos, lastSignificantChar - colonPos, StandardCharsets.ISO_8859_1);
headers.put(headerName, value);
}
return true;
}
private void throwIOException(String msg) throws IOException {
error = true;
throw new IOException(msg);
}
private void throwEOFException(String msg) throws IOException {
error = true;
throw new EOFException(msg);
}
private void checkError() throws IOException {
if (error) {
throw new IOException(sm.getString("chunkedInputFilter.error"));
}
}
@Override
public void setByteBuffer(ByteBuffer buffer) {
readChunk = buffer;
}
@Override
public ByteBuffer getByteBuffer() {
return readChunk;
}
@Override
public void expand(int size) {
// no-op
}
}
16
View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : how2j
Copyright Apache License 2.0
Author : how2j
/**
* Chunked input filter. Parses chunked data according to
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http
* ://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
*
* @author Remy Maucherat
* @author Filip Hanik
*/
public clreplaced ChunkedInputFilter implements InputFilter {
private static final StringManager sm = StringManager.getManager(ChunkedInputFilter.clreplaced.getPackage().getName());
// -------------------------------------------------------------- Constants
protected static final String ENCODING_NAME = "chunked";
protected static final ByteChunk ENCODING = new ByteChunk();
// ----------------------------------------------------- Static Initializer
static {
ENCODING.setBytes(ENCODING_NAME.getBytes(Charset.defaultCharset()), 0, ENCODING_NAME.length());
}
// ----------------------------------------------------- Instance Variables
/**
* Next buffer in the pipeline.
*/
protected InputBuffer buffer;
/**
* Number of bytes remaining in the current chunk.
*/
protected int remaining = 0;
/**
* Position in the buffer.
*/
protected int pos = 0;
/**
* Last valid byte in the buffer.
*/
protected int lastValid = 0;
/**
* Read bytes buffer.
*/
protected byte[] buf = null;
/**
* Byte chunk used to read bytes.
*/
protected ByteChunk readChunk = new ByteChunk();
/**
* Flag set to true when the end chunk has been read.
*/
protected boolean endChunk = false;
/**
* Byte chunk used to store trailing headers.
*/
protected ByteChunk trailingHeaders = new ByteChunk();
/**
* Flag set to true if the next call to doRead() must parse a CRLF pair
* before doing anything else.
*/
protected boolean needCRLFParse = false;
/**
* Request being parsed.
*/
private Request request;
/**
* Limit for extension size.
*/
private final long maxExtensionSize;
/**
* Limit for trailer size.
*/
private final int maxTrailerSize;
/**
* Size of extensions processed for this request.
*/
private long extensionSize;
private final int maxSwallowSize;
/**
* Flag that indicates if an error has occurred.
*/
private boolean error;
private final Set<String> allowedTrailerHeaders;
// ----------------------------------------------------------- Constructors
public ChunkedInputFilter(int maxTrailerSize, Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize) {
this.trailingHeaders.setLimit(maxTrailerSize);
this.allowedTrailerHeaders = allowedTrailerHeaders;
this.maxExtensionSize = maxExtensionSize;
this.maxTrailerSize = maxTrailerSize;
this.maxSwallowSize = maxSwallowSize;
}
// ---------------------------------------------------- InputBuffer Methods
/**
* Read bytes.
*
* @return If the filter does request length control, this value is
* significant; it should be the number of bytes consumed from the
* buffer, up until the end of the current request body, or the
* buffer length, whichever is greater. If the filter does not do
* request body length control, the returned value should be -1.
*/
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
if (endChunk) {
return -1;
}
checkError();
if (needCRLFParse) {
needCRLFParse = false;
parseCRLF(false);
}
if (remaining <= 0) {
if (!parseChunkHeader()) {
throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
}
if (endChunk) {
parseEndChunk();
return -1;
}
}
int result = 0;
if (pos >= lastValid) {
if (readBytes() < 0) {
throwIOException(sm.getString("chunkedInputFilter.eos"));
}
}
if (remaining > (lastValid - pos)) {
result = lastValid - pos;
remaining = remaining - result;
chunk.setBytes(buf, pos, result);
pos = lastValid;
} else {
result = remaining;
chunk.setBytes(buf, pos, remaining);
pos = pos + remaining;
remaining = 0;
// we need a CRLF
if ((pos + 1) >= lastValid) {
// if we call parseCRLF we overrun the buffer here
// so we defer it to the next call BZ 11117
needCRLFParse = true;
} else {
// parse the CRLF immediately
parseCRLF(false);
}
}
return result;
}
// ---------------------------------------------------- InputFilter Methods
/**
* Read the content length from the request.
*/
@Override
public void setRequest(Request request) {
this.request = request;
}
/**
* End the current request.
*/
@Override
public long end() throws IOException {
long swallowed = 0;
int read = 0;
// Consume extra bytes : parse the stream until the end chunk is found
while ((read = doRead(readChunk, null)) >= 0) {
swallowed += read;
if (maxSwallowSize > -1 && swallowed > maxSwallowSize) {
throwIOException(sm.getString("inputFilter.maxSwallow"));
}
}
// Return the number of extra bytes which were consumed
return lastValid - pos;
}
/**
* Amount of bytes still available in a buffer.
*/
@Override
public int available() {
return lastValid - pos;
}
/**
* Set the next buffer in the filter pipeline.
*/
@Override
public void setBuffer(InputBuffer buffer) {
this.buffer = buffer;
}
/**
* Make the filter ready to process the next request.
*/
@Override
public void recycle() {
remaining = 0;
pos = 0;
lastValid = 0;
endChunk = false;
needCRLFParse = false;
trailingHeaders.recycle();
trailingHeaders.setLimit(maxTrailerSize);
extensionSize = 0;
error = false;
}
/**
* Return the name of the replacedociated encoding; Here, the value is
* "idenreplacedy".
*/
@Override
public ByteChunk getEncodingName() {
return ENCODING;
}
// ------------------------------------------------------ Protected Methods
/**
* Read bytes from the previous buffer.
*/
protected int readBytes() throws IOException {
int nRead = buffer.doRead(readChunk, null);
pos = readChunk.getStart();
lastValid = pos + nRead;
buf = readChunk.getBytes();
return nRead;
}
/**
* Parse the header of a chunk. A chunk header can look like one of the
* following:<br />
* A10CRLF<br />
* F23;chunk-extension to be ignoredCRLF
*
* <p>
* The letters before CRLF or ';' (whatever comes first) must be valid hex
* digits. We should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid header
* according to the spec.
*/
protected boolean parseChunkHeader() throws IOException {
int result = 0;
boolean eol = false;
int readDigit = 0;
boolean extension = false;
while (!eol) {
if (pos >= lastValid) {
if (readBytes() <= 0)
return false;
}
if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
parseCRLF(false);
eol = true;
} else if (buf[pos] == Constants.SEMI_COLON && !extension) {
// First semi-colon marks the start of the extension. Further
// semi-colons may appear to separate multiple chunk-extensions.
// These need to be processed as part of parsing the extensions.
extension = true;
extensionSize++;
} else if (!extension) {
// don't read data after the trailer
int charValue = HexUtils.getDec(buf[pos]);
if (charValue != -1 && readDigit < 8) {
readDigit++;
result = (result << 4) | charValue;
} else {
// we shouldn't allow invalid, non hex characters
// in the chunked header
return false;
}
} else {
// Extension 'parsing'
// Note that the chunk-extension is neither parsed nor
// validated. Currently it is simply ignored.
extensionSize++;
if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
}
}
// Parsing the CRLF increments pos
if (!eol) {
pos++;
}
}
if (readDigit == 0 || result < 0) {
return false;
}
if (result == 0) {
endChunk = true;
}
remaining = result;
return true;
}
/**
* Parse CRLF at end of chunk.
*
* @deprecated Use {@link #parseCRLF(boolean)}
*/
@Deprecated
protected boolean parseCRLF() throws IOException {
parseCRLF(false);
return true;
}
/**
* Parse CRLF at end of chunk.
*
* @param tolerant
* Should tolerant parsing (LF and CRLF) be used? This is
* recommended (RFC2616, section 19.3) for message headers.
*/
protected void parseCRLF(boolean tolerant) throws IOException {
boolean eol = false;
boolean crfound = false;
while (!eol) {
if (pos >= lastValid) {
if (readBytes() <= 0) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
}
}
if (buf[pos] == Constants.CR) {
if (crfound) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
}
crfound = true;
} else if (buf[pos] == Constants.LF) {
if (!tolerant && !crfound) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoCR"));
}
eol = true;
} else {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlf"));
}
pos++;
}
}
/**
* Parse end chunk data.
*/
protected void parseEndChunk() throws IOException {
// Handle optional trailer headers
while (parseHeader()) {
// Loop until we run out of headers
}
}
private boolean parseHeader() throws IOException {
MimeHeaders headers = request.getMimeHeaders();
byte chr = 0;
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
// CRLF terminates the request
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(false);
return false;
}
// Mark the current buffer position
int startPos = trailingHeaders.getEnd();
//
// Reading the header name
// Header name is always US-ASCII
//
boolean colon = false;
while (!colon) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
if ((chr >= Constants.A) && (chr <= Constants.Z)) {
chr = (byte) (chr - Constants.LC_OFFSET);
}
if (chr == Constants.COLON) {
colon = true;
} else {
trailingHeaders.append(chr);
}
pos++;
}
int colonPos = trailingHeaders.getEnd();
//
// Reading the header value (which can be spanned over multiple lines)
//
boolean eol = false;
boolean validLine = true;
int lastSignificantChar = 0;
while (validLine) {
boolean space = true;
// Skipping spaces
while (space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
if ((chr == Constants.SP) || (chr == Constants.HT)) {
pos++;
// If we swallow whitespace, make sure it counts towards the
// limit placed on trailing header size
int newlimit = trailingHeaders.getLimit() - 1;
if (trailingHeaders.getEnd() > newlimit) {
throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
}
trailingHeaders.setLimit(newlimit);
} else {
space = false;
}
}
// Reading bytes until the end of the line
while (!eol) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(true);
eol = true;
} else if (chr == Constants.SP) {
trailingHeaders.append(chr);
} else {
trailingHeaders.append(chr);
lastSignificantChar = trailingHeaders.getEnd();
}
if (!eol) {
pos++;
}
}
// 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 (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
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)
trailingHeaders.append(chr);
}
}
String headerName = new String(trailingHeaders.getBytes(), startPos, colonPos - startPos, "ISO_8859_1");
if (allowedTrailerHeaders.contains(headerName.toLowerCase(Locale.ENGLISH))) {
MessageBytes headerValue = headers.addValue(headerName);
// Set the header value
headerValue.setBytes(trailingHeaders.getBytes(), colonPos, lastSignificantChar - colonPos);
}
return true;
}
private void throwIOException(String msg) throws IOException {
error = true;
throw new IOException(msg);
}
private void throwEOFException(String msg) throws IOException {
error = true;
throw new EOFException(msg);
}
private void checkError() throws IOException {
if (error) {
throw new IOException(sm.getString("chunkedInputFilter.error"));
}
}
}
16
View Complete Implementation : Stream.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
clreplaced Stream extends AbstractStream implements HeaderEmitter {
private static final Log log = LogFactory.getLog(Stream.clreplaced);
private static final StringManager sm = StringManager.getManager(Stream.clreplaced);
private static final int HEADER_STATE_START = 0;
private static final int HEADER_STATE_PSEUDO = 1;
private static final int HEADER_STATE_REGULAR = 2;
private static final int HEADER_STATE_TRAILER = 3;
private static final MimeHeaders ACK_HEADERS;
private static final Integer HTTP_UPGRADE_STREAM = Integer.valueOf(1);
static {
Response response = new Response();
response.setStatus(100);
StreamProcessor.prepareHeaders(null, response, true, null, null);
ACK_HEADERS = response.getMimeHeaders();
}
private volatile int weight = Constants.DEFAULT_WEIGHT;
private volatile long contentLengthReceived = 0;
private final Http2UpgradeHandler handler;
private final StreamStateMachine state;
private final WindowAllocationManager allocationManager = new WindowAllocationManager(this);
// State machine would be too much overhead
private int headerState = HEADER_STATE_START;
private StreamException headerException = null;
// TODO: null these when finished to reduce memory used by closed stream
private final Request coyoteRequest;
private StringBuilder cookieHeader = null;
private final Response coyoteResponse = new Response();
private final StreamInputBuffer inputBuffer;
private final StreamOutputBuffer streamOutputBuffer = new StreamOutputBuffer();
private final Http2OutputBuffer http2OutputBuffer = new Http2OutputBuffer(coyoteResponse, streamOutputBuffer);
Stream(Integer identifier, Http2UpgradeHandler handler) {
this(identifier, handler, null);
}
Stream(Integer identifier, Http2UpgradeHandler handler, Request coyoteRequest) {
super(identifier);
this.handler = handler;
handler.addChild(this);
setWindowSize(handler.getRemoteSettings().getInitialWindowSize());
state = new StreamStateMachine(this);
if (coyoteRequest == null) {
// HTTP/2 new request
this.coyoteRequest = new Request();
this.inputBuffer = new StreamInputBuffer();
this.coyoteRequest.setInputBuffer(inputBuffer);
} else {
// HTTP/2 Push or HTTP/1.1 upgrade
this.coyoteRequest = coyoteRequest;
this.inputBuffer = null;
// Headers have been read by this point
state.receivedStartOfHeaders();
if (HTTP_UPGRADE_STREAM.equals(identifier)) {
// Populate coyoteRequest from headers (HTTP/1.1 only)
try {
prepareRequest();
} catch (IllegalArgumentException iae) {
// Something in the headers is invalid
// Set correct return status
coyoteResponse.setStatus(400);
// Set error flag. This triggers error processing rather than
// the normal mapping
coyoteResponse.setError();
}
}
// TODO replaceduming the body has been read at this point is not valid
state.receivedEndOfStream();
}
this.coyoteRequest.setSendfile(handler.hasAsyncIO() && handler.getProtocol().getUseSendfile());
this.coyoteResponse.setOutputBuffer(http2OutputBuffer);
this.coyoteRequest.setResponse(coyoteResponse);
this.coyoteRequest.protocol().setString("HTTP/2.0");
if (this.coyoteRequest.getStartTime() < 0) {
this.coyoteRequest.setStartTime(System.currentTimeMillis());
}
}
private void prepareRequest() {
MessageBytes hostValueMB = coyoteRequest.getMimeHeaders().getUniqueValue("host");
if (hostValueMB == null) {
throw new IllegalArgumentException();
}
// This processing expects bytes. Server push will have used a String
// to trigger a conversion if required.
hostValueMB.toBytes();
ByteChunk valueBC = hostValueMB.getByteChunk();
byte[] valueB = valueBC.getBytes();
int valueL = valueBC.getLength();
int valueS = valueBC.getStart();
int colonPos = Host.parse(hostValueMB);
if (colonPos != -1) {
int port = 0;
for (int i = colonPos + 1; i < valueL; i++) {
char c = (char) valueB[i + valueS];
if (c < '0' || c > '9') {
throw new IllegalArgumentException();
}
port = port * 10 + c - '0';
}
coyoteRequest.setServerPort(port);
// Only need to copy the host name up to the :
valueL = colonPos;
}
// Extract the host name
char[] hostNameC = new char[valueL];
for (int i = 0; i < valueL; i++) {
hostNameC[i] = (char) valueB[i + valueS];
}
coyoteRequest.serverName().setChars(hostNameC, 0, valueL);
}
final void rePrioritise(AbstractStream parent, boolean exclusive, int weight) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.reprioritisation.debug", getConnectionId(), getIdentifier(), Boolean.toString(exclusive), parent.getIdentifier(), Integer.toString(weight)));
}
// Check if new parent is a descendant of this stream
if (isDescendant(parent)) {
parent.detachFromParent();
// Cast is always safe since any descendant of this stream must be
// an instance of Stream
getParentStream().addChild((Stream) parent);
}
if (exclusive) {
// Need to move children of the new parent to be children of this
// stream. Slightly convoluted to avoid concurrent modification.
Iterator<Stream> parentsChildren = parent.getChildStreams().iterator();
while (parentsChildren.hasNext()) {
Stream parentsChild = parentsChildren.next();
parentsChildren.remove();
this.addChild(parentsChild);
}
}
detachFromParent();
parent.addChild(this);
this.weight = weight;
}
/*
* Used when removing closed streams from the tree and we know there is no
* need to check for circular references.
*/
final void rePrioritise(AbstractStream parent, int weight) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.reprioritisation.debug", getConnectionId(), getIdentifier(), Boolean.FALSE, parent.getIdentifier(), Integer.toString(weight)));
}
parent.addChild(this);
this.weight = weight;
}
final void receiveReset(long errorCode) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.reset.receive", getConnectionId(), getIdentifier(), Long.toString(errorCode)));
}
// Set the new state first since read and write both check this
state.receivedReset();
// Reads wait internally so need to call a method to break the wait()
if (inputBuffer != null) {
inputBuffer.receiveReset();
}
cancelAllocationRequests();
}
final void cancelAllocationRequests() {
allocationManager.notifyAny();
}
final void checkState(FrameType frameType) throws Http2Exception {
state.checkFrameType(frameType);
}
@Override
final synchronized void incrementWindowSize(int windowSizeIncrement) throws Http2Exception {
// If this is zero then any thread that has been trying to write for
// this stream will be waiting. Notify that thread it can continue. Use
// notify all even though only one thread is waiting to be on the safe
// side.
boolean notify = getWindowSize() < 1;
super.incrementWindowSize(windowSizeIncrement);
if (notify && getWindowSize() > 0) {
allocationManager.notifyStream();
}
}
final synchronized int reserveWindowSize(int reservation, boolean block) throws IOException {
long windowSize = getWindowSize();
while (windowSize < 1) {
if (!canWrite()) {
throw new CloseNowException(sm.getString("stream.notWritable", getConnectionId(), getIdentifier()));
}
if (block) {
try {
long writeTimeout = handler.getProtocol().getStreamWriteTimeout();
allocationManager.waitForStream(writeTimeout);
windowSize = getWindowSize();
if (windowSize == 0) {
doWriteTimeout();
}
} catch (InterruptedException e) {
// Possible shutdown / rst or similar. Use an IOException to
// signal to the client that further I/O isn't possible for this
// Stream.
throw new IOException(e);
}
} else {
allocationManager.waitForStreamNonBlocking();
return 0;
}
}
int allocation;
if (windowSize < reservation) {
allocation = (int) windowSize;
} else {
allocation = reservation;
}
decrementWindowSize(allocation);
return allocation;
}
void doWriteTimeout() throws CloseNowException {
String msg = sm.getString("stream.writeTimeout");
StreamException se = new StreamException(msg, Http2Error.ENHANCE_YOUR_CALM, getIdAsInt());
// Prevent the application making further writes
streamOutputBuffer.closed = true;
// Prevent Tomcat's error handling trying to write
coyoteResponse.setError();
coyoteResponse.setErrorReported();
// Trigger a reset once control returns to Tomcat
streamOutputBuffer.reset = se;
throw new CloseNowException(msg, se);
}
void waitForConnectionAllocation(long timeout) throws InterruptedException {
allocationManager.waitForConnection(timeout);
}
void waitForConnectionAllocationNonBlocking() {
allocationManager.waitForConnectionNonBlocking();
}
void notifyConnection() {
allocationManager.notifyConnection();
}
@Override
public final void emitHeader(String name, String value) throws HpackException {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.header.debug", getConnectionId(), getIdentifier(), name, value));
}
// Header names must be lower case
if (!name.toLowerCase(Locale.US).equals(name)) {
throw new HpackException(sm.getString("stream.header.case", getConnectionId(), getIdentifier(), name));
}
if ("connection".equals(name)) {
throw new HpackException(sm.getString("stream.header.connection", getConnectionId(), getIdentifier()));
}
if ("te".equals(name)) {
if (!"trailers".equals(value)) {
throw new HpackException(sm.getString("stream.header.te", getConnectionId(), getIdentifier(), value));
}
}
if (headerException != null) {
// Don't bother processing the header since the stream is going to
// be reset anyway
return;
}
if (name.length() == 0) {
throw new HpackException(sm.getString("stream.header.empty", getConnectionId(), getIdentifier()));
}
boolean pseudoHeader = name.charAt(0) == ':';
if (pseudoHeader && headerState != HEADER_STATE_PSEUDO) {
headerException = new StreamException(sm.getString("stream.header.unexpectedPseudoHeader", getConnectionId(), getIdentifier(), name), Http2Error.PROTOCOL_ERROR, getIdAsInt());
// No need for further processing. The stream will be reset.
return;
}
if (headerState == HEADER_STATE_PSEUDO && !pseudoHeader) {
headerState = HEADER_STATE_REGULAR;
}
switch(name) {
case ":method":
{
if (coyoteRequest.method().isNull()) {
coyoteRequest.method().setString(value);
} else {
throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":method"));
}
break;
}
case ":scheme":
{
if (coyoteRequest.scheme().isNull()) {
coyoteRequest.scheme().setString(value);
} else {
throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":scheme"));
}
break;
}
case ":path":
{
if (!coyoteRequest.requestURI().isNull()) {
throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":path"));
}
if (value.length() == 0) {
throw new HpackException(sm.getString("stream.header.noPath", getConnectionId(), getIdentifier()));
}
int queryStart = value.indexOf('?');
String uri;
if (queryStart == -1) {
uri = value;
} else {
uri = value.substring(0, queryStart);
String query = value.substring(queryStart + 1);
coyoteRequest.queryString().setString(query);
}
// Bug 61120. Set the URI as bytes rather than String so:
// - any path parameters are correctly processed
// - the normalization security checks are performed that prevent
// directory traversal attacks
byte[] uriBytes = uri.getBytes(StandardCharsets.ISO_8859_1);
coyoteRequest.requestURI().setBytes(uriBytes, 0, uriBytes.length);
break;
}
case ":authority":
{
if (coyoteRequest.serverName().isNull()) {
int i;
try {
i = Host.parse(value);
} catch (IllegalArgumentException iae) {
// Host value invalid
throw new HpackException(sm.getString("stream.header.invalid", getConnectionId(), getIdentifier(), ":authority", value));
}
if (i > -1) {
coyoteRequest.serverName().setString(value.substring(0, i));
coyoteRequest.setServerPort(Integer.parseInt(value.substring(i + 1)));
} else {
coyoteRequest.serverName().setString(value);
}
} else {
throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":authority"));
}
break;
}
case "cookie":
{
// Cookie headers need to be concatenated into a single header
// See RFC 7540 8.1.2.5
if (cookieHeader == null) {
cookieHeader = new StringBuilder();
} else {
cookieHeader.append("; ");
}
cookieHeader.append(value);
break;
}
default:
{
if (headerState == HEADER_STATE_TRAILER && !handler.getProtocol().isTrailerHeaderAllowed(name)) {
break;
}
if ("expect".equals(name) && "100-continue".equals(value)) {
coyoteRequest.setExpectation(true);
}
if (pseudoHeader) {
headerException = new StreamException(sm.getString("stream.header.unknownPseudoHeader", getConnectionId(), getIdentifier(), name), Http2Error.PROTOCOL_ERROR, getIdAsInt());
}
if (headerState == HEADER_STATE_TRAILER) {
// HTTP/2 headers are already always lower case
coyoteRequest.getTrailerFields().put(name, value);
} else {
coyoteRequest.getMimeHeaders().addValue(name).setString(value);
}
}
}
}
@Override
public void setHeaderException(StreamException streamException) {
if (headerException == null) {
headerException = streamException;
}
}
@Override
public void validateHeaders() throws StreamException {
if (headerException == null) {
return;
}
throw headerException;
}
final boolean receivedEndOfHeaders() throws ConnectionException {
if (coyoteRequest.method().isNull() || coyoteRequest.scheme().isNull() || coyoteRequest.requestURI().isNull()) {
throw new ConnectionException(sm.getString("stream.header.required", getConnectionId(), getIdentifier()), Http2Error.PROTOCOL_ERROR);
}
// Cookie headers need to be concatenated into a single header
// See RFC 7540 8.1.2.5
// Can only do this once the headers are fully received
if (cookieHeader != null) {
coyoteRequest.getMimeHeaders().addValue("cookie").setString(cookieHeader.toString());
}
return headerState == HEADER_STATE_REGULAR || headerState == HEADER_STATE_PSEUDO;
}
final void writeHeaders() throws IOException {
boolean endOfStream = streamOutputBuffer.hasNoBody() && coyoteResponse.getTrailerFields() == null;
handler.writeHeaders(this, 0, coyoteResponse.getMimeHeaders(), endOfStream, Constants.DEFAULT_HEADERS_FRAME_SIZE);
}
final void addOutputFilter(OutputFilter filter) {
http2OutputBuffer.addFilter(filter);
}
final void writeTrailers() throws IOException {
Supplier<Map<String, String>> supplier = coyoteResponse.getTrailerFields();
if (supplier == null) {
// No supplier was set, end of stream will already have been sent
return;
}
// We can re-use the MimeHeaders from the response since they have
// already been processed by the encoder at this point
MimeHeaders mimeHeaders = coyoteResponse.getMimeHeaders();
mimeHeaders.recycle();
Map<String, String> headerMap = supplier.get();
if (headerMap == null) {
headerMap = Collections.emptyMap();
}
// Copy the contents of the Map to the MimeHeaders
// TODO: Is there benefit in refactoring this? Is MimeHeaders too
// heavyweight? Can we reduce the copy/conversions?
for (Map.Entry<String, String> headerEntry : headerMap.entrySet()) {
MessageBytes mb = mimeHeaders.addValue(headerEntry.getKey());
mb.setString(headerEntry.getValue());
}
handler.writeHeaders(this, 0, mimeHeaders, true, Constants.DEFAULT_HEADERS_FRAME_SIZE);
}
final void writeAck() throws IOException {
handler.writeHeaders(this, 0, ACK_HEADERS, false, Constants.DEFAULT_HEADERS_ACK_FRAME_SIZE);
}
@Override
final String getConnectionId() {
return handler.getConnectionId();
}
@Override
final int getWeight() {
return weight;
}
final Request getCoyoteRequest() {
return coyoteRequest;
}
final Response getCoyoteResponse() {
return coyoteResponse;
}
final ByteBuffer getInputByteBuffer() {
return inputBuffer.getInBuffer();
}
final void receivedStartOfHeaders(boolean headersEndStream) throws Http2Exception {
if (headerState == HEADER_STATE_START) {
headerState = HEADER_STATE_PSEUDO;
handler.getHpackDecoder().setMaxHeaderCount(handler.getProtocol().getMaxHeaderCount());
handler.getHpackDecoder().setMaxHeaderSize(handler.getProtocol().getMaxHeaderSize());
} else if (headerState == HEADER_STATE_PSEUDO || headerState == HEADER_STATE_REGULAR) {
// Trailer headers MUST include the end of stream flag
if (headersEndStream) {
headerState = HEADER_STATE_TRAILER;
handler.getHpackDecoder().setMaxHeaderCount(handler.getProtocol().getMaxTrailerCount());
handler.getHpackDecoder().setMaxHeaderSize(handler.getProtocol().getMaxTrailerSize());
} else {
throw new ConnectionException(sm.getString("stream.trailerHeader.noEndOfStream", getConnectionId(), getIdentifier()), Http2Error.PROTOCOL_ERROR);
}
}
// Parser will catch attempt to send a headers frame after the stream
// has closed.
state.receivedStartOfHeaders();
}
final void receivedData(int payloadSize) throws ConnectionException {
contentLengthReceived += payloadSize;
long contentLengthHeader = coyoteRequest.getContentLengthLong();
if (contentLengthHeader > -1 && contentLengthReceived > contentLengthHeader) {
throw new ConnectionException(sm.getString("stream.header.contentLength", getConnectionId(), getIdentifier(), Long.valueOf(contentLengthHeader), Long.valueOf(contentLengthReceived)), Http2Error.PROTOCOL_ERROR);
}
}
final void receivedEndOfStream() throws ConnectionException {
if (isContentLengthInconsistent()) {
throw new ConnectionException(sm.getString("stream.header.contentLength", getConnectionId(), getIdentifier(), Long.valueOf(coyoteRequest.getContentLengthLong()), Long.valueOf(contentLengthReceived)), Http2Error.PROTOCOL_ERROR);
}
state.receivedEndOfStream();
if (inputBuffer != null) {
inputBuffer.notifyEof();
}
}
final boolean isContentLengthInconsistent() {
long contentLengthHeader = coyoteRequest.getContentLengthLong();
if (contentLengthHeader > -1 && contentLengthReceived != contentLengthHeader) {
return true;
}
return false;
}
final void sentHeaders() {
state.sentHeaders();
}
final void sentEndOfStream() {
streamOutputBuffer.endOfStreamSent = true;
state.sentEndOfStream();
}
final boolean isReadyForWrite() {
return streamOutputBuffer.isReady();
}
final boolean flush(boolean block) throws IOException {
return streamOutputBuffer.flush(block);
}
final StreamInputBuffer getInputBuffer() {
return inputBuffer;
}
final HttpOutputBuffer getOutputBuffer() {
return http2OutputBuffer;
}
final void sentPushPromise() {
state.sentPushPromise();
}
final boolean isActive() {
return state.isActive();
}
final boolean canWrite() {
return state.canWrite();
}
final boolean isClosedFinal() {
return state.isClosedFinal();
}
final void closeIfIdle() {
state.closeIfIdle();
}
final boolean isInputFinished() {
return !state.isFrameTypePermitted(FrameType.DATA);
}
final void close(Http2Exception http2Exception) {
if (http2Exception instanceof StreamException) {
try {
StreamException se = (StreamException) http2Exception;
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.reset.send", getConnectionId(), getIdentifier(), se.getError()));
}
state.sendReset();
handler.sendStreamReset(se);
} catch (IOException ioe) {
ConnectionException ce = new ConnectionException(sm.getString("stream.reset.fail"), Http2Error.PROTOCOL_ERROR);
ce.initCause(ioe);
handler.closeConnection(ce);
}
} else {
handler.closeConnection(http2Exception);
}
}
final boolean isPushSupported() {
return handler.getRemoteSettings().getEnablePush();
}
final void push(Request request) throws IOException {
// Can only push when supported and from a peer initiated stream
if (!isPushSupported() || getIdAsInt() % 2 == 0) {
return;
}
// Set the special HTTP/2 headers
request.getMimeHeaders().addValue(":method").duplicate(request.method());
request.getMimeHeaders().addValue(":scheme").duplicate(request.scheme());
StringBuilder path = new StringBuilder(request.requestURI().toString());
if (!request.queryString().isNull()) {
path.append('?');
path.append(request.queryString().toString());
}
request.getMimeHeaders().addValue(":path").setString(path.toString());
// Authority needs to include the port only if a non-standard port is
// being used.
if (!(request.scheme().equals("http") && request.getServerPort() == 80) && !(request.scheme().equals("https") && request.getServerPort() == 443)) {
request.getMimeHeaders().addValue(":authority").setString(request.serverName().getString() + ":" + request.getServerPort());
} else {
request.getMimeHeaders().addValue(":authority").duplicate(request.serverName());
}
push(handler, request, this);
}
boolean isTrailerFieldsReady() {
// Once EndOfStream has been received, canRead will be false
return !state.canRead();
}
boolean isTrailerFieldsSupported() {
return !streamOutputBuffer.endOfStreamSent;
}
StreamException getResetException() {
return streamOutputBuffer.reset;
}
private static void push(final Http2UpgradeHandler handler, final Request request, final Stream stream) throws IOException {
if (org.apache.coyote.Constants.IS_SECURITY_ENABLED) {
try {
AccessController.doPrivileged(new PrivilegedPush(handler, request, stream));
} catch (PrivilegedActionException ex) {
Exception e = ex.getException();
if (e instanceof IOException) {
throw (IOException) e;
} else {
throw new IOException(ex);
}
}
} else {
handler.push(request, stream);
}
}
private static clreplaced PrivilegedPush implements PrivilegedExceptionAction<Void> {
private final Http2UpgradeHandler handler;
private final Request request;
private final Stream stream;
public PrivilegedPush(Http2UpgradeHandler handler, Request request, Stream stream) {
this.handler = handler;
this.request = request;
this.stream = stream;
}
@Override
public Void run() throws IOException {
handler.push(request, stream);
return null;
}
}
clreplaced StreamOutputBuffer implements HttpOutputBuffer, WriteBuffer.Sink {
private final ByteBuffer buffer = ByteBuffer.allocate(8 * 1024);
private final WriteBuffer writeBuffer = new WriteBuffer(32 * 1024);
// Flag that indicates that data was left over on a previous
// non-blocking write. Once set, this flag stays set until all the data
// has been written.
private boolean dataLeft;
private volatile long written = 0;
private int streamReservation = 0;
private volatile boolean closed = false;
private volatile StreamException reset = null;
private volatile boolean endOfStreamSent = false;
/* The write methods are synchronized to ensure that only one thread at
* a time is able to access the buffer. Without this protection, a
* client that performed concurrent writes could corrupt the buffer.
*/
@Override
public final synchronized int doWrite(ByteBuffer chunk) throws IOException {
if (closed) {
throw new IllegalStateException(sm.getString("stream.closed", getConnectionId(), getIdentifier()));
}
// chunk is always fully written
int result = chunk.remaining();
if (writeBuffer.isEmpty()) {
int chunkLimit = chunk.limit();
while (chunk.remaining() > 0) {
int thisTime = Math.min(buffer.remaining(), chunk.remaining());
chunk.limit(chunk.position() + thisTime);
buffer.put(chunk);
chunk.limit(chunkLimit);
if (chunk.remaining() > 0 && !buffer.hasRemaining()) {
// Only flush if we have more data to write and the buffer
// is full
if (flush(true, coyoteResponse.getWriteListener() == null)) {
writeBuffer.add(chunk);
dataLeft = true;
break;
}
}
}
} else {
writeBuffer.add(chunk);
}
written += result;
return result;
}
final synchronized boolean flush(boolean block) throws IOException {
/*
* Need to ensure that there is exactly one call to flush even when
* there is no data to write.
* Too few calls (i.e. zero) and the end of stream message is not
* sent for a completed asynchronous write.
* Too many calls and the end of stream message is sent too soon and
* trailer headers are not sent.
*/
boolean dataInBuffer = buffer.position() > 0;
boolean flushed = false;
if (dataInBuffer) {
dataInBuffer = flush(false, block);
flushed = true;
}
if (dataInBuffer) {
dataLeft = true;
} else {
if (writeBuffer.isEmpty()) {
// Both buffer and writeBuffer are empty.
if (flushed) {
dataLeft = false;
} else {
dataLeft = flush(false, block);
}
} else {
dataLeft = writeBuffer.write(this, block);
}
}
return dataLeft;
}
private final synchronized boolean flush(boolean writeInProgress, boolean block) throws IOException {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.outputBuffer.flush.debug", getConnectionId(), getIdentifier(), Integer.toString(buffer.position()), Boolean.toString(writeInProgress), Boolean.toString(closed)));
}
if (buffer.position() == 0) {
if (closed && !endOfStreamSent) {
// Handling this special case here is simpler than trying
// to modify the following code to handle it.
handler.writeBody(Stream.this, buffer, 0, coyoteResponse.getTrailerFields() == null);
}
// Buffer is empty. Nothing to do.
return false;
}
buffer.flip();
int left = buffer.remaining();
while (left > 0) {
if (streamReservation == 0) {
streamReservation = reserveWindowSize(left, block);
if (streamReservation == 0) {
// Must be non-blocking.
// Note: Can't add to the writeBuffer here as the write
// may originate from the writeBuffer.
buffer.compact();
return true;
}
}
while (streamReservation > 0) {
int connectionReservation = handler.reserveWindowSize(Stream.this, streamReservation, block);
if (connectionReservation == 0) {
// Must be non-blocking.
// Note: Can't add to the writeBuffer here as the write
// may originate from the writeBuffer.
buffer.compact();
return true;
}
// Do the write
handler.writeBody(Stream.this, buffer, connectionReservation, !writeInProgress && closed && left == connectionReservation && coyoteResponse.getTrailerFields() == null);
streamReservation -= connectionReservation;
left -= connectionReservation;
}
}
buffer.clear();
return false;
}
final synchronized boolean isReady() {
// Bug 63682
// Only want to return false if the window size is zero AND we are
// already waiting for an allocation.
if (getWindowSize() > 0 && allocationManager.isWaitingForStream() || handler.getWindowSize() > 0 && allocationManager.isWaitingForConnection() || dataLeft) {
return false;
} else {
return true;
}
}
@Override
public final long getBytesWritten() {
return written;
}
@Override
public final void end() throws IOException {
if (reset != null) {
throw new CloseNowException(reset);
}
if (!closed) {
closed = true;
flush(true);
writeTrailers();
}
}
/**
* @return <code>true</code> if it is certain that the replacedociated
* response has no body.
*/
final boolean hasNoBody() {
return ((written == 0) && closed);
}
@Override
public void flush() throws IOException {
/*
* This method should only be called during blocking I/O. All the
* Servlet API calls that end up here are illegal during
* non-blocking I/O. Servlet 5.4.
* However, the wording Servlet specification states that the
* behaviour is undefined so we do the best we can which is to
* perform a flush using blocking I/O or non-blocking I/O based
* depending which is currently in use.
*/
flush(getCoyoteResponse().getWriteListener() == null);
}
@Override
public synchronized boolean writeFromBuffer(ByteBuffer src, boolean blocking) throws IOException {
int chunkLimit = src.limit();
while (src.remaining() > 0) {
int thisTime = Math.min(buffer.remaining(), src.remaining());
src.limit(src.position() + thisTime);
buffer.put(src);
src.limit(chunkLimit);
if (flush(false, blocking)) {
return true;
}
}
return false;
}
}
clreplaced StreamInputBuffer implements InputBuffer {
/* Two buffers are required to avoid various multi-threading issues.
* These issues arise from the fact that the Stream (or the
* Request/Response) used by the application is processed in one thread
* but the connection is processed in another. Therefore it is possible
* that a request body frame could be received before the application
* is ready to read it. If it isn't buffered, processing of the
* connection (and hence all streams) would block until the application
* read the data. Hence the incoming data has to be buffered.
* If only one buffer was used then it could become corrupted if the
* connection thread is trying to add to it at the same time as the
* application is read it. While it should be possible to avoid this
* corruption by careful use of the buffer it would still require the
* same copies as using two buffers and the behaviour would be less
* clear.
*
* The buffers are created lazily because they quickly add up to a lot
* of memory and most requests do not have bodies.
*/
// This buffer is used to populate the ByteChunk preplaceded in to the read
// method
private byte[] outBuffer;
// This buffer is the destination for incoming data. It is normally is
// 'write mode'.
private volatile ByteBuffer inBuffer;
private volatile boolean readInterest;
private boolean resetReceived = false;
@Override
public final int doRead(ApplicationBufferHandler applicationBufferHandler) throws IOException {
ensureBuffersExist();
int written = -1;
// Ensure that only one thread accesses inBuffer at a time
synchronized (inBuffer) {
boolean canRead = false;
while (inBuffer.position() == 0 && (canRead = isActive() && !isInputFinished())) {
// Need to block until some data is written
try {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.inputBuffer.empty"));
}
long readTimeout = handler.getProtocol().getStreamReadTimeout();
if (readTimeout < 0) {
inBuffer.wait();
} else {
inBuffer.wait(readTimeout);
}
if (resetReceived) {
throw new IOException(sm.getString("stream.inputBuffer.reset"));
}
if (inBuffer.position() == 0 && isActive() && !isInputFinished()) {
String msg = sm.getString("stream.inputBuffer.readTimeout");
StreamException se = new StreamException(msg, Http2Error.ENHANCE_YOUR_CALM, getIdAsInt());
// Trigger a reset once control returns to Tomcat
coyoteResponse.setError();
streamOutputBuffer.reset = se;
throw new CloseNowException(msg, se);
}
} catch (InterruptedException e) {
// Possible shutdown / rst or similar. Use an
// IOException to signal to the client that further I/O
// isn't possible for this Stream.
throw new IOException(e);
}
}
if (inBuffer.position() > 0) {
// Data is available in the inBuffer. Copy it to the
// outBuffer.
inBuffer.flip();
written = inBuffer.remaining();
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.inputBuffer.copy", Integer.toString(written)));
}
inBuffer.get(outBuffer, 0, written);
inBuffer.clear();
} else if (!canRead) {
return -1;
} else {
// Should never happen
throw new IllegalStateException();
}
}
applicationBufferHandler.setByteBuffer(ByteBuffer.wrap(outBuffer, 0, written));
// Increment client-side flow control windows by the number of bytes
// read
handler.writeWindowUpdate(Stream.this, written, true);
return written;
}
final boolean isReadyForRead() {
ensureBuffersExist();
synchronized (this) {
if (available() > 0) {
return true;
}
if (!isRequestBodyFullyRead()) {
readInterest = true;
}
return false;
}
}
final synchronized boolean isRequestBodyFullyRead() {
return (inBuffer == null || inBuffer.position() == 0) && isInputFinished();
}
final synchronized int available() {
if (inBuffer == null) {
return 0;
}
return inBuffer.position();
}
/*
* Called after placing some data in the inBuffer.
*/
final synchronized boolean onDataAvailable() {
if (readInterest) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.inputBuffer.dispatch"));
}
readInterest = false;
coyoteRequest.action(ActionCode.DISPATCH_READ, null);
// Always need to dispatch since this thread is processing
// the incoming connection and streams are processed on their
// own.
coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
return true;
} else {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.inputBuffer.signal"));
}
synchronized (inBuffer) {
inBuffer.notifyAll();
}
return false;
}
}
private final ByteBuffer getInBuffer() {
ensureBuffersExist();
return inBuffer;
}
final synchronized void insertReplayedBody(ByteChunk body) {
inBuffer = ByteBuffer.wrap(body.getBytes(), body.getOffset(), body.getLength());
}
private final void ensureBuffersExist() {
if (inBuffer == null) {
// The client must obey Tomcat's window size when sending so
// this is the initial window size set by Tomcat that the client
// uses (i.e. the local setting is required here).
int size = handler.getLocalSettings().getInitialWindowSize();
synchronized (this) {
if (inBuffer == null) {
inBuffer = ByteBuffer.allocate(size);
outBuffer = new byte[size];
}
}
}
}
private final void receiveReset() {
if (inBuffer != null) {
synchronized (inBuffer) {
resetReceived = true;
inBuffer.notifyAll();
}
}
}
private final void notifyEof() {
if (inBuffer != null) {
synchronized (inBuffer) {
inBuffer.notifyAll();
}
}
}
}
}
16
View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
/**
* The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
* OutputBuffer, adapted to handle input instead of output. This allows
* complete recycling of the facade objects (the ServletInputStream and the
* BufferedReader).
*
* @author Remy Maucherat
*/
public clreplaced InputBuffer extends Reader implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel, CharChunk.CharOutputChannel {
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(Constants.Package);
// -------------------------------------------------------------- Constants
public static final String DEFAULT_ENCODING = org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
// The buffer can be used for byte[] and char[] reading
// ( this is needed to support ServletInputStream and BufferedReader )
public final int INITIAL_STATE = 0;
public final int CHAR_STATE = 1;
public final int BYTE_STATE = 2;
// ----------------------------------------------------- Instance Variables
/**
* The byte buffer.
*/
private final ByteChunk bb;
/**
* The chunk buffer.
*/
private CharChunk cb;
/**
* State of the output buffer.
*/
private int state = 0;
/**
* Flag which indicates if the input buffer is closed.
*/
private boolean closed = false;
/**
* Encoding to use.
*/
private String enc;
/**
* List of encoders.
*/
protected final ConcurrentHashMap<String, B2CConverter> encoders = new ConcurrentHashMap<>();
/**
* Current byte to char converter.
*/
protected B2CConverter conv;
/**
* replacedociated Coyote request.
*/
private Request coyoteRequest;
/**
* Buffer position.
*/
private int markPos = -1;
/**
* Buffer size.
*/
private final int size;
// ----------------------------------------------------------- Constructors
/**
* Default constructor. Allocate the buffer with the default buffer size.
*/
public InputBuffer() {
this(DEFAULT_BUFFER_SIZE);
}
/**
* Alternate constructor which allows specifying the initial buffer size.
*
* @param size Buffer size to use
*/
public InputBuffer(int size) {
this.size = size;
bb = new ByteChunk(size);
bb.setLimit(size);
bb.setByteInputChannel(this);
cb = new CharChunk(size);
cb.setLimit(size);
cb.setOptimizedWrite(false);
cb.setCharInputChannel(this);
cb.setCharOutputChannel(this);
}
// ------------------------------------------------------------- Properties
/**
* replacedociated Coyote request.
*
* @param coyoteRequest replacedociated Coyote request
*/
public void setRequest(Request coyoteRequest) {
this.coyoteRequest = coyoteRequest;
}
// --------------------------------------------------------- Public Methods
/**
* Recycle the output buffer.
*/
public void recycle() {
state = INITIAL_STATE;
// If usage of mark made the buffer too big, reallocate it
if (cb.getChars().length > size) {
cb = new CharChunk(size);
cb.setLimit(size);
cb.setOptimizedWrite(false);
cb.setCharInputChannel(this);
cb.setCharOutputChannel(this);
} else {
cb.recycle();
}
markPos = -1;
bb.recycle();
closed = false;
if (conv != null) {
conv.recycle();
conv = null;
}
enc = null;
}
/**
* Clear cached encoders (to save memory for Comet requests).
*/
public void clearEncoders() {
encoders.clear();
}
/**
* Close the input buffer.
*
* @throws IOException An underlying IOException occurred
*/
@Override
public void close() throws IOException {
closed = true;
}
public int available() {
int available = 0;
if (state == BYTE_STATE) {
available = bb.getLength();
} else if (state == CHAR_STATE) {
available = cb.getLength();
}
if (available == 0) {
// Written this way to avoid use of IS_COMET action where possible
boolean readForAvailable = coyoteRequest.getReadListener() != null;
if (!readForAvailable) {
AtomicBoolean isComet = new AtomicBoolean();
coyoteRequest.action(ActionCode.IS_COMET, isComet);
readForAvailable = isComet.get();
}
coyoteRequest.action(ActionCode.AVAILABLE, Boolean.valueOf(readForAvailable));
available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
}
return available;
}
public void setReadListener(ReadListener listener) {
coyoteRequest.setReadListener(listener);
// The container is responsible for the first call to
// listener.onDataAvailable(). If isReady() returns true, the container
// needs to call listener.onDataAvailable() from a new thread. If
// isReady() returns false, the socket will be registered for read and
// the container will call listener.onDataAvailable() once data arrives.
// Must call isFinished() first as a call to isReady() if the request
// has been finished will register the socket for read interest and that
// is not required.
if (!coyoteRequest.isFinished() && isReady()) {
coyoteRequest.action(ActionCode.DISPATCH_READ, null);
if (!ContainerThreadMarker.isContainerThread()) {
// Not on a container thread so need to execute the dispatch
coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
}
}
}
public boolean isFinished() {
int available = 0;
if (state == BYTE_STATE) {
available = bb.getLength();
} else if (state == CHAR_STATE) {
available = cb.getLength();
}
if (available > 0) {
return false;
} else {
return coyoteRequest.isFinished();
}
}
public boolean isReady() {
if (coyoteRequest.getReadListener() == null) {
throw new IllegalStateException(sm.getString("inputBuffer.requiresNonBlocking"));
}
// Need to check is finished before we check available() as BIO always
// returns 1 for isAvailable()
if (isFinished()) {
// If this is a non-container thread, need to trigger a read
// which will eventually lead to a call to onAllDataRead() via a
// container thread.
if (!ContainerThreadMarker.isContainerThread()) {
coyoteRequest.action(ActionCode.DISPATCH_READ, null);
coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
}
return false;
}
boolean result = available() > 0;
if (!result) {
coyoteRequest.action(ActionCode.NB_READ_INTEREST, null);
}
return result;
}
boolean isBlocking() {
return coyoteRequest.getReadListener() == null;
}
// ------------------------------------------------- Bytes Handling Methods
/**
* Reads new bytes in the byte chunk.
*
* @param cbuf Byte buffer to be written to the response
* @param off Offset
* @param len Length
*
* @throws IOException An underlying IOException occurred
*/
@Override
public int realReadBytes(byte[] cbuf, int off, int len) throws IOException {
if (closed) {
return -1;
}
if (coyoteRequest == null) {
return -1;
}
if (state == INITIAL_STATE) {
state = BYTE_STATE;
}
int result = coyoteRequest.doRead(bb);
return result;
}
public int readByte() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return bb.substract();
}
public int read(byte[] b, int off, int len) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return bb.substract(b, off, len);
}
// ------------------------------------------------- Chars Handling Methods
/**
* Since the converter will use append, it is possible to get chars to
* be removed from the buffer for "writing". Since the chars have already
* been read before, they are ignored. If a mark was set, then the
* mark is lost.
*/
@Override
public void realWriteChars(char[] c, int off, int len) throws IOException {
markPos = -1;
cb.setOffset(0);
cb.setEnd(0);
}
public void setEncoding(String s) {
enc = s;
}
@Override
public int realReadChars(char[] cbuf, int off, int len) throws IOException {
if (conv == null) {
setConverter();
}
boolean eof = false;
if (bb.getLength() <= 0) {
int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length);
if (nRead < 0) {
eof = true;
}
}
if (markPos == -1) {
cb.setOffset(0);
cb.setEnd(0);
} else {
// Make sure there's enough space in the worst case
cb.makeSpace(bb.getLength());
if ((cb.getBuffer().length - cb.getEnd()) == 0 && bb.getLength() != 0) {
// We went over the limit
cb.setOffset(0);
cb.setEnd(0);
markPos = -1;
}
}
state = CHAR_STATE;
conv.convert(bb, cb, eof);
if (cb.getLength() == 0 && eof) {
return -1;
} else {
return cb.getLength();
}
}
@Override
public int read() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return cb.substract();
}
@Override
public int read(char[] cbuf) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return read(cbuf, 0, cbuf.length);
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return cb.substract(cbuf, off, len);
}
@Override
public long skip(long n) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (n < 0) {
throw new IllegalArgumentException();
}
long nRead = 0;
while (nRead < n) {
if (cb.getLength() >= n) {
cb.setOffset(cb.getStart() + (int) n);
nRead = n;
} else {
nRead += cb.getLength();
cb.setOffset(cb.getEnd());
int toRead = 0;
if (cb.getChars().length < (n - nRead)) {
toRead = cb.getChars().length;
} else {
toRead = (int) (n - nRead);
}
int nb = realReadChars(cb.getChars(), 0, toRead);
if (nb < 0) {
break;
}
}
}
return nRead;
}
@Override
public boolean ready() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (state == INITIAL_STATE) {
state = CHAR_STATE;
}
return (available() > 0);
}
@Override
public boolean markSupported() {
return true;
}
@Override
public void mark(int readAheadLimit) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (cb.getLength() <= 0) {
cb.setOffset(0);
cb.setEnd(0);
} else {
if ((cb.getBuffer().length > (2 * size)) && (cb.getLength()) < (cb.getStart())) {
System.arraycopy(cb.getBuffer(), cb.getStart(), cb.getBuffer(), 0, cb.getLength());
cb.setEnd(cb.getLength());
cb.setOffset(0);
}
}
cb.setLimit(cb.getStart() + readAheadLimit + size);
markPos = cb.getStart();
}
@Override
public void reset() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (state == CHAR_STATE) {
if (markPos < 0) {
cb.recycle();
markPos = -1;
throw new IOException();
} else {
cb.setOffset(markPos);
}
} else {
bb.recycle();
}
}
public void checkConverter() throws IOException {
if (conv == null) {
setConverter();
}
}
protected void setConverter() throws IOException {
if (coyoteRequest != null) {
enc = coyoteRequest.getCharacterEncoding();
}
if (enc == null) {
enc = DEFAULT_ENCODING;
}
conv = encoders.get(enc);
if (conv == null) {
if (SecurityUtil.isPackageProtectionEnabled()) {
try {
conv = AccessController.doPrivileged(new PrivilegedExceptionAction<B2CConverter>() {
@Override
public B2CConverter run() throws IOException {
return new B2CConverter(enc);
}
});
} catch (PrivilegedActionException ex) {
Exception e = ex.getException();
if (e instanceof IOException) {
throw (IOException) e;
} else {
throw new IOException(e);
}
}
} else {
conv = new B2CConverter(enc);
}
encoders.put(enc, conv);
}
}
}
16
View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : wangyingjie
Copyright Apache License 2.0
Author : wangyingjie
/**
* The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
* OutputBuffer, adapted to handle input instead of output. This allows
* complete recycling of the facade objects (the ServletInputStream and the
* BufferedReader).
*
* @author Remy Maucherat
*/
public clreplaced InputBuffer extends Reader implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel, CharChunk.CharOutputChannel {
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(Constants.Package);
// -------------------------------------------------------------- Constants
public static final String DEFAULT_ENCODING = org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
// The buffer can be used for byte[] and char[] reading
// ( this is needed to support ServletInputStream and BufferedReader )
public final int INITIAL_STATE = 0;
public final int CHAR_STATE = 1;
public final int BYTE_STATE = 2;
// ----------------------------------------------------- Instance Variables
/**
* The byte buffer.
*/
private final ByteChunk bb;
/**
* The chunk buffer.
*/
private CharChunk cb;
/**
* State of the output buffer.
*/
private int state = 0;
/**
* Flag which indicates if the input buffer is closed.
*/
private boolean closed = false;
/**
* Encoding to use.
*/
private String enc;
/**
* Encoder is set.
*/
private boolean gotEnc = false;
/**
* List of encoders.
*/
private final Map<String, B2CConverter> encoders = new ConcurrentHashMap<String, B2CConverter>();
/**
* Current byte to char converter.
*/
protected B2CConverter conv;
/**
* replacedociated Coyote request.
*/
private Request coyoteRequest;
/**
* Buffer position.
*/
private int markPos = -1;
/**
* Buffer size.
*/
private int size = -1;
// ----------------------------------------------------------- Constructors
/**
* Default constructor. Allocate the buffer with the default buffer size.
*/
public InputBuffer() {
this(DEFAULT_BUFFER_SIZE);
}
/**
* Alternate constructor which allows specifying the initial buffer size.
*
* @param size Buffer size to use
*/
public InputBuffer(int size) {
this.size = size;
bb = new ByteChunk(size);
bb.setLimit(size);
bb.setByteInputChannel(this);
cb = new CharChunk(size);
cb.setLimit(size);
cb.setOptimizedWrite(false);
cb.setCharInputChannel(this);
cb.setCharOutputChannel(this);
}
// ------------------------------------------------------------- Properties
/**
* replacedociated Coyote request.
*
* @param coyoteRequest replacedociated Coyote request
*/
public void setRequest(Request coyoteRequest) {
this.coyoteRequest = coyoteRequest;
}
/**
* Get replacedociated Coyote request.
*
* @return the replacedociated Coyote request
*/
@Deprecated
public Request getRequest() {
return this.coyoteRequest;
}
// --------------------------------------------------------- Public Methods
/**
* Recycle the output buffer.
*/
public void recycle() {
state = INITIAL_STATE;
// If usage of mark made the buffer too big, reallocate it
if (cb.getChars().length > size) {
cb = new CharChunk(size);
cb.setLimit(size);
cb.setOptimizedWrite(false);
cb.setCharInputChannel(this);
cb.setCharOutputChannel(this);
} else {
cb.recycle();
}
markPos = -1;
bb.recycle();
closed = false;
if (conv != null) {
conv.recycle();
}
gotEnc = false;
enc = null;
}
/**
* Clear cached encoders (to save memory for Comet requests).
*/
public void clearEncoders() {
encoders.clear();
}
/**
* Close the input buffer.
*
* @throws IOException An underlying IOException occurred
*/
@Override
public void close() throws IOException {
closed = true;
}
public int available() {
int available = 0;
if (state == BYTE_STATE) {
available = bb.getLength();
} else if (state == CHAR_STATE) {
available = cb.getLength();
}
if (available == 0) {
coyoteRequest.action(ActionCode.AVAILABLE, null);
available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
}
return available;
}
// ------------------------------------------------- Bytes Handling Methods
/**
* Reads new bytes in the byte chunk.
*
* @param cbuf Byte buffer to be written to the response
* @param off Offset
* @param len Length
*
* @throws IOException An underlying IOException occurred
*/
@Override
public int realReadBytes(byte[] cbuf, int off, int len) throws IOException {
if (closed) {
return -1;
}
if (coyoteRequest == null) {
return -1;
}
if (state == INITIAL_STATE) {
state = BYTE_STATE;
}
int result = coyoteRequest.doRead(bb);
return result;
}
public int readByte() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return bb.substract();
}
public int read(byte[] b, int off, int len) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return bb.substract(b, off, len);
}
// ------------------------------------------------- Chars Handling Methods
/**
* Since the converter will use append, it is possible to get chars to
* be removed from the buffer for "writing". Since the chars have already
* been read before, they are ignored. If a mark was set, then the
* mark is lost.
*/
@Override
public void realWriteChars(char[] c, int off, int len) throws IOException {
markPos = -1;
cb.setOffset(0);
cb.setEnd(0);
}
public void setEncoding(String s) {
enc = s;
}
@Override
public int realReadChars(char[] cbuf, int off, int len) throws IOException {
if (!gotEnc) {
setConverter();
}
boolean eof = false;
if (bb.getLength() <= 0) {
int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length);
if (nRead < 0) {
eof = true;
}
}
if (markPos == -1) {
cb.setOffset(0);
cb.setEnd(0);
} else {
// Make sure there's enough space in the worst case
cb.makeSpace(bb.getLength());
if ((cb.getBuffer().length - cb.getEnd()) == 0 && bb.getLength() != 0) {
// We went over the limit
cb.setOffset(0);
cb.setEnd(0);
markPos = -1;
}
}
state = CHAR_STATE;
conv.convert(bb, cb, eof);
if (cb.getLength() == 0 && eof) {
return -1;
} else {
return cb.getLength();
}
}
@Override
public int read() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return cb.substract();
}
@Override
public int read(char[] cbuf) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return read(cbuf, 0, cbuf.length);
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return cb.substract(cbuf, off, len);
}
@Override
public long skip(long n) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (n < 0) {
throw new IllegalArgumentException();
}
long nRead = 0;
while (nRead < n) {
if (cb.getLength() >= n) {
cb.setOffset(cb.getStart() + (int) n);
nRead = n;
} else {
nRead += cb.getLength();
cb.setOffset(cb.getEnd());
int toRead = 0;
if (cb.getChars().length < (n - nRead)) {
toRead = cb.getChars().length;
} else {
toRead = (int) (n - nRead);
}
int nb = realReadChars(cb.getChars(), 0, toRead);
if (nb < 0) {
break;
}
}
}
return nRead;
}
@Override
public boolean ready() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return (available() > 0);
}
@Override
public boolean markSupported() {
return true;
}
@Override
public void mark(int readAheadLimit) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (cb.getLength() <= 0) {
cb.setOffset(0);
cb.setEnd(0);
} else {
if ((cb.getBuffer().length > (2 * size)) && (cb.getLength()) < (cb.getStart())) {
System.arraycopy(cb.getBuffer(), cb.getStart(), cb.getBuffer(), 0, cb.getLength());
cb.setEnd(cb.getLength());
cb.setOffset(0);
}
}
cb.setLimit(cb.getStart() + readAheadLimit + size);
markPos = cb.getStart();
}
@Override
public void reset() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (state == CHAR_STATE) {
if (markPos < 0) {
cb.recycle();
markPos = -1;
throw new IOException();
} else {
cb.setOffset(markPos);
}
} else {
bb.recycle();
}
}
public void checkConverter() throws IOException {
if (!gotEnc) {
setConverter();
}
}
protected void setConverter() throws IOException {
if (coyoteRequest != null) {
enc = coyoteRequest.getCharacterEncoding();
}
gotEnc = true;
if (enc == null) {
enc = DEFAULT_ENCODING;
}
conv = encoders.get(enc);
if (conv == null) {
if (SecurityUtil.isPackageProtectionEnabled()) {
try {
conv = AccessController.doPrivileged(new PrivilegedExceptionAction<B2CConverter>() {
@Override
public B2CConverter run() throws IOException {
return new B2CConverter(enc);
}
});
} catch (PrivilegedActionException ex) {
Exception e = ex.getException();
if (e instanceof IOException) {
throw (IOException) e;
}
}
} else {
conv = new B2CConverter(enc);
}
encoders.put(enc, conv);
}
}
}
16
View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputBuffer Methods
/**
* Read bytes.
*
* @return If the filter does request length control, this value is
* significant; it should be the number of bytes consumed from the buffer,
* up until the end of the current request body, or the buffer length,
* whichever is greater. If the filter does not do request body length
* control, the returned value should be -1.
*/
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
if (endChunk) {
return -1;
}
checkError();
if (needCRLFParse) {
needCRLFParse = false;
parseCRLF(false);
}
if (remaining <= 0) {
if (!parseChunkHeader()) {
throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
}
if (endChunk) {
parseEndChunk();
return -1;
}
}
int result = 0;
if (pos >= lastValid) {
if (readBytes() < 0) {
throwIOException(sm.getString("chunkedInputFilter.eos"));
}
}
if (remaining > (lastValid - pos)) {
result = lastValid - pos;
remaining = remaining - result;
chunk.setBytes(buf, pos, result);
pos = lastValid;
} else {
result = remaining;
chunk.setBytes(buf, pos, remaining);
pos = pos + remaining;
remaining = 0;
// we need a CRLF
if ((pos + 1) >= lastValid) {
// if we call parseCRLF we overrun the buffer here
// so we defer it to the next call BZ 11117
needCRLFParse = true;
} else {
// parse the CRLF immediately
parseCRLF(false);
}
}
return result;
}
16
View Complete Implementation : ChunkedInputFilter.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
/**
* Chunked input filter. Parses chunked data according to
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
*
* @author Remy Maucherat
*/
public clreplaced ChunkedInputFilter implements InputFilter {
private static final StringManager sm = StringManager.getManager(ChunkedInputFilter.clreplaced.getPackage().getName());
// -------------------------------------------------------------- Constants
protected static final String ENCODING_NAME = "chunked";
protected static final ByteChunk ENCODING = new ByteChunk();
// ----------------------------------------------------- Static Initializer
static {
ENCODING.setBytes(ENCODING_NAME.getBytes(StandardCharsets.ISO_8859_1), 0, ENCODING_NAME.length());
}
// ----------------------------------------------------- Instance Variables
/**
* Next buffer in the pipeline.
*/
protected InputBuffer buffer;
/**
* Number of bytes remaining in the current chunk.
*/
protected int remaining = 0;
/**
* Position in the buffer.
*/
protected int pos = 0;
/**
* Last valid byte in the buffer.
*/
protected int lastValid = 0;
/**
* Read bytes buffer.
*/
protected byte[] buf = null;
/**
* Byte chunk used to read bytes.
*/
protected final ByteChunk readChunk = new ByteChunk();
/**
* Flag set to true when the end chunk has been read.
*/
protected boolean endChunk = false;
/**
* Byte chunk used to store trailing headers.
*/
protected final ByteChunk trailingHeaders = new ByteChunk();
/**
* Flag set to true if the next call to doRead() must parse a CRLF pair
* before doing anything else.
*/
protected boolean needCRLFParse = false;
/**
* Request being parsed.
*/
private Request request;
/**
* Limit for extension size.
*/
private final long maxExtensionSize;
/**
* Limit for trailer size.
*/
private final int maxTrailerSize;
/**
* Size of extensions processed for this request.
*/
private long extensionSize;
private final int maxSwallowSize;
/**
* Flag that indicates if an error has occurred.
*/
private boolean error;
private final Set<String> allowedTrailerHeaders;
// ----------------------------------------------------------- Constructors
public ChunkedInputFilter(int maxTrailerSize, Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize) {
this.trailingHeaders.setLimit(maxTrailerSize);
this.allowedTrailerHeaders = allowedTrailerHeaders;
this.maxExtensionSize = maxExtensionSize;
this.maxTrailerSize = maxTrailerSize;
this.maxSwallowSize = maxSwallowSize;
}
// ---------------------------------------------------- InputBuffer Methods
/**
* Read bytes.
*
* @return If the filter does request length control, this value is
* significant; it should be the number of bytes consumed from the buffer,
* up until the end of the current request body, or the buffer length,
* whichever is greater. If the filter does not do request body length
* control, the returned value should be -1.
*/
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
if (endChunk) {
return -1;
}
checkError();
if (needCRLFParse) {
needCRLFParse = false;
parseCRLF(false);
}
if (remaining <= 0) {
if (!parseChunkHeader()) {
throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
}
if (endChunk) {
parseEndChunk();
return -1;
}
}
int result = 0;
if (pos >= lastValid) {
if (readBytes() < 0) {
throwIOException(sm.getString("chunkedInputFilter.eos"));
}
}
if (remaining > (lastValid - pos)) {
result = lastValid - pos;
remaining = remaining - result;
chunk.setBytes(buf, pos, result);
pos = lastValid;
} else {
result = remaining;
chunk.setBytes(buf, pos, remaining);
pos = pos + remaining;
remaining = 0;
// we need a CRLF
if ((pos + 1) >= lastValid) {
// if we call parseCRLF we overrun the buffer here
// so we defer it to the next call BZ 11117
needCRLFParse = true;
} else {
// parse the CRLF immediately
parseCRLF(false);
}
}
return result;
}
// ---------------------------------------------------- InputFilter Methods
/**
* Read the content length from the request.
*/
@Override
public void setRequest(Request request) {
this.request = request;
}
/**
* End the current request.
*/
@Override
public long end() throws IOException {
long swallowed = 0;
int read = 0;
// Consume extra bytes : parse the stream until the end chunk is found
while ((read = doRead(readChunk, null)) >= 0) {
swallowed += read;
if (maxSwallowSize > -1 && swallowed > maxSwallowSize) {
throwIOException(sm.getString("inputFilter.maxSwallow"));
}
}
// Return the number of extra bytes which were consumed
return lastValid - pos;
}
/**
* Amount of bytes still available in a buffer.
*/
@Override
public int available() {
return lastValid - pos;
}
/**
* Set the next buffer in the filter pipeline.
*/
@Override
public void setBuffer(InputBuffer buffer) {
this.buffer = buffer;
}
/**
* Make the filter ready to process the next request.
*/
@Override
public void recycle() {
remaining = 0;
pos = 0;
lastValid = 0;
endChunk = false;
needCRLFParse = false;
trailingHeaders.recycle();
trailingHeaders.setLimit(maxTrailerSize);
extensionSize = 0;
error = false;
}
/**
* Return the name of the replacedociated encoding; Here, the value is
* "idenreplacedy".
*/
@Override
public ByteChunk getEncodingName() {
return ENCODING;
}
@Override
public boolean isFinished() {
return endChunk;
}
// ------------------------------------------------------ Protected Methods
/**
* Read bytes from the previous buffer.
*/
protected int readBytes() throws IOException {
int nRead = buffer.doRead(readChunk, null);
pos = readChunk.getStart();
lastValid = pos + nRead;
buf = readChunk.getBytes();
return nRead;
}
/**
* Parse the header of a chunk.
* A chunk header can look like one of the following:<br>
* A10CRLF<br>
* F23;chunk-extension to be ignoredCRLF
*
* <p>
* The letters before CRLF or ';' (whatever comes first) must be valid hex
* digits. We should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid
* header according to the spec.
*/
protected boolean parseChunkHeader() throws IOException {
int result = 0;
boolean eol = false;
int readDigit = 0;
boolean extension = false;
while (!eol) {
if (pos >= lastValid) {
if (readBytes() <= 0)
return false;
}
if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
parseCRLF(false);
eol = true;
} else if (buf[pos] == Constants.SEMI_COLON && !extension) {
// First semi-colon marks the start of the extension. Further
// semi-colons may appear to separate multiple chunk-extensions.
// These need to be processed as part of parsing the extensions.
extension = true;
extensionSize++;
} else if (!extension) {
// don't read data after the trailer
int charValue = HexUtils.getDec(buf[pos]);
if (charValue != -1 && readDigit < 8) {
readDigit++;
result = (result << 4) | charValue;
} else {
// we shouldn't allow invalid, non hex characters
// in the chunked header
return false;
}
} else {
// Extension 'parsing'
// Note that the chunk-extension is neither parsed nor
// validated. Currently it is simply ignored.
extensionSize++;
if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
}
}
// Parsing the CRLF increments pos
if (!eol) {
pos++;
}
}
if (readDigit == 0 || result < 0) {
return false;
}
if (result == 0) {
endChunk = true;
}
remaining = result;
return true;
}
/**
* Parse CRLF at end of chunk.
*
* @param tolerant Should tolerant parsing (LF and CRLF) be used? This
* is recommended (RFC2616, section 19.3) for message
* headers.
*/
protected void parseCRLF(boolean tolerant) throws IOException {
boolean eol = false;
boolean crfound = false;
while (!eol) {
if (pos >= lastValid) {
if (readBytes() <= 0) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
}
}
if (buf[pos] == Constants.CR) {
if (crfound) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
}
crfound = true;
} else if (buf[pos] == Constants.LF) {
if (!tolerant && !crfound) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoCR"));
}
eol = true;
} else {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlf"));
}
pos++;
}
}
/**
* Parse end chunk data.
*/
protected void parseEndChunk() throws IOException {
// Handle optional trailer headers
while (parseHeader()) {
// Loop until we run out of headers
}
}
private boolean parseHeader() throws IOException {
MimeHeaders headers = request.getMimeHeaders();
byte chr = 0;
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
// CRLF terminates the request
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(false);
return false;
}
// Mark the current buffer position
int startPos = trailingHeaders.getEnd();
//
// Reading the header name
// Header name is always US-ASCII
//
boolean colon = false;
while (!colon) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
if ((chr >= Constants.A) && (chr <= Constants.Z)) {
chr = (byte) (chr - Constants.LC_OFFSET);
}
if (chr == Constants.COLON) {
colon = true;
} else {
trailingHeaders.append(chr);
}
pos++;
}
int colonPos = trailingHeaders.getEnd();
//
// Reading the header value (which can be spanned over multiple lines)
//
boolean eol = false;
boolean validLine = true;
int lastSignificantChar = 0;
while (validLine) {
boolean space = true;
// Skipping spaces
while (space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
if ((chr == Constants.SP) || (chr == Constants.HT)) {
pos++;
// If we swallow whitespace, make sure it counts towards the
// limit placed on trailing header size
int newlimit = trailingHeaders.getLimit() - 1;
if (trailingHeaders.getEnd() > newlimit) {
throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
}
trailingHeaders.setLimit(newlimit);
} else {
space = false;
}
}
// Reading bytes until the end of the line
while (!eol) {
// Read new bytes if needed
if (pos >= lastValid) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = buf[pos];
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(true);
eol = true;
} else if (chr == Constants.SP) {
trailingHeaders.append(chr);
} else {
trailingHeaders.append(chr);
lastSignificantChar = trailingHeaders.getEnd();
}
if (!eol) {
pos++;
}
}
// 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 (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
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)
trailingHeaders.append(chr);
}
}
String headerName = new String(trailingHeaders.getBytes(), startPos, colonPos - startPos, StandardCharsets.ISO_8859_1);
if (allowedTrailerHeaders.contains(headerName.toLowerCase(Locale.ENGLISH))) {
MessageBytes headerValue = headers.addValue(headerName);
// Set the header value
headerValue.setBytes(trailingHeaders.getBytes(), colonPos, lastSignificantChar - colonPos);
}
return true;
}
private void throwIOException(String msg) throws IOException {
error = true;
throw new IOException(msg);
}
private void throwEOFException(String msg) throws IOException {
error = true;
throw new EOFException(msg);
}
private void checkError() throws IOException {
if (error) {
throw new IOException(sm.getString("chunkedInputFilter.error"));
}
}
}
16
View Complete Implementation : ChunkedInputFilter.java
Copyright MIT License
Author : chenmudu
Copyright MIT License
Author : chenmudu
/**
* Chunked input filter. Parses chunked data according to
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
*
* @author Remy Maucherat
*/
public clreplaced ChunkedInputFilter implements InputFilter, ApplicationBufferHandler {
private static final StringManager sm = StringManager.getManager(ChunkedInputFilter.clreplaced.getPackage().getName());
// -------------------------------------------------------------- Constants
protected static final String ENCODING_NAME = "chunked";
protected static final ByteChunk ENCODING = new ByteChunk();
// ----------------------------------------------------- Static Initializer
static {
ENCODING.setBytes(ENCODING_NAME.getBytes(StandardCharsets.ISO_8859_1), 0, ENCODING_NAME.length());
}
// ----------------------------------------------------- Instance Variables
/**
* Next buffer in the pipeline.
*/
protected InputBuffer buffer;
/**
* Number of bytes remaining in the current chunk.
*/
protected int remaining = 0;
/**
* Byte chunk used to read bytes.
*/
protected ByteBuffer readChunk;
/**
* Flag set to true when the end chunk has been read.
*/
protected boolean endChunk = false;
/**
* Byte chunk used to store trailing headers.
*/
protected final ByteChunk trailingHeaders = new ByteChunk();
/**
* Flag set to true if the next call to doRead() must parse a CRLF pair
* before doing anything else.
*/
protected boolean needCRLFParse = false;
/**
* Request being parsed.
*/
private Request request;
/**
* Limit for extension size.
*/
private final long maxExtensionSize;
/**
* Limit for trailer size.
*/
private final int maxTrailerSize;
/**
* Size of extensions processed for this request.
*/
private long extensionSize;
private final int maxSwallowSize;
/**
* Flag that indicates if an error has occurred.
*/
private boolean error;
private final Set<String> allowedTrailerHeaders;
// ----------------------------------------------------------- Constructors
public ChunkedInputFilter(int maxTrailerSize, Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize) {
this.trailingHeaders.setLimit(maxTrailerSize);
this.allowedTrailerHeaders = allowedTrailerHeaders;
this.maxExtensionSize = maxExtensionSize;
this.maxTrailerSize = maxTrailerSize;
this.maxSwallowSize = maxSwallowSize;
}
// ---------------------------------------------------- InputBuffer Methods
/**
* @deprecated Unused. Will be removed in Tomcat 9. Use
* {@link #doRead(ApplicationBufferHandler)}
*/
@Deprecated
@Override
public int doRead(ByteChunk chunk) throws IOException {
if (endChunk) {
return -1;
}
checkError();
if (needCRLFParse) {
needCRLFParse = false;
parseCRLF(false);
}
if (remaining <= 0) {
if (!parseChunkHeader()) {
throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
}
if (endChunk) {
parseEndChunk();
return -1;
}
}
int result = 0;
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() < 0) {
throwIOException(sm.getString("chunkedInputFilter.eos"));
}
}
if (remaining > readChunk.remaining()) {
result = readChunk.remaining();
remaining = remaining - result;
chunk.setBytes(readChunk.array(), readChunk.arrayOffset() + readChunk.position(), result);
readChunk.position(readChunk.limit());
} else {
result = remaining;
chunk.setBytes(readChunk.array(), readChunk.arrayOffset() + readChunk.position(), remaining);
readChunk.position(readChunk.position() + remaining);
remaining = 0;
// we need a CRLF
if ((readChunk.position() + 1) >= readChunk.limit()) {
// if we call parseCRLF we overrun the buffer here
// so we defer it to the next call BZ 11117
needCRLFParse = true;
} else {
// parse the CRLF immediately
parseCRLF(false);
}
}
return result;
}
@Override
public int doRead(ApplicationBufferHandler handler) throws IOException {
if (endChunk) {
return -1;
}
checkError();
if (needCRLFParse) {
needCRLFParse = false;
parseCRLF(false);
}
if (remaining <= 0) {
if (!parseChunkHeader()) {
throwIOException(sm.getString("chunkedInputFilter.invalidHeader"));
}
if (endChunk) {
parseEndChunk();
return -1;
}
}
int result = 0;
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() < 0) {
throwIOException(sm.getString("chunkedInputFilter.eos"));
}
}
if (remaining > readChunk.remaining()) {
result = readChunk.remaining();
remaining = remaining - result;
if (readChunk != handler.getByteBuffer()) {
handler.setByteBuffer(readChunk.duplicate());
}
readChunk.position(readChunk.limit());
} else {
result = remaining;
if (readChunk != handler.getByteBuffer()) {
handler.setByteBuffer(readChunk.duplicate());
handler.getByteBuffer().limit(readChunk.position() + remaining);
}
readChunk.position(readChunk.position() + remaining);
remaining = 0;
// we need a CRLF
if ((readChunk.position() + 1) >= readChunk.limit()) {
// if we call parseCRLF we overrun the buffer here
// so we defer it to the next call BZ 11117
needCRLFParse = true;
} else {
// parse the CRLF immediately
parseCRLF(false);
}
}
return result;
}
// ---------------------------------------------------- InputFilter Methods
/**
* Read the content length from the request.
*/
@Override
public void setRequest(Request request) {
this.request = request;
}
/**
* End the current request.
*/
@Override
public long end() throws IOException {
long swallowed = 0;
int read = 0;
// Consume extra bytes : parse the stream until the end chunk is found
while ((read = doRead(this)) >= 0) {
swallowed += read;
if (maxSwallowSize > -1 && swallowed > maxSwallowSize) {
throwIOException(sm.getString("inputFilter.maxSwallow"));
}
}
// Return the number of extra bytes which were consumed
return readChunk.remaining();
}
/**
* Amount of bytes still available in a buffer.
*/
@Override
public int available() {
return readChunk != null ? readChunk.remaining() : 0;
}
/**
* Set the next buffer in the filter pipeline.
*/
@Override
public void setBuffer(InputBuffer buffer) {
this.buffer = buffer;
}
/**
* Make the filter ready to process the next request.
*/
@Override
public void recycle() {
remaining = 0;
if (readChunk != null) {
readChunk.position(0).limit(0);
}
endChunk = false;
needCRLFParse = false;
trailingHeaders.recycle();
trailingHeaders.setLimit(maxTrailerSize);
extensionSize = 0;
error = false;
}
/**
* Return the name of the replacedociated encoding; Here, the value is
* "idenreplacedy".
*/
@Override
public ByteChunk getEncodingName() {
return ENCODING;
}
@Override
public boolean isFinished() {
return endChunk;
}
// ------------------------------------------------------ Protected Methods
/**
* Read bytes from the previous buffer.
* @return The byte count which has been read
* @throws IOException Read error
*/
protected int readBytes() throws IOException {
return buffer.doRead(this);
}
/**
* Parse the header of a chunk.
* A chunk header can look like one of the following:<br>
* A10CRLF<br>
* F23;chunk-extension to be ignoredCRLF
*
* <p>
* The letters before CRLF or ';' (whatever comes first) must be valid hex
* digits. We should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid
* header according to the spec.
* @return <code>true</code> if the chunk header has been
* successfully parsed
* @throws IOException Read error
*/
protected boolean parseChunkHeader() throws IOException {
int result = 0;
boolean eol = false;
int readDigit = 0;
boolean extension = false;
while (!eol) {
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() <= 0)
return false;
}
byte chr = readChunk.get(readChunk.position());
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(false);
eol = true;
} else if (chr == Constants.SEMI_COLON && !extension) {
// First semi-colon marks the start of the extension. Further
// semi-colons may appear to separate multiple chunk-extensions.
// These need to be processed as part of parsing the extensions.
extension = true;
extensionSize++;
} else if (!extension) {
// don't read data after the trailer
int charValue = HexUtils.getDec(chr);
if (charValue != -1 && readDigit < 8) {
readDigit++;
result = (result << 4) | charValue;
} else {
// we shouldn't allow invalid, non hex characters
// in the chunked header
return false;
}
} else {
// Extension 'parsing'
// Note that the chunk-extension is neither parsed nor
// validated. Currently it is simply ignored.
extensionSize++;
if (maxExtensionSize > -1 && extensionSize > maxExtensionSize) {
throwIOException(sm.getString("chunkedInputFilter.maxExtension"));
}
}
// Parsing the CRLF increments pos
if (!eol) {
readChunk.position(readChunk.position() + 1);
}
}
if (readDigit == 0 || result < 0) {
return false;
}
if (result == 0) {
endChunk = true;
}
remaining = result;
return true;
}
/**
* Parse CRLF at end of chunk.
*
* @param tolerant Should tolerant parsing (LF and CRLF) be used? This
* is recommended (RFC2616, section 19.3) for message
* headers.
* @throws IOException An error occurred parsing CRLF
*/
protected void parseCRLF(boolean tolerant) throws IOException {
boolean eol = false;
boolean crfound = false;
while (!eol) {
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() <= 0) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoData"));
}
}
byte chr = readChunk.get(readChunk.position());
if (chr == Constants.CR) {
if (crfound) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfCRCR"));
}
crfound = true;
} else if (chr == Constants.LF) {
if (!tolerant && !crfound) {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlfNoCR"));
}
eol = true;
} else {
throwIOException(sm.getString("chunkedInputFilter.invalidCrlf"));
}
readChunk.position(readChunk.position() + 1);
}
}
/**
* Parse end chunk data.
* @throws IOException Error propagation
*/
protected void parseEndChunk() throws IOException {
// Handle optional trailer headers
while (parseHeader()) {
// Loop until we run out of headers
}
}
private boolean parseHeader() throws IOException {
MimeHeaders headers = request.getMimeHeaders();
byte chr = 0;
// Read new bytes if needed
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
// readBytes() above will set readChunk unless it returns a value < 0
chr = readChunk.get(readChunk.position());
// CRLF terminates the request
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(false);
return false;
}
// Mark the current buffer position
int startPos = trailingHeaders.getEnd();
//
// Reading the header name
// Header name is always US-ASCII
//
boolean colon = false;
while (!colon) {
// Read new bytes if needed
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
// readBytes() above will set readChunk unless it returns a value < 0
chr = readChunk.get(readChunk.position());
if ((chr >= Constants.A) && (chr <= Constants.Z)) {
chr = (byte) (chr - Constants.LC_OFFSET);
}
if (chr == Constants.COLON) {
colon = true;
} else {
trailingHeaders.append(chr);
}
readChunk.position(readChunk.position() + 1);
}
int colonPos = trailingHeaders.getEnd();
//
// Reading the header value (which can be spanned over multiple lines)
//
boolean eol = false;
boolean validLine = true;
int lastSignificantChar = 0;
while (validLine) {
boolean space = true;
// Skipping spaces
while (space) {
// Read new bytes if needed
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = readChunk.get(readChunk.position());
if ((chr == Constants.SP) || (chr == Constants.HT)) {
readChunk.position(readChunk.position() + 1);
// If we swallow whitespace, make sure it counts towards the
// limit placed on trailing header size
int newlimit = trailingHeaders.getLimit() - 1;
if (trailingHeaders.getEnd() > newlimit) {
throwIOException(sm.getString("chunkedInputFilter.maxTrailer"));
}
trailingHeaders.setLimit(newlimit);
} else {
space = false;
}
}
// Reading bytes until the end of the line
while (!eol) {
// Read new bytes if needed
if (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = readChunk.get(readChunk.position());
if (chr == Constants.CR || chr == Constants.LF) {
parseCRLF(true);
eol = true;
} else if (chr == Constants.SP) {
trailingHeaders.append(chr);
} else {
trailingHeaders.append(chr);
lastSignificantChar = trailingHeaders.getEnd();
}
if (!eol) {
readChunk.position(readChunk.position() + 1);
}
}
// 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 (readChunk == null || readChunk.position() >= readChunk.limit()) {
if (readBytes() < 0) {
throwEOFException(sm.getString("chunkedInputFilter.eosTrailer"));
}
}
chr = readChunk.get(readChunk.position());
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)
trailingHeaders.append(chr);
}
}
String headerName = new String(trailingHeaders.getBytes(), startPos, colonPos - startPos, StandardCharsets.ISO_8859_1);
if (allowedTrailerHeaders.contains(headerName.toLowerCase(Locale.ENGLISH))) {
MessageBytes headerValue = headers.addValue(headerName);
// Set the header value
headerValue.setBytes(trailingHeaders.getBytes(), colonPos, lastSignificantChar - colonPos);
}
return true;
}
private void throwIOException(String msg) throws IOException {
error = true;
throw new IOException(msg);
}
private void throwEOFException(String msg) throws IOException {
error = true;
throw new EOFException(msg);
}
private void checkError() throws IOException {
if (error) {
throw new IOException(sm.getString("chunkedInputFilter.error"));
}
}
@Override
public void setByteBuffer(ByteBuffer buffer) {
readChunk = buffer;
}
@Override
public ByteBuffer getByteBuffer() {
return readChunk;
}
@Override
public void expand(int size) {
// no-op
}
}
16
View Complete Implementation : Stream.java
Copyright MIT License
Author : chenmudu
Copyright MIT License
Author : chenmudu
public clreplaced Stream extends AbstractStream implements HeaderEmitter {
private static final Log log = LogFactory.getLog(Stream.clreplaced);
private static final StringManager sm = StringManager.getManager(Stream.clreplaced);
private static final int HEADER_STATE_START = 0;
private static final int HEADER_STATE_PSEUDO = 1;
private static final int HEADER_STATE_REGULAR = 2;
private static final int HEADER_STATE_TRAILER = 3;
private static final MimeHeaders ACK_HEADERS;
private static final Integer HTTP_UPGRADE_STREAM = Integer.valueOf(1);
static {
Response response = new Response();
response.setStatus(100);
StreamProcessor.prepareHeaders(null, response, null, null);
ACK_HEADERS = response.getMimeHeaders();
}
private volatile int weight = Constants.DEFAULT_WEIGHT;
private volatile long contentLengthReceived = 0;
private final Http2UpgradeHandler handler;
private final StreamStateMachine state;
private final WindowAllocationManager allocationManager = new WindowAllocationManager(this);
// State machine would be too much overhead
private int headerState = HEADER_STATE_START;
private StreamException headerException = null;
// TODO: null these when finished to reduce memory used by closed stream
private final Request coyoteRequest;
private StringBuilder cookieHeader = null;
private final Response coyoteResponse = new Response();
private final StreamInputBuffer inputBuffer;
private final StreamOutputBuffer streamOutputBuffer = new StreamOutputBuffer();
private final Http2OutputBuffer http2OutputBuffer = new Http2OutputBuffer(coyoteResponse, streamOutputBuffer);
public Stream(Integer identifier, Http2UpgradeHandler handler) {
this(identifier, handler, null);
}
public Stream(Integer identifier, Http2UpgradeHandler handler, Request coyoteRequest) {
super(identifier);
this.handler = handler;
handler.addChild(this);
setWindowSize(handler.getRemoteSettings().getInitialWindowSize());
state = new StreamStateMachine(this);
if (coyoteRequest == null) {
// HTTP/2 new request
this.coyoteRequest = new Request();
this.inputBuffer = new StreamInputBuffer();
this.coyoteRequest.setInputBuffer(inputBuffer);
} else {
// HTTP/2 Push or HTTP/1.1 upgrade
this.coyoteRequest = coyoteRequest;
this.inputBuffer = null;
// Headers have been read by this point
state.receivedStartOfHeaders();
if (HTTP_UPGRADE_STREAM.equals(identifier)) {
// Populate coyoteRequest from headers (HTTP/1.1 only)
try {
prepareRequest();
} catch (IllegalArgumentException iae) {
// Something in the headers is invalid
// Set correct return status
coyoteResponse.setStatus(400);
// Set error flag. This triggers error processing rather than
// the normal mapping
coyoteResponse.setError();
}
}
// TODO replaceduming the body has been read at this point is not valid
state.receivedEndOfStream();
}
// No sendfile for HTTP/2 (it is enabled by default in the request)
this.coyoteRequest.setSendfile(false);
this.coyoteResponse.setOutputBuffer(http2OutputBuffer);
this.coyoteRequest.setResponse(coyoteResponse);
this.coyoteRequest.protocol().setString("HTTP/2.0");
if (this.coyoteRequest.getStartTime() < 0) {
this.coyoteRequest.setStartTime(System.currentTimeMillis());
}
}
private void prepareRequest() {
MessageBytes hostValueMB = coyoteRequest.getMimeHeaders().getUniqueValue("host");
if (hostValueMB == null) {
throw new IllegalArgumentException();
}
// This processing expects bytes. Server push will have used a String
// to trigger a conversion if required.
hostValueMB.toBytes();
ByteChunk valueBC = hostValueMB.getByteChunk();
byte[] valueB = valueBC.getBytes();
int valueL = valueBC.getLength();
int valueS = valueBC.getStart();
int colonPos = Host.parse(hostValueMB);
if (colonPos != -1) {
int port = 0;
for (int i = colonPos + 1; i < valueL; i++) {
char c = (char) valueB[i + valueS];
if (c < '0' || c > '9') {
throw new IllegalArgumentException();
}
port = port * 10 + c - '0';
}
coyoteRequest.setServerPort(port);
// Only need to copy the host name up to the :
valueL = colonPos;
}
// Extract the host name
char[] hostNameC = new char[valueL];
for (int i = 0; i < valueL; i++) {
hostNameC[i] = (char) valueB[i + valueS];
}
coyoteRequest.serverName().setChars(hostNameC, 0, valueL);
}
void rePrioritise(AbstractStream parent, boolean exclusive, int weight) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.reprioritisation.debug", getConnectionId(), getIdentifier(), Boolean.toString(exclusive), parent.getIdentifier(), Integer.toString(weight)));
}
// Check if new parent is a descendant of this stream
if (isDescendant(parent)) {
parent.detachFromParent();
// Cast is always safe since any descendant of this stream must be
// an instance of Stream
getParentStream().addChild((Stream) parent);
}
if (exclusive) {
// Need to move children of the new parent to be children of this
// stream. Slightly convoluted to avoid concurrent modification.
Iterator<Stream> parentsChildren = parent.getChildStreams().iterator();
while (parentsChildren.hasNext()) {
Stream parentsChild = parentsChildren.next();
parentsChildren.remove();
this.addChild(parentsChild);
}
}
detachFromParent();
parent.addChild(this);
this.weight = weight;
}
/*
* Used when removing closed streams from the tree and we know there is no
* need to check for circular references.
*/
final void rePrioritise(AbstractStream parent, int weight) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.reprioritisation.debug", getConnectionId(), getIdentifier(), Boolean.FALSE, parent.getIdentifier(), Integer.toString(weight)));
}
parent.addChild(this);
this.weight = weight;
}
void receiveReset(long errorCode) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.reset.debug", getConnectionId(), getIdentifier(), Long.toString(errorCode)));
}
// Set the new state first since read and write both check this
state.receivedReset();
// Reads wait internally so need to call a method to break the wait()
if (inputBuffer != null) {
inputBuffer.receiveReset();
}
cancelAllocationRequests();
}
final void cancelAllocationRequests() {
allocationManager.notifyAny();
}
void checkState(FrameType frameType) throws Http2Exception {
state.checkFrameType(frameType);
}
@Override
protected synchronized void incrementWindowSize(int windowSizeIncrement) throws Http2Exception {
// If this is zero then any thread that has been trying to write for
// this stream will be waiting. Notify that thread it can continue. Use
// notify all even though only one thread is waiting to be on the safe
// side.
boolean notify = getWindowSize() < 1;
super.incrementWindowSize(windowSizeIncrement);
if (notify && getWindowSize() > 0) {
allocationManager.notifyStream();
}
}
private synchronized int reserveWindowSize(int reservation, boolean block) throws IOException {
long windowSize = getWindowSize();
while (windowSize < 1) {
if (!canWrite()) {
throw new CloseNowException(sm.getString("stream.notWritable", getConnectionId(), getIdentifier()));
}
if (block) {
try {
long writeTimeout = handler.getProtocol().getStreamWriteTimeout();
allocationManager.waitForStream(writeTimeout);
windowSize = getWindowSize();
if (windowSize == 0) {
doWriteTimeout();
}
} catch (InterruptedException e) {
// Possible shutdown / rst or similar. Use an IOException to
// signal to the client that further I/O isn't possible for this
// Stream.
throw new IOException(e);
}
} else {
allocationManager.waitForStreamNonBlocking();
return 0;
}
}
int allocation;
if (windowSize < reservation) {
allocation = (int) windowSize;
} else {
allocation = reservation;
}
decrementWindowSize(allocation);
return allocation;
}
void doWriteTimeout() throws CloseNowException {
String msg = sm.getString("stream.writeTimeout");
StreamException se = new StreamException(msg, Http2Error.ENHANCE_YOUR_CALM, getIdAsInt());
// Prevent the application making further writes
streamOutputBuffer.closed = true;
// Prevent Tomcat's error handling trying to write
coyoteResponse.setError();
coyoteResponse.setErrorReported();
// Trigger a reset once control returns to Tomcat
streamOutputBuffer.reset = se;
throw new CloseNowException(msg, se);
}
void waitForConnectionAllocation(long timeout) throws InterruptedException {
allocationManager.waitForConnection(timeout);
}
void waitForConnectionAllocationNonBlocking() {
allocationManager.waitForConnectionNonBlocking();
}
void notifyConnection() {
allocationManager.notifyConnection();
}
@Override
@Deprecated
protected synchronized void doNotifyAll() {
// NO-OP. Unused.
}
@Override
public final void emitHeader(String name, String value) throws HpackException {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.header.debug", getConnectionId(), getIdentifier(), name, value));
}
// Header names must be lower case
if (!name.toLowerCase(Locale.US).equals(name)) {
throw new HpackException(sm.getString("stream.header.case", getConnectionId(), getIdentifier(), name));
}
if ("connection".equals(name)) {
throw new HpackException(sm.getString("stream.header.connection", getConnectionId(), getIdentifier()));
}
if ("te".equals(name)) {
if (!"trailers".equals(value)) {
throw new HpackException(sm.getString("stream.header.te", getConnectionId(), getIdentifier(), value));
}
}
if (headerException != null) {
// Don't bother processing the header since the stream is going to
// be reset anyway
return;
}
boolean pseudoHeader = name.charAt(0) == ':';
if (pseudoHeader && headerState != HEADER_STATE_PSEUDO) {
headerException = new StreamException(sm.getString("stream.header.unexpectedPseudoHeader", getConnectionId(), getIdentifier(), name), Http2Error.PROTOCOL_ERROR, getIdAsInt());
// No need for further processing. The stream will be reset.
return;
}
if (headerState == HEADER_STATE_PSEUDO && !pseudoHeader) {
headerState = HEADER_STATE_REGULAR;
}
switch(name) {
case ":method":
{
if (coyoteRequest.method().isNull()) {
coyoteRequest.method().setString(value);
} else {
throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":method"));
}
break;
}
case ":scheme":
{
if (coyoteRequest.scheme().isNull()) {
coyoteRequest.scheme().setString(value);
} else {
throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":scheme"));
}
break;
}
case ":path":
{
if (!coyoteRequest.requestURI().isNull()) {
throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":path"));
}
if (value.length() == 0) {
throw new HpackException(sm.getString("stream.header.noPath", getConnectionId(), getIdentifier()));
}
int queryStart = value.indexOf('?');
String uri;
if (queryStart == -1) {
uri = value;
} else {
uri = value.substring(0, queryStart);
String query = value.substring(queryStart + 1);
coyoteRequest.queryString().setString(query);
}
// Bug 61120. Set the URI as bytes rather than String so:
// - any path parameters are correctly processed
// - the normalization security checks are performed that prevent
// directory traversal attacks
byte[] uriBytes = uri.getBytes(StandardCharsets.ISO_8859_1);
coyoteRequest.requestURI().setBytes(uriBytes, 0, uriBytes.length);
break;
}
case ":authority":
{
if (coyoteRequest.serverName().isNull()) {
int i;
try {
i = Host.parse(value);
} catch (IllegalArgumentException iae) {
// Host value invalid
throw new HpackException(sm.getString("stream.header.invalid", getConnectionId(), getIdentifier(), ":authority", value));
}
if (i > -1) {
coyoteRequest.serverName().setString(value.substring(0, i));
coyoteRequest.setServerPort(Integer.parseInt(value.substring(i + 1)));
} else {
coyoteRequest.serverName().setString(value);
}
} else {
throw new HpackException(sm.getString("stream.header.duplicate", getConnectionId(), getIdentifier(), ":authority"));
}
break;
}
case "cookie":
{
// Cookie headers need to be concatenated into a single header
// See RFC 7540 8.1.2.5
if (cookieHeader == null) {
cookieHeader = new StringBuilder();
} else {
cookieHeader.append("; ");
}
cookieHeader.append(value);
break;
}
default:
{
if (headerState == HEADER_STATE_TRAILER && !handler.isTrailerHeaderAllowed(name)) {
break;
}
if ("expect".equals(name) && "100-continue".equals(value)) {
coyoteRequest.setExpectation(true);
}
if (pseudoHeader) {
headerException = new StreamException(sm.getString("stream.header.unknownPseudoHeader", getConnectionId(), getIdentifier(), name), Http2Error.PROTOCOL_ERROR, getIdAsInt());
}
// replacedume other HTTP header
coyoteRequest.getMimeHeaders().addValue(name).setString(value);
}
}
}
@Override
public void setHeaderException(StreamException streamException) {
if (headerException == null) {
headerException = streamException;
}
}
@Override
public void validateHeaders() throws StreamException {
if (headerException == null) {
return;
}
throw headerException;
}
final boolean receivedEndOfHeaders() throws ConnectionException {
if (coyoteRequest.method().isNull() || coyoteRequest.scheme().isNull() || coyoteRequest.requestURI().isNull()) {
throw new ConnectionException(sm.getString("stream.header.required", getConnectionId(), getIdentifier()), Http2Error.PROTOCOL_ERROR);
}
// Cookie headers need to be concatenated into a single header
// See RFC 7540 8.1.2.5
// Can only do this once the headers are fully received
if (cookieHeader != null) {
coyoteRequest.getMimeHeaders().addValue("cookie").setString(cookieHeader.toString());
}
return headerState == HEADER_STATE_REGULAR || headerState == HEADER_STATE_PSEUDO;
}
void writeHeaders() throws IOException {
boolean endOfStream = streamOutputBuffer.hasNoBody();
handler.writeHeaders(this, 0, coyoteResponse.getMimeHeaders(), endOfStream, Constants.DEFAULT_HEADERS_FRAME_SIZE);
}
final void addOutputFilter(OutputFilter filter) {
http2OutputBuffer.addFilter(filter);
}
void writeAck() throws IOException {
handler.writeHeaders(this, 0, ACK_HEADERS, false, Constants.DEFAULT_HEADERS_ACK_FRAME_SIZE);
}
@Override
protected final String getConnectionId() {
return handler.getConnectionId();
}
@Override
protected int getWeight() {
return weight;
}
Request getCoyoteRequest() {
return coyoteRequest;
}
Response getCoyoteResponse() {
return coyoteResponse;
}
ByteBuffer getInputByteBuffer() {
return inputBuffer.getInBuffer();
}
final void receivedStartOfHeaders(boolean headersEndStream) throws Http2Exception {
if (headerState == HEADER_STATE_START) {
headerState = HEADER_STATE_PSEUDO;
handler.getHpackDecoder().setMaxHeaderCount(handler.getMaxHeaderCount());
handler.getHpackDecoder().setMaxHeaderSize(handler.getMaxHeaderSize());
} else if (headerState == HEADER_STATE_PSEUDO || headerState == HEADER_STATE_REGULAR) {
// Trailer headers MUST include the end of stream flag
if (headersEndStream) {
headerState = HEADER_STATE_TRAILER;
handler.getHpackDecoder().setMaxHeaderCount(handler.getMaxTrailerCount());
handler.getHpackDecoder().setMaxHeaderSize(handler.getMaxTrailerSize());
} else {
throw new ConnectionException(sm.getString("stream.trailerHeader.noEndOfStream", getConnectionId(), getIdentifier()), Http2Error.PROTOCOL_ERROR);
}
}
// Parser will catch attempt to send a headers frame after the stream
// has closed.
state.receivedStartOfHeaders();
}
final void receivedData(int payloadSize) throws ConnectionException {
contentLengthReceived += payloadSize;
long contentLengthHeader = coyoteRequest.getContentLengthLong();
if (contentLengthHeader > -1 && contentLengthReceived > contentLengthHeader) {
throw new ConnectionException(sm.getString("stream.header.contentLength", getConnectionId(), getIdentifier(), Long.valueOf(contentLengthHeader), Long.valueOf(contentLengthReceived)), Http2Error.PROTOCOL_ERROR);
}
}
final void receivedEndOfStream() throws ConnectionException {
long contentLengthHeader = coyoteRequest.getContentLengthLong();
if (contentLengthHeader > -1 && contentLengthReceived != contentLengthHeader) {
throw new ConnectionException(sm.getString("stream.header.contentLength", getConnectionId(), getIdentifier(), Long.valueOf(contentLengthHeader), Long.valueOf(contentLengthReceived)), Http2Error.PROTOCOL_ERROR);
}
state.receivedEndOfStream();
if (inputBuffer != null) {
inputBuffer.notifyEof();
}
}
final void sentHeaders() {
state.sentStartOfHeaders();
}
final void sentEndOfStream() {
streamOutputBuffer.endOfStreamSent = true;
state.sentEndOfStream();
}
final boolean isReadyForWrite() {
return streamOutputBuffer.isReady();
}
final boolean flush(boolean block) throws IOException {
return streamOutputBuffer.flush(block);
}
StreamInputBuffer getInputBuffer() {
return inputBuffer;
}
final HttpOutputBuffer getOutputBuffer() {
return http2OutputBuffer;
}
void sentPushPromise() {
state.sentPushPromise();
}
boolean isActive() {
return state.isActive();
}
boolean canWrite() {
return state.canWrite();
}
boolean isClosedFinal() {
return state.isClosedFinal();
}
void closeIfIdle() {
state.closeIfIdle();
}
boolean isInputFinished() {
return !state.isFrameTypePermitted(FrameType.DATA);
}
void close(Http2Exception http2Exception) {
if (http2Exception instanceof StreamException) {
try {
StreamException se = (StreamException) http2Exception;
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.reset.send", getConnectionId(), getIdentifier(), se.getError()));
}
state.sendReset();
handler.sendStreamReset(se);
} catch (IOException ioe) {
ConnectionException ce = new ConnectionException(sm.getString("stream.reset.fail"), Http2Error.PROTOCOL_ERROR);
ce.initCause(ioe);
handler.closeConnection(ce);
}
} else {
handler.closeConnection(http2Exception);
}
// Reads wait internally so need to call a method to break the wait()
if (inputBuffer != null) {
inputBuffer.receiveReset();
}
}
boolean isPushSupported() {
return handler.getRemoteSettings().getEnablePush();
}
final void push(Request request) throws IOException {
// Can only push when supported and from a peer initiated stream
if (!isPushSupported() || getIdAsInt() % 2 == 0) {
return;
}
// Set the special HTTP/2 headers
request.getMimeHeaders().addValue(":method").duplicate(request.method());
request.getMimeHeaders().addValue(":scheme").duplicate(request.scheme());
StringBuilder path = new StringBuilder(request.requestURI().toString());
if (!request.queryString().isNull()) {
path.append('?');
path.append(request.queryString().toString());
}
request.getMimeHeaders().addValue(":path").setString(path.toString());
// Authority needs to include the port only if a non-standard port is
// being used.
if (!(request.scheme().equals("http") && request.getServerPort() == 80) && !(request.scheme().equals("https") && request.getServerPort() == 443)) {
request.getMimeHeaders().addValue(":authority").setString(request.serverName().getString() + ":" + request.getServerPort());
} else {
request.getMimeHeaders().addValue(":authority").duplicate(request.serverName());
}
push(handler, request, this);
}
StreamException getResetException() {
return streamOutputBuffer.reset;
}
private static void push(final Http2UpgradeHandler handler, final Request request, final Stream stream) throws IOException {
if (org.apache.coyote.Constants.IS_SECURITY_ENABLED) {
try {
AccessController.doPrivileged(new PrivilegedPush(handler, request, stream));
} catch (PrivilegedActionException ex) {
Exception e = ex.getException();
if (e instanceof IOException) {
throw (IOException) e;
} else {
throw new IOException(ex);
}
}
} else {
handler.push(request, stream);
}
}
private static clreplaced PrivilegedPush implements PrivilegedExceptionAction<Void> {
private final Http2UpgradeHandler handler;
private final Request request;
private final Stream stream;
public PrivilegedPush(Http2UpgradeHandler handler, Request request, Stream stream) {
this.handler = handler;
this.request = request;
this.stream = stream;
}
@Override
public Void run() throws IOException {
handler.push(request, stream);
return null;
}
}
clreplaced StreamOutputBuffer implements HttpOutputBuffer, WriteBuffer.Sink {
private final ByteBuffer buffer = ByteBuffer.allocate(8 * 1024);
private final WriteBuffer writeBuffer = new WriteBuffer(32 * 1024);
// Flag that indicates that data was left over on a previous
// non-blocking write. Once set, this flag stays set until all the data
// has been written.
private boolean dataLeft;
private volatile long written = 0;
private volatile int streamReservation = 0;
private volatile boolean closed = false;
private volatile StreamException reset = null;
private volatile boolean endOfStreamSent = false;
/* The write methods are synchronized to ensure that only one thread at
* a time is able to access the buffer. Without this protection, a
* client that performed concurrent writes could corrupt the buffer.
*/
/**
* @deprecated Unused. Will be removed in Tomcat 9. Use
* {@link #doWrite(ByteBuffer)}
*/
@Deprecated
@Override
public synchronized int doWrite(ByteChunk chunk) throws IOException {
if (closed) {
throw new IllegalStateException(sm.getString("stream.closed", getConnectionId(), getIdentifier()));
}
if (!coyoteResponse.isCommitted()) {
coyoteResponse.sendHeaders();
}
int len = chunk.getLength();
int offset = 0;
while (len > 0) {
int thisTime = Math.min(buffer.remaining(), len);
buffer.put(chunk.getBytes(), chunk.getOffset() + offset, thisTime);
offset += thisTime;
len -= thisTime;
if (len > 0 && !buffer.hasRemaining()) {
// Only flush if we have more data to write and the buffer
// is full
if (flush(true, coyoteResponse.getWriteListener() == null)) {
break;
}
}
}
written += offset;
return offset;
}
@Override
public synchronized int doWrite(ByteBuffer chunk) throws IOException {
if (closed) {
throw new IllegalStateException(sm.getString("stream.closed", getConnectionId(), getIdentifier()));
}
int totalThisTime = 0;
if (writeBuffer.isEmpty()) {
int chunkLimit = chunk.limit();
while (chunk.remaining() > 0) {
int thisTime = Math.min(buffer.remaining(), chunk.remaining());
chunk.limit(chunk.position() + thisTime);
buffer.put(chunk);
chunk.limit(chunkLimit);
totalThisTime += thisTime;
if (chunk.remaining() > 0 && !buffer.hasRemaining()) {
// Only flush if we have more data to write and the buffer
// is full
if (flush(true, coyoteResponse.getWriteListener() == null)) {
totalThisTime = chunk.remaining();
writeBuffer.add(chunk);
dataLeft = true;
break;
}
}
}
} else {
totalThisTime = chunk.remaining();
writeBuffer.add(chunk);
}
written += totalThisTime;
return totalThisTime;
}
public synchronized boolean flush(boolean block) throws IOException {
/*
* Need to ensure that there is exactly one call to flush even when
* there is no data to write.
* Too few calls (i.e. zero) and the end of stream message is not
* sent for a completed asynchronous write.
* Too many calls and the end of stream message is sent too soon and
* trailer headers are not sent.
*/
boolean dataInBuffer = buffer.position() > 0;
boolean flushed = false;
if (dataInBuffer) {
dataInBuffer = flush(false, block);
flushed = true;
}
if (dataInBuffer) {
dataLeft = true;
} else {
if (writeBuffer.isEmpty()) {
// Both buffer and writeBuffer are empty.
if (flushed) {
dataLeft = false;
} else {
dataLeft = flush(false, block);
}
} else {
dataLeft = writeBuffer.write(this, block);
}
}
return dataLeft;
}
private synchronized boolean flush(boolean writeInProgress, boolean block) throws IOException {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.outputBuffer.flush.debug", getConnectionId(), getIdentifier(), Integer.toString(buffer.position()), Boolean.toString(writeInProgress), Boolean.toString(closed)));
}
if (buffer.position() == 0) {
if (closed && !endOfStreamSent) {
// Handling this special case here is simpler than trying
// to modify the following code to handle it.
handler.writeBody(Stream.this, buffer, 0, true);
}
// Buffer is empty. Nothing to do.
return false;
}
buffer.flip();
int left = buffer.remaining();
while (left > 0) {
if (streamReservation == 0) {
streamReservation = reserveWindowSize(left, block);
if (streamReservation == 0) {
// Must be non-blocking.
// Note: Can't add to the writeBuffer here as the write
// may originate from the writeBuffer.
buffer.compact();
return true;
}
}
while (streamReservation > 0) {
int connectionReservation = handler.reserveWindowSize(Stream.this, streamReservation, block);
if (connectionReservation == 0) {
// Must be non-blocking.
// Note: Can't add to the writeBuffer here as the write
// may originate from the writeBuffer.
buffer.compact();
return true;
}
// Do the write
handler.writeBody(Stream.this, buffer, connectionReservation, !writeInProgress && closed && left == connectionReservation);
streamReservation -= connectionReservation;
left -= connectionReservation;
}
}
buffer.clear();
return false;
}
synchronized boolean isReady() {
// Bug 63682
// Only want to return false if the window size is zero AND we are
// already waiting for an allocation.
if (getWindowSize() > 0 && allocationManager.isWaitingForStream() || handler.getWindowSize() > 0 && allocationManager.isWaitingForConnection() || dataLeft) {
return false;
} else {
return true;
}
}
@Override
public long getBytesWritten() {
return written;
}
@Override
public final void end() throws IOException {
if (reset != null) {
throw new CloseNowException(reset);
}
if (!closed) {
closed = true;
flush(true);
}
}
public boolean isClosed() {
return closed;
}
/**
* @return <code>true</code> if it is certain that the replacedociated
* response has no body.
*/
public boolean hasNoBody() {
return ((written == 0) && closed);
}
@Override
public void flush() throws IOException {
/*
* This method should only be called during blocking I/O. All the
* Servlet API calls that end up here are illegal during
* non-blocking I/O. Servlet 5.4.
* However, the wording Servlet specification states that the
* behaviour is undefined so we do the best we can which is to
* perform a flush using blocking I/O or non-blocking I/O based
* depending which is currently in use.
*/
flush(getCoyoteResponse().getWriteListener() == null);
}
@Override
public synchronized boolean writeFromBuffer(ByteBuffer src, boolean blocking) throws IOException {
int chunkLimit = src.limit();
while (src.remaining() > 0) {
int thisTime = Math.min(buffer.remaining(), src.remaining());
src.limit(src.position() + thisTime);
buffer.put(src);
src.limit(chunkLimit);
if (flush(false, blocking)) {
return true;
}
}
return false;
}
}
clreplaced StreamInputBuffer implements InputBuffer {
/* Two buffers are required to avoid various multi-threading issues.
* These issues arise from the fact that the Stream (or the
* Request/Response) used by the application is processed in one thread
* but the connection is processed in another. Therefore it is possible
* that a request body frame could be received before the application
* is ready to read it. If it isn't buffered, processing of the
* connection (and hence all streams) would block until the application
* read the data. Hence the incoming data has to be buffered.
* If only one buffer was used then it could become corrupted if the
* connection thread is trying to add to it at the same time as the
* application is read it. While it should be possible to avoid this
* corruption by careful use of the buffer it would still require the
* same copies as using two buffers and the behaviour would be less
* clear.
*
* The buffers are created lazily because they quickly add up to a lot
* of memory and most requests do not have bodies.
*/
// This buffer is used to populate the ByteChunk preplaceded in to the read
// method
private byte[] outBuffer;
// This buffer is the destination for incoming data. It is normally is
// 'write mode'.
private volatile ByteBuffer inBuffer;
private volatile boolean readInterest;
private boolean resetReceived = false;
/**
* @deprecated Unused. Will be removed in Tomcat 9. Use
* {@link #doRead(ApplicationBufferHandler)}
*/
@Deprecated
@Override
public int doRead(ByteChunk chunk) throws IOException {
ensureBuffersExist();
int written = -1;
// Ensure that only one thread accesses inBuffer at a time
synchronized (inBuffer) {
boolean canRead = false;
while (inBuffer.position() == 0 && (canRead = isActive() && !isInputFinished())) {
// Need to block until some data is written
try {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.inputBuffer.empty"));
}
long readTimeout = handler.getProtocol().getStreamReadTimeout();
if (readTimeout < 0) {
inBuffer.wait();
} else {
inBuffer.wait(readTimeout);
}
if (resetReceived) {
throw new IOException(sm.getString("stream.inputBuffer.reset"));
}
if (inBuffer.position() == 0) {
String msg = sm.getString("stream.inputBuffer.readTimeout");
StreamException se = new StreamException(msg, Http2Error.ENHANCE_YOUR_CALM, getIdAsInt());
// Trigger a reset once control returns to Tomcat
coyoteResponse.setError();
streamOutputBuffer.reset = se;
throw new CloseNowException(msg, se);
}
} catch (InterruptedException e) {
// Possible shutdown / rst or similar. Use an
// IOException to signal to the client that further I/O
// isn't possible for this Stream.
throw new IOException(e);
}
}
if (inBuffer.position() > 0) {
// Data is available in the inBuffer. Copy it to the
// outBuffer.
inBuffer.flip();
written = inBuffer.remaining();
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.inputBuffer.copy", Integer.toString(written)));
}
inBuffer.get(outBuffer, 0, written);
inBuffer.clear();
} else if (!canRead) {
return -1;
} else {
// Should never happen
throw new IllegalStateException();
}
}
chunk.setBytes(outBuffer, 0, written);
// Increment client-side flow control windows by the number of bytes
// read
handler.writeWindowUpdate(Stream.this, written, true);
return written;
}
@Override
public int doRead(ApplicationBufferHandler applicationBufferHandler) throws IOException {
ensureBuffersExist();
int written = -1;
// Ensure that only one thread accesses inBuffer at a time
synchronized (inBuffer) {
boolean canRead = false;
while (inBuffer.position() == 0 && (canRead = isActive() && !isInputFinished())) {
// Need to block until some data is written
try {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.inputBuffer.empty"));
}
long readTimeout = handler.getProtocol().getStreamReadTimeout();
if (readTimeout < 0) {
inBuffer.wait();
} else {
inBuffer.wait(readTimeout);
}
if (resetReceived) {
throw new IOException(sm.getString("stream.inputBuffer.reset"));
}
if (inBuffer.position() == 0 && isActive() && !isInputFinished()) {
String msg = sm.getString("stream.inputBuffer.readTimeout");
StreamException se = new StreamException(msg, Http2Error.ENHANCE_YOUR_CALM, getIdAsInt());
// Trigger a reset once control returns to Tomcat
coyoteResponse.setError();
streamOutputBuffer.reset = se;
throw new CloseNowException(msg, se);
}
} catch (InterruptedException e) {
// Possible shutdown / rst or similar. Use an
// IOException to signal to the client that further I/O
// isn't possible for this Stream.
throw new IOException(e);
}
}
if (inBuffer.position() > 0) {
// Data is available in the inBuffer. Copy it to the
// outBuffer.
inBuffer.flip();
written = inBuffer.remaining();
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.inputBuffer.copy", Integer.toString(written)));
}
inBuffer.get(outBuffer, 0, written);
inBuffer.clear();
} else if (!canRead) {
return -1;
} else {
// Should never happen
throw new IllegalStateException();
}
}
applicationBufferHandler.setByteBuffer(ByteBuffer.wrap(outBuffer, 0, written));
// Increment client-side flow control windows by the number of bytes
// read
handler.writeWindowUpdate(Stream.this, written, true);
return written;
}
final boolean isReadyForRead() {
ensureBuffersExist();
synchronized (this) {
if (available() > 0) {
return true;
}
if (!isRequestBodyFullyRead()) {
readInterest = true;
}
return false;
}
}
synchronized boolean isRequestBodyFullyRead() {
return (inBuffer == null || inBuffer.position() == 0) && isInputFinished();
}
synchronized int available() {
if (inBuffer == null) {
return 0;
}
return inBuffer.position();
}
/*
* Called after placing some data in the inBuffer.
*/
synchronized boolean onDataAvailable() {
if (readInterest) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.inputBuffer.dispatch"));
}
readInterest = false;
coyoteRequest.action(ActionCode.DISPATCH_READ, null);
// Always need to dispatch since this thread is processing
// the incoming connection and streams are processed on their
// own.
coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
return true;
} else {
if (log.isDebugEnabled()) {
log.debug(sm.getString("stream.inputBuffer.signal"));
}
synchronized (inBuffer) {
inBuffer.notifyAll();
}
return false;
}
}
public ByteBuffer getInBuffer() {
ensureBuffersExist();
return inBuffer;
}
protected synchronized void insertReplayedBody(ByteChunk body) {
inBuffer = ByteBuffer.wrap(body.getBytes(), body.getOffset(), body.getLength());
}
private void ensureBuffersExist() {
if (inBuffer == null) {
// The client must obey Tomcat's window size when sending so
// this is the initial window size set by Tomcat that the client
// uses (i.e. the local setting is required here).
int size = handler.getLocalSettings().getInitialWindowSize();
synchronized (this) {
if (inBuffer == null) {
inBuffer = ByteBuffer.allocate(size);
outBuffer = new byte[size];
}
}
}
}
protected void receiveReset() {
if (inBuffer != null) {
synchronized (inBuffer) {
resetReceived = true;
inBuffer.notifyAll();
}
}
}
private final void notifyEof() {
if (inBuffer != null) {
synchronized (inBuffer) {
inBuffer.notifyAll();
}
}
}
}
}
16
View Complete Implementation : InputBuffer.java
Copyright Apache License 2.0
Author : codefollower
Copyright Apache License 2.0
Author : codefollower
/**
* The buffer used by Tomcat request. This is a derivative of the Tomcat 3.3
* OutputBuffer, adapted to handle input instead of output. This allows
* complete recycling of the facade objects (the ServletInputStream and the
* BufferedReader).
*
* @author Remy Maucherat
*/
public clreplaced InputBuffer extends Reader implements ByteChunk.ByteInputChannel, CharChunk.CharInputChannel, CharChunk.CharOutputChannel {
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(Constants.Package);
// -------------------------------------------------------------- Constants
public static final String DEFAULT_ENCODING = org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
// The buffer can be used for byte[] and char[] reading
// ( this is needed to support ServletInputStream and BufferedReader )
public final int INITIAL_STATE = 0;
public final int CHAR_STATE = 1;
public final int BYTE_STATE = 2;
// ----------------------------------------------------- Instance Variables
/**
* The byte buffer.
*/
private final ByteChunk bb;
/**
* The chunk buffer.
*/
private CharChunk cb;
/**
* State of the output buffer.
*/
private int state = 0;
/**
* Flag which indicates if the input buffer is closed.
*/
private boolean closed = false;
/**
* Encoding to use.
*/
private String enc;
/**
* Encoder is set.
*/
private boolean gotEnc = false;
/**
* List of encoders.
*/
protected final HashMap<String, B2CConverter> encoders = new HashMap<>();
/**
* Current byte to char converter.
*/
protected B2CConverter conv;
/**
* replacedociated Coyote request.
*/
private Request coyoteRequest;
/**
* Buffer position.
*/
private int markPos = -1;
/**
* Buffer size.
*/
private final int size;
// ----------------------------------------------------------- Constructors
/**
* Default constructor. Allocate the buffer with the default buffer size.
*/
public InputBuffer() {
this(DEFAULT_BUFFER_SIZE);
}
/**
* Alternate constructor which allows specifying the initial buffer size.
*
* @param size Buffer size to use
*/
public InputBuffer(int size) {
this.size = size;
bb = new ByteChunk(size);
bb.setLimit(size);
bb.setByteInputChannel(this);
cb = new CharChunk(size);
cb.setLimit(size);
cb.setOptimizedWrite(false);
cb.setCharInputChannel(this);
cb.setCharOutputChannel(this);
}
// ------------------------------------------------------------- Properties
/**
* replacedociated Coyote request.
*
* @param coyoteRequest replacedociated Coyote request
*/
public void setRequest(Request coyoteRequest) {
this.coyoteRequest = coyoteRequest;
}
// --------------------------------------------------------- Public Methods
/**
* Recycle the output buffer.
*/
public void recycle() {
state = INITIAL_STATE;
// If usage of mark made the buffer too big, reallocate it
if (cb.getChars().length > size) {
cb = new CharChunk(size);
cb.setLimit(size);
cb.setOptimizedWrite(false);
cb.setCharInputChannel(this);
cb.setCharOutputChannel(this);
} else {
cb.recycle();
}
markPos = -1;
bb.recycle();
closed = false;
if (conv != null) {
conv.recycle();
}
gotEnc = false;
enc = null;
}
/**
* Clear cached encoders (to save memory for Comet requests).
*/
public void clearEncoders() {
encoders.clear();
}
/**
* Close the input buffer.
*
* @throws IOException An underlying IOException occurred
*/
@Override
public void close() throws IOException {
closed = true;
}
public int available() {
int available = 0;
if (state == BYTE_STATE) {
available = bb.getLength();
} else if (state == CHAR_STATE) {
available = cb.getLength();
}
if (available == 0) {
coyoteRequest.action(ActionCode.AVAILABLE, null);
available = (coyoteRequest.getAvailable() > 0) ? 1 : 0;
}
return available;
}
public void setReadListener(ReadListener listener) {
coyoteRequest.setReadListener(listener);
// The container is responsible for the first call to
// listener.onDataAvailable(). If isReady() returns true, the container
// needs to call listener.onDataAvailable() from a new thread. If
// isReady() returns false, the socket will be registered for read and
// the container will call listener.onDataAvailable() once data arrives.
// Must call isFinished() first as a call to isReady() if the request
// has been finished will register the socket for read interest and that
// is not required.
if (!coyoteRequest.isFinished() && isReady()) {
coyoteRequest.action(ActionCode.DISPATCH_READ, null);
}
}
public boolean isFinished() {
return coyoteRequest.isFinished();
}
public boolean isReady() {
if (coyoteRequest.getReadListener() == null) {
throw new IllegalStateException("not in non blocking mode.");
}
int available = available();
boolean result = available > 0;
if (!result) {
coyoteRequest.action(ActionCode.NB_READ_INTEREST, null);
}
return result;
}
boolean isBlocking() {
return coyoteRequest.getReadListener() != null;
}
// ------------------------------------------------- Bytes Handling Methods
/**
* Reads new bytes in the byte chunk.
*
* @param cbuf Byte buffer to be written to the response
* @param off Offset
* @param len Length
*
* @throws IOException An underlying IOException occurred
*/
@Override
public int realReadBytes(byte[] cbuf, int off, int len) throws IOException {
if (closed) {
return -1;
}
if (coyoteRequest == null) {
return -1;
}
if (state == INITIAL_STATE) {
state = BYTE_STATE;
}
int result = coyoteRequest.doRead(bb);
return result;
}
public int readByte() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return bb.substract();
}
public int read(byte[] b, int off, int len) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return bb.substract(b, off, len);
}
// ------------------------------------------------- Chars Handling Methods
/**
* Since the converter will use append, it is possible to get chars to
* be removed from the buffer for "writing". Since the chars have already
* been read before, they are ignored. If a mark was set, then the
* mark is lost.
*/
@Override
public void realWriteChars(char[] c, int off, int len) throws IOException {
markPos = -1;
cb.setOffset(0);
cb.setEnd(0);
}
public void setEncoding(String s) {
enc = s;
}
@Override
public int realReadChars(char[] cbuf, int off, int len) throws IOException {
if (!gotEnc) {
setConverter();
}
boolean eof = false;
if (bb.getLength() <= 0) {
int nRead = realReadBytes(bb.getBytes(), 0, bb.getBytes().length);
if (nRead < 0) {
eof = true;
}
}
if (markPos == -1) {
cb.setOffset(0);
cb.setEnd(0);
} else {
// Make sure there's enough space in the worst case
cb.makeSpace(bb.getLength());
if ((cb.getBuffer().length - cb.getEnd()) == 0) {
// We went over the limit
cb.setOffset(0);
cb.setEnd(0);
markPos = -1;
}
}
state = CHAR_STATE;
conv.convert(bb, cb, eof);
if (cb.getLength() == 0 && eof) {
return -1;
} else {
return cb.getLength();
}
}
@Override
public int read() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return cb.substract();
}
@Override
public int read(char[] cbuf) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return read(cbuf, 0, cbuf.length);
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
return cb.substract(cbuf, off, len);
}
@Override
public long skip(long n) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (n < 0) {
throw new IllegalArgumentException();
}
long nRead = 0;
while (nRead < n) {
if (cb.getLength() >= n) {
cb.setOffset(cb.getStart() + (int) n);
nRead = n;
} else {
nRead += cb.getLength();
cb.setOffset(cb.getEnd());
int toRead = 0;
if (cb.getChars().length < (n - nRead)) {
toRead = cb.getChars().length;
} else {
toRead = (int) (n - nRead);
}
int nb = realReadChars(cb.getChars(), 0, toRead);
if (nb < 0) {
break;
}
}
}
return nRead;
}
@Override
public boolean ready() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (state == INITIAL_STATE) {
state = CHAR_STATE;
}
return (available() > 0);
}
@Override
public boolean markSupported() {
return true;
}
@Override
public void mark(int readAheadLimit) throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (cb.getLength() <= 0) {
cb.setOffset(0);
cb.setEnd(0);
} else {
if ((cb.getBuffer().length > (2 * size)) && (cb.getLength()) < (cb.getStart())) {
System.arraycopy(cb.getBuffer(), cb.getStart(), cb.getBuffer(), 0, cb.getLength());
cb.setEnd(cb.getLength());
cb.setOffset(0);
}
}
cb.setLimit(cb.getStart() + readAheadLimit + size);
markPos = cb.getStart();
}
@Override
public void reset() throws IOException {
if (closed) {
throw new IOException(sm.getString("inputBuffer.streamClosed"));
}
if (state == CHAR_STATE) {
if (markPos < 0) {
cb.recycle();
markPos = -1;
throw new IOException();
} else {
cb.setOffset(markPos);
}
} else {
bb.recycle();
}
}
public void checkConverter() throws IOException {
if (!gotEnc) {
setConverter();
}
}
protected void setConverter() throws IOException {
if (coyoteRequest != null) {
enc = coyoteRequest.getCharacterEncoding();
}
gotEnc = true;
if (enc == null) {
enc = DEFAULT_ENCODING;
}
conv = encoders.get(enc);
if (conv == null) {
if (SecurityUtil.isPackageProtectionEnabled()) {
try {
conv = AccessController.doPrivileged(new PrivilegedExceptionAction<B2CConverter>() {
@Override
public B2CConverter run() throws IOException {
return new B2CConverter(enc);
}
});
} catch (PrivilegedActionException ex) {
Exception e = ex.getException();
if (e instanceof IOException) {
throw (IOException) e;
}
}
} else {
conv = new B2CConverter(enc);
}
encoders.put(enc, conv);
}
}
}
16
View Complete Implementation : AbstractInputBuffer.java
Copyright Apache License 2.0
Author : codefollower
Copyright Apache License 2.0
Author : codefollower
public abstract clreplaced AbstractInputBuffer<S> implements InputBuffer {
protected static final boolean[] HTTP_TOKEN_CHAR = new boolean[128];
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(Constants.Package);
static {
for (int i = 0; i < 128; i++) {
if (i < 32) {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == 127) {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '(') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == ')') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '<') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '>') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '@') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == ',') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == ';') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == ':') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '\\') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '\"') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '/') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '[') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == ']') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '?') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '=') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '{') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '}') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == ' ') {
HTTP_TOKEN_CHAR[i] = false;
} else if (i == '\t') {
HTTP_TOKEN_CHAR[i] = false;
} else {
HTTP_TOKEN_CHAR[i] = true;
}
}
}
/**
* replacedociated Coyote request.
*/
protected Request request;
/**
* Headers of the replacedociated 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[0] is always the "chunked" filter.
*/
protected InputFilter[] filterLibrary;
/**
* Active filters (in order).
*/
protected InputFilter[] activeFilters;
/**
* Index of the last active filter.
*/
protected int lastActiveFilter;
// ------------------------------------------------------------- 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;
}
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) {
int npos = 0;
int opos = pos;
while (lastValid - opos > opos - npos) {
System.arraycopy(buf, opos, buf, npos, opos - npos);
npos += pos;
opos += pos;
}
System.arraycopy(buf, opos, buf, npos, lastValid - opos);
}
// Recycle filters
for (int i = 0; i <= lastActiveFilter; i++) {
activeFilters[i].recycle();
}
// Reset pointers
lastValid = lastValid - pos;
pos = 0;
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() {
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) {
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 cirreplacedstances
* 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. replacedume request is not finished. EOF will signal end of
// request.
return false;
}
}
// ---------------------------------------------------- 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);
}
}
15
View Complete Implementation : Http11InputBuffer.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
/**
* InputBuffer for HTTP that provides request header parsing as well as transfer
* encoding.
*/
public clreplaced Http11InputBuffer implements InputBuffer, ApplicationBufferHandler {
// -------------------------------------------------------------- Constants
private static final Log log = LogFactory.getLog(Http11InputBuffer.clreplaced);
/**
* The string manager for this package.
*/
private static final StringManager sm = StringManager.getManager(Http11InputBuffer.clreplaced);
private static final byte[] CLIENT_PREFACE_START = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1);
/**
* replacedociated Coyote request.
*/
private final Request request;
/**
* Headers of the replacedociated request.
*/
private final MimeHeaders headers;
private final boolean rejectIllegalHeaderName;
/**
* State.
*/
private 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 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 boolean parsingRequestLine;
private int parsingRequestLinePhase = 0;
private boolean parsingRequestLineEol = false;
private int parsingRequestLineStart = 0;
private int parsingRequestLineQPos = -1;
private HeaderParsePosition headerParsePos;
private final HeaderParseData headerData = new HeaderParseData();
private final HttpParser httpParser;
/**
* 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;
// ----------------------------------------------------------- Constructors
public Http11InputBuffer(Request request, int headerBufferSize, boolean rejectIllegalHeaderName, HttpParser httpParser) {
this.request = request;
headers = request.getMimeHeaders();
this.headerBufferSize = headerBufferSize;
this.rejectIllegalHeaderName = rejectIllegalHeaderName;
this.httpParser = httpParser;
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;
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();
}
byteBuffer.limit(0).position(0);
lastActiveFilter = -1;
parsingHeader = true;
swallowInput = true;
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.
*/
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;
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
*/
boolean parseRequestLine(boolean keptAlive, int connectionTimeout, int keepAliveTimeout) throws IOException {
// check state
if (!parsingRequestLine) {
return true;
}
//
// Skipping blank lines
//
if (parsingRequestLinePhase < 2) {
byte chr = 0;
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)) {
// A read is pending, so no longer in initial state
parsingRequestLinePhase = 1;
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 - 1) {
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 (request.getStartTime() < 0) {
request.setStartTime(System.currentTimeMillis());
}
chr = byteBuffer.get();
} while ((chr == Constants.CR) || (chr == Constants.LF));
byteBuffer.position(byteBuffer.position() - 1);
parsingRequestLineStart = byteBuffer.position();
parsingRequestLinePhase = 2;
if (log.isDebugEnabled()) {
log.debug("Received [" + new String(byteBuffer.array(), byteBuffer.position(), byteBuffer.remaining(), StandardCharsets.ISO_8859_1) + "]");
}
}
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 (// request line parsing
!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();
byte chr = byteBuffer.get();
if (chr == Constants.SP || chr == Constants.HT) {
space = true;
request.method().setBytes(byteBuffer.array(), parsingRequestLineStart, pos - parsingRequestLineStart);
} else if (!HttpParser.isToken(chr)) {
byteBuffer.position(byteBuffer.position() - 1);
// Avoid unknown protocol triggering an additional error
request.protocol().setString(Constants.HTTP_11);
throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));
}
}
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 (// request line parsing
!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 = 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 (// request line parsing
!fill(false))
return false;
}
int pos = byteBuffer.position();
byte chr = byteBuffer.get();
if (chr == Constants.SP || chr == Constants.HT) {
space = true;
end = pos;
} else if (chr == Constants.CR || chr == Constants.LF) {
// HTTP/0.9 style request
parsingRequestLineEol = true;
space = true;
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
throw new IllegalArgumentException(sm.getString("iib.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()
throw new IllegalArgumentException(sm.getString("iib.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);
}
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 (// request line parsing
!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 (// request line parsing
!fill(false))
return false;
}
int pos = byteBuffer.position();
byte chr = byteBuffer.get();
if (chr == Constants.CR) {
end = pos;
} else if (chr == Constants.LF) {
if (end == 0) {
end = pos;
}
parsingRequestLineEol = true;
} else if (!HttpParser.isHttpProtocol(chr)) {
throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
}
}
if ((end - parsingRequestLineStart) > 0) {
request.protocol().setBytes(byteBuffer.array(), 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);
}
/**
* Parse the HTTP headers.
*/
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 (byteBuffer.position() > headerBufferSize || byteBuffer.capacity() - byteBuffer.position() < socketReadBufferSize) {
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;
}
/**
* 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);
}
}
/**
* Available bytes in the buffers (note that due to encoding, this may not
* correspond).
*/
int available(boolean read) {
int available = byteBuffer.remaining();
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 {
if (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.
*/
boolean isFinished() {
if (byteBuffer.limit() > byteBuffer.position()) {
// Data to read in the buffer so not finished
return false;
}
/*
* Don't use fill(false) here because in the following cirreplacedstances
* 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. replacedume 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);
}
}
// --------------------------------------------------------- Private Methods
/**
* 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>
*/
private boolean fill(boolean block) throws IOException {
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);
}
byteBuffer.mark();
if (byteBuffer.position() < byteBuffer.limit()) {
byteBuffer.position(byteBuffer.limit());
}
byteBuffer.limit(byteBuffer.capacity());
SocketWrapperBase<?> socketWrapper = this.wrapper;
int nRead = -1;
if (socketWrapper != null) {
nRead = socketWrapper.read(block, byteBuffer);
} else {
throw new CloseNowException(sm.getString("iib.eof.error"));
}
byteBuffer.limit(byteBuffer.position()).reset();
if (nRead > 0) {
return true;
} else if (nRead == -1) {
throw new EOFException(sm.getString("iib.eof.error"));
} 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 (byteBuffer.position() >= byteBuffer.limit()) {
if (!fill(false)) {
// parse header
headerParsePos = HeaderParsePosition.HEADER_START;
return HeaderParseStatus.NEED_MORE_DATA;
}
}
chr = byteBuffer.get();
if (chr == Constants.CR) {
// Skip
} else if (chr == Constants.LF) {
return HeaderParseStatus.DONE;
} else {
byteBuffer.position(byteBuffer.position() - 1);
break;
}
}
if (headerParsePos == HeaderParsePosition.HEADER_START) {
// Mark the current buffer position
headerData.start = byteBuffer.position();
headerData.lineStart = headerData.start;
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 (byteBuffer.position() >= byteBuffer.limit()) {
if (!fill(false)) {
// parse header
return HeaderParseStatus.NEED_MORE_DATA;
}
}
int pos = byteBuffer.position();
chr = byteBuffer.get();
if (chr == Constants.COLON) {
headerParsePos = HeaderParsePosition.HEADER_VALUE_START;
headerData.headerValue = headers.addValue(byteBuffer.array(), headerData.start, pos - headerData.start);
pos = byteBuffer.position();
// Mark the current buffer position
headerData.start = pos;
headerData.realPos = pos;
headerData.lastSignificantChar = pos;
break;
} else if (!HttpParser.isToken(chr)) {
// Non-token characters are illegal in header names
// Parsing continues so the error can be reported in context
headerData.lastSignificantChar = pos;
byteBuffer.position(byteBuffer.position() - 1);
// skipLine() will handle the error
return skipLine();
}
// chr is next byte of header name. Convert to lowercase.
if ((chr >= Constants.A) && (chr <= Constants.Z)) {
byteBuffer.put(pos, (byte) (chr - Constants.LC_OFFSET));
}
}
// 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 (byteBuffer.position() >= byteBuffer.limit()) {
if (!fill(false)) {
// parse header
// HEADER_VALUE_START
return HeaderParseStatus.NEED_MORE_DATA;
}
}
chr = byteBuffer.get();
if (!(chr == Constants.SP || chr == Constants.HT)) {
headerParsePos = HeaderParsePosition.HEADER_VALUE;
byteBuffer.position(byteBuffer.position() - 1);
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 (byteBuffer.position() >= byteBuffer.limit()) {
if (!fill(false)) {
// parse header
// HEADER_VALUE
return HeaderParseStatus.NEED_MORE_DATA;
}
}
chr = byteBuffer.get();
if (chr == Constants.CR) {
// Skip
} else if (chr == Constants.LF) {
eol = true;
} else if (chr == Constants.SP || chr == Constants.HT) {
byteBuffer.put(headerData.realPos, chr);
headerData.realPos++;
} else {
byteBuffer.put(headerData.realPos, chr);
headerData.realPos++;
headerData.lastSignificantChar = headerData.realPos;
}
}
// 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 (byteBuffer.position() >= byteBuffer.limit()) {
if (!fill(false)) {
// parse header
// HEADER_MULTI_LINE
return HeaderParseStatus.NEED_MORE_DATA;
}
}
chr = byteBuffer.get(byteBuffer.position());
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)
byteBuffer.put(headerData.realPos, chr);
headerData.realPos++;
headerParsePos = HeaderParsePosition.HEADER_VALUE_START;
}
}
}
// Set the header value
headerData.headerValue.setBytes(byteBuffer.array(), headerData.start, headerData.lastSignificantChar - headerData.start);
headerData.recycle();
return HeaderParseStatus.HAVE_MORE_HEADERS;
}
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 (byteBuffer.position() >= byteBuffer.limit()) {
if (!fill(false)) {
return HeaderParseStatus.NEED_MORE_DATA;
}
}
int pos = byteBuffer.position();
byte chr = byteBuffer.get();
if (chr == Constants.CR) {
// Skip
} else if (chr == Constants.LF) {
eol = true;
} else {
headerData.lastSignificantChar = pos;
}
}
if (rejectIllegalHeaderName || log.isDebugEnabled()) {
String message = sm.getString("iib.invalidheader", HeaderUtil.toPrintableString(byteBuffer.array(), headerData.lineStart, headerData.lastSignificantChar - headerData.lineStart + 1));
if (rejectIllegalHeaderName) {
throw new IllegalArgumentException(message);
}
log.debug(message);
}
headerParsePos = HeaderParsePosition.HEADER_START;
return HeaderParseStatus.HAVE_MORE_HEADERS;
}
// ----------------------------------------------------------- Inner clreplacedes
private enum HeaderParseStatus {
DONE, HAVE_MORE_HEADERS, NEED_MORE_DATA
}
private 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.<br>
* 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
}
private static clreplaced HeaderParseData {
/**
* The first character of the header line.
*/
int lineStart = 0;
/**
* When parsing header name: first character of the header.<br>
* When skipping broken header line: first character of the header.<br>
* When parsing header value: first character after ':'.
*/
int start = 0;
/**
* When parsing header name: not used (stays as 0).<br>
* When skipping broken header line: not used (stays as 0).<br>
* 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.<br>
*/
int realPos = 0;
/**
* When parsing header name: not used (stays as 0).<br>
* When skipping broken header line: last non-CR/non-LF character.<br>
* When parsing header value: position after the last not-LWS character.<br>
*/
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() {
lineStart = 0;
start = 0;
realPos = 0;
lastSignificantChar = 0;
headerValue = null;
}
}
// ------------------------------------- InputStreamInputBuffer Inner Clreplaced
/**
* This clreplaced is an input buffer which will read its data from an input
* stream.
*/
private clreplaced SocketInputBuffer implements InputBuffer {
@Override
public int doRead(ApplicationBufferHandler handler) throws IOException {
if (byteBuffer.position() >= byteBuffer.limit()) {
// The application is reading the HTTP request body which is
// always a blocking operation.
if (!fill(true))
return -1;
}
int length = byteBuffer.remaining();
handler.setByteBuffer(byteBuffer.duplicate());
byteBuffer.position(byteBuffer.limit());
return length;
}
}
@Override
public void setByteBuffer(ByteBuffer buffer) {
byteBuffer = buffer;
}
@Override
public ByteBuffer getByteBuffer() {
return byteBuffer;
}
@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();
temp = null;
}
}
15
View Complete Implementation : Http2Protocol.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
@Override
public boolean accept(Request request) {
// Should only be one HTTP2-Settings header
Enumeration<String> settings = request.getMimeHeaders().values("HTTP2-Settings");
int count = 0;
while (settings.hasMoreElements()) {
count++;
settings.nextElement();
}
if (count != 1) {
return false;
}
Enumeration<String> connection = request.getMimeHeaders().values("Connection");
boolean found = false;
while (connection.hasMoreElements() && !found) {
found = connection.nextElement().contains("HTTP2-Settings");
}
return found;
}
15
View Complete Implementation : AbstractInputBuffer.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
public abstract clreplaced AbstractInputBuffer<S> implements InputBuffer {
/**
* The string manager for this package.
*/
protected static final StringManager sm = StringManager.getManager(Constants.Package);
/**
* replacedociated Coyote request.
*/
protected Request request;
/**
* Headers of the replacedociated 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 cirreplacedstances
* 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. replacedume 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);
}
}
15
View Complete Implementation : BufferedInputFilter.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
/**
* Fills the given ByteChunk with the buffered request body.
*/
@Override
public int doRead(ByteChunk chunk, Request request) throws IOException {
if (hasRead || buffered.getLength() <= 0) {
return -1;
}
chunk.setBytes(buffered.getBytes(), buffered.getStart(), buffered.getLength());
hasRead = true;
return chunk.getLength();
}
15
View Complete Implementation : IdentityInputFilter.java
Copyright Apache License 2.0
Author : apache
Copyright Apache License 2.0
Author : apache
// ---------------------------------------------------- InputBuffer Methods
/**
* Read bytes.
*
* @return If the filter does request length control, this value is
* significant; it should be the number of bytes consumed from the buffer,
* up until the end of the current request body, or the buffer length,
* whichever is greater. If the filter does not do request body length
* control, the returned value should be -1.
*/
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
int result = -1;
if (contentLength >= 0) {
if (remaining > 0) {
int nRead = buffer.doRead(chunk, req);
if (nRead > remaining) {
// The chunk is longer than the number of bytes remaining
// in the body; changing the chunk length to the number
// of bytes remaining
chunk.setBytes(chunk.getBytes(), chunk.getStart(), (int) remaining);
result = (int) remaining;
} else {
result = nRead;
}
if (nRead > 0) {
remaining = remaining - nRead;
}
} else {
// No more bytes left to be read : return -1 and clear the
// buffer
chunk.recycle();
result = -1;
}
}
return result;
}
15
View Complete Implementation : Http11InputBuffer.java
Copyright MIT License
Author : chenmudu
Copyright MIT License
Author : chenmudu
/**
* InputBuffer for HTTP that provides request header parsing as well as transfer
* encoding.
* 用于和Http消息的读取和写入操作。
*/
public clreplaced Http11InputBuffer implements InputBuffer, ApplicationBufferHandler {
// -------------------------------------------------------------- Constants
private static final Log log = LogFactory.getLog(Http11InputBuffer.clreplaced);
/**
* The string manager for this package.
*/
private static final StringManager sm = StringManager.getManager(Http11InputBuffer.clreplaced);
private static final byte[] CLIENT_PREFACE_START = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1);
/**
* replacedociated Coyote request.
*/
private final Request request;
/**
* Headers of the replacedociated request.
*/
private final MimeHeaders headers;
private final boolean rejectIllegalHeaderName;
/**
* State.
*/
private 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 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 boolean parsingRequestLine;
private int parsingRequestLinePhase = 0;
private boolean parsingRequestLineEol = false;
private int parsingRequestLineStart = 0;
private int parsingRequestLineQPos = -1;
private HeaderParsePosition headerParsePos;
private final HeaderParseData headerData = new HeaderParseData();
private final HttpParser httpParser;
/**
* 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;
// ----------------------------------------------------------- Constructors
public Http11InputBuffer(Request request, int headerBufferSize, boolean rejectIllegalHeaderName, HttpParser httpParser) {
this.request = request;
headers = request.getMimeHeaders();
this.headerBufferSize = headerBufferSize;
this.rejectIllegalHeaderName = rejectIllegalHeaderName;
this.httpParser = httpParser;
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;
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 = 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.
*/
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
/**
* @deprecated Unused. Will be removed in Tomcat 9. Use
* {@link #doRead(ApplicationBufferHandler)}
*/
@Deprecated
@Override
public int doRead(ByteChunk chunk) throws IOException {
if (lastActiveFilter == -1)
return inputStreamInputBuffer.doRead(chunk);
else
return activeFilters[lastActiveFilter].doRead(chunk);
}
@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();
}
byteBuffer.limit(0).position(0);
lastActiveFilter = -1;
parsingHeader = true;
swallowInput = true;
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.
*/
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;
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
*/
boolean parseRequestLine(boolean keptAlive) throws IOException {
// check state
if (!parsingRequestLine) {
return true;
}
//
// Skipping blank lines
//
if (parsingRequestLinePhase < 2) {
byte chr = 0;
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(wrapper.getEndpoint().getKeepAliveTimeout());
}
if (!fill(false)) {
// A read is pending, so no longer in initial state
parsingRequestLinePhase = 1;
return false;
}
// At least one byte of the request has been received.
// Switch to the socket timeout.
wrapper.setReadTimeout(wrapper.getEndpoint().getConnectionTimeout());
}
if (!keptAlive && byteBuffer.position() == 0 && byteBuffer.limit() >= CLIENT_PREFACE_START.length - 1) {
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 (request.getStartTime() < 0) {
request.setStartTime(System.currentTimeMillis());
}
chr = byteBuffer.get();
} while ((chr == Constants.CR) || (chr == Constants.LF));
byteBuffer.position(byteBuffer.position() - 1);
parsingRequestLineStart = byteBuffer.position();
parsingRequestLinePhase = 2;
if (log.isDebugEnabled()) {
log.debug("Received [" + new String(byteBuffer.array(), byteBuffer.position(), byteBuffer.remaining(), StandardCharsets.ISO_8859_1) + "]");
}
}
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 (// request line parsing
!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();
byte chr = byteBuffer.get();
if (chr == Constants.SP || chr == Constants.HT) {
space = true;
request.method().setBytes(byteBuffer.array(), parsingRequestLineStart, pos - parsingRequestLineStart);
} else if (!HttpParser.isToken(chr)) {
byteBuffer.position(byteBuffer.position() - 1);
// Avoid unknown protocol triggering an additional error
request.protocol().setString(Constants.HTTP_11);
throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));
}
}
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 (// request line parsing
!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 = 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 (// request line parsing
!fill(false))
return false;
}
int pos = byteBuffer.position();
byte chr = byteBuffer.get();
if (chr == Constants.SP || chr == Constants.HT) {
space = true;
end = pos;
} else if (chr == Constants.CR || chr == Constants.LF) {
// HTTP/0.9 style request
parsingRequestLineEol = true;
space = true;
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
throw new IllegalArgumentException(sm.getString("iib.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()
throw new IllegalArgumentException(sm.getString("iib.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);
}
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 (// request line parsing
!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 (// request line parsing
!fill(false))
return false;
}
int pos = byteBuffer.position();
byte chr = byteBuffer.get();
if (chr == Constants.CR) {
end = pos;
} else if (chr == Constants.LF) {
if (end == 0) {
end = pos;
}
parsingRequestLineEol = true;
} else if (!HttpParser.isHttpProtocol(chr)) {
throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
}
}
if ((end - parsingRequestLineStart) > 0) {
request.protocol().setBytes(byteBuffer.array(), 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);
}
/**
* Parse the HTTP headers.
*/
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 (byteBuffer.position() > headerBufferSize || byteBuffer.capacity() - byteBuffer.position() < socketReadBufferSize) {
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;
}
/**
* 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);
}
}
/**
* Available bytes in the buffers (note that due to encoding, this may not
* correspond).
*/
int available(boolean read) {
int available = byteBuffer.remaining();
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 {
if (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.
*/
boolean isFinished() {
if (byteBuffer.limit() > byteBuffer.position()) {
// Data to read in the buffer so not finished
return false;
}
/*
* Don't use fill(false) here because in the following cirreplacedstances
* 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. replacedume 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;
}
}
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);
}
}
// --------------------------------------------------------- Private Methods
/**
* 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>
*/
private boolean fill(boolean block) throws IOException {
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);
}
byteBuffer.mark();
if (byteBuffer.position() < byteBuffer.limit()) {
byteBuffer.position(byteBuffer.limit());
}
byteBuffer.limit(byteBuffer.capacity());
int nRead = wrapper.read(block, byteBuffer);
byteBuffer.limit(byteBuffer.position()).reset();
if (nRead > 0) {
return true;
} else if (nRead == -1) {
throw new EOFException(sm.getString("iib.eof.error"));
} 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 (byteBuffer.position() >= byteBuffer.limit()) {
if (!fill(false)) {
// parse header
headerParsePos = HeaderParsePosition.HEADER_START;
return HeaderParseStatus.NEED_MORE_DATA;
}
}
chr = byteBuffer.get();
if (chr == Constants.CR) {
// Skip
} else if (chr == Constants.LF) {
return HeaderParseStatus.DONE;
} else {
byteBuffer.position(byteBuffer.position() - 1);
break;
}
}
if (headerParsePos == HeaderParsePosition.HEADER_START) {
// Mark the current buffer position
headerData.start = byteBuffer.position();
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 (byteBuffer.position() >= byteBuffer.limit()) {
// 解析http头的时候是不会阻塞的。
if (!fill(false)) {
// parse header
return HeaderParseStatus.NEED_MORE_DATA;
}
}
int pos = byteBuffer.position();
chr = byteBuffer.get();
if (chr == Constants.COLON) {
headerParsePos = HeaderParsePosition.HEADER_VALUE_START;
headerData.headerValue = headers.addValue(byteBuffer.array(), headerData.start, pos - headerData.start);
pos = byteBuffer.position();
// Mark the current buffer position
headerData.start = pos;
headerData.realPos = pos;
headerData.lastSignificantChar = pos;
break;
} else if (!HttpParser.isToken(chr)) {
// Non-token characters are illegal in header names
// Parsing continues so the error can be reported in context
headerData.lastSignificantChar = pos;
byteBuffer.position(byteBuffer.position() - 1);
// skipLine() will handle the error
return skipLine();
}
// chr is next byte of header name. Convert to lowercase.
if ((chr >= Constants.A) && (chr <= Constants.Z)) {
byteBuffer.put(pos, (byte) (chr - Constants.LC_OFFSET));
}
}
// 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 (byteBuffer.position() >= byteBuffer.limit()) {
if (!fill(false)) {
// parse header
// HEADER_VALUE_START
return HeaderParseStatus.NEED_MORE_DATA;
}
}
chr = byteBuffer.get();
if (!(chr == Constants.SP || chr == Constants.HT)) {
headerParsePos = HeaderParsePosition.HEADER_VALUE;
byteBuffer.position(byteBuffer.position() - 1);
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 (byteBuffer.position() >= byteBuffer.limit()) {
if (!fill(false)) {
// parse header
// HEADER_VALUE
return HeaderParseStatus.NEED_MORE_DATA;
}
}
chr = byteBuffer.get();
if (chr == Constants.CR) {
// Skip
} else if (chr == Constants.LF) {
eol = true;
} else if (chr == Constants.SP || chr == Constants.HT) {
byteBuffer.put(headerData.realPos, chr);
headerData.realPos++;
} else {
byteBuffer.put(headerData.realPos, chr);
headerData.realPos++;
headerData.lastSignificantChar = headerData.realPos;
}
}
// 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 (byteBuffer.position() >= byteBuffer.limit()) {
if (!fill(false)) {
// parse header
// HEADER_MULTI_LINE
return HeaderParseStatus.NEED_MORE_DATA;
}
}
chr = byteBuffer.get(byteBuffer.position());
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)
byteBuffer.put(headerData.realPos, chr);
headerData.realPos++;
headerParsePos = HeaderParsePosition.HEADER_VALUE_START;
}
}
}
// Set the header value
headerData.headerValue.setBytes(byteBuffer.array(), headerData.start, headerData.lastSignificantChar - headerData.start);
headerData.recycle();
return HeaderParseStatus.HAVE_MORE_HEADERS;
}
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 (byteBuffer.position() >= byteBuffer.limit()) {
if (!fill(false)) {
return HeaderParseStatus.NEED_MORE_DATA;
}
}
int pos = byteBuffer.position();
byte chr = byteBuffer.get();
if (chr == Constants.CR) {
// Skip
} else if (chr == Constants.LF) {
eol = true;
} else {
headerData.lastSignificantChar = pos;
}
}
if (rejectIllegalHeaderName || log.isDebugEnabled()) {
String message = sm.getString("iib.invalidheader", new String(byteBuffer.array(), headerData.start, headerData.lastSignificantChar - headerData.start + 1, StandardCharsets.ISO_8859_1));
if (rejectIllegalHeaderName) {
throw new IllegalArgumentException(message);
}
log.debug(message);
}
headerParsePos = HeaderParsePosition.HEADER_START;
return HeaderParseStatus.HAVE_MORE_HEADERS;
}
// ----------------------------------------------------------- Inner clreplacedes
private enum HeaderParseStatus {
DONE, HAVE_MORE_HEADERS, NEED_MORE_DATA
}
private 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.<br>
* 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
}
private static clreplaced HeaderParseData {
/**
* When parsing header name: first character of the header.<br>
* When skipping broken header line: first character of the header.<br>
* When parsing header value: first character after ':'.
*/
int start = 0;
/**
* When parsing header name: not used (stays as 0).<br>
* When skipping broken header line: not used (stays as 0).<br>
* 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.<br>
*/
int realPos = 0;
/**
* When parsing header name: not used (stays as 0).<br>
* When skipping broken header line: last non-CR/non-LF character.<br>
* When parsing header value: position after the last not-LWS character.<br>
*/
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;
}
}
// ------------------------------------- InputStreamInputBuffer Inner Clreplaced
/**
* This clreplaced is an input buffer which will read its data from an input
* stream.
*/
private clreplaced SocketInputBuffer implements InputBuffer {
/**
* @deprecated Unused. Will be removed in Tomcat 9. Use
* {@link #doRead(ApplicationBufferHandler)}
*/
@Deprecated
@Override
public int doRead(ByteChunk chunk) throws IOException {
if (byteBuffer.position() >= byteBuffer.limit()) {
// The application is reading the HTTP request body which is
// always a blocking operation.
/**
* 读取HttpBody的时候是会阻塞的。
*/
if (!fill(true))
return -1;
}
int length = byteBuffer.remaining();
chunk.setBytes(byteBuffer.array(), byteBuffer.position(), length);
byteBuffer.position(byteBuffer.limit());
return length;
}
@Override
public int doRead(ApplicationBufferHandler handler) throws IOException {
if (byteBuffer.position() >= byteBuffer.limit()) {
// The application is reading the HTTP request body which is
// always a blocking operation.
if (!fill(true))
return -1;
}
int length = byteBuffer.remaining();
handler.setByteBuffer(byteBuffer.duplicate());
byteBuffer.position(byteBuffer.limit());
return length;
}
}
@Override
public void setByteBuffer(ByteBuffer buffer) {
byteBuffer = buffer;
}
@Override
public ByteBuffer getByteBuffer() {
return byteBuffer;
}
@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();
temp = null;
}
}
15
View Complete Implementation : IdentityInputFilter.java
Copyright Apache License 2.0
Author : codefollower
Copyright Apache License 2.0
Author : codefollower
// ---------------------------------------------------- InputBuffer Methods
/**
* Read bytes.
*
* @return If the filter does request length control, this value is
* significant; it should be the number of bytes consumed from the buffer,
* up until the end of the current request body, or the buffer length,
* whichever is greater. If the filter does not do request body length
* control, the returned value should be -1.
*/
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
int result = -1;
if (contentLength >= 0) {
if (remaining > 0) {
int nRead = buffer.doRead(chunk, req);
if (nRead > remaining) {
// The chunk is longer than the number of bytes remaining
// in the body; changing the chunk length to the number
// of bytes remaining
chunk.setBytes(chunk.getBytes(), chunk.getStart(), (int) remaining);
result = (int) remaining;
} else {
result = nRead;
}
remaining = remaining - nRead;
} else {
// No more bytes left to be read : return -1 and clear the
// buffer
chunk.recycle();
result = -1;
}
}
return result;
}
15
View Complete Implementation : IdentityInputFilter.java
Copyright Apache License 2.0
Author : how2j
Copyright Apache License 2.0
Author : how2j
// ---------------------------------------------------- InputBuffer Methods
/**
* Read bytes.
*
* @return If the filter does request length control, this value is
* significant; it should be the number of bytes consumed from the
* buffer, up until the end of the current request body, or the
* buffer length, whichever is greater. If the filter does not do
* request body length control, the returned value should be -1.
*/
@Override
public int doRead(ByteChunk chunk, Request req) throws IOException {
int result = -1;
if (contentLength >= 0) {
if (remaining > 0) {
int nRead = buffer.doRead(chunk, req);
if (nRead > remaining) {
// The chunk is longer than the number of bytes remaining
// in the body; changing the chunk length to the number
// of bytes remaining
chunk.setBytes(chunk.getBytes(), chunk.getStart(), (int) remaining);
result = (int) remaining;
} else {
result = nRead;
}
if (nRead > 0) {
remaining = remaining - nRead;
}
} else {
// No more bytes left to be read : return -1 and clear the
// buffer
chunk.recycle();
result = -1;
}
}
return result;
}