blob: 57ee9025ec7c05ee9a59232d05a88e666bc481e3 [file] [log] [blame]
// 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() {}
}