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.annotation.NotThreadSafe;
41  import org.apache.http.config.MessageConstraints;
42  import org.apache.http.message.BasicLineParser;
43  import org.apache.http.message.LineParser;
44  import org.apache.http.nio.NHttpMessageParser;
45  import org.apache.http.nio.reactor.SessionInputBuffer;
46  import org.apache.http.params.HttpParamConfig;
47  import org.apache.http.params.HttpParams;
48  import org.apache.http.util.Args;
49  import org.apache.http.util.CharArrayBuffer;
50  
51  /**
52   * Abstract {@link NHttpMessageParser} that serves as a base for all message
53   * parser implementations.
54   *
55   * @since 4.0
56   */
57  @SuppressWarnings("deprecation")
58  @NotThreadSafe
59  public abstract class AbstractMessageParser<T extends HttpMessage> implements NHttpMessageParser<T> {
60  
61      private final SessionInputBuffer sessionBuffer;
62  
63      private static final int READ_HEAD_LINE = 0;
64      private static final int READ_HEADERS   = 1;
65      private static final int COMPLETED      = 2;
66  
67      private int state;
68      private boolean endOfStream;
69  
70      private T message;
71      private CharArrayBuffer lineBuf;
72      private final List<CharArrayBuffer> headerBufs;
73  
74      protected final LineParser lineParser;
75      private final MessageConstraints constraints;
76  
77      /**
78       * Creates an instance of this class.
79       *
80       * @param buffer the session input buffer.
81       * @param lineParser the line parser.
82       * @param params HTTP parameters.
83       *
84       * @deprecated (4.3) use
85       *   {@link AbstractMessageParser#AbstractMessageParser(SessionInputBuffer, LineParser,
86       *     MessageConstraints)}
87       */
88      @Deprecated
89      public AbstractMessageParser(
90              final SessionInputBuffer buffer,
91              final LineParser lineParser,
92              final HttpParams params) {
93          super();
94          Args.notNull(buffer, "Session input buffer");
95          Args.notNull(params, "HTTP parameters");
96          this.sessionBuffer = buffer;
97          this.state = READ_HEAD_LINE;
98          this.endOfStream = false;
99          this.headerBufs = new ArrayList<CharArrayBuffer>();
100         this.constraints = HttpParamConfig.getMessageConstraints(params);
101         this.lineParser = (lineParser != null) ? lineParser : BasicLineParser.INSTANCE;
102     }
103 
104     /**
105      * Creates an instance of AbstractMessageParser.
106      *
107      * @param buffer the session input buffer.
108      * @param lineParser the line parser. If <code>null</code> {@link BasicLineParser#INSTANCE}
109      *   will be used.
110      * @param constraints Message constraints. If <code>null</code>
111      *   {@link MessageConstraints#DEFAULT} will be used.
112      *
113      * @since 4.3
114      */
115     public AbstractMessageParser(
116             final SessionInputBuffer buffer,
117             final LineParser lineParser,
118             final MessageConstraints constraints) {
119         super();
120         this.sessionBuffer = Args.notNull(buffer, "Session input buffer");
121         this.lineParser = lineParser != null ? lineParser : BasicLineParser.INSTANCE;
122         this.constraints = constraints != null ? constraints : MessageConstraints.DEFAULT;
123         this.headerBufs = new ArrayList<CharArrayBuffer>();
124         this.state = READ_HEAD_LINE;
125         this.endOfStream = false;
126     }
127 
128     @Override
129     public void reset() {
130         this.state = READ_HEAD_LINE;
131         this.endOfStream = false;
132         this.headerBufs.clear();
133         this.message = null;
134     }
135 
136     @Override
137     public int fillBuffer(final ReadableByteChannel channel) throws IOException {
138         final int bytesRead = this.sessionBuffer.fill(channel);
139         if (bytesRead == -1) {
140             this.endOfStream = true;
141         }
142         return bytesRead;
143     }
144 
145     /**
146      * Creates {@link HttpMessage} instance based on the content of the input
147      *  buffer containing the first line of the incoming HTTP message.
148      *
149      * @param buffer the line buffer.
150      * @return HTTP message.
151      * @throws HttpException in case of HTTP protocol violation
152      * @throws ParseException in case of a parse error.
153      */
154     protected abstract T createMessage(CharArrayBuffer buffer)
155         throws HttpException, ParseException;
156 
157     private void parseHeadLine() throws HttpException, ParseException {
158         this.message = createMessage(this.lineBuf);
159     }
160 
161     private void parseHeader() throws IOException {
162         final CharArrayBuffer current = this.lineBuf;
163         final int count = this.headerBufs.size();
164         if ((this.lineBuf.charAt(0) == ' ' || this.lineBuf.charAt(0) == '\t') && count > 0) {
165             // Handle folded header line
166             final CharArrayBuffer previous = this.headerBufs.get(count - 1);
167             int i = 0;
168             while (i < current.length()) {
169                 final char ch = current.charAt(i);
170                 if (ch != ' ' && ch != '\t') {
171                     break;
172                 }
173                 i++;
174             }
175             final int maxLineLen = this.constraints.getMaxLineLength();
176             if (maxLineLen > 0 && previous.length() + 1 + current.length() - i > maxLineLen) {
177                 throw new MessageConstraintException("Maximum line length limit exceeded");
178             }
179             previous.append(' ');
180             previous.append(current, i, current.length() - i);
181         } else {
182             this.headerBufs.add(current);
183             this.lineBuf = null;
184         }
185     }
186 
187     @Override
188     public T parse() throws IOException, HttpException {
189         while (this.state != COMPLETED) {
190             if (this.lineBuf == null) {
191                 this.lineBuf = new CharArrayBuffer(64);
192             } else {
193                 this.lineBuf.clear();
194             }
195             final boolean lineComplete = this.sessionBuffer.readLine(this.lineBuf, this.endOfStream);
196             final int maxLineLen = this.constraints.getMaxLineLength();
197             if (maxLineLen > 0 &&
198                     (this.lineBuf.length() > maxLineLen ||
199                             (!lineComplete && this.sessionBuffer.length() > maxLineLen))) {
200                 throw new MessageConstraintException("Maximum line length limit exceeded");
201             }
202             if (!lineComplete) {
203                 break;
204             }
205 
206             switch (this.state) {
207             case READ_HEAD_LINE:
208                 try {
209                     parseHeadLine();
210                 } catch (final ParseException px) {
211                     throw new ProtocolException(px.getMessage(), px);
212                 }
213                 this.state = READ_HEADERS;
214                 break;
215             case READ_HEADERS:
216                 if (this.lineBuf.length() > 0) {
217                     final int maxHeaderCount = this.constraints.getMaxHeaderCount();
218                     if (maxHeaderCount > 0 && headerBufs.size() >= maxHeaderCount) {
219                         throw new MessageConstraintException("Maximum header count exceeded");
220                     }
221 
222                     parseHeader();
223                 } else {
224                     this.state = COMPLETED;
225                 }
226                 break;
227             }
228             if (this.endOfStream && !this.sessionBuffer.hasData()) {
229                 this.state = COMPLETED;
230             }
231         }
232         if (this.state == COMPLETED) {
233             for (final CharArrayBuffer buffer : this.headerBufs) {
234                 try {
235                     this.message.addHeader(lineParser.parseHeader(buffer));
236                 } catch (final ParseException ex) {
237                     throw new ProtocolException(ex.getMessage(), ex);
238                 }
239             }
240             return this.message;
241         } else {
242             return null;
243         }
244     }
245 
246 }