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