001/* 002 * This file is part of the JDrupes non-blocking HTTP Codec 003 * Copyright (C) 2016, 2017 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Lesser General Public License as published 007 * by the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 013 * License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.httpcodec.protocols.http.server; 020 021import java.net.URI; 022import java.net.URISyntaxException; 023import java.nio.Buffer; 024import java.nio.ByteBuffer; 025import java.util.List; 026import java.util.Optional; 027import java.util.regex.Matcher; 028import java.util.regex.Pattern; 029 030import org.jdrupes.httpcodec.ProtocolException; 031import static org.jdrupes.httpcodec.protocols.http.HttpConstants.*; 032import org.jdrupes.httpcodec.protocols.http.HttpDecoder; 033import org.jdrupes.httpcodec.protocols.http.HttpField; 034import org.jdrupes.httpcodec.protocols.http.HttpProtocolException; 035import org.jdrupes.httpcodec.protocols.http.HttpRequest; 036import org.jdrupes.httpcodec.protocols.http.HttpResponse; 037import org.jdrupes.httpcodec.types.Converters; 038import org.jdrupes.httpcodec.types.StringList; 039 040/** 041 * A decoder for HTTP requests. It accepts data from a sequence of 042 * {@link ByteBuffer}s and decodes them into {@link HttpRequest}s 043 * and their (optional) payload. 044 * 045 * ![HttpRequestDecoder](httprequestdecoder.svg) 046 * 047 * @startuml httprequestdecoder.svg 048 * class HttpRequestDecoder { 049 * +Result decode(ByteBuffer in, Buffer out, boolean endOfInput) 050 * } 051 * 052 * class HttpDecoder<T extends HttpMessageHeader, R extends HttpMessageHeader> { 053 * } 054 * 055 * HttpDecoder <|-- HttpRequestDecoder: <<bind>> <T -> HttpRequest, R -> HttpResponse> 056 * @enduml 057 */ 058public class HttpRequestDecoder 059 extends HttpDecoder<HttpRequest, HttpResponse> { 060 061 // RFC 7230 3.1.1 062 private static final Pattern requestLinePatter = Pattern 063 .compile("^(" + TOKEN_REGEXP + ")" + SP + "([^ \\t]+)" + SP + "(" 064 + HTTP_VERSION + ")$"); 065 private final Result.Factory resultFactory = new Result.Factory(this); 066 067 private boolean reportHeaderReceived = false; 068 069 /* (non-Javadoc) 070 * @see org.jdrupes.httpcodec.protocols.http.HttpDecoder#resultFactory() 071 */ 072 @Override 073 protected Result.Factory resultFactory() { 074 return resultFactory; 075 } 076 077 /* (non-Javadoc) 078 * @see org.jdrupes.httpcodec.Decoder#decoding() 079 */ 080 @Override 081 public Class<HttpRequest> decoding() { 082 return HttpRequest.class; 083 } 084 085 /* (non-Javadoc) 086 * @see org.jdrupes.httpcodec.HttpDecoder#decode(java.nio.ByteBuffer) 087 */ 088 @Override 089 public Result decode(ByteBuffer in, Buffer out, boolean endOfInput) { 090 try { 091 return (Result)super.decode(in, out, endOfInput); 092 } catch (HttpProtocolException e) { 093 HttpResponse response = new HttpResponse(e.httpVersion(), 094 e.statusCode(), e.reasonPhrase(), false); 095 response.setField(new HttpField<>(HttpField.CONNECTION, 096 new StringList("close"), Converters.STRING_LIST)); 097 return resultFactory().newResult( 098 false, false, false, response, true); 099 } catch (ProtocolException e) { 100 HttpResponse response = new HttpResponse(HttpProtocol.HTTP_1_1, 101 HttpStatus.INTERNAL_SERVER_ERROR.statusCode(), 102 e.getMessage(), false); 103 response.setField(new HttpField<>(HttpField.CONNECTION, 104 new StringList("close"), Converters.STRING_LIST)); 105 return resultFactory().newResult(false, false, false, response, true); 106 } 107 } 108 109 /** 110 * Checks whether the first line of a message is a valid request. 111 * If so, create a new request message object with basic information, else 112 * throw an exception. 113 * <P> 114 * Called by the base class when a first line is received. 115 * 116 * @param startLine the first line 117 * @throws HttpProtocolException if the line is not a correct request line 118 */ 119 @Override 120 protected HttpRequest newMessage(String startLine) 121 throws HttpProtocolException { 122 Matcher requestMatcher = requestLinePatter.matcher(startLine); 123 if (!requestMatcher.matches()) { 124 // RFC 7230 3.1.1 125 throw new HttpProtocolException(protocolVersion, 126 HttpStatus.BAD_REQUEST.statusCode(), 127 "Illegal request line"); 128 } 129 String httpVersion = requestMatcher.group(3); 130 boolean found = false; 131 for (HttpProtocol v : HttpProtocol.values()) { 132 if (v.toString().equals(httpVersion)) { 133 protocolVersion = v; 134 found = true; 135 } 136 } 137 if (!found) { 138 throw new HttpProtocolException(HttpProtocol.HTTP_1_1, 139 HttpStatus.HTTP_VERSION_NOT_SUPPORTED); 140 } 141 String method = requestMatcher.group(1); 142 String uriGroup = requestMatcher.group(2); 143 URI uri = null; 144 if ("*".equals(uriGroup)) { 145 uri = HttpRequest.ASTERISK_REQUEST; 146 } else { 147 try { 148 uri = new URI(uriGroup); 149 } catch (URISyntaxException e) { 150 throw new HttpProtocolException(protocolVersion, 151 HttpStatus.BAD_REQUEST.statusCode(), e.getMessage()); 152 } 153 } 154 HttpRequest request = new HttpRequest( 155 method, uri, protocolVersion, false); 156 HttpResponse response = (new HttpResponse(protocolVersion, 157 HttpStatus.NOT_IMPLEMENTED, false)).setRequest(request); 158 return request.setResponse(response); 159 } 160 161 /* (non-Javadoc) 162 * @see org.jdrupes.httpcodec.HttpDecoder#headerReceived(org.jdrupes.httpcodec.HttpMessage) 163 */ 164 @Override 165 protected BodyMode headerReceived(HttpRequest message) 166 throws HttpProtocolException { 167 reportHeaderReceived = true; 168 // Handle field of special interest 169 Optional<HttpField<String>> host = message.findField( 170 HttpField.HOST, Converters.STRING); 171 if (host.isPresent()) { 172 try { 173 URI parsed = new URI("http://" + host.get().value()); 174 message.setHostAndPort(parsed.getHost(), parsed.getPort()); 175 } catch (URISyntaxException e) { 176 throw new HttpProtocolException(protocolVersion, 177 HttpStatus.BAD_REQUEST.statusCode(), 178 "Invalid Host port."); 179 } 180 } else { 181 // RFC 7230 5.4. 182 if (message.protocol().compareTo(HttpProtocol.HTTP_1_1) >= 0) { 183 throw new HttpProtocolException(protocolVersion, 184 HttpStatus.BAD_REQUEST.statusCode(), 185 "HTTP 1.1 request must have a Host field."); 186 } 187 } 188 if (message.findField(HttpField.CONNECTION, 189 Converters.STRING_LIST).map(h -> h.value()) 190 .map(f -> f.containsIgnoreCase("close")).orElse(false)) { 191 // RFC 7230 6.6. 192 message.response().get().setField(new HttpField<>( 193 HttpField.CONNECTION, new StringList("close"), 194 Converters.STRING_LIST)); 195 } 196 197 // Find out about body 198 Optional<HttpField<StringList>> transEncs = message.findField( 199 HttpField.TRANSFER_ENCODING, Converters.STRING_LIST); 200 if (transEncs.isPresent()) { 201 List<String> tecs = transEncs.get().value(); 202 // RFC 7230 3.3.1, currently only chunked is supported 203 if (tecs.stream().anyMatch(s -> !s.equalsIgnoreCase( 204 TransferCoding.CHUNKED.toString()))) { 205 throw new HttpProtocolException(protocolVersion, 206 HttpStatus.NOT_IMPLEMENTED); 207 } 208 // RFC 7230 3.3.3 (3.) 209 if (tecs.size() > 0 && tecs.get(tecs.size() - 1) 210 .equalsIgnoreCase(TransferCoding.CHUNKED.toString())) { 211 return BodyMode.CHUNKED; 212 } else { 213 throw new HttpProtocolException(protocolVersion, 214 HttpStatus.BAD_REQUEST); 215 } 216 } 217 // RFC 7230 3.3.3 (5.) 218 if (message.fields().containsKey(HttpField.CONTENT_LENGTH)) { 219 return BodyMode.LENGTH; 220 } 221 // RFC 7230 3.3.3 (6.) 222 return BodyMode.NO_BODY; 223 } 224 225 /** 226 * Results from {@link HttpRequestDecoder} add no additional 227 * information to 228 * {@link org.jdrupes.httpcodec.protocols.http.HttpDecoder.Result}. This 229 * class just provides a factory for creating concrete results. 230 * 231 * The class is declared abstract to promote the usage of the factory 232 * method. 233 */ 234 public abstract static class Result 235 extends HttpDecoder.Result<HttpResponse> { 236 237 /** 238 * Creates a new result. 239 * @param overflow 240 * {@code true} if the data didn't fit in the out buffer 241 * @param underflow 242 * {@code true} if more data is expected 243 * @param headerCompleted 244 * {@code true} if the header has completely been decoded 245 * @param response 246 * a response to send due to an error 247 * @param responseOnly 248 * if the result includes a response this flag indicates that 249 * no further processing besides sending the response is 250 * required 251 */ 252 public Result(boolean overflow, boolean underflow, 253 boolean headerCompleted, HttpResponse response, 254 boolean responseOnly) { 255 super(overflow, underflow, false, headerCompleted, response, 256 responseOnly); 257 } 258 259 protected static class Factory 260 extends HttpDecoder.Result.Factory<HttpResponse> { 261 262 private HttpRequestDecoder decoder; 263 264 /** 265 * Creates a new factory for the given decoder. 266 * 267 * @param decoder the decoder 268 */ 269 protected Factory(HttpRequestDecoder decoder) { 270 super(); 271 this.decoder = decoder; 272 } 273 274 /** 275 * Create a new result. 276 * 277 * @param overflow 278 * {@code true} if the data didn't fit in the out buffer 279 * @param underflow 280 * {@code true} if more data is expected 281 * @param headerCompleted 282 * {@code true} if the header has completely been decoded 283 * @param response 284 * a response to send due to an error 285 * @param responseOnly 286 * if the result includes a response this flag indicates 287 * that no further processing besides sending the 288 * response is required 289 * @return the result 290 */ 291 public Result newResult(boolean overflow, boolean underflow, 292 boolean headerCompleted, HttpResponse response, 293 boolean responseOnly) { 294 return new Result(overflow, underflow, 295 headerCompleted, response, responseOnly) { 296 }; 297 } 298 299 /** 300 * Overrides the base interface's factory method in order to make 301 * it return the extended return type. As the {@link HttpRequestDecoder} 302 * does not know about a response, this implementation always 303 * returns a result without one. This may be a preliminary result 304 * and replaced in {@link HttpRequestDecoder#decode(ByteBuffer, Buffer, boolean)}. 305 * 306 * @param overflow 307 * {@code true} if the data didn't fit in the out buffer 308 * @param underflow 309 * {@code true} if more data is expected 310 */ 311 @Override 312 protected Result newResult( 313 boolean overflow, boolean underflow) { 314 Result result = new Result(overflow, underflow, 315 decoder.reportHeaderReceived, null, false) { 316 }; 317 decoder.reportHeaderReceived = false; 318 return result; 319 } 320 } 321 } 322}