| // Copyright 2016 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.devtools.build.lib.bazel.repository.downloader; |
| |
| import com.google.common.base.Ascii; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** Utility class for parsing HTTP messages. */ |
| final class HttpParser { |
| |
| /** Exhausts request line and headers of HTTP request. */ |
| static void readHttpRequest(InputStream stream) throws IOException { |
| readHttpRequest(stream, new HashMap<String, String>()); |
| } |
| |
| /** |
| * Parses request line and headers of HTTP request. |
| * |
| * <p>This parser is correct and extremely lax. This implementation is Θ(n) and the stream should |
| * be buffered. All decoding is ISO-8859-1. A 1mB upper bound on memory is enforced. |
| * |
| * @throws IOException if reading failed or premature end of stream encountered |
| * @throws HttpParserError if 400 error should be sent to client and connection must be closed |
| */ |
| static void readHttpRequest(InputStream stream, Map<String, String> output) throws IOException { |
| StringBuilder builder = new StringBuilder(256); |
| State state = State.METHOD; |
| String key = ""; |
| int toto = 0; |
| while (true) { |
| int c = stream.read(); |
| if (c == -1) { |
| throw new IOException(); // RFC7230 § 3.4 |
| } |
| if (++toto == 1024 * 1024) { |
| throw new HttpParserError(); // RFC7230 § 3.2.5 |
| } |
| switch (state) { |
| case METHOD: |
| if (c == ' ') { |
| if (builder.length() == 0) { |
| throw new HttpParserError(); |
| } |
| output.put("x-method", builder.toString()); |
| builder.setLength(0); |
| state = State.URI; |
| } else if (c == '\r' || c == '\n') { |
| break; // RFC7230 § 3.5 |
| } else { |
| builder.append(Ascii.toUpperCase((char) c)); |
| } |
| break; |
| case URI: |
| if (c == ' ') { |
| if (builder.length() == 0) { |
| throw new HttpParserError(); |
| } |
| output.put("x-request-uri", builder.toString()); |
| builder.setLength(0); |
| state = State.VERSION; |
| } else { |
| builder.append((char) c); |
| } |
| break; |
| case VERSION: |
| if (c == '\r' || c == '\n') { |
| output.put("x-version", builder.toString()); |
| builder.setLength(0); |
| state = c == '\r' ? State.CR1 : State.LF1; |
| } else { |
| builder.append(Ascii.toUpperCase((char) c)); |
| } |
| break; |
| case CR1: |
| if (c == '\n') { |
| state = State.LF1; |
| break; |
| } |
| throw new HttpParserError(); |
| case LF1: |
| if (c == '\r') { |
| state = State.LF2; |
| break; |
| } else if (c == '\n') { |
| return; |
| } else if (c == ' ' || c == '\t') { |
| throw new HttpParserError("Line folding unacceptable"); // RFC7230 § 3.2.4 |
| } |
| state = State.HKEY; |
| // fall through |
| case HKEY: |
| if (c == ':') { |
| key = builder.toString(); |
| builder.setLength(0); |
| state = State.HSEP; |
| } else { |
| builder.append(Ascii.toLowerCase((char) c)); |
| } |
| break; |
| case HSEP: |
| if (c == ' ' || c == '\t') { |
| break; |
| } |
| state = State.HVAL; |
| // fall through |
| case HVAL: |
| if (c == '\r' || c == '\n') { |
| output.put(key, builder.toString()); |
| builder.setLength(0); |
| state = c == '\r' ? State.CR1 : State.LF1; |
| } else { |
| builder.append((char) c); |
| } |
| break; |
| case LF2: |
| if (c == '\n') { |
| return; |
| } |
| throw new HttpParserError(); |
| default: |
| throw new AssertionError(); |
| } |
| } |
| } |
| |
| static final class HttpParserError extends IOException { |
| HttpParserError() { |
| this("Malformed Request"); |
| } |
| |
| HttpParserError(String messageForClient) { |
| super(messageForClient); |
| } |
| } |
| |
| private enum State { METHOD, URI, VERSION, HKEY, HSEP, HVAL, CR1, LF1, LF2 } |
| |
| private HttpParser() {} |
| } |