SPDY: better encapsulation of header encoding/decoding
This commit is contained in:
parent
14f2e29af9
commit
0d9aecbbc1
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
@ -29,16 +29,16 @@ final class SpdyCodecUtil {
|
||||
|
||||
static final byte SPDY_DATA_FLAG_FIN = 0x01;
|
||||
|
||||
static final int SPDY_DATA_FRAME = 0;
|
||||
static final int SPDY_SYN_STREAM_FRAME = 1;
|
||||
static final int SPDY_SYN_REPLY_FRAME = 2;
|
||||
static final int SPDY_RST_STREAM_FRAME = 3;
|
||||
static final int SPDY_SETTINGS_FRAME = 4;
|
||||
static final int SPDY_NOOP_FRAME = 5;
|
||||
static final int SPDY_PUSH_PROMISE_FRAME = 5;
|
||||
static final int SPDY_PING_FRAME = 6;
|
||||
static final int SPDY_GOAWAY_FRAME = 7;
|
||||
static final int SPDY_HEADERS_FRAME = 8;
|
||||
static final int SPDY_WINDOW_UPDATE_FRAME = 9;
|
||||
static final int SPDY_CREDENTIAL_FRAME = 10;
|
||||
|
||||
static final byte SPDY_FLAG_FIN = 0x01;
|
||||
static final byte SPDY_FLAG_UNIDIRECTIONAL = 0x02;
|
||||
|
@ -16,11 +16,9 @@
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.MessageList;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
@ -29,11 +27,13 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
*/
|
||||
public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
|
||||
private static final SpdyProtocolException INVALID_FRAME =
|
||||
new SpdyProtocolException("Received invalid frame");
|
||||
|
||||
private final int spdyVersion;
|
||||
private final int maxChunkSize;
|
||||
private final int maxHeaderSize;
|
||||
|
||||
private final SpdyHeaderBlockDecompressor headerBlockDecompressor;
|
||||
private final SpdyHeaderBlockDecoder headerBlockDecoder;
|
||||
|
||||
private State state;
|
||||
private SpdySettingsFrame spdySettingsFrame;
|
||||
@ -44,12 +44,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
private int length;
|
||||
private int version;
|
||||
private int type;
|
||||
private int streamID;
|
||||
|
||||
// Header block decoding fields
|
||||
private int headerSize;
|
||||
private int numHeaders;
|
||||
private ByteBuf decompressed;
|
||||
private int streamId;
|
||||
|
||||
private enum State {
|
||||
READ_COMMON_HEADER,
|
||||
@ -74,6 +69,11 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
* Creates a new instance with the specified parameters.
|
||||
*/
|
||||
public SpdyFrameDecoder(int version, int maxChunkSize, int maxHeaderSize) {
|
||||
this(version, maxChunkSize, SpdyHeaderBlockDecoder.newInstance(version, maxHeaderSize));
|
||||
}
|
||||
|
||||
protected SpdyFrameDecoder(
|
||||
int version, int maxChunkSize, SpdyHeaderBlockDecoder headerBlockDecoder) {
|
||||
if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
@ -82,14 +82,9 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
throw new IllegalArgumentException(
|
||||
"maxChunkSize must be a positive integer: " + maxChunkSize);
|
||||
}
|
||||
if (maxHeaderSize <= 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"maxHeaderSize must be a positive integer: " + maxHeaderSize);
|
||||
}
|
||||
spdyVersion = version;
|
||||
this.maxChunkSize = maxChunkSize;
|
||||
this.maxHeaderSize = maxHeaderSize;
|
||||
headerBlockDecompressor = SpdyHeaderBlockDecompressor.newInstance(version);
|
||||
this.headerBlockDecoder = headerBlockDecoder;
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
|
||||
@ -98,7 +93,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
try {
|
||||
decode(ctx, in, out);
|
||||
} finally {
|
||||
headerBlockDecompressor.end();
|
||||
headerBlockDecoder.end();
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,7 +106,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
if (version != spdyVersion) {
|
||||
fireProtocolException(ctx, "Unsupported version: " + version);
|
||||
} else {
|
||||
fireInvalidControlFrameException(ctx);
|
||||
fireInvalidFrameException(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,13 +114,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
// All length 0 frames must be generated now
|
||||
if (length == 0) {
|
||||
if (state == State.READ_DATA_FRAME) {
|
||||
if (streamID == 0) {
|
||||
state = State.FRAME_ERROR;
|
||||
fireProtocolException(ctx, "Received invalid data frame");
|
||||
return;
|
||||
}
|
||||
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID);
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId);
|
||||
spdyDataFrame.setLast((flags & SPDY_DATA_FLAG_FIN) != 0);
|
||||
state = State.READ_COMMON_HEADER;
|
||||
out.add(spdyDataFrame);
|
||||
@ -147,7 +136,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
return;
|
||||
} catch (IllegalArgumentException e) {
|
||||
state = State.FRAME_ERROR;
|
||||
fireInvalidControlFrameException(ctx);
|
||||
fireInvalidFrameException(ctx);
|
||||
}
|
||||
return;
|
||||
|
||||
@ -164,7 +153,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
// Each ID/Value entry is 8 bytes
|
||||
if ((length & 0x07) != 0 || length >> 3 != numEntries) {
|
||||
state = State.FRAME_ERROR;
|
||||
fireInvalidControlFrameException(ctx);
|
||||
fireInvalidFrameException(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -198,7 +187,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
if (ID == 0) {
|
||||
state = State.FRAME_ERROR;
|
||||
spdySettingsFrame = null;
|
||||
fireInvalidControlFrameException(ctx);
|
||||
fireInvalidFrameException(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -235,7 +224,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
return;
|
||||
} catch (IllegalArgumentException e) {
|
||||
state = State.FRAME_ERROR;
|
||||
fireInvalidControlFrameException(ctx);
|
||||
fireInvalidFrameException(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -244,11 +233,10 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
length -= compressedBytes;
|
||||
|
||||
try {
|
||||
decodeHeaderBlock(buffer.readSlice(compressedBytes));
|
||||
headerBlockDecoder.decode(buffer.readSlice(compressedBytes), spdyHeadersFrame);
|
||||
} catch (Exception e) {
|
||||
state = State.FRAME_ERROR;
|
||||
spdyHeadersFrame = null;
|
||||
decompressed = null;
|
||||
ctx.fireExceptionCaught(e);
|
||||
return;
|
||||
}
|
||||
@ -256,8 +244,8 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
if (spdyHeadersFrame != null && spdyHeadersFrame.isInvalid()) {
|
||||
Object frame = spdyHeadersFrame;
|
||||
spdyHeadersFrame = null;
|
||||
decompressed = null;
|
||||
if (length == 0) {
|
||||
headerBlockDecoder.reset();
|
||||
state = State.READ_COMMON_HEADER;
|
||||
}
|
||||
out.add(frame);
|
||||
@ -267,6 +255,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
if (length == 0) {
|
||||
Object frame = spdyHeadersFrame;
|
||||
spdyHeadersFrame = null;
|
||||
headerBlockDecoder.reset();
|
||||
state = State.READ_COMMON_HEADER;
|
||||
out.add(frame);
|
||||
return;
|
||||
@ -274,7 +263,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
return;
|
||||
|
||||
case READ_DATA_FRAME:
|
||||
if (streamID == 0) {
|
||||
if (streamId == 0) {
|
||||
state = State.FRAME_ERROR;
|
||||
fireProtocolException(ctx, "Received invalid data frame");
|
||||
return;
|
||||
@ -290,7 +279,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
|
||||
ByteBuf data = ctx.alloc().buffer(dataLength);
|
||||
data.writeBytes(buffer, dataLength);
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID, data);
|
||||
SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamId, data);
|
||||
length -= dataLength;
|
||||
|
||||
if (length == 0) {
|
||||
@ -341,44 +330,51 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
int typeOffset = frameOffset + SPDY_HEADER_TYPE_OFFSET;
|
||||
type = getUnsignedShort(buffer, typeOffset);
|
||||
|
||||
// Check version first then validity
|
||||
if (version != spdyVersion || !isValidControlFrameHeader()) {
|
||||
return State.FRAME_ERROR;
|
||||
}
|
||||
|
||||
// Make sure decoder will produce a frame or consume input
|
||||
State nextState;
|
||||
if (willGenerateControlFrame()) {
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
case SPDY_HEADERS_FRAME:
|
||||
nextState = State.READ_HEADER_BLOCK_FRAME;
|
||||
break;
|
||||
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
nextState = State.READ_SETTINGS_FRAME;
|
||||
break;
|
||||
|
||||
default:
|
||||
nextState = State.READ_CONTROL_FRAME;
|
||||
}
|
||||
} else if (length != 0) {
|
||||
nextState = State.DISCARD_FRAME;
|
||||
} else {
|
||||
nextState = State.READ_COMMON_HEADER;
|
||||
}
|
||||
return nextState;
|
||||
streamId = 0;
|
||||
} else {
|
||||
// Decode data frame common header
|
||||
streamID = getUnsignedInt(buffer, frameOffset);
|
||||
version = spdyVersion; // Default to expected version
|
||||
|
||||
return State.READ_DATA_FRAME;
|
||||
type = SPDY_DATA_FRAME;
|
||||
|
||||
streamId = getUnsignedInt(buffer, frameOffset);
|
||||
}
|
||||
// Check version first then validity
|
||||
if (version != spdyVersion || !isValidFrameHeader()) {
|
||||
return State.FRAME_ERROR;
|
||||
}
|
||||
|
||||
// Make sure decoder will produce a frame or consume input
|
||||
State nextState;
|
||||
if (willGenerateFrame()) {
|
||||
switch (type) {
|
||||
case SPDY_DATA_FRAME:
|
||||
nextState = State.READ_DATA_FRAME;
|
||||
break;
|
||||
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
case SPDY_HEADERS_FRAME:
|
||||
nextState = State.READ_HEADER_BLOCK_FRAME;
|
||||
break;
|
||||
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
nextState = State.READ_SETTINGS_FRAME;
|
||||
break;
|
||||
|
||||
default:
|
||||
nextState = State.READ_CONTROL_FRAME;
|
||||
}
|
||||
} else if (length != 0) {
|
||||
nextState = State.DISCARD_FRAME;
|
||||
} else {
|
||||
nextState = State.READ_COMMON_HEADER;
|
||||
}
|
||||
return nextState;
|
||||
}
|
||||
|
||||
private Object readControlFrame(ByteBuf buffer) {
|
||||
int streamID;
|
||||
int streamId;
|
||||
int statusCode;
|
||||
switch (type) {
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
@ -386,11 +382,11 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
return null;
|
||||
}
|
||||
|
||||
streamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
streamId = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
statusCode = getSignedInt(buffer, buffer.readerIndex() + 4);
|
||||
buffer.skipBytes(8);
|
||||
|
||||
return new DefaultSpdyRstStreamFrame(streamID, statusCode);
|
||||
return new DefaultSpdyRstStreamFrame(streamId, statusCode);
|
||||
|
||||
case SPDY_PING_FRAME:
|
||||
if (buffer.readableBytes() < 4) {
|
||||
@ -425,11 +421,11 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
return null;
|
||||
}
|
||||
|
||||
streamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
streamId = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
int deltaWindowSize = getUnsignedInt(buffer, buffer.readerIndex() + 4);
|
||||
buffer.skipBytes(8);
|
||||
|
||||
return new DefaultSpdyWindowUpdateFrame(streamID, deltaWindowSize);
|
||||
return new DefaultSpdyWindowUpdateFrame(streamId, deltaWindowSize);
|
||||
|
||||
default:
|
||||
throw new Error("Shouldn't reach here.");
|
||||
@ -438,7 +434,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
|
||||
private SpdyHeadersFrame readHeaderBlockFrame(ByteBuf buffer) {
|
||||
int minLength;
|
||||
int streamID;
|
||||
int streamId;
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
minLength = version < 3 ? 12 : 10;
|
||||
@ -447,7 +443,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
}
|
||||
|
||||
int offset = buffer.readerIndex();
|
||||
streamID = getUnsignedInt(buffer, offset);
|
||||
streamId = getUnsignedInt(buffer, offset);
|
||||
int associatedToStreamId = getUnsignedInt(buffer, offset + 4);
|
||||
byte priority = (byte) (buffer.getByte(offset + 8) >> 5 & 0x07);
|
||||
if (version < 3) {
|
||||
@ -463,7 +459,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
}
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame =
|
||||
new DefaultSpdySynStreamFrame(streamID, associatedToStreamId, priority);
|
||||
new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority);
|
||||
spdySynStreamFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
|
||||
spdySynStreamFrame.setUnidirectional((flags & SPDY_FLAG_UNIDIRECTIONAL) != 0);
|
||||
|
||||
@ -475,7 +471,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
return null;
|
||||
}
|
||||
|
||||
streamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
streamId = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
length -= 4;
|
||||
|
||||
@ -491,7 +487,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
length = 0;
|
||||
}
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
|
||||
SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId);
|
||||
spdySynReplyFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
|
||||
|
||||
return spdySynReplyFrame;
|
||||
@ -506,7 +502,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
return null;
|
||||
}
|
||||
|
||||
streamID = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
streamId = getUnsignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
length -= 4;
|
||||
|
||||
@ -522,7 +518,7 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
length = 0;
|
||||
}
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamID);
|
||||
SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId);
|
||||
spdyHeadersFrame.setLast((flags & SPDY_FLAG_FIN) != 0);
|
||||
|
||||
return spdyHeadersFrame;
|
||||
@ -532,175 +528,11 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ensureBytes(int bytes) throws Exception {
|
||||
if (decompressed.readableBytes() >= bytes) {
|
||||
return true;
|
||||
}
|
||||
// Perhaps last call to decode filled output buffer
|
||||
int numBytes;
|
||||
boolean done;
|
||||
do {
|
||||
numBytes = headerBlockDecompressor.decode(decompressed);
|
||||
done = decompressed.readableBytes() >= bytes;
|
||||
} while (!done && numBytes > 0);
|
||||
return done;
|
||||
}
|
||||
|
||||
private int readLengthField() {
|
||||
if (version < 3) {
|
||||
return decompressed.readUnsignedShort();
|
||||
} else {
|
||||
return decompressed.readInt();
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeHeaderBlock(ByteBuf buffer) throws Exception {
|
||||
if (decompressed == null) {
|
||||
// First time we start to decode a header block
|
||||
// Initialize header block decoding fields
|
||||
headerSize = 0;
|
||||
numHeaders = -1;
|
||||
decompressed = Unpooled.buffer(8192);
|
||||
}
|
||||
|
||||
// Accumulate decompressed data
|
||||
headerBlockDecompressor.setInput(buffer);
|
||||
headerBlockDecompressor.decode(decompressed);
|
||||
|
||||
if (spdyHeadersFrame == null) {
|
||||
// Only decompressing data to keep decompression context in sync
|
||||
decompressed = null;
|
||||
return;
|
||||
}
|
||||
|
||||
int lengthFieldSize = version < 3 ? 2 : 4;
|
||||
|
||||
if (numHeaders == -1) {
|
||||
// Read number of Name/Value pairs
|
||||
if (decompressed.readableBytes() < lengthFieldSize) {
|
||||
return;
|
||||
}
|
||||
numHeaders = readLengthField();
|
||||
if (numHeaders < 0) {
|
||||
spdyHeadersFrame.setInvalid();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (numHeaders > 0) {
|
||||
int headerSize = this.headerSize;
|
||||
decompressed.markReaderIndex();
|
||||
|
||||
// Try to read length of name
|
||||
if (!ensureBytes(lengthFieldSize)) {
|
||||
decompressed.resetReaderIndex();
|
||||
decompressed.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
int nameLength = readLengthField();
|
||||
|
||||
// Recipients of a zero-length name must issue a stream error
|
||||
if (nameLength <= 0) {
|
||||
spdyHeadersFrame.setInvalid();
|
||||
return;
|
||||
}
|
||||
headerSize += nameLength;
|
||||
if (headerSize > maxHeaderSize) {
|
||||
throw new TooLongFrameException(
|
||||
"Header block exceeds " + maxHeaderSize);
|
||||
}
|
||||
|
||||
// Try to read name
|
||||
if (!ensureBytes(nameLength)) {
|
||||
decompressed.resetReaderIndex();
|
||||
decompressed.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
byte[] nameBytes = new byte[nameLength];
|
||||
decompressed.readBytes(nameBytes);
|
||||
String name = new String(nameBytes, "UTF-8");
|
||||
|
||||
// Check for identically named headers
|
||||
if (spdyHeadersFrame.headers().contains(name)) {
|
||||
spdyHeadersFrame.setInvalid();
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to read length of value
|
||||
if (!ensureBytes(lengthFieldSize)) {
|
||||
decompressed.resetReaderIndex();
|
||||
decompressed.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
int valueLength = readLengthField();
|
||||
|
||||
// Recipients of illegal value fields must issue a stream error
|
||||
if (valueLength < 0) {
|
||||
spdyHeadersFrame.setInvalid();
|
||||
return;
|
||||
}
|
||||
|
||||
// SPDY/3 allows zero-length (empty) header values
|
||||
if (valueLength == 0) {
|
||||
if (version < 3) {
|
||||
spdyHeadersFrame.setInvalid();
|
||||
return;
|
||||
} else {
|
||||
spdyHeadersFrame.headers().add(name, "");
|
||||
numHeaders --;
|
||||
this.headerSize = headerSize;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
headerSize += valueLength;
|
||||
if (headerSize > maxHeaderSize) {
|
||||
throw new TooLongFrameException(
|
||||
"Header block exceeds " + maxHeaderSize);
|
||||
}
|
||||
|
||||
// Try to read value
|
||||
if (!ensureBytes(valueLength)) {
|
||||
decompressed.resetReaderIndex();
|
||||
decompressed.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
byte[] valueBytes = new byte[valueLength];
|
||||
decompressed.readBytes(valueBytes);
|
||||
|
||||
// Add Name/Value pair to headers
|
||||
int index = 0;
|
||||
int offset = 0;
|
||||
while (index < valueLength) {
|
||||
while (index < valueBytes.length && valueBytes[index] != (byte) 0) {
|
||||
index ++;
|
||||
}
|
||||
if (index < valueBytes.length && valueBytes[index + 1] == (byte) 0) {
|
||||
// Received multiple, in-sequence NULL characters
|
||||
// Recipients of illegal value fields must issue a stream error
|
||||
spdyHeadersFrame.setInvalid();
|
||||
return;
|
||||
}
|
||||
String value = new String(valueBytes, offset, index - offset, "UTF-8");
|
||||
|
||||
try {
|
||||
spdyHeadersFrame.headers().add(name, value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Name contains NULL or non-ascii characters
|
||||
spdyHeadersFrame.setInvalid();
|
||||
return;
|
||||
}
|
||||
index ++;
|
||||
offset = index;
|
||||
}
|
||||
numHeaders --;
|
||||
this.headerSize = headerSize;
|
||||
}
|
||||
decompressed = null;
|
||||
}
|
||||
|
||||
private boolean isValidControlFrameHeader() {
|
||||
private boolean isValidFrameHeader() {
|
||||
switch (type) {
|
||||
case SPDY_DATA_FRAME:
|
||||
return streamId != 0;
|
||||
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
return version < 3 ? length >= 12 : length >= 10;
|
||||
|
||||
@ -713,9 +545,6 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
return length >= 4;
|
||||
|
||||
case SPDY_NOOP_FRAME:
|
||||
return length == 0;
|
||||
|
||||
case SPDY_PING_FRAME:
|
||||
return length == 4;
|
||||
|
||||
@ -732,14 +561,14 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
return length == 8;
|
||||
|
||||
case SPDY_CREDENTIAL_FRAME:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean willGenerateControlFrame() {
|
||||
private boolean willGenerateFrame() {
|
||||
switch (type) {
|
||||
case SPDY_DATA_FRAME:
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
@ -750,57 +579,13 @@ public class SpdyFrameDecoder extends ByteToMessageDecoder {
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
return true;
|
||||
|
||||
case SPDY_NOOP_FRAME:
|
||||
case SPDY_CREDENTIAL_FRAME:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void fireInvalidControlFrameException(ChannelHandlerContext ctx) {
|
||||
String message = "Received invalid control frame";
|
||||
switch (type) {
|
||||
case SPDY_SYN_STREAM_FRAME:
|
||||
message = "Received invalid SYN_STREAM control frame";
|
||||
break;
|
||||
|
||||
case SPDY_SYN_REPLY_FRAME:
|
||||
message = "Received invalid SYN_REPLY control frame";
|
||||
break;
|
||||
|
||||
case SPDY_RST_STREAM_FRAME:
|
||||
message = "Received invalid RST_STREAM control frame";
|
||||
break;
|
||||
|
||||
case SPDY_SETTINGS_FRAME:
|
||||
message = "Received invalid SETTINGS control frame";
|
||||
break;
|
||||
|
||||
case SPDY_NOOP_FRAME:
|
||||
message = "Received invalid NOOP control frame";
|
||||
break;
|
||||
|
||||
case SPDY_PING_FRAME:
|
||||
message = "Received invalid PING control frame";
|
||||
break;
|
||||
|
||||
case SPDY_GOAWAY_FRAME:
|
||||
message = "Received invalid GOAWAY control frame";
|
||||
break;
|
||||
|
||||
case SPDY_HEADERS_FRAME:
|
||||
message = "Received invalid HEADERS control frame";
|
||||
break;
|
||||
|
||||
case SPDY_WINDOW_UPDATE_FRAME:
|
||||
message = "Received invalid WINDOW_UPDATE control frame";
|
||||
break;
|
||||
|
||||
case SPDY_CREDENTIAL_FRAME:
|
||||
message = "Received invalid CREDENTIAL control frame";
|
||||
break;
|
||||
}
|
||||
fireProtocolException(ctx, message);
|
||||
private static void fireInvalidFrameException(ChannelHandlerContext ctx) {
|
||||
ctx.fireExceptionCaught(INVALID_FRAME);
|
||||
}
|
||||
|
||||
private static void fireProtocolException(ChannelHandlerContext ctx, String message) {
|
||||
|
@ -34,8 +34,7 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
|
||||
|
||||
private final int version;
|
||||
private boolean finished;
|
||||
private final SpdyHeaderBlockCompressor headerBlockCompressor;
|
||||
private final SpdyHeaderBlockEncoder headerBlockEncoder;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@code version} and the
|
||||
@ -50,13 +49,17 @@ public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
|
||||
* Creates a new instance with the specified parameters.
|
||||
*/
|
||||
public SpdyFrameEncoder(int version, int compressionLevel, int windowBits, int memLevel) {
|
||||
this(version, SpdyHeaderBlockEncoder.newInstance(
|
||||
version, compressionLevel, windowBits, memLevel));
|
||||
}
|
||||
|
||||
protected SpdyFrameEncoder(int version, SpdyHeaderBlockEncoder headerBlockEncoder) {
|
||||
if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unknown version: " + version);
|
||||
}
|
||||
this.version = version;
|
||||
headerBlockCompressor = SpdyHeaderBlockCompressor.newInstance(
|
||||
version, compressionLevel, windowBits, memLevel);
|
||||
this.headerBlockEncoder = headerBlockEncoder;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -64,12 +67,8 @@ public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
|
||||
ctx.channel().closeFuture().addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
synchronized (headerBlockCompressor) {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
finished = true;
|
||||
headerBlockCompressor.end();
|
||||
synchronized (headerBlockEncoder) {
|
||||
headerBlockEncoder.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -91,8 +90,7 @@ public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
|
||||
} else if (msg instanceof SpdySynStreamFrame) {
|
||||
|
||||
SpdySynStreamFrame spdySynStreamFrame = (SpdySynStreamFrame) msg;
|
||||
ByteBuf data = compressHeaderBlock(
|
||||
encodeHeaderBlock(version, spdySynStreamFrame));
|
||||
ByteBuf data = headerBlockEncoder.encode(spdySynStreamFrame);
|
||||
byte flags = spdySynStreamFrame.isLast() ? SPDY_FLAG_FIN : 0;
|
||||
if (spdySynStreamFrame.isUnidirectional()) {
|
||||
flags |= SPDY_FLAG_UNIDIRECTIONAL;
|
||||
@ -129,8 +127,7 @@ public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
|
||||
} else if (msg instanceof SpdySynReplyFrame) {
|
||||
|
||||
SpdySynReplyFrame spdySynReplyFrame = (SpdySynReplyFrame) msg;
|
||||
ByteBuf data = compressHeaderBlock(
|
||||
encodeHeaderBlock(version, spdySynReplyFrame));
|
||||
ByteBuf data = headerBlockEncoder.encode(spdySynReplyFrame);
|
||||
byte flags = spdySynReplyFrame.isLast() ? SPDY_FLAG_FIN : 0;
|
||||
int headerBlockLength = data.readableBytes();
|
||||
int length;
|
||||
@ -227,8 +224,7 @@ public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
|
||||
} else if (msg instanceof SpdyHeadersFrame) {
|
||||
|
||||
SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg;
|
||||
ByteBuf data = compressHeaderBlock(
|
||||
encodeHeaderBlock(version, spdyHeadersFrame));
|
||||
ByteBuf data = headerBlockEncoder.encode(spdyHeadersFrame);
|
||||
byte flags = spdyHeadersFrame.isLast() ? SPDY_FLAG_FIN : 0;
|
||||
int headerBlockLength = data.readableBytes();
|
||||
int length;
|
||||
@ -261,83 +257,4 @@ public class SpdyFrameEncoder extends MessageToByteEncoder<SpdyFrame> {
|
||||
throw new UnsupportedMessageTypeException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeLengthField(int version, ByteBuf buffer, int length) {
|
||||
if (version < 3) {
|
||||
buffer.writeShort(length);
|
||||
} else {
|
||||
buffer.writeInt(length);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setLengthField(int version, ByteBuf buffer, int writerIndex, int length) {
|
||||
if (version < 3) {
|
||||
buffer.setShort(writerIndex, length);
|
||||
} else {
|
||||
buffer.setInt(writerIndex, length);
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuf encodeHeaderBlock(int version, SpdyHeadersFrame headerFrame)
|
||||
throws Exception {
|
||||
Set<String> names = headerFrame.headers().names();
|
||||
int numHeaders = names.size();
|
||||
if (numHeaders == 0) {
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
if (numHeaders > SPDY_MAX_NV_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"header block contains too many headers");
|
||||
}
|
||||
ByteBuf headerBlock = Unpooled.buffer();
|
||||
writeLengthField(version, headerBlock, numHeaders);
|
||||
for (String name: names) {
|
||||
byte[] nameBytes = name.getBytes(CharsetUtil.UTF_8);
|
||||
writeLengthField(version, headerBlock, nameBytes.length);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
int savedIndex = headerBlock.writerIndex();
|
||||
int valueLength = 0;
|
||||
writeLengthField(version, headerBlock, valueLength);
|
||||
for (String value: headerFrame.headers().getAll(name)) {
|
||||
byte[] valueBytes = value.getBytes(CharsetUtil.UTF_8);
|
||||
if (valueBytes.length > 0) {
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
headerBlock.writeByte(0);
|
||||
valueLength += valueBytes.length + 1;
|
||||
}
|
||||
}
|
||||
if (valueLength == 0) {
|
||||
if (version < 3) {
|
||||
throw new IllegalArgumentException(
|
||||
"header value cannot be empty: " + name);
|
||||
}
|
||||
} else {
|
||||
valueLength --;
|
||||
}
|
||||
if (valueLength > SPDY_MAX_NV_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"header exceeds allowable length: " + name);
|
||||
}
|
||||
if (valueLength > 0) {
|
||||
setLengthField(version, headerBlock, savedIndex, valueLength);
|
||||
headerBlock.writerIndex(headerBlock.writerIndex() - 1);
|
||||
}
|
||||
}
|
||||
return headerBlock;
|
||||
}
|
||||
|
||||
private synchronized ByteBuf compressHeaderBlock(
|
||||
ByteBuf uncompressed) throws Exception {
|
||||
if (uncompressed.readableBytes() == 0) {
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
ByteBuf compressed = Unpooled.buffer();
|
||||
synchronized (headerBlockCompressor) {
|
||||
if (!finished) {
|
||||
headerBlockCompressor.setInput(uncompressed);
|
||||
headerBlockCompressor.encode(compressed);
|
||||
}
|
||||
}
|
||||
return compressed;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
@ -17,13 +17,13 @@ package io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
abstract class SpdyHeaderBlockDecompressor {
|
||||
abstract class SpdyHeaderBlockDecoder {
|
||||
|
||||
static SpdyHeaderBlockDecompressor newInstance(int version) {
|
||||
return new SpdyHeaderBlockZlibDecompressor(version);
|
||||
static SpdyHeaderBlockDecoder newInstance(int version, int maxHeaderSize) {
|
||||
return new SpdyHeaderBlockZlibDecoder(version, maxHeaderSize);
|
||||
}
|
||||
|
||||
abstract void setInput(ByteBuf compressed);
|
||||
abstract int decode(ByteBuf decompressed) throws Exception;
|
||||
abstract void decode(ByteBuf encoded, SpdyHeadersFrame frame) throws Exception;
|
||||
abstract void reset();
|
||||
abstract void end();
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
@ -18,21 +18,20 @@ package io.netty.handler.codec.spdy;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
abstract class SpdyHeaderBlockCompressor {
|
||||
abstract class SpdyHeaderBlockEncoder {
|
||||
|
||||
static SpdyHeaderBlockCompressor newInstance(
|
||||
static SpdyHeaderBlockEncoder newInstance(
|
||||
int version, int compressionLevel, int windowBits, int memLevel) {
|
||||
|
||||
if (PlatformDependent.javaVersion() >= 7) {
|
||||
return new SpdyHeaderBlockZlibCompressor(
|
||||
return new SpdyHeaderBlockZlibEncoder(
|
||||
version, compressionLevel);
|
||||
} else {
|
||||
return new SpdyHeaderBlockJZlibCompressor(
|
||||
return new SpdyHeaderBlockJZlibEncoder(
|
||||
version, compressionLevel, windowBits, memLevel);
|
||||
}
|
||||
}
|
||||
|
||||
abstract void setInput(ByteBuf decompressed);
|
||||
abstract void encode(ByteBuf compressed);
|
||||
abstract ByteBuf encode(SpdyHeadersFrame frame) throws Exception;
|
||||
abstract void end();
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
@ -20,18 +20,18 @@ import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
import com.jcraft.jzlib.Deflater;
|
||||
import com.jcraft.jzlib.JZlib;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.handler.codec.compression.CompressionException;
|
||||
|
||||
class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
|
||||
class SpdyHeaderBlockJZlibEncoder extends SpdyHeaderBlockRawEncoder {
|
||||
|
||||
private final Deflater z = new Deflater();
|
||||
|
||||
public SpdyHeaderBlockJZlibCompressor(
|
||||
private boolean finished;
|
||||
|
||||
public SpdyHeaderBlockJZlibEncoder(
|
||||
int version, int compressionLevel, int windowBits, int memLevel) {
|
||||
if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
super(version);
|
||||
if (compressionLevel < 0 || compressionLevel > 9) {
|
||||
throw new IllegalArgumentException(
|
||||
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
|
||||
@ -63,8 +63,7 @@ class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInput(ByteBuf decompressed) {
|
||||
private void setInput(ByteBuf decompressed) {
|
||||
byte[] in = new byte[decompressed.readableBytes()];
|
||||
decompressed.readBytes(in);
|
||||
z.next_in = in;
|
||||
@ -72,8 +71,7 @@ class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
|
||||
z.avail_in = in.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf compressed) {
|
||||
private void encode(ByteBuf compressed) {
|
||||
try {
|
||||
byte[] out = new byte[(int) Math.ceil(z.next_in.length * 1.001) + 12];
|
||||
z.next_out = out;
|
||||
@ -99,7 +97,32 @@ class SpdyHeaderBlockJZlibCompressor extends SpdyHeaderBlockCompressor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
public synchronized ByteBuf encode(SpdyHeadersFrame frame) throws Exception {
|
||||
if (frame == null) {
|
||||
throw new IllegalArgumentException("frame");
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
throw new IllegalAccessException("compressor closed");
|
||||
}
|
||||
|
||||
ByteBuf decompressed = super.encode(frame);
|
||||
if (decompressed.readableBytes() == 0) {
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
ByteBuf compressed = Unpooled.buffer();
|
||||
setInput(decompressed);
|
||||
encode(compressed);
|
||||
return compressed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void end() {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
finished = true;
|
||||
z.deflateEnd();
|
||||
z.next_in = null;
|
||||
z.next_out = null;
|
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project 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 io.netty.handler.codec.spdy;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
public class SpdyHeaderBlockRawDecoder extends SpdyHeaderBlockDecoder {
|
||||
|
||||
private final int version;
|
||||
private final int maxHeaderSize;
|
||||
private final int lengthFieldSize;
|
||||
|
||||
// Header block decoding fields
|
||||
private int headerSize;
|
||||
private int numHeaders;
|
||||
|
||||
public SpdyHeaderBlockRawDecoder(int version, int maxHeaderSize) {
|
||||
if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
|
||||
this.version = version;
|
||||
this.maxHeaderSize = maxHeaderSize;
|
||||
lengthFieldSize = version < 3 ? 2 : 4;
|
||||
reset();
|
||||
}
|
||||
|
||||
private int readLengthField(ByteBuf buffer) {
|
||||
int length;
|
||||
if (version < 3) {
|
||||
length = getUnsignedShort(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(2);
|
||||
} else {
|
||||
length = getSignedInt(buffer, buffer.readerIndex());
|
||||
buffer.skipBytes(4);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
void decode(ByteBuf encoded, SpdyHeadersFrame frame) throws Exception {
|
||||
if (encoded == null) {
|
||||
throw new NullPointerException("encoded");
|
||||
}
|
||||
if (frame == null) {
|
||||
throw new NullPointerException("frame");
|
||||
}
|
||||
|
||||
if (numHeaders == -1) {
|
||||
// Read number of Name/Value pairs
|
||||
if (encoded.readableBytes() < lengthFieldSize) {
|
||||
return;
|
||||
}
|
||||
numHeaders = readLengthField(encoded);
|
||||
if (numHeaders < 0) {
|
||||
frame.setInvalid();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while (numHeaders > 0) {
|
||||
int headerSize = this.headerSize;
|
||||
encoded.markReaderIndex();
|
||||
|
||||
// Try to read length of name
|
||||
if (encoded.readableBytes() < lengthFieldSize) {
|
||||
encoded.resetReaderIndex();
|
||||
encoded.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
int nameLength = readLengthField(encoded);
|
||||
|
||||
// Recipients of a zero-length name must issue a stream error
|
||||
if (nameLength <= 0) {
|
||||
frame.setInvalid();
|
||||
return;
|
||||
}
|
||||
headerSize += nameLength;
|
||||
if (headerSize > maxHeaderSize) {
|
||||
throw new TooLongFrameException(
|
||||
"Header block exceeds " + maxHeaderSize);
|
||||
}
|
||||
|
||||
// Try to read name
|
||||
if (encoded.readableBytes() < nameLength) {
|
||||
encoded.resetReaderIndex();
|
||||
encoded.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
byte[] nameBytes = new byte[nameLength];
|
||||
encoded.readBytes(nameBytes);
|
||||
String name = new String(nameBytes, "UTF-8");
|
||||
|
||||
// Check for identically named headers
|
||||
if (frame.headers().contains(name)) {
|
||||
frame.setInvalid();
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to read length of value
|
||||
if (encoded.readableBytes() < lengthFieldSize) {
|
||||
encoded.resetReaderIndex();
|
||||
encoded.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
int valueLength = readLengthField(encoded);
|
||||
|
||||
// Recipients of illegal value fields must issue a stream error
|
||||
if (valueLength < 0) {
|
||||
frame.setInvalid();
|
||||
return;
|
||||
}
|
||||
|
||||
// SPDY/3 allows zero-length (empty) header values
|
||||
if (valueLength == 0) {
|
||||
if (version < 3) {
|
||||
frame.setInvalid();
|
||||
return;
|
||||
} else {
|
||||
frame.headers().add(name, "");
|
||||
numHeaders --;
|
||||
this.headerSize = headerSize;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
headerSize += valueLength;
|
||||
if (headerSize > maxHeaderSize) {
|
||||
throw new TooLongFrameException(
|
||||
"Header block exceeds " + maxHeaderSize);
|
||||
}
|
||||
|
||||
// Try to read value
|
||||
if (encoded.readableBytes() < valueLength) {
|
||||
encoded.resetReaderIndex();
|
||||
encoded.discardReadBytes();
|
||||
return;
|
||||
}
|
||||
byte[] valueBytes = new byte[valueLength];
|
||||
encoded.readBytes(valueBytes);
|
||||
|
||||
// Add Name/Value pair to headers
|
||||
int index = 0;
|
||||
int offset = 0;
|
||||
while (index < valueLength) {
|
||||
while (index < valueBytes.length && valueBytes[index] != (byte) 0) {
|
||||
index ++;
|
||||
}
|
||||
if (index < valueBytes.length && valueBytes[index + 1] == (byte) 0) {
|
||||
// Received multiple, in-sequence NULL characters
|
||||
// Recipients of illegal value fields must issue a stream error
|
||||
frame.setInvalid();
|
||||
return;
|
||||
}
|
||||
String value = new String(valueBytes, offset, index - offset, "UTF-8");
|
||||
|
||||
try {
|
||||
frame.headers().add(name, value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Name contains NULL or non-ascii characters
|
||||
frame.setInvalid();
|
||||
return;
|
||||
}
|
||||
index ++;
|
||||
offset = index;
|
||||
}
|
||||
numHeaders --;
|
||||
this.headerSize = headerSize;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void reset() {
|
||||
// Initialize header block decoding fields
|
||||
headerSize = 0;
|
||||
numHeaders = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
void end() {
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project 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 io.netty.handler.codec.spdy;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Set;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
public class SpdyHeaderBlockRawEncoder extends SpdyHeaderBlockEncoder {
|
||||
|
||||
private final int version;
|
||||
|
||||
public SpdyHeaderBlockRawEncoder(int version) {
|
||||
if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unknown version: " + version);
|
||||
}
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
private void setLengthField(ByteBuf buffer, int writerIndex, int length) {
|
||||
if (version < 3) {
|
||||
buffer.setShort(writerIndex, length);
|
||||
} else {
|
||||
buffer.setInt(writerIndex, length);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeLengthField(ByteBuf buffer, int length) {
|
||||
if (version < 3) {
|
||||
buffer.writeShort(length);
|
||||
} else {
|
||||
buffer.writeInt(length);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf encode(SpdyHeadersFrame frame) throws Exception {
|
||||
Set<String> names = frame.headers().names();
|
||||
int numHeaders = names.size();
|
||||
if (numHeaders == 0) {
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
if (numHeaders > SPDY_MAX_NV_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"header block contains too many headers");
|
||||
}
|
||||
ByteBuf headerBlock = Unpooled.buffer();
|
||||
writeLengthField(headerBlock, numHeaders);
|
||||
for (String name: names) {
|
||||
byte[] nameBytes = name.getBytes("UTF-8");
|
||||
writeLengthField(headerBlock, nameBytes.length);
|
||||
headerBlock.writeBytes(nameBytes);
|
||||
int savedIndex = headerBlock.writerIndex();
|
||||
int valueLength = 0;
|
||||
writeLengthField(headerBlock, valueLength);
|
||||
for (String value: frame.headers().getAll(name)) {
|
||||
byte[] valueBytes = value.getBytes("UTF-8");
|
||||
if (valueBytes.length > 0) {
|
||||
headerBlock.writeBytes(valueBytes);
|
||||
headerBlock.writeByte(0);
|
||||
valueLength += valueBytes.length + 1;
|
||||
}
|
||||
}
|
||||
if (valueLength == 0) {
|
||||
if (version < 3) {
|
||||
throw new IllegalArgumentException(
|
||||
"header value cannot be empty: " + name);
|
||||
}
|
||||
} else {
|
||||
valueLength --;
|
||||
}
|
||||
if (valueLength > SPDY_MAX_NV_LENGTH) {
|
||||
throw new IllegalArgumentException(
|
||||
"header exceeds allowable length: " + name);
|
||||
}
|
||||
if (valueLength > 0) {
|
||||
setLengthField(headerBlock, savedIndex, valueLength);
|
||||
headerBlock.writerIndex(headerBlock.writerIndex() - 1);
|
||||
}
|
||||
}
|
||||
return headerBlock;
|
||||
}
|
||||
|
||||
@Override
|
||||
void end() {
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
@ -16,34 +16,46 @@
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
class SpdyHeaderBlockZlibDecompressor extends SpdyHeaderBlockDecompressor {
|
||||
class SpdyHeaderBlockZlibDecoder extends SpdyHeaderBlockRawDecoder {
|
||||
|
||||
private final int version;
|
||||
private final byte[] out = new byte[8192];
|
||||
private final Inflater decompressor = new Inflater();
|
||||
|
||||
public SpdyHeaderBlockZlibDecompressor(int version) {
|
||||
if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
private ByteBuf decompressed;
|
||||
|
||||
public SpdyHeaderBlockZlibDecoder(int version, int maxHeaderSize) {
|
||||
super(version, maxHeaderSize);
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInput(ByteBuf compressed) {
|
||||
void decode(ByteBuf encoded, SpdyHeadersFrame frame) throws Exception {
|
||||
setInput(encoded);
|
||||
|
||||
int numBytes;
|
||||
do {
|
||||
numBytes = decompress(frame);
|
||||
} while (!decompressed.readable() && numBytes > 0);
|
||||
}
|
||||
|
||||
private void setInput(ByteBuf compressed) {
|
||||
byte[] in = new byte[compressed.readableBytes()];
|
||||
compressed.readBytes(in);
|
||||
decompressor.setInput(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int decode(ByteBuf decompressed) throws Exception {
|
||||
private int decompress(SpdyHeadersFrame frame) throws Exception {
|
||||
if (decompressed == null) {
|
||||
decompressed = decompressed = Unpooled.buffer(8192);
|
||||
}
|
||||
try {
|
||||
int numBytes = decompressor.inflate(out);
|
||||
if (numBytes == 0 && decompressor.needsDictionary()) {
|
||||
@ -54,7 +66,10 @@ class SpdyHeaderBlockZlibDecompressor extends SpdyHeaderBlockDecompressor {
|
||||
}
|
||||
numBytes = decompressor.inflate(out);
|
||||
}
|
||||
decompressed.writeBytes(out, 0, numBytes);
|
||||
if (frame != null) {
|
||||
decompressed.writeBytes(out, 0, numBytes);
|
||||
super.decode(decompressed, frame);
|
||||
}
|
||||
return numBytes;
|
||||
} catch (DataFormatException e) {
|
||||
throw new SpdyProtocolException(
|
||||
@ -62,8 +77,16 @@ class SpdyHeaderBlockZlibDecompressor extends SpdyHeaderBlockDecompressor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
decompressed = null;
|
||||
super.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
decompressed = null;
|
||||
decompressor.end();
|
||||
super.end();
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
@ -16,20 +16,21 @@
|
||||
package io.netty.handler.codec.spdy;
|
||||
|
||||
import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
class SpdyHeaderBlockZlibCompressor extends SpdyHeaderBlockCompressor {
|
||||
class SpdyHeaderBlockZlibEncoder extends SpdyHeaderBlockRawEncoder {
|
||||
|
||||
private final byte[] out = new byte[8192];
|
||||
private final Deflater compressor;
|
||||
|
||||
public SpdyHeaderBlockZlibCompressor(int version, int compressionLevel) {
|
||||
if (version < SpdyConstants.SPDY_MIN_VERSION || version > SpdyConstants.SPDY_MAX_VERSION) {
|
||||
throw new IllegalArgumentException(
|
||||
"unsupported version: " + version);
|
||||
}
|
||||
private boolean finished;
|
||||
|
||||
public SpdyHeaderBlockZlibEncoder(int version, int compressionLevel) {
|
||||
super(version);
|
||||
if (compressionLevel < 0 || compressionLevel > 9) {
|
||||
throw new IllegalArgumentException(
|
||||
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
|
||||
@ -42,15 +43,13 @@ class SpdyHeaderBlockZlibCompressor extends SpdyHeaderBlockCompressor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInput(ByteBuf decompressed) {
|
||||
private void setInput(ByteBuf decompressed) {
|
||||
byte[] in = new byte[decompressed.readableBytes()];
|
||||
decompressed.readBytes(in);
|
||||
compressor.setInput(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf compressed) {
|
||||
private void encode(ByteBuf compressed) {
|
||||
int numBytes = out.length;
|
||||
while (numBytes == out.length) {
|
||||
numBytes = compressor.deflate(out, 0, out.length, Deflater.SYNC_FLUSH);
|
||||
@ -58,8 +57,33 @@ class SpdyHeaderBlockZlibCompressor extends SpdyHeaderBlockCompressor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized ByteBuf encode(SpdyHeadersFrame frame) throws Exception {
|
||||
if (frame == null) {
|
||||
throw new IllegalArgumentException("frame");
|
||||
}
|
||||
if (finished) {
|
||||
throw new IllegalAccessException("compressor closed");
|
||||
}
|
||||
|
||||
ByteBuf decompressed = super.encode(frame);
|
||||
if (decompressed.readableBytes() == 0) {
|
||||
return Unpooled.EMPTY_BUFFER;
|
||||
}
|
||||
|
||||
ByteBuf compressed = Unpooled.buffer();
|
||||
setInput(decompressed);
|
||||
encode(compressed);
|
||||
return compressed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
finished = true;
|
||||
compressor.end();
|
||||
super.end();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user