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     public void reset() {
129         this.state = READ_HEAD_LINE;
130         this.endOfStream = false;
131         this.headerBufs.clear();
132         this.message = null;
133     }
134 
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     public T parse() throws IOException, HttpException {
186         while (this.state != COMPLETED) {
187             if (this.lineBuf == null) {
188                 this.lineBuf = new CharArrayBuffer(64);
189             } else {
190                 this.lineBuf.clear();
191             }
192             final boolean lineComplete = this.sessionBuffer.readLine(this.lineBuf, this.endOfStream);
193             final int maxLineLen = this.constraints.getMaxLineLength();
194             if (maxLineLen > 0 &&
195                     (this.lineBuf.length() > maxLineLen ||
196                             (!lineComplete && this.sessionBuffer.length() > maxLineLen))) {
197                 throw new MessageConstraintException("Maximum line length limit exceeded");
198             }
199             if (!lineComplete) {
200                 break;
201             }
202 
203             switch (this.state) {
204             case READ_HEAD_LINE:
205                 try {
206                     parseHeadLine();
207                 } catch (final ParseException px) {
208                     throw new ProtocolException(px.getMessage(), px);
209                 }
210                 this.state = READ_HEADERS;
211                 break;
212             case READ_HEADERS:
213                 if (this.lineBuf.length() > 0) {
214                     final int maxHeaderCount = this.constraints.getMaxHeaderCount();
215                     if (maxHeaderCount > 0 && headerBufs.size() >= maxHeaderCount) {
216                         throw new MessageConstraintException("Maximum header count exceeded");
217                     }
218 
219                     parseHeader();
220                 } else {
221                     this.state = COMPLETED;
222                 }
223                 break;
224             }
225             if (this.endOfStream && !this.sessionBuffer.hasData()) {
226                 this.state = COMPLETED;
227             }
228         }
229         if (this.state == COMPLETED) {
230             for (final CharArrayBuffer buffer : this.headerBufs) {
231                 try {
232                     this.message.addHeader(lineParser.parseHeader(buffer));
233                 } catch (final ParseException ex) {
234                     throw new ProtocolException(ex.getMessage(), ex);
235                 }
236             }
237             return this.message;
238         } else {
239             return null;
240         }
241     }
242 
243 }