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}