1 module hunt.http.codec.http.decode.HeadersBodyParser;
2 
3 import hunt.http.codec.http.decode.BodyParser;
4 import hunt.http.codec.http.decode.HeaderParser;
5 import hunt.http.codec.http.decode.HeaderBlockFragments;
6 import hunt.http.codec.http.decode.HeaderBlockParser;
7 import hunt.http.codec.http.decode.Parser;
8 
9 import hunt.http.codec.http.frame.ErrorCode;
10 import hunt.http.codec.http.frame.Flags;
11 import hunt.http.codec.http.frame.HeadersFrame;
12 import hunt.http.codec.http.frame.PriorityFrame;
13 import hunt.http.HttpMetaData;
14 
15 import hunt.io.BufferUtils;
16 import hunt.io.ByteBuffer;
17 
18 import hunt.Exceptions;
19 
20 import std.algorithm;
21 
22 /**
23 */
24 class HeadersBodyParser :BodyParser {
25 	private HeaderBlockParser headerBlockParser;
26 	private HeaderBlockFragments headerBlockFragments;
27 	private State state = State.PREPARE;
28 	private int cursor;
29 	private int length;
30 	private int paddingLength;
31 	private bool exclusive;
32 	private int parentStreamId;
33 	private int weight;
34 
35 	this(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser,
36 			HeaderBlockFragments headerBlockFragments) {
37 		super(headerParser, listener);
38 		this.headerBlockParser = headerBlockParser;
39 		this.headerBlockFragments = headerBlockFragments;
40 	}
41 
42 	private void reset() {
43 		state = State.PREPARE;
44 		cursor = 0;
45 		length = 0;
46 		paddingLength = 0;
47 		exclusive = false;
48 		parentStreamId = 0;
49 		weight = 0;
50 	}
51 
52 	override
53 	protected void emptyBody(ByteBuffer buffer) {
54 		if (hasFlag(Flags.END_HEADERS)) {
55 			HttpMetaData metaData = headerBlockParser.parse(BufferUtils.EMPTY_BUFFER, 0);
56 			onHeaders(0, 0, false, metaData);
57 		} else {
58 			headerBlockFragments.setStreamId(getStreamId());
59 			headerBlockFragments.setEndStream(isEndStream());
60 			if (hasFlag(Flags.PRIORITY))
61 				connectionFailure(buffer, cast(int)ErrorCode.PROTOCOL_ERROR, "invalid_headers_priority_frame");
62 		}
63 	}
64 
65 	override
66 	bool parse(ByteBuffer buffer) {
67 		bool loop = false;
68 		while (buffer.hasRemaining() || loop) {
69 			switch (state) {
70 			case State.PREPARE: {
71 				// SPEC: wrong streamId is treated as connection error.
72 				if (getStreamId() == 0)
73 					return connectionFailure(buffer, cast(int)ErrorCode.PROTOCOL_ERROR, "invalid_headers_frame");
74 
75 				length = getBodyLength();
76 
77 				if (isPadding()) {
78 					state = State.PADDING_LENGTH;
79 				} else if (hasFlag(Flags.PRIORITY)) {
80 					state = State.EXCLUSIVE;
81 				} else {
82 					state = State.HEADERS;
83 				}
84 				break;
85 			}
86 			case State.PADDING_LENGTH: {
87 				paddingLength = buffer.get() & 0xFF;
88 				--length;
89 				length -= paddingLength;
90 				state = hasFlag(Flags.PRIORITY) ? State.EXCLUSIVE : State.HEADERS;
91 				loop = length == 0;
92 				if (length < 0)
93 					return connectionFailure(buffer, cast(int)ErrorCode.FRAME_SIZE_ERROR, "invalid_headers_frame_padding");
94 				break;
95 			}
96 			case State.EXCLUSIVE: {
97 				// We must only peek the first byte and not advance the buffer
98 				// because the 31 least significant bits represent the stream
99 				// id.
100 				int currByte = buffer.get(buffer.position());
101 				exclusive = (currByte & 0x80) == 0x80;
102 				state = State.PARENT_STREAM_ID;
103 				break;
104 			}
105 			case State.PARENT_STREAM_ID: {
106 				if (buffer.remaining() >= 4) {
107 					parentStreamId = buffer.get!int();
108 					parentStreamId &= 0x7F_FF_FF_FF;
109 					length -= 4;
110 					state = State.WEIGHT;
111 					if (length < 1)
112 						return connectionFailure(buffer, cast(int)ErrorCode.FRAME_SIZE_ERROR, "invalid_headers_frame");
113 				} else {
114 					state = State.PARENT_STREAM_ID_BYTES;
115 					cursor = 4;
116 				}
117 				break;
118 			}
119 			case State.PARENT_STREAM_ID_BYTES: {
120 				int currByte = buffer.get() & 0xFF;
121 				--cursor;
122 				parentStreamId += currByte << (8 * cursor);
123 				--length;
124 				if (cursor > 0 && length <= 0)
125 					return connectionFailure(buffer, cast(int)ErrorCode.FRAME_SIZE_ERROR, "invalid_headers_frame");
126 				if (cursor == 0) {
127 					parentStreamId &= 0x7F_FF_FF_FF;
128 					state = State.WEIGHT;
129 					if (length < 1)
130 						return connectionFailure(buffer, cast(int)ErrorCode.FRAME_SIZE_ERROR, "invalid_headers_frame");
131 				}
132 				break;
133 			}
134 			case State.WEIGHT: {
135 				weight = (buffer.get() & 0xFF) + 1;
136 				--length;
137 				state = State.HEADERS;
138 				loop = length == 0;
139 				break;
140 			}
141 			case State.HEADERS: {
142 				if (hasFlag(Flags.END_HEADERS)) {
143 					HttpMetaData metaData = headerBlockParser.parse(buffer, length);
144 					if (metaData !is null) {
145 						state = State.PADDING;
146 						loop = paddingLength == 0;
147 						onHeaders(parentStreamId, weight, exclusive, metaData);
148 					}
149 				} else {
150 					int remaining = buffer.remaining();
151 					if (remaining < length) {
152 						headerBlockFragments.storeFragment(buffer, remaining, false);
153 						length -= remaining;
154 					} else {
155 						headerBlockFragments.setStreamId(getStreamId());
156 						headerBlockFragments.setEndStream(isEndStream());
157 						if (hasFlag(Flags.PRIORITY))
158 							headerBlockFragments.setPriorityFrame(
159 									new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive));
160 						headerBlockFragments.storeFragment(buffer, length, false);
161 						state = State.PADDING;
162 						loop = paddingLength == 0;
163 					}
164 				}
165 				break;
166 			}
167 			case State.PADDING: {
168 				int size = std.algorithm.min(buffer.remaining(), paddingLength);
169 				buffer.position(buffer.position() + size);
170 				paddingLength -= size;
171 				if (paddingLength == 0) {
172 					reset();
173 					return true;
174 				}
175 				break;
176 			}
177 			default: {
178 				throw new IllegalStateException("");
179 			}
180 			}
181 		}
182 		return false;
183 	}
184 
185 	private void onHeaders(int parentStreamId, int weight, bool exclusive, HttpMetaData metaData) {
186 		PriorityFrame priorityFrame = null;
187 		if (hasFlag(Flags.PRIORITY))
188 			priorityFrame = new PriorityFrame(getStreamId(), parentStreamId, weight, exclusive);
189 		HeadersFrame frame = new HeadersFrame(getStreamId(), metaData, priorityFrame, isEndStream());
190 		notifyHeaders(frame);
191 	}
192 
193 	private enum State {
194 		PREPARE, PADDING_LENGTH, EXCLUSIVE, PARENT_STREAM_ID, PARENT_STREAM_ID_BYTES, WEIGHT, HEADERS, PADDING
195 	}
196 }