package org.deftserver.web.http; import java.net.InetAddress; import java.nio.ByteBuffer; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import org.deftserver.io.IOLoop; import org.deftserver.util.ArrayUtil; import org.deftserver.web.HttpVerb; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableMultimap; public class HttpRequest { private IOLoop ioLoop; private final String requestLine; private final HttpVerb method; private final String requestedPath; // correct name? private final String version; private Map<String, String> headers; private ImmutableMultimap<String, String> parameters; private String body; private boolean keepAlive; private InetAddress remoteHost; private InetAddress serverHost; private int remotePort; private int serverPort; /** Regex to parse HttpRequest Request Line */ public static final Pattern REQUEST_LINE_PATTERN = Pattern.compile(" ") ; /** Regex to parse out QueryString from HttpRequest */ public static final Pattern QUERY_STRING_PATTERN = Pattern.compile("\\?") ; /** Regex to parse out parameters from query string */ public static final Pattern PARAM_STRING_PATTERN = Pattern.compile("\\&|;"); //Delimiter is either & or ; /** Regex to parse out key/value pairs */ public static final Pattern KEY_VALUE_PATTERN = Pattern.compile("="); /** Regex to parse raw headers and body */ public static final Pattern RAW_VALUE_PATTERN = Pattern.compile("\\r\\n\\r\\n"); //TODO fix a better regexp for this /** Regex to parse raw headers from body */ public static final Pattern HEADERS_BODY_PATTERN = Pattern.compile("\\r\\n"); /** Regex to parse header name and value */ public static final Pattern HEADER_VALUE_PATTERN = Pattern.compile(": "); /** * Creates a new HttpRequest * @param requestLine The Http request text line * @param headers The Http request headers */ public HttpRequest(String requestLine, Map<String, String> headers) { this.requestLine = requestLine; String[] elements = REQUEST_LINE_PATTERN.split(requestLine); method = HttpVerb.valueOf(elements[0]); String[] pathFrags = QUERY_STRING_PATTERN.split(elements[1]); requestedPath = pathFrags[0]; version = elements[2]; this.headers = headers; body = null; initKeepAlive(); parameters = parseParameters(elements[1]); } /** * Creates a new HttpRequest * @param requestLine The Http request text line * @param headers The Http request headers * @param body The Http request posted body */ public HttpRequest(String requestLine, Map<String, String> headers, String body) { this(requestLine, headers); this.body = body; } public static HttpRequest of(ByteBuffer buffer) { try { String raw = new String(buffer.array(), 0, buffer.limit(), Charsets.ISO_8859_1); String[] headersAndBody = RAW_VALUE_PATTERN.split(raw); String[] headerFields = HEADERS_BODY_PATTERN.split(headersAndBody[0]); headerFields = ArrayUtil.dropFromEndWhile(headerFields, ""); String requestLine = headerFields[0]; Map<String, String> generalHeaders = new HashMap<String, String>(); for (int i = 1; i < headerFields.length; i++) { String[] header = HEADER_VALUE_PATTERN.split(headerFields[i]); generalHeaders.put(header[0].toLowerCase(), header[1]); } String body = ""; for (int i = 1; i < headersAndBody.length; ++i) { //First entry contains headers body += headersAndBody[i]; } if (requestLine.contains("POST")) { int contentLength = Integer.parseInt(generalHeaders.get("content-length")); if (contentLength > body.length()) { return new PartialHttpRequest(requestLine, generalHeaders, body); } } return new HttpRequest(requestLine, generalHeaders, body); } catch (Exception t) { return MalFormedHttpRequest.instance; } } public static HttpRequest continueParsing(ByteBuffer buffer, PartialHttpRequest unfinished) { String nextChunk = new String(buffer.array(), 0, buffer.limit(), Charsets.US_ASCII); unfinished.appendBody(nextChunk); int contentLength = Integer.parseInt(unfinished.getHeader("Content-Length")); if (contentLength > unfinished.getBody().length()) { return unfinished; } else { return new HttpRequest(unfinished.getRequestLine(), unfinished.getHeaders(), unfinished.getBody()); } } protected void setIOLoop(IOLoop ioLoop) { this.ioLoop = ioLoop; } public IOLoop getIOLoop() { return ioLoop; } public String getRequestLine() { return requestLine; } public String getRequestedPath() { return requestedPath; } public String getVersion() { return version; } public Map<String, String> getHeaders() { return Collections.unmodifiableMap(headers); } public String getHeader(String name) { return headers.get(name.toLowerCase()); } public HttpVerb getMethod() { return method; } /** * Returns the value of a request parameter as a String, or null if the parameter does not exist. * * You should only use this method when you are sure the parameter has only one value. If the parameter * might have more than one value, use getParameterValues(java.lang.String). * If you use this method with a multi-valued parameter, the value returned is equal to the first value in * the array returned by getParameterValues. */ public String getParameter(String name) { Collection<String> values = parameters.get(name); return values.isEmpty() ? null : values.iterator().next(); } public Map<String, Collection<String>> getParameters() { return parameters.asMap(); } public String getBody() { return body; } public InetAddress getRemoteHost() { return remoteHost; } public InetAddress getServerHost() { return serverHost; } public int getRemotePort() { return remotePort; } public int getServerPort() { return serverPort; } protected void setRemoteHost(InetAddress host) { remoteHost = host; } protected void setServerHost(InetAddress host) { serverHost = host; } protected void setRemotePort(int port) { remotePort = port; } protected void setServerPort(int port) { serverPort = port; } /** * Returns a collection of all values associated with the provided parameter. * If no values are found and empty collection is returned. */ public Collection<String> getParameterValues(String name) { return parameters.get(name); } public boolean isKeepAlive() { return keepAlive; } @Override public String toString() { String result = "METHOD: " + method + "\n"; result += "VERSION: " + version + "\n"; result += "PATH: " + requestedPath + "\n"; result += "--- HEADER --- \n"; for (String key : headers.keySet()) { String value = headers.get(key); result += key + ":" + value + "\n"; } result += "--- PARAMETERS --- \n"; for (String key : parameters.keySet()) { Collection<String> values = parameters.get(key); for (String value : values) { result += key + ":" + value + "\n"; } } return result; } private ImmutableMultimap<String, String> parseParameters(String requestLine) { ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.builder(); String[] str = QUERY_STRING_PATTERN.split(requestLine); //Parameters exist if (str.length > 1) { String[] paramArray = PARAM_STRING_PATTERN.split(str[1]); for (String keyValue : paramArray) { String[] keyValueArray = KEY_VALUE_PATTERN.split(keyValue); //We need to check if the parameter has a value associated with it. if (keyValueArray.length > 1) { builder.put(keyValueArray[0], keyValueArray[1]); //name, value } } } return builder.build(); } private void initKeepAlive() { String connection = getHeader("Connection"); if ("keep-alive".equalsIgnoreCase(connection)) { keepAlive = true; } else if ("close".equalsIgnoreCase(connection) || requestLine.contains("1.0")) { keepAlive = false; } else { keepAlive = true; } } }