001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.impl.io; 029 030import java.io.IOException; 031import java.util.ArrayList; 032import java.util.List; 033 034import org.apache.http.Header; 035import org.apache.http.HttpException; 036import org.apache.http.HttpMessage; 037import org.apache.http.MessageConstraintException; 038import org.apache.http.ParseException; 039import org.apache.http.ProtocolException; 040import org.apache.http.config.MessageConstraints; 041import org.apache.http.io.HttpMessageParser; 042import org.apache.http.io.SessionInputBuffer; 043import org.apache.http.message.BasicLineParser; 044import org.apache.http.message.LineParser; 045import org.apache.http.params.HttpParamConfig; 046import org.apache.http.params.HttpParams; 047import org.apache.http.util.Args; 048import org.apache.http.util.CharArrayBuffer; 049 050/** 051 * Abstract base class for HTTP message parsers that obtain input from 052 * an instance of {@link SessionInputBuffer}. 053 * 054 * @since 4.0 055 */ 056@SuppressWarnings("deprecation") 057public abstract class AbstractMessageParser<T extends HttpMessage> implements HttpMessageParser<T> { 058 059 private static final int HEAD_LINE = 0; 060 private static final int HEADERS = 1; 061 062 private final SessionInputBuffer sessionBuffer; 063 private final MessageConstraints messageConstraints; 064 private final List<CharArrayBuffer> headerLines; 065 protected final LineParser lineParser; 066 067 private int state; 068 private T message; 069 070 /** 071 * Creates an instance of AbstractMessageParser. 072 * 073 * @param buffer the session input buffer. 074 * @param parser the line parser. 075 * @param params HTTP parameters. 076 * 077 * @deprecated (4.3) use {@link AbstractMessageParser#AbstractMessageParser(SessionInputBuffer, 078 * LineParser, MessageConstraints)} 079 */ 080 @Deprecated 081 public AbstractMessageParser( 082 final SessionInputBuffer buffer, 083 final LineParser parser, 084 final HttpParams params) { 085 super(); 086 Args.notNull(buffer, "Session input buffer"); 087 Args.notNull(params, "HTTP parameters"); 088 this.sessionBuffer = buffer; 089 this.messageConstraints = HttpParamConfig.getMessageConstraints(params); 090 this.lineParser = (parser != null) ? parser : BasicLineParser.INSTANCE; 091 this.headerLines = new ArrayList<CharArrayBuffer>(); 092 this.state = HEAD_LINE; 093 } 094 095 /** 096 * Creates new instance of AbstractMessageParser. 097 * 098 * @param buffer the session input buffer. 099 * @param lineParser the line parser. If {@code null} {@link BasicLineParser#INSTANCE} 100 * will be used. 101 * @param constraints the message constraints. If {@code null} 102 * {@link MessageConstraints#DEFAULT} will be used. 103 * 104 * @since 4.3 105 */ 106 public AbstractMessageParser( 107 final SessionInputBuffer buffer, 108 final LineParser lineParser, 109 final MessageConstraints constraints) { 110 super(); 111 this.sessionBuffer = Args.notNull(buffer, "Session input buffer"); 112 this.lineParser = lineParser != null ? lineParser : BasicLineParser.INSTANCE; 113 this.messageConstraints = constraints != null ? constraints : MessageConstraints.DEFAULT; 114 this.headerLines = new ArrayList<CharArrayBuffer>(); 115 this.state = HEAD_LINE; 116 } 117 118 /** 119 * Parses HTTP headers from the data receiver stream according to the generic 120 * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3. 121 * 122 * @param inbuffer Session input buffer 123 * @param maxHeaderCount maximum number of headers allowed. If the number 124 * of headers received from the data stream exceeds maxCount value, an 125 * IOException will be thrown. Setting this parameter to a negative value 126 * or zero will disable the check. 127 * @param maxLineLen maximum number of characters for a header line, 128 * including the continuation lines. Setting this parameter to a negative 129 * value or zero will disable the check. 130 * @return array of HTTP headers 131 * @param parser line parser to use. Can be {@code null}, in which case 132 * the default implementation of this interface will be used. 133 * 134 * @throws IOException in case of an I/O error 135 * @throws HttpException in case of HTTP protocol violation 136 */ 137 public static Header[] parseHeaders( 138 final SessionInputBuffer inbuffer, 139 final int maxHeaderCount, 140 final int maxLineLen, 141 final LineParser parser) throws HttpException, IOException { 142 final List<CharArrayBuffer> headerLines = new ArrayList<CharArrayBuffer>(); 143 return parseHeaders(inbuffer, maxHeaderCount, maxLineLen, 144 parser != null ? parser : BasicLineParser.INSTANCE, 145 headerLines); 146 } 147 148 /** 149 * Parses HTTP headers from the data receiver stream according to the generic 150 * format as given in Section 3.1 of RFC 822, RFC-2616 Section 4 and 19.3. 151 * 152 * @param inbuffer Session input buffer 153 * @param maxHeaderCount maximum number of headers allowed. If the number 154 * of headers received from the data stream exceeds maxCount value, an 155 * IOException will be thrown. Setting this parameter to a negative value 156 * or zero will disable the check. 157 * @param maxLineLen maximum number of characters for a header line, 158 * including the continuation lines. Setting this parameter to a negative 159 * value or zero will disable the check. 160 * @param parser line parser to use. 161 * @param headerLines List of header lines. This list will be used to store 162 * intermediate results. This makes it possible to resume parsing of 163 * headers in case of a {@link java.io.InterruptedIOException}. 164 * 165 * @return array of HTTP headers 166 * 167 * @throws IOException in case of an I/O error 168 * @throws HttpException in case of HTTP protocol violation 169 * 170 * @since 4.1 171 */ 172 public static Header[] parseHeaders( 173 final SessionInputBuffer inbuffer, 174 final int maxHeaderCount, 175 final int maxLineLen, 176 final LineParser parser, 177 final List<CharArrayBuffer> headerLines) throws HttpException, IOException { 178 Args.notNull(inbuffer, "Session input buffer"); 179 Args.notNull(parser, "Line parser"); 180 Args.notNull(headerLines, "Header line list"); 181 182 CharArrayBuffer current = null; 183 CharArrayBuffer previous = null; 184 for (;;) { 185 if (current == null) { 186 current = new CharArrayBuffer(64); 187 } else { 188 current.clear(); 189 } 190 final int l = inbuffer.readLine(current); 191 if (l == -1 || current.length() < 1) { 192 break; 193 } 194 // Parse the header name and value 195 // Check for folded headers first 196 // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2 197 // discussion on folded headers 198 if ((current.charAt(0) == ' ' || current.charAt(0) == '\t') && previous != null) { 199 // we have continuation folded header 200 // so append value 201 int i = 0; 202 while (i < current.length()) { 203 final char ch = current.charAt(i); 204 if (ch != ' ' && ch != '\t') { 205 break; 206 } 207 i++; 208 } 209 if (maxLineLen > 0 210 && previous.length() + 1 + current.length() - i > maxLineLen) { 211 throw new MessageConstraintException("Maximum line length limit exceeded"); 212 } 213 previous.append(' '); 214 previous.append(current, i, current.length() - i); 215 } else { 216 headerLines.add(current); 217 previous = current; 218 current = null; 219 } 220 if (maxHeaderCount > 0 && headerLines.size() >= maxHeaderCount) { 221 throw new MessageConstraintException("Maximum header count exceeded"); 222 } 223 } 224 final Header[] headers = new Header[headerLines.size()]; 225 for (int i = 0; i < headerLines.size(); i++) { 226 final CharArrayBuffer buffer = headerLines.get(i); 227 try { 228 headers[i] = parser.parseHeader(buffer); 229 } catch (final ParseException ex) { 230 throw new ProtocolException(ex.getMessage()); 231 } 232 } 233 return headers; 234 } 235 236 /** 237 * Subclasses must override this method to generate an instance of 238 * {@link HttpMessage} based on the initial input from the session buffer. 239 * <p> 240 * Usually this method is expected to read just the very first line or 241 * the very first valid from the data stream and based on the input generate 242 * an appropriate instance of {@link HttpMessage}. 243 * 244 * @param sessionBuffer the session input buffer. 245 * @return HTTP message based on the input from the session buffer. 246 * @throws IOException in case of an I/O error. 247 * @throws HttpException in case of HTTP protocol violation. 248 * @throws ParseException in case of a parse error. 249 */ 250 protected abstract T parseHead(SessionInputBuffer sessionBuffer) 251 throws IOException, HttpException, ParseException; 252 253 @Override 254 public T parse() throws IOException, HttpException { 255 final int st = this.state; 256 switch (st) { 257 case HEAD_LINE: 258 try { 259 this.message = parseHead(this.sessionBuffer); 260 } catch (final ParseException px) { 261 throw new ProtocolException(px.getMessage(), px); 262 } 263 this.state = HEADERS; 264 //$FALL-THROUGH$ 265 case HEADERS: 266 final Header[] headers = AbstractMessageParser.parseHeaders( 267 this.sessionBuffer, 268 this.messageConstraints.getMaxHeaderCount(), 269 this.messageConstraints.getMaxLineLength(), 270 this.lineParser, 271 this.headerLines); 272 this.message.setHeaders(headers); 273 final T result = this.message; 274 this.message = null; 275 this.headerLines.clear(); 276 this.state = HEAD_LINE; 277 return result; 278 default: 279 throw new IllegalStateException("Inconsistent parser state"); 280 } 281 } 282 283}