All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fireflysource.net.http.common.v2.hpack.HpackDecoder Maven / Gradle / Ivy

There is a newer version: 5.0.2
Show newest version
package com.fireflysource.net.http.common.v2.hpack;

import com.fireflysource.common.object.TypeUtils;
import com.fireflysource.common.sys.SystemLogger;
import com.fireflysource.net.http.common.model.HttpField;
import com.fireflysource.net.http.common.model.HttpHeader;
import com.fireflysource.net.http.common.model.HttpTokens;
import com.fireflysource.net.http.common.model.MetaData;
import com.fireflysource.net.http.common.v2.hpack.HpackContext.Entry;
import org.slf4j.Logger;

import java.nio.ByteBuffer;

/**
 * Hpack Decoder
 * 

* This is not thread safe and may only be called by 1 thread at a time. *

*/ public class HpackDecoder { public static final Logger LOG = SystemLogger.create(HpackDecoder.class); public static final HttpField.LongValueHttpField CONTENT_LENGTH_0 = new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH, 0L); private final HpackContext context; private final MetaDataBuilder builder; private int localMaxDynamicTableSize; /** * @param localMaxDynamicTableSize The maximum allowed size of the local dynamic header field table. * @param maxHeaderSize The maximum allowed size of a headers block, expressed as total of all name and value characters, plus 32 per field */ public HpackDecoder(int localMaxDynamicTableSize, int maxHeaderSize) { context = new HpackContext(localMaxDynamicTableSize); this.localMaxDynamicTableSize = localMaxDynamicTableSize; builder = new MetaDataBuilder(maxHeaderSize); } public HpackContext getHpackContext() { return context; } public void setLocalMaxDynamicTableSize(int localMaxdynamciTableSize) { localMaxDynamicTableSize = localMaxdynamciTableSize; } public MetaData decode(ByteBuffer buffer) throws HpackException.SessionException, HpackException.StreamException { if (LOG.isDebugEnabled()) LOG.debug(String.format("CtxTbl[%x] decoding %d octets", context.hashCode(), buffer.remaining())); // If the buffer is big, don't even think about decoding it if (buffer.remaining() > builder.getMaxSize()) throw new HpackException.SessionException("431 Request Header Fields too large"); boolean emitted = false; while (buffer.hasRemaining()) { if (LOG.isDebugEnabled() && buffer.hasArray()) { int l = Math.min(buffer.remaining(), 32); LOG.debug("decode {}{}", TypeUtils.toHexString(buffer.array(), buffer.arrayOffset() + buffer.position(), l), l < buffer.remaining() ? "..." : ""); } byte b = buffer.get(); if (b < 0) { // 7.1 indexed if the high bit is set int index = NBitInteger.decode(buffer, 7); Entry entry = context.get(index); if (entry == null) throw new HpackException.SessionException("Unknown index %d", index); if (entry.isStatic()) { if (LOG.isDebugEnabled()) LOG.debug("decode IdxStatic {}", entry); // emit field emitted = true; builder.emit(entry.getHttpField()); // TODO copy and add to reference set if there is room // _context.add(entry.getHttpField()); } else { if (LOG.isDebugEnabled()) LOG.debug("decode Idx {}", entry); // emit emitted = true; builder.emit(entry.getHttpField()); } } else { // look at the first nibble in detail byte f = (byte) ((b & 0xF0) >> 4); String name; HttpHeader header; String value; boolean indexed; int nameIndex; switch (f) { case 2: // 7.3 case 3: // 7.3 // change table size int size = NBitInteger.decode(buffer, 5); if (LOG.isDebugEnabled()) LOG.debug("decode resize=" + size); if (size > localMaxDynamicTableSize) throw new IllegalArgumentException(); if (emitted) throw new HpackException.CompressionException("Dynamic table resize after fields"); context.resize(size); continue; case 0: // 7.2.2 case 1: // 7.2.3 indexed = false; nameIndex = NBitInteger.decode(buffer, 4); break; case 4: // 7.2.1 case 5: // 7.2.1 case 6: // 7.2.1 case 7: // 7.2.1 indexed = true; nameIndex = NBitInteger.decode(buffer, 6); break; default: throw new IllegalStateException(); } boolean huffmanName = false; // decode the name if (nameIndex > 0) { Entry nameEntry = context.get(nameIndex); name = nameEntry.getHttpField().getName(); header = nameEntry.getHttpField().getHeader(); } else { huffmanName = (buffer.get() & 0x80) == 0x80; int length = NBitInteger.decode(buffer, 7); builder.checkSize(length, huffmanName); if (huffmanName) name = Huffman.decode(buffer, length); else name = toASCIIString(buffer, length); check: for (int i = name.length(); i-- > 0; ) { char c = name.charAt(i); if (c > 0xff) { builder.streamException("Illegal header name %s", name); break; } HttpTokens.Token token = HttpTokens.TOKENS[0xFF & c]; switch (token.getType()) { case ALPHA: if (c >= 'A' && c <= 'Z') { builder.streamException("Uppercase header name %s", name); break check; } break; case COLON: case TCHAR: case DIGIT: break; default: builder.streamException("Illegal header name %s", name); break check; } } header = HttpHeader.CACHE.get(name); } // decode the value boolean huffmanValue = (buffer.get() & 0x80) == 0x80; int length = NBitInteger.decode(buffer, 7); builder.checkSize(length, huffmanValue); if (huffmanValue) value = Huffman.decode(buffer, length); else value = toASCIIString(buffer, length); // Make the new field HttpField field; if (header == null) { // just make a normal field and bypass header name lookup field = new HttpField(null, name, value); } else { // might be worthwhile to create a value HttpField if it is indexed // and/or of a type that may be looked up multiple times. switch (header) { case C_STATUS: if (indexed) field = new HttpField.IntValueHttpField(header, name, value); else field = new HttpField(header, name, value); break; case C_AUTHORITY: field = new AuthorityHttpField(value); break; case CONTENT_LENGTH: if ("0".equals(value)) field = CONTENT_LENGTH_0; else field = new HttpField.LongValueHttpField(header, name, value); break; default: field = new HttpField(header, name, value); break; } } if (LOG.isDebugEnabled()) { LOG.debug("decoded '{}' by {}/{}/{}", field, nameIndex > 0 ? "IdxName" : (huffmanName ? "HuffName" : "LitName"), huffmanValue ? "HuffVal" : "LitVal", indexed ? "Idx" : ""); } // emit the field emitted = true; builder.emit(field); // if indexed add to dynamic table if (indexed) context.add(field); } } return builder.build(); } public static String toASCIIString(ByteBuffer buffer, int length) { StringBuilder builder = new StringBuilder(length); int position = buffer.position(); int start = buffer.arrayOffset() + position; int end = start + length; buffer.position(position + length); byte[] array = buffer.array(); for (int i = start; i < end; i++) { builder.append((char) (0x7f & array[i])); } return builder.toString(); } @Override public String toString() { return String.format("HpackDecoder@%x{%s}", hashCode(), context); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy