View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.http2.parser;
20  
21  import java.nio.ByteBuffer;
22  
23  import org.eclipse.jetty.http.MetaData;
24  import org.eclipse.jetty.http2.ErrorCode;
25  import org.eclipse.jetty.http2.Flags;
26  import org.eclipse.jetty.http2.frames.PushPromiseFrame;
27  
28  public class PushPromiseBodyParser extends BodyParser
29  {
30      private final HeaderBlockParser headerBlockParser;
31      private State state = State.PREPARE;
32      private int cursor;
33      private int length;
34      private int paddingLength;
35      private int streamId;
36  
37      public PushPromiseBodyParser(HeaderParser headerParser, Parser.Listener listener, HeaderBlockParser headerBlockParser)
38      {
39          super(headerParser, listener);
40          this.headerBlockParser = headerBlockParser;
41      }
42  
43      private void reset()
44      {
45          state = State.PREPARE;
46          cursor = 0;
47          length = 0;
48          paddingLength = 0;
49          streamId = 0;
50      }
51  
52      @Override
53      public boolean parse(ByteBuffer buffer)
54      {
55          boolean loop = false;
56          while (buffer.hasRemaining() || loop)
57          {
58              switch (state)
59              {
60                  case PREPARE:
61                  {
62                      // SPEC: wrong streamId is treated as connection error.
63                      if (getStreamId() == 0)
64                          return connectionFailure(buffer, ErrorCode.PROTOCOL_ERROR.code, "invalid_push_promise_frame");
65  
66                      // For now we don't support PUSH_PROMISE frames that don't have END_HEADERS.
67                      if (!hasFlag(Flags.END_HEADERS))
68                          return connectionFailure(buffer, ErrorCode.INTERNAL_ERROR.code, "unsupported_push_promise_frame");
69  
70                      length = getBodyLength();
71  
72                      if (isPadding())
73                      {
74                          state = State.PADDING_LENGTH;
75                      }
76                      else
77                      {
78                          state = State.STREAM_ID;
79                      }
80                      break;
81                  }
82                  case PADDING_LENGTH:
83                  {
84                      paddingLength = buffer.get() & 0xFF;
85                      --length;
86                      length -= paddingLength;
87                      state = State.STREAM_ID;
88                      if (length < 4)
89                          return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_push_promise_frame");
90                      break;
91                  }
92                  case STREAM_ID:
93                  {
94                      if (buffer.remaining() >= 4)
95                      {
96                          streamId = buffer.getInt();
97                          streamId &= 0x7F_FF_FF_FF;
98                          length -= 4;
99                          state = State.HEADERS;
100                         loop = length == 0;
101                     }
102                     else
103                     {
104                         state = State.STREAM_ID_BYTES;
105                         cursor = 4;
106                     }
107                     break;
108                 }
109                 case STREAM_ID_BYTES:
110                 {
111                     int currByte = buffer.get() & 0xFF;
112                     --cursor;
113                     streamId += currByte << (8 * cursor);
114                     --length;
115                     if (cursor > 0 && length <= 0)
116                         return connectionFailure(buffer, ErrorCode.FRAME_SIZE_ERROR.code, "invalid_push_promise_frame");
117                     if (cursor == 0)
118                     {
119                         streamId &= 0x7F_FF_FF_FF;
120                         state = State.HEADERS;
121                         loop = length == 0;
122                     }
123                     break;
124                 }
125                 case HEADERS:
126                 {
127                     MetaData metaData = headerBlockParser.parse(buffer, length);
128                     if (metaData != null)
129                     {
130                         state = State.PADDING;
131                         loop = paddingLength == 0;
132                         onPushPromise(streamId, metaData);
133                     }
134                     break;
135                 }
136                 case PADDING:
137                 {
138                     int size = Math.min(buffer.remaining(), paddingLength);
139                     buffer.position(buffer.position() + size);
140                     paddingLength -= size;
141                     if (paddingLength == 0)
142                     {
143                         reset();
144                         return true;
145                     }
146                     break;
147                 }
148                 default:
149                 {
150                     throw new IllegalStateException();
151                 }
152             }
153         }
154         return false;
155     }
156 
157     private void onPushPromise(int streamId, MetaData metaData)
158     {
159         PushPromiseFrame frame = new PushPromiseFrame(getStreamId(), streamId, metaData);
160         notifyPushPromise(frame);
161     }
162 
163     private enum State
164     {
165         PREPARE, PADDING_LENGTH, STREAM_ID, STREAM_ID_BYTES, HEADERS, PADDING
166     }
167 }