/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/ package org.apache.coyote.http2;
/** * The object that receives the headers that are emitted from this decoder
*/ private HeaderEmitter headerEmitter;
/** * The header table
*/ private Hpack.HeaderField[] headerTable;
/** * The current HEAD position of the header table. We use a ring buffer type construct as it would be silly to * actually shuffle the items around in the array.
*/ privateint firstSlotPosition = 0;
/** * The current table size by index (aka the number of index positions that are filled up)
*/ privateint filledTableSlots = 0;
/** * the current calculates memory size, as per the HPACK algorithm
*/ privateint currentMemorySize = 0;
/** * The maximum allowed memory size set by the container.
*/ privateint maxMemorySizeHard; /** * The maximum memory size currently in use. May be less than the hard limit.
*/ privateint maxMemorySizeSoft;
/** * Decodes the provided frame data. If this method leaves data in the buffer then this buffer should be compacted so * this data is preserved, unless there is no more data in which case this should be considered a protocol error. * * @param buffer The buffer * * @throws HpackException If the packed data is not valid
*/ void decode(ByteBuffer buffer) throws HpackException { while (buffer.hasRemaining()) { int originalPos = buffer.position(); byte b = buffer.get(); if ((b & 0b10000000) != 0) { // if the first bit is set it is an indexed header field
buffer.position(buffer.position() - 1); // unget the byte int index = Hpack.decodeInteger(buffer, 7); // prefix is 7 if (index == -1) {
buffer.position(originalPos); return;
} elseif (index == 0) { thrownew HpackException(sm.getString("hpackdecoder.zeroNotValidHeaderTableIndex"));
}
handleIndex(index);
} elseif ((b & 0b01000000) != 0) { // Literal Header Field with Incremental Indexing
String headerName = readHeaderName(buffer, 6); if (headerName == null) {
buffer.position(originalPos); return;
}
String headerValue = readHpackString(buffer); if (headerValue == null) {
buffer.position(originalPos); return;
}
emitHeader(headerName, headerValue);
addEntryToHeaderTable(new Hpack.HeaderField(headerName, headerValue));
} elseif ((b & 0b11110000) == 0) { // Literal Header Field without Indexing
String headerName = readHeaderName(buffer, 4); if (headerName == null) {
buffer.position(originalPos); return;
}
String headerValue = readHpackString(buffer); if (headerValue == null) {
buffer.position(originalPos); return;
}
emitHeader(headerName, headerValue);
} elseif ((b & 0b11110000) == 0b00010000) { // Literal Header Field never indexed
String headerName = readHeaderName(buffer, 4); if (headerName == null) {
buffer.position(originalPos); return;
}
String headerValue = readHpackString(buffer); if (headerValue == null) {
buffer.position(originalPos); return;
}
emitHeader(headerName, headerValue);
} elseif ((b & 0b11100000) == 0b00100000) { // context update max table size change if (!handleMaxMemorySizeChange(buffer, originalPos)) { return;
}
} else { thrownew RuntimeException(sm.getString("hpackdecoder.notImplemented"));
}
}
}
privateboolean handleMaxMemorySizeChange(ByteBuffer buffer, int originalPos) throws HpackException { if (headerCount != 0) { thrownew HpackException(sm.getString("hpackdecoder.tableSizeUpdateNotAtStart"));
}
buffer.position(buffer.position() - 1); // unget the byte int size = Hpack.decodeInteger(buffer, 5); if (size == -1) {
buffer.position(originalPos); returnfalse;
} if (size > maxMemorySizeHard) { thrownew HpackException(sm.getString("hpackdecoder.maxMemorySizeExceeded", Integer.valueOf(size),
Integer.valueOf(maxMemorySizeHard)));
}
maxMemorySizeSoft = size; if (currentMemorySize > maxMemorySizeSoft) { int newTableSlots = filledTableSlots; int tableLength = headerTable.length; int newSize = currentMemorySize; while (newSize > maxMemorySizeSoft) { int clearIndex = firstSlotPosition;
firstSlotPosition++; if (firstSlotPosition == tableLength) {
firstSlotPosition = 0;
}
Hpack.HeaderField oldData = headerTable[clearIndex];
headerTable[clearIndex] = null;
newSize -= oldData.size;
newTableSlots--;
} this.filledTableSlots = newTableSlots;
currentMemorySize = newSize;
} returntrue;
}
private String readHeaderName(ByteBuffer buffer, int prefixLength) throws HpackException {
buffer.position(buffer.position() - 1); // unget the byte int index = Hpack.decodeInteger(buffer, prefixLength); if (index == -1) { returnnull;
} elseif (index != 0) { return handleIndexedHeaderName(index);
} else { return readHpackString(buffer);
}
}
private String readHpackString(ByteBuffer buffer) throws HpackException { if (!buffer.hasRemaining()) { returnnull;
} byte data = buffer.get(buffer.position());
int length = Hpack.decodeInteger(buffer, 7); if (buffer.remaining() < length || length == -1) { returnnull;
} boolean huffman = (data & 0b10000000) != 0; if (huffman) { return readHuffmanString(length, buffer);
}
StringBuilder stringBuilder = new StringBuilder(length); for (int i = 0; i < length; ++i) {
stringBuilder.append((char) buffer.get());
} return stringBuilder.toString();
}
private String handleIndexedHeaderName(int index) throws HpackException { if (index <= Hpack.STATIC_TABLE_LENGTH) { return Hpack.STATIC_TABLE[index].name;
} else { // index is 1 based if (index > Hpack.STATIC_TABLE_LENGTH + filledTableSlots) { thrownew HpackException(sm.getString("hpackdecoder.headerTableIndexInvalid", Integer.valueOf(index),
Integer.valueOf(Hpack.STATIC_TABLE_LENGTH), Integer.valueOf(filledTableSlots)));
} int adjustedIndex = getRealIndex(index - Hpack.STATIC_TABLE_LENGTH);
Hpack.HeaderField res = headerTable[adjustedIndex]; if (res == null) { thrownew HpackException(sm.getString("hpackdecoder.nullHeader", Integer.valueOf(index)));
} return res.name;
}
}
/** * Handle an indexed header representation * * @param index The index * * @throws HpackException If an error occurs processing the given index
*/ privatevoid handleIndex(int index) throws HpackException { if (index <= Hpack.STATIC_TABLE_LENGTH) {
addStaticTableEntry(index);
} else { int adjustedIndex = getRealIndex(index - Hpack.STATIC_TABLE_LENGTH); if (log.isDebugEnabled()) {
log.debug(sm.getString("hpackdecoder.useDynamic", Integer.valueOf(adjustedIndex)));
}
Hpack.HeaderField headerField = headerTable[adjustedIndex];
emitHeader(headerField.name, headerField.value);
}
}
/** * because we use a ring buffer type construct, and don't actually shuffle items in the array, we need to figure out * the real index to use. * <p/> * package private for unit tests * * @param index The index from the hpack * * @return the real index into the array
*/ int getRealIndex(int index) throws HpackException { // the index is one based, but our table is zero based, hence -1 // also because of our ring buffer setup the indexes are reversed // index = 1 is at position firstSlotPosition + filledSlots int realIndex = (firstSlotPosition + (filledTableSlots - index)) % headerTable.length; if (realIndex < 0) { thrownew HpackException(sm.getString("hpackdecoder.headerTableIndexInvalid", Integer.valueOf(index),
Integer.valueOf(Hpack.STATIC_TABLE_LENGTH), Integer.valueOf(filledTableSlots)));
} return realIndex;
}
privatevoid addStaticTableEntry(int index) throws HpackException { // adds an entry from the static table. if (log.isDebugEnabled()) {
log.debug(sm.getString("hpackdecoder.useStatic", Integer.valueOf(index)));
}
Hpack.HeaderField entry = Hpack.STATIC_TABLE[index];
emitHeader(entry.name, (entry.value == null) ? "" : entry.value);
}
privatevoid addEntryToHeaderTable(Hpack.HeaderField entry) { if (entry.size > maxMemorySizeSoft) { if (log.isDebugEnabled()) {
log.debug(sm.getString("hpackdecoder.clearDynamic"));
} // it is to big to fit, so we just completely clear the table. while (filledTableSlots > 0) {
headerTable[firstSlotPosition] = null;
firstSlotPosition++; if (firstSlotPosition == headerTable.length) {
firstSlotPosition = 0;
}
filledTableSlots--;
}
currentMemorySize = 0; return;
}
resizeIfRequired(); int newTableSlots = filledTableSlots + 1; int tableLength = headerTable.length; int index = (firstSlotPosition + filledTableSlots) % tableLength; if (log.isDebugEnabled()) {
log.debug(sm.getString("hpackdecoder.addDynamic", Integer.valueOf(index), entry.name, entry.value));
}
headerTable[index] = entry; int newSize = currentMemorySize + entry.size; while (newSize > maxMemorySizeSoft) { int clearIndex = firstSlotPosition;
firstSlotPosition++; if (firstSlotPosition == tableLength) {
firstSlotPosition = 0;
}
Hpack.HeaderField oldData = headerTable[clearIndex];
headerTable[clearIndex] = null;
newSize -= oldData.size;
newTableSlots--;
} this.filledTableSlots = newTableSlots;
currentMemorySize = newSize;
}
privatevoid resizeIfRequired() { if (filledTableSlots == headerTable.length) {
Hpack.HeaderField[] newArray = new Hpack.HeaderField[headerTable.length + 10]; // we only grow slowly for (int i = 0; i < headerTable.length; ++i) {
newArray[i] = headerTable[(firstSlotPosition + i) % headerTable.length];
}
firstSlotPosition = 0;
headerTable = newArray;
}
}
/** * Interface implemented by the intended recipient of the headers.
*/ interface HeaderEmitter { /** * Pass a single header to the recipient. * * @param name Header name * @param value Header value * * @throws HpackException If a header is received that is not compliant with the HTTP/2 specification
*/ void emitHeader(String name, String value) throws HpackException;
/** * Inform the recipient of the headers that a stream error needs to be triggered using the given message when * {@link #validateHeaders()} is called. This is used when the Parser becomes aware of an error that is not * visible to the recipient. * * @param streamException The exception to use when resetting the stream
*/ void setHeaderException(StreamException streamException);
/** * Are the headers pass to the recipient so far valid? The decoder needs to process all the headers to maintain * state even if there is a problem. In addition, it is easy for the the intended recipient to track if the * complete set of headers is valid since to do that state needs to be maintained between the parsing of the * initial headers and the parsing of any trailer headers. The recipient is the best place to maintain that * state. * * @throws StreamException If the headers received to date are not valid
*/ void validateHeaders() throws StreamException;
}
privatevoid emitHeader(String name, String value) throws HpackException { // Header names are forced to lower case if ("cookie".equals(name)) { // Only count the cookie header once since HTTP/2 splits it into // multiple headers to aid compression if (!countedCookie) {
headerCount++;
countedCookie = true;
}
} else {
headerCount++;
} // Overhead will vary. The main concern is that lots of small headers // trigger the limiting mechanism correctly. Therefore, use an overhead // estimate of 3 which is the worst case for small headers. int inc = 3 + name.length() + value.length();
headerSize += inc; if (!isHeaderCountExceeded() && !isHeaderSizeExceeded(0)) { if (log.isDebugEnabled()) {
log.debug(sm.getString("hpackdecoder.emitHeader", name, value));
}
headerEmitter.emitHeader(name, value);
}
}
boolean isHeaderSwallowSizeExceeded(int unreadSize) { if (maxHeaderSize < 0) { returnfalse;
} // Swallow the same again before closing the connection. return (headerSize + unreadSize) > (2 * maxHeaderSize);
}
// package private fields for unit tests
int getFirstSlotPosition() { return firstSlotPosition;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.