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.hc.core5.http.impl.nio;
29  
30  import java.io.IOException;
31  import java.util.ArrayList;
32  import java.util.List;
33  
34  import org.apache.hc.core5.http.HttpException;
35  import org.apache.hc.core5.http.HttpMessage;
36  import org.apache.hc.core5.http.MessageConstraintException;
37  import org.apache.hc.core5.http.config.Http1Config;
38  import org.apache.hc.core5.http.message.LazyLineParser;
39  import org.apache.hc.core5.http.message.LineParser;
40  import org.apache.hc.core5.http.nio.NHttpMessageParser;
41  import org.apache.hc.core5.http.nio.SessionInputBuffer;
42  import org.apache.hc.core5.util.Args;
43  import org.apache.hc.core5.util.CharArrayBuffer;
44  
45  /**
46   * Abstract {@link NHttpMessageParser} that serves as a base for all message
47   * parser implementations.
48   *
49   * @since 4.0
50   */
51  public abstract class AbstractMessageParser<T extends HttpMessage> implements NHttpMessageParser<T> {
52  
53      private enum State {
54          READ_HEAD_LINE, READ_HEADERS, COMPLETED
55      }
56  
57      private final Http1Config http1Config;
58      private final LineParser lineParser;
59  
60      private State state;
61  
62      private T message;
63      private CharArrayBuffer lineBuf;
64      private final List<CharArrayBuffer> headerBufs;
65      private int emptyLineCount;
66  
67      /**
68       * @since 5.3
69       */
70      public AbstractMessageParser(final Http1Config http1Config, final LineParser lineParser) {
71          super();
72          this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT;
73          this.lineParser = lineParser != null ? lineParser : LazyLineParser.INSTANCE;
74          this.headerBufs = new ArrayList<>();
75          this.state = State.READ_HEAD_LINE;
76      }
77  
78      /**
79       * @deprecated Use {@link #AbstractMessageParser(Http1Config, LineParser)}
80       */
81      @Deprecated
82      public AbstractMessageParser(final LineParser lineParser, final Http1Config messageConstraints) {
83          this(messageConstraints, lineParser);
84      }
85  
86      LineParser getLineParser() {
87          return this.lineParser;
88      }
89  
90      @Override
91      public void reset() {
92          this.state = State.READ_HEAD_LINE;
93          this.headerBufs.clear();
94          this.emptyLineCount = 0;
95          this.message = null;
96      }
97  
98      /**
99       * Creates {@link HttpMessage} instance based on the content of the input
100      *  buffer containing the first line of the incoming HTTP message.
101      *
102      * @param buffer the line buffer.
103      * @return HTTP message.
104      * @throws HttpException in case of HTTP protocol violation
105      */
106     protected abstract T createMessage(CharArrayBuffer buffer) throws HttpException;
107 
108     private T parseHeadLine() throws IOException, HttpException {
109         if (this.lineBuf.isEmpty()) {
110             this.emptyLineCount++;
111             if (this.emptyLineCount >= this.http1Config.getMaxEmptyLineCount()) {
112                 throw new MessageConstraintException("Maximum empty line limit exceeded");
113             }
114             return null;
115         }
116         return createMessage(this.lineBuf);
117     }
118 
119     private void parseHeader() throws IOException {
120         final CharArrayBuffer current = this.lineBuf;
121         final int count = this.headerBufs.size();
122         if ((this.lineBuf.charAt(0) == ' ' || this.lineBuf.charAt(0) == '\t') && count > 0) {
123             // Handle folded header line
124             final CharArrayBuffer previous = this.headerBufs.get(count - 1);
125             int i = 0;
126             while (i < current.length()) {
127                 final char ch = current.charAt(i);
128                 if (ch != ' ' && ch != '\t') {
129                     break;
130                 }
131                 i++;
132             }
133             final int maxLineLen = this.http1Config.getMaxLineLength();
134             if (maxLineLen > 0 && previous.length() + 1 + current.length() - i > maxLineLen) {
135                 throw new MessageConstraintException("Maximum line length limit exceeded");
136             }
137             previous.append(' ');
138             previous.append(current, i, current.length() - i);
139         } else {
140             this.headerBufs.add(current);
141             this.lineBuf = null;
142         }
143     }
144 
145     @Override
146     public T parse(
147             final SessionInputBuffer sessionBuffer, final boolean endOfStream) throws IOException, HttpException {
148         Args.notNull(sessionBuffer, "Session input buffer");
149         while (this.state !=State.COMPLETED) {
150             if (this.lineBuf == null) {
151                 this.lineBuf = new CharArrayBuffer(64);
152             } else {
153                 this.lineBuf.clear();
154             }
155             final boolean lineComplete = sessionBuffer.readLine(this.lineBuf, endOfStream);
156             final int maxLineLen = this.http1Config.getMaxLineLength();
157             if (maxLineLen > 0 &&
158                     (this.lineBuf.length() > maxLineLen ||
159                             (!lineComplete && sessionBuffer.length() > maxLineLen))) {
160                 throw new MessageConstraintException("Maximum line length limit exceeded");
161             }
162             if (!lineComplete) {
163                 break;
164             }
165 
166             switch (this.state) {
167             case READ_HEAD_LINE:
168                 this.message = parseHeadLine();
169                 if (this.message != null) {
170                     this.state = State.READ_HEADERS;
171                 }
172                 break;
173             case READ_HEADERS:
174                 if (this.lineBuf.length() > 0) {
175                     final int maxHeaderCount = this.http1Config.getMaxHeaderCount();
176                     if (maxHeaderCount > 0 && headerBufs.size() >= maxHeaderCount) {
177                         throw new MessageConstraintException("Maximum header count exceeded");
178                     }
179 
180                     parseHeader();
181                 } else {
182                     this.state = State.COMPLETED;
183                 }
184                 break;
185             }
186             if (endOfStream && !sessionBuffer.hasData()) {
187                 this.state = State.COMPLETED;
188             }
189         }
190         if (this.state ==State. COMPLETED) {
191             for (final CharArrayBuffer buffer : this.headerBufs) {
192                 this.message.addHeader(this.lineParser.parseHeader(buffer));
193             }
194             return this.message;
195         }
196         return null;
197     }
198 
199 }