All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.bottlecaps.webapp.server.HttpRequest Maven / Gradle / Ivy

package de.bottlecaps.webapp.server;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import com.sun.net.httpserver.HttpExchange;

import de.bottlecaps.webapp.Cookie;
import de.bottlecaps.webapp.MultiPart;
import de.bottlecaps.webapp.Request;

//TODO: header names case-insensitive

@SuppressWarnings("all")
public class HttpRequest implements Request
{
  private static final String MULTIPART_MIME_TYPE = "multipart/form-data; boundary=";
  private static final byte[] CR_LF = new byte[]{'\r', '\n'};
  private static final byte[] MINUS_MINUS = new byte[]{'-', '-'};

  private HttpExchange httpExchange;
  private String contentTypeHeader;
  private List parts;
  private Cookie[] cookies = null;

  public HttpRequest(HttpExchange httpExchange) throws Exception
  {
    this.httpExchange = httpExchange;

    List contentTypeHeaders = httpExchange.getRequestHeaders().get("Content-Type");
    contentTypeHeader = contentTypeHeaders == null || contentTypeHeaders.size() != 1
        ? null
        : contentTypeHeaders.get(0);
    if (contentTypeHeader != null)
    {
      if (contentTypeHeader.startsWith(MULTIPART_MIME_TYPE))
        getMultiParts();
      else
        getParameters();
    }
  }

  @Override
  public String getContextPath()
  {
    return "";
  }

  @Override
  public Collection getParts() throws IOException
  {
    return parts;
  }

  @Override
  public MultiPart getPart(String partName) throws IOException
  {
    return parts.stream()
        .filter(p -> partName.equals(p.getName()))
        .findFirst()
        .orElse(null);
  }

  @Override
  public String getCharacterEncoding()
  {
    String contentType = httpExchange.getRequestHeaders().getFirst("Content-Type");
    if (contentType != null)
    {
      String charset = "charset=";
      int charsetIndex = contentType.indexOf(charset);
      if (charsetIndex >= 0)
        return contentType.substring(charsetIndex + charset.length()).replace("\"", "").trim();
    }
    return null;
  }

  @Override
  public Enumeration getParameterNames()
  {
    return Collections.enumeration(getParameters().keySet());
  }

  @Override
  public String[] getParameterValues(String name)
  {
    List valueList = getParameters().get(name);
    return valueList == null ? null : valueList.toArray(new String[valueList.size()]);
  }

  private Map> parameters = null;
  private Map> getParameters()
  {
    if (parameters != null)
      return parameters;

    parameters = new HashMap<>();
    String encoding = getCharacterEncoding();
    if (encoding == null)
      encoding = StandardCharsets.UTF_8.name();
    String query = null;
    switch (httpExchange.getRequestMethod())
    {
    case "POST":
      if ("application/x-www-form-urlencoded".equals(contentTypeHeader))
      {
        try (Scanner scanner = new Scanner(httpExchange.getRequestBody(), encoding).useDelimiter("\\A"))
        {
          query = scanner.hasNext() ? scanner.next() : "";
        }
      }
    default:
      query = httpExchange.getRequestURI().getRawQuery();
      break;
    }
    if (query != null)
    {
      Arrays.stream(query.split("&"))
          .map(parameter ->
            {
              try
              {
                return URLDecoder.decode(parameter, StandardCharsets.UTF_8.name());
              }
              catch (UnsupportedEncodingException e)
              {
                throw new RuntimeException(e.getMessage(), e);
              }
            })
          .forEach(parameter ->
            {
              int equalsSignIndex = parameter.indexOf('=');
              String key = equalsSignIndex < 0 ? parameter : parameter.substring(0, equalsSignIndex);
              String value = equalsSignIndex < 0 ? null : parameter.substring(equalsSignIndex + 1);
              parameters.compute(key, (k, v) ->
                {
                  if (v == null)
                    v = new ArrayList<>();
                  v.add(value);
                  return v;
                });
            });
    }
    return parameters;
  }

  @Override
  public String getContentType()
  {
    return contentTypeHeader;
  }

  @Override
  public String getHeader(String name)
  {
    return httpExchange.getRequestHeaders().getFirst(name);
  }

  @Override
  public Object getMethod()
  {
    return httpExchange.getRequestMethod();
  }

  @Override
  public Cookie[] getCookies()
  {
    if (cookies == null)
    {
      List cookie = httpExchange
          .getRequestHeaders()
          .get("Cookie");
      if (cookie == null)
        cookies = new Cookie[0];
      else
        cookies = cookie
            .stream()
            .flatMap(v -> Arrays.stream(v.split("; ")))
            .map(v ->
              {
                String[] parts = v.split("=", -1);
                return new HttpCookie(parts[0], parts[1]);
              }
            )
            .toArray(Cookie[]::new);
    }
    return cookies;
  }

  public void getMultiParts() throws Exception
  {
    BufferedInputStream inputStream = new BufferedInputStream(httpExchange.getRequestBody());

    String boundary = contentTypeHeader.substring(MULTIPART_MIME_TYPE.length());
    byte[] delimiter = ("--" + boundary).getBytes(StandardCharsets.UTF_8);
    byte[] fragment = getFragment(inputStream, delimiter);
    if (fragment == null)
      throw new IllegalArgumentException();
    delimiter = ("\r\n--" + boundary).getBytes(StandardCharsets.UTF_8);

    parts = new ArrayList<>();
    for (;;)
    {
      fragment = getFragment(inputStream, CR_LF);
      if (fragment == null)
        throw new IllegalArgumentException();
      if (Arrays.equals(fragment, MINUS_MINUS))
        break;
      if (fragment.length != 0)
        throw new IllegalArgumentException();
      Map> partHeaders = collectHeaders(inputStream);
      fragment = getFragment(inputStream, delimiter);
      if (fragment == null)
        throw new IllegalArgumentException();
      parts.add(new HttpMultiPart(partHeaders, fragment));
    }

    fragment = getFragment(inputStream, CR_LF);
    if (fragment != null)
      throw new IllegalArgumentException();
  }

  private static Map> collectHeaders(BufferedInputStream inputStream) throws Exception
  {
    byte[] fragment;
    Map> headers = new LinkedHashMap<>();
    for (;;)
    {
      fragment = getFragment(inputStream, new byte[]{'\r', '\n'});
      if (fragment == null)
        throw new IllegalArgumentException();
      if (fragment.length == 0)
        break;
      String rawHeader = string(fragment);
      int index = rawHeader.indexOf(": ");
      if (index < 0)
        throw new IllegalArgumentException();
      headers.compute(rawHeader.substring(0, index), (k, v) ->
      {
        if (v == null)
          v = new ArrayList<>();
        v.add(rawHeader.substring(index + 2));
        return v;
      });
    }
    return headers;
  }

  private static String string(byte[] fragment)
  {
    return new String(fragment, StandardCharsets.UTF_8);
  }

  private static byte[] getFragment(BufferedInputStream inputStream, byte[] terminator) throws Exception
  {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    int nextMatch = 0;
    for (;;)
    {
      int nextByte = inputStream.read();
      if (nextByte == -1)
      {
        if (outputStream.toByteArray().length == 0)
          return null;
        else
          throw new IllegalArgumentException();
      }
      else if (nextByte == terminator[nextMatch])
      {
        if (++nextMatch == terminator.length)
          return outputStream.toByteArray();
        if (nextMatch == 1)
          inputStream.mark(terminator.length - 1);
      }
      else if (nextMatch == 0)
      {
        outputStream.write(nextByte);
      }
      else
      {
        outputStream.write(terminator[0]);
        inputStream.reset();
        nextMatch = 0;
      }
    }
  }

  @Override
  public String getPathInfo()
  {
    return httpExchange.getRequestURI().getPath();
  }

  @Override
  public String getRequestURI()
  {
    return httpExchange.getRequestURI().toString();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy