View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.impl.nio.codecs;
29  
30  import java.io.IOException;
31  import java.nio.channels.ReadableByteChannel;
32  import java.util.ArrayList;
33  import java.util.List;
34  
35  import org.apache.http.HttpException;
36  import org.apache.http.HttpMessage;
37  import org.apache.http.MessageConstraintException;
38  import org.apache.http.ParseException;
39  import org.apache.http.ProtocolException;
40  import org.apache.http.config.MessageConstraints;
41  import org.apache.http.message.BasicLineParser;
42  import org.apache.http.message.LineParser;
43  import org.apache.http.nio.NHttpMessageParser;
44  import org.apache.http.nio.reactor.SessionInputBuffer;
45  import org.apache.http.params.HttpParamConfig;
46  import org.apache.http.params.HttpParams;
47  import org.apache.http.util.Args;
48  import org.apache.http.util.CharArrayBuffer;
49  
50  /**
51   * Abstract {@link NHttpMessageParser} that serves as a base for all message
52   * parser implementations.
53   *
54   * @since 4.0
55   */
56  @SuppressWarnings("deprecation")
57  public abstract class AbstractMessageParser<T extends HttpMessage> implements NHttpMessageParser<T> {
58  
59      private final SessionInputBuffer sessionBuffer;
60  
61      private static final int READ_HEAD_LINE = 0;
62      private static final int READ_HEADERS   = 1;
63      private static final int COMPLETED      = 2;
64  
65      private int state;
66      private boolean endOfStream;
67  
68      private T message;
69      private CharArrayBuffer lineBuf;
70      private final List<CharArrayBuffer> headerBufs;
71  
72      protected final LineParser lineParser;
73      private final MessageConstraints constraints;
74  
75      /**
76       * Creates an instance of this class.
77       *
78       * @param buffer the session input buffer.
79       * @param lineParser the line parser.
80       * @param params HTTP parameters.
81       *
82       * @deprecated (4.3) use
83       *   {@link AbstractMessageParser#AbstractMessageParser(SessionInputBuffer, LineParser,
84       *     MessageConstraints)}
85       */
86      @Deprecated
87      public AbstractMessageParser(
88              final SessionInputBuffer buffer,
89              final LineParser lineParser,
90              final HttpParams params) {
91          super();
92          Args.notNull(buffer, "Session input buffer");
93          Args.notNull(params, "HTTP parameters");
94          this.sessionBuffer = buffer;
95          this.state = READ_HEAD_LINE;
96          this.endOfStream = false;
97          this.headerBufs = new ArrayList<CharArrayBuffer>();
98          this.constraints = HttpParamConfig.getMessageConstraints(params);
99          this.lineParser = (lineParser != null) ? lineParser : BasicLineParser.INSTANCE;
100     }
101 
102     /**
103      * Creates an instance of AbstractMessageParser.
104      *
105      * @param buffer the session input buffer.
106      * @param lineParser the line parser. If {@code null} {@link BasicLineParser#INSTANCE}
107      *   will be used.
108      * @param constraints Message constraints. If {@code null}
109      *   {@link MessageConstraints#DEFAULT} will be used.
110      *
111      * @since 4.3
112      */
113     public AbstractMessageParser(
114             final SessionInputBuffer buffer,
115             final LineParser lineParser,
116             final MessageConstraints constraints) {
117         super();
118         this.sessionBuffer = Args.notNull(buffer, "Session input buffer");
119         this.lineParser = lineParser != null ? lineParser : BasicLineParser.INSTANCE;
120         this.constraints = constraints != null ? constraints : MessageConstraints.DEFAULT;
121         this.headerBufs = new ArrayList<CharArrayBuffer>();
122         this.state = READ_HEAD_LINE;
123         this.endOfStream = false;
124     }
125 
126     @Override
127     public void reset() {
128         this.state = READ_HEAD_LINE;
129         this.endOfStream = false;
130         this.headerBufs.clear();
131         this.message = null;
132     }
133 
134     @Override
135     public int fillBuffer(final ReadableByteChannel channel) throws IOException {
136         final int bytesRead = this.sessionBuffer.fill(channel);
137         if (bytesRead == -1) {
138             this.endOfStream = true;
139         }
140         return bytesRead;
141     }
142 
143     /**
144      * Creates {@link HttpMessage} instance based on the content of the input
145      *  buffer containing the first line of the incoming HTTP message.
146      *
147      * @param buffer the line buffer.
148      * @return HTTP message.
149      * @throws HttpException in case of HTTP protocol violation
150      * @throws ParseException in case of a parse error.
151      */
152     protected abstract T createMessage(CharArrayBuffer buffer)
153         throws HttpException, ParseException;
154 
155     private void parseHeadLine() throws HttpException, ParseException {
156         this.message = createMessage(this.lineBuf);
157     }
158 
159     private void parseHeader() throws IOException {
160         final CharArrayBuffer current = this.lineBuf;
161         final int count = this.headerBufs.size();
162         if ((this.lineBuf.charAt(0) == ' ' || this.lineBuf.charAt(0) == '\t') && count > 0) {
163             // Handle folded header line
164             final CharArrayBuffer previous = this.headerBufs.get(count - 1);
165             int i = 0;
166             while (i < current.length()) {
167                 final char ch = current.charAt(i);
168                 if (ch != ' ' && ch != '\t') {
169                     break;
170                 }
171                 i++;
172             }
173             final int maxLineLen = this.constraints.getMaxLineLength();
174             if (maxLineLen > 0 && previous.length() + 1 + current.length() - i > maxLineLen) {
175                 throw new MessageConstraintException("Maximum line length limit exceeded");
176             }
177             previous.append(' ');
178             previous.append(current, i, current.length() - i);
179         } else {
180             this.headerBufs.add(current);
181             this.lineBuf = null;
182         }
183     }
184 
185     @Override
186     public T parse() throws IOException, HttpException {
187         while (this.state != COMPLETED) {
188             if (this.lineBuf == null) {
189                 this.lineBuf = new CharArrayBuffer(64);
190             } else {
191                 this.lineBuf.clear();
192             }
193             final boolean lineComplete = this.sessionBuffer.readLine(this.lineBuf, this.endOfStream);
194             final int maxLineLen = this.constraints.getMaxLineLength();
195             if (maxLineLen > 0 &&
196                     (this.lineBuf.length() > maxLineLen ||
197                             (!lineComplete && this.sessionBuffer.length() > maxLineLen))) {
198                 throw new MessageConstraintException("Maximum line length limit exceeded");
199             }
200             if (!lineComplete) {
201                 break;
202             }
203 
204             switch (this.state) {
205             case READ_HEAD_LINE:
206                 try {
207                     parseHeadLine();
208                 } catch (final ParseException px) {
209                     throw new ProtocolException(px.getMessage(), px);
210                 }
211                 this.state = READ_HEADERS;
212                 break;
213             case READ_HEADERS:
214                 if (this.lineBuf.length() > 0) {
215                     final int maxHeaderCount = this.constraints.getMaxHeaderCount();
216                     if (maxHeaderCount > 0 && headerBufs.size() >= maxHeaderCount) {
217                         throw new MessageConstraintException("Maximum header count exceeded");
218                     }
219 
220                     parseHeader();
221                 } else {
222                     this.state = COMPLETED;
223                 }
224                 break;
225             }
226             if (this.endOfStream && !this.sessionBuffer.hasData()) {
227                 this.state = COMPLETED;
228             }
229         }
230         if (this.state == COMPLETED) {
231             for (final CharArrayBuffer buffer : this.headerBufs) {
232                 try {
233                     this.message.addHeader(lineParser.parseHeader(buffer));
234                 } catch (final ParseException ex) {
235                     throw new ProtocolException(ex.getMessage(), ex);
236                 }
237             }
238             return this.message;
239         }
240         return null;
241     }
242 
243 }