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.nio.codecs; 029 030import java.io.IOException; 031import java.nio.channels.ReadableByteChannel; 032import java.util.ArrayList; 033import java.util.List; 034 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.message.BasicLineParser; 042import org.apache.http.message.LineParser; 043import org.apache.http.nio.NHttpMessageParser; 044import org.apache.http.nio.reactor.SessionInputBuffer; 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 {@link NHttpMessageParser} that serves as a base for all message 052 * parser implementations. 053 * 054 * @since 4.0 055 */ 056@SuppressWarnings("deprecation") 057public abstract class AbstractMessageParser<T extends HttpMessage> implements NHttpMessageParser<T> { 058 059 private final SessionInputBuffer sessionBuffer; 060 061 private static final int READ_HEAD_LINE = 0; 062 private static final int READ_HEADERS = 1; 063 private static final int COMPLETED = 2; 064 065 private int state; 066 private boolean endOfStream; 067 068 private T message; 069 private CharArrayBuffer lineBuf; 070 private final List<CharArrayBuffer> headerBufs; 071 072 protected final LineParser lineParser; 073 private final MessageConstraints constraints; 074 075 /** 076 * Creates an instance of this class. 077 * 078 * @param buffer the session input buffer. 079 * @param lineParser the line parser. 080 * @param params HTTP parameters. 081 * 082 * @deprecated (4.3) use 083 * {@link AbstractMessageParser#AbstractMessageParser(SessionInputBuffer, LineParser, 084 * MessageConstraints)} 085 */ 086 @Deprecated 087 public AbstractMessageParser( 088 final SessionInputBuffer buffer, 089 final LineParser lineParser, 090 final HttpParams params) { 091 super(); 092 Args.notNull(buffer, "Session input buffer"); 093 Args.notNull(params, "HTTP parameters"); 094 this.sessionBuffer = buffer; 095 this.state = READ_HEAD_LINE; 096 this.endOfStream = false; 097 this.headerBufs = new ArrayList<CharArrayBuffer>(); 098 this.constraints = HttpParamConfig.getMessageConstraints(params); 099 this.lineParser = (lineParser != null) ? lineParser : BasicLineParser.INSTANCE; 100 } 101 102 /** 103 * Creates an instance of AbstractMessageParser. 104 * 105 * @param buffer the session input buffer. 106 * @param lineParser the line parser. If {@code null} {@link BasicLineParser#INSTANCE} 107 * will be used. 108 * @param constraints Message constraints. If {@code null} 109 * {@link MessageConstraints#DEFAULT} will be used. 110 * 111 * @since 4.3 112 */ 113 public AbstractMessageParser( 114 final SessionInputBuffer buffer, 115 final LineParser lineParser, 116 final MessageConstraints constraints) { 117 super(); 118 this.sessionBuffer = Args.notNull(buffer, "Session input buffer"); 119 this.lineParser = lineParser != null ? lineParser : BasicLineParser.INSTANCE; 120 this.constraints = constraints != null ? constraints : MessageConstraints.DEFAULT; 121 this.headerBufs = new ArrayList<CharArrayBuffer>(); 122 this.state = READ_HEAD_LINE; 123 this.endOfStream = false; 124 } 125 126 @Override 127 public void reset() { 128 this.state = READ_HEAD_LINE; 129 this.endOfStream = false; 130 this.headerBufs.clear(); 131 this.message = null; 132 } 133 134 @Override 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 @Override 186 public T parse() throws IOException, HttpException { 187 while (this.state != COMPLETED) { 188 if (this.lineBuf == null) { 189 this.lineBuf = new CharArrayBuffer(64); 190 } else { 191 this.lineBuf.clear(); 192 } 193 final boolean lineComplete = this.sessionBuffer.readLine(this.lineBuf, this.endOfStream); 194 final int maxLineLen = this.constraints.getMaxLineLength(); 195 if (maxLineLen > 0 && 196 (this.lineBuf.length() > maxLineLen || 197 (!lineComplete && this.sessionBuffer.length() > maxLineLen))) { 198 throw new MessageConstraintException("Maximum line length limit exceeded"); 199 } 200 if (!lineComplete) { 201 break; 202 } 203 204 switch (this.state) { 205 case READ_HEAD_LINE: 206 try { 207 parseHeadLine(); 208 } catch (final ParseException px) { 209 throw new ProtocolException(px.getMessage(), px); 210 } 211 this.state = READ_HEADERS; 212 break; 213 case READ_HEADERS: 214 if (this.lineBuf.length() > 0) { 215 final int maxHeaderCount = this.constraints.getMaxHeaderCount(); 216 if (maxHeaderCount > 0 && headerBufs.size() >= maxHeaderCount) { 217 throw new MessageConstraintException("Maximum header count exceeded"); 218 } 219 220 parseHeader(); 221 } else { 222 this.state = COMPLETED; 223 } 224 break; 225 } 226 if (this.endOfStream && !this.sessionBuffer.hasData()) { 227 this.state = COMPLETED; 228 } 229 } 230 if (this.state == COMPLETED) { 231 for (final CharArrayBuffer buffer : this.headerBufs) { 232 try { 233 this.message.addHeader(lineParser.parseHeader(buffer)); 234 } catch (final ParseException ex) { 235 throw new ProtocolException(ex.getMessage(), ex); 236 } 237 } 238 return this.message; 239 } else { 240 return null; 241 } 242 } 243 244}