View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.spdy;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufAllocator;
20  
21  import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
22  
23  public class SpdyHeaderBlockRawDecoder extends SpdyHeaderBlockDecoder {
24  
25      private static final int LENGTH_FIELD_SIZE = 4;
26  
27      private final int maxHeaderSize;
28  
29      private State state;
30  
31      private ByteBuf cumulation;
32  
33      private int headerSize;
34      private int numHeaders;
35      private int length;
36      private String name;
37  
38      private enum State {
39          READ_NUM_HEADERS,
40          READ_NAME_LENGTH,
41          READ_NAME,
42          SKIP_NAME,
43          READ_VALUE_LENGTH,
44          READ_VALUE,
45          SKIP_VALUE,
46          END_HEADER_BLOCK,
47          ERROR
48      }
49  
50      public SpdyHeaderBlockRawDecoder(SpdyVersion spdyVersion, int maxHeaderSize) {
51          if (spdyVersion == null) {
52              throw new NullPointerException("spdyVersion");
53          }
54          this.maxHeaderSize = maxHeaderSize;
55          state = State.READ_NUM_HEADERS;
56      }
57  
58      private static int readLengthField(ByteBuf buffer) {
59          int length = getSignedInt(buffer, buffer.readerIndex());
60          buffer.skipBytes(LENGTH_FIELD_SIZE);
61          return length;
62      }
63  
64      @Override
65      void decode(ByteBufAllocator alloc, ByteBuf headerBlock, SpdyHeadersFrame frame) throws Exception {
66          if (headerBlock == null) {
67              throw new NullPointerException("headerBlock");
68          }
69          if (frame == null) {
70              throw new NullPointerException("frame");
71          }
72  
73          if (cumulation == null) {
74              decodeHeaderBlock(headerBlock, frame);
75              if (headerBlock.isReadable()) {
76                  cumulation = alloc.buffer(headerBlock.readableBytes());
77                  cumulation.writeBytes(headerBlock);
78              }
79          } else {
80              cumulation.writeBytes(headerBlock);
81              decodeHeaderBlock(cumulation, frame);
82              if (cumulation.isReadable()) {
83                  cumulation.discardReadBytes();
84              } else {
85                  releaseBuffer();
86              }
87          }
88      }
89  
90      protected void decodeHeaderBlock(ByteBuf headerBlock, SpdyHeadersFrame frame) throws Exception {
91          int skipLength;
92          while (headerBlock.isReadable()) {
93              switch(state) {
94                  case READ_NUM_HEADERS:
95                      if (headerBlock.readableBytes() < LENGTH_FIELD_SIZE) {
96                          return;
97                      }
98  
99                      numHeaders = readLengthField(headerBlock);
100 
101                     if (numHeaders < 0) {
102                         state = State.ERROR;
103                         frame.setInvalid();
104                     } else if (numHeaders == 0) {
105                         state = State.END_HEADER_BLOCK;
106                     } else {
107                         state = State.READ_NAME_LENGTH;
108                     }
109                     break;
110 
111                 case READ_NAME_LENGTH:
112                     if (headerBlock.readableBytes() < LENGTH_FIELD_SIZE) {
113                         return;
114                     }
115 
116                     length = readLengthField(headerBlock);
117 
118                     // Recipients of a zero-length name must issue a stream error
119                     if (length <= 0) {
120                         state = State.ERROR;
121                         frame.setInvalid();
122                     } else if (length > maxHeaderSize || headerSize > maxHeaderSize - length) {
123                         headerSize = maxHeaderSize + 1;
124                         state = State.SKIP_NAME;
125                         frame.setTruncated();
126                     } else {
127                         headerSize += length;
128                         state = State.READ_NAME;
129                     }
130                     break;
131 
132                 case READ_NAME:
133                     if (headerBlock.readableBytes() < length) {
134                         return;
135                     }
136 
137                     byte[] nameBytes = new byte[length];
138                     headerBlock.readBytes(nameBytes);
139                     name = new String(nameBytes, "UTF-8");
140 
141                     // Check for identically named headers
142                     if (frame.headers().contains(name)) {
143                         state = State.ERROR;
144                         frame.setInvalid();
145                     } else {
146                         state = State.READ_VALUE_LENGTH;
147                     }
148                     break;
149 
150                 case SKIP_NAME:
151                     skipLength = Math.min(headerBlock.readableBytes(), length);
152                     headerBlock.skipBytes(skipLength);
153                     length -= skipLength;
154 
155                     if (length == 0) {
156                         state = State.READ_VALUE_LENGTH;
157                     }
158                     break;
159 
160                 case READ_VALUE_LENGTH:
161                     if (headerBlock.readableBytes() < LENGTH_FIELD_SIZE) {
162                         return;
163                     }
164 
165                     length = readLengthField(headerBlock);
166 
167                     // Recipients of illegal value fields must issue a stream error
168                     if (length < 0) {
169                         state = State.ERROR;
170                         frame.setInvalid();
171                     } else if (length == 0) {
172                         if (!frame.isTruncated()) {
173                             // SPDY/3 allows zero-length (empty) header values
174                             frame.headers().add(name, "");
175                         }
176 
177                         name = null;
178                         if (--numHeaders == 0) {
179                             state = State.END_HEADER_BLOCK;
180                         } else {
181                             state = State.READ_NAME_LENGTH;
182                         }
183 
184                     } else if (length > maxHeaderSize || headerSize > maxHeaderSize - length) {
185                         headerSize = maxHeaderSize + 1;
186                         name = null;
187                         state = State.SKIP_VALUE;
188                         frame.setTruncated();
189                     } else {
190                         headerSize += length;
191                         state = State.READ_VALUE;
192                     }
193                     break;
194 
195                 case READ_VALUE:
196                     if (headerBlock.readableBytes() < length) {
197                         return;
198                     }
199 
200                     byte[] valueBytes = new byte[length];
201                     headerBlock.readBytes(valueBytes);
202 
203                     // Add Name/Value pair to headers
204                     int index = 0;
205                     int offset = 0;
206 
207                     // Value must not start with a NULL character
208                     if (valueBytes[0] == (byte) 0) {
209                         state = State.ERROR;
210                         frame.setInvalid();
211                         break;
212                     }
213 
214                     while (index < length) {
215                         while (index < valueBytes.length && valueBytes[index] != (byte) 0) {
216                             index ++;
217                         }
218                         if (index < valueBytes.length) {
219                             // Received NULL character
220                             if (index + 1 == valueBytes.length || valueBytes[index + 1] == (byte) 0) {
221                                 // Value field ended with a NULL character or
222                                 // received multiple, in-sequence NULL characters.
223                                 // Recipients of illegal value fields must issue a stream error
224                                 state = State.ERROR;
225                                 frame.setInvalid();
226                                 break;
227                             }
228                         }
229                         String value = new String(valueBytes, offset, index - offset, "UTF-8");
230 
231                         try {
232                             frame.headers().add(name, value);
233                         } catch (IllegalArgumentException e) {
234                             // Name contains NULL or non-ascii characters
235                             state = State.ERROR;
236                             frame.setInvalid();
237                             break;
238                         }
239                         index ++;
240                         offset = index;
241                     }
242 
243                     name = null;
244 
245                     // If we broke out of the add header loop, break here
246                     if (state == State.ERROR) {
247                         break;
248                     }
249 
250                     if (--numHeaders == 0) {
251                         state = State.END_HEADER_BLOCK;
252                     } else {
253                         state = State.READ_NAME_LENGTH;
254                     }
255                     break;
256 
257                 case SKIP_VALUE:
258                     skipLength = Math.min(headerBlock.readableBytes(), length);
259                     headerBlock.skipBytes(skipLength);
260                     length -= skipLength;
261 
262                     if (length == 0) {
263                         if (--numHeaders == 0) {
264                             state = State.END_HEADER_BLOCK;
265                         } else {
266                             state = State.READ_NAME_LENGTH;
267                         }
268                     }
269                     break;
270 
271                 case END_HEADER_BLOCK:
272                     state = State.ERROR;
273                     frame.setInvalid();
274                     break;
275 
276                 case ERROR:
277                     headerBlock.skipBytes(headerBlock.readableBytes());
278                     return;
279 
280                 default:
281                     throw new Error("Shouldn't reach here.");
282             }
283         }
284     }
285 
286     @Override
287     void endHeaderBlock(SpdyHeadersFrame frame) throws Exception {
288         if (state != State.END_HEADER_BLOCK) {
289             frame.setInvalid();
290         }
291 
292         releaseBuffer();
293 
294         // Initialize header block decoding fields
295         headerSize = 0;
296         name = null;
297         state = State.READ_NUM_HEADERS;
298     }
299 
300     @Override
301     void end() {
302         releaseBuffer();
303     }
304 
305     private void releaseBuffer() {
306         if (cumulation != null) {
307             cumulation.release();
308             cumulation = null;
309         }
310     }
311 }