Package com.google.opengse.core

Source Code of com.google.opengse.core.HttpRequestImpl$QueryStringParameterSet

// Copyright 2002-2006 Google Inc.
//
// 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.opengse.core;

import com.google.opengse.ConnectionInformation;
import com.google.opengse.GSEConstants;
import com.google.opengse.HttpRequest;
import com.google.opengse.RequestUtils;
import com.google.opengse.ServletEngineConfiguration;
import com.google.opengse.httputil.AcceptHeader;
import com.google.opengse.httputil.ChunkHeader;
import com.google.opengse.httputil.ContentType;
import com.google.opengse.httputil.FormUrlDecoder;
import com.google.opengse.httputil.Locales;
import com.google.opengse.httputil.ParamMap;
import com.google.opengse.httputil.Range;
import com.google.opengse.iobuffer.IOBuffer;
import com.google.opengse.iobuffer.IOBufferInputStream;
import com.google.opengse.iobuffer.IOBufferReader;
import com.google.opengse.util.IteratorEnumeration;
import com.google.opengse.util.string.StringUtil;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;

import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;

/**
* Implements the <code>HttpServletRequestSubset</code> interface.
*
* @author Peter Mattis
* @author Spencer Kimball
*/
final class HttpRequestImpl implements HttpRequest, ConnectionInformation {

  private static final Logger LOGGER
      = Logger.getLogger(HttpRequestImpl.class.getName());

  private static final int READLINE_LIMIT = 8192;

  private static final Locale DEFAULT_LOCALE = Locale.getDefault();

  // lazily initialized set of GET parameters
  private ParameterSet queryStringParams = null;

  // container for http method and version
  private RequestContext requestContext = new RequestContext();

  // Tuning parameters for secure session ids.
  // @TODO(pkr, 1/2007) consider adding methods to make these settable
  // from application level.

  // A simple regex that validates host name is in roughly canonical format.
  private static final Pattern CANONICAL_HOST_PATTERN = Pattern.compile(
      "^([a-zA-Z0-9][-_a-zA-Z0-9]*)(\\.[a-zA-Z0-9][-_a-zA-Z0-9]*)*\\.?$");

  /**
   * The connection that is servicing this request
   */
  protected final HttpConnection conn_;
  protected final HttpServer server_;


  /**
   * The content type structure. Used in HttpMultipartRequest subclass to get
   * the content boundary parameter
   */
  protected ContentType ctype_ = null;

  /**
   * Private variables
   */
  private ParamMap parsed_params_ = null;
  private String char_encoding_ = null;
  private String content_type_ = null;
  private ArrayList<Cookie> cookies_ = null;
  private List<Locale> locales_ = null;
  private List<AcceptHeader> codings_ = null;
  private List<AcceptHeader> charsets_ = null;
  private ChunkHeader chunk_header_ = null;
  private IOBuffer post_body_ = null;
  private IOBufferInputStream input_stream_ = null;
  private BufferedReader input_reader_ = null;
  private final ServletEngineConfiguration config;

  /**
   * Constructor for normal usage.
   *
   * NOTE: this constructor is no longer public. If your code has "new
   * HttpRequest(null)" please change it to use
   * "new com.google.opengse.test.TestHttpRequest()" instead.
   */
  protected HttpRequestImpl(HttpConnection conn) {
    this.conn_ = conn;
    this.server_ = conn.server_;
    config = conn.getConfiguration();
  }


  /**
   * Parse an HTTP request.
   */
  boolean parse(final ByteArrayOutputStream baos, final IOBuffer buf)
      throws IOException {
    return RequestContextFactory.parse(requestContext, baos, buf);
  }

  void setURI(String uri) {
    requestContext.setURI(uri);
    this.parsed_params_ = null// Query string may have changed.
  }

  URI getURI() {
    return requestContext.getURI();
  }

  int _getMajorVersion() {
    return requestContext.getMajorVersion();
  }

  int _getMinorVersion() {
    return requestContext.getMinorVersion();
  }

  /**
   * Returns the parsed "Range" header as a
   * {@link com.google.opengse.httputil.Range} object, or <code>null</code> if
   * no "Range" header exists. This method is not part of the
   * HttpServletRequest interface. Servlets that intend to use
   * the Range header directly must cast their request object first.
   *
   * @return the parsed range specifcation if a "Range" header was supplied with
   *         the request; else <code>null</code>.
   */
  Range _getRange() {
    final String range = getHeader("Range");
    if (range == null) {
      return null;
    } else {
      return Range.parse(range);
    }
  }

  /**
   * Returns the host header value if it exists. If not, the default server name
   * that the server was configured with is used. The format of this value is
   * host:port, though the :port suffix is only reported for non-standard
   * ports.<p>
   *
   * This method reports the host that the client browser is accessing, which is
   * not always the actual DNS name of the GSE server. In production, this is
   * rarely true. The client helpfully reports the server it is accessing via
   * the HTTP/1.1 Host header. For HTTP/1.0 clients, the default for the server
   * is used as the fallback.
   *
   * @return the host:port pair (the :port is optional)
   */
  private String getHost() {
    String host = getHeader("Host");
    if (host == null) {
      // use the configured server name if host is not present
      host = getNameFromServer();
    }
    return host;
  }

  protected String getNameFromServer() {
    return (server_ == null) ? null : server_.getName();
  }


  /**
   * Can we perform keep-alive on this request?
   */
  boolean _canKeepAlive() {
    // We only perform keep-alive on HTTP/1.0 and HTTP/1.1 requests
    if (requestContext.getMajorVersion() != 1) {
      return false;
    }

    if (requestContext.getMethod() == GSEConstants.POST ||
        requestContext.getMethod() == GSEConstants.PUT) {
      // We need a valid content length for POST & PUT keep-alives
      if (getHeader("Content-Length") == null) {
        return false;
      }
    } else if ((requestContext.getMethod() != GSEConstants.OPTIONS)
        && (requestContext.getMethod() != GSEConstants.GET)
        && (requestContext.getMethod() != GSEConstants.HEAD)
        && (requestContext.getMethod() != GSEConstants.DELETE)
        && (requestContext.getMethod() != GSEConstants.TRACE)
        && (requestContext.getMethod() != GSEConstants.CONNECT)) {
      // Why the redundant test above?
      return false;
    }

    if (requestContext.getMinorVersion() == 0) {
      // HTTP/1.0 defaults to closing connections
      return requestContext.getHeaders().headerHasValue("Connection",
          "keep-alive");
    } else if (requestContext.getMinorVersion() == 1) {
      // HTTP/1.1 defaults to keeping connections alive
      return !requestContext.getHeaders().headerHasValue("Connection", "close");
    }
    return false;
  }

  void print(PrintWriter writer) {
    writer.print(requestContext.getMethod() + " ");
    if (requestContext.getURI() != null) {
      writer.print(requestContext.getURI().toASCIIString());
    } else {
      writer.print("<null>");
    }
    if (requestContext.getMajorVersion() >= 1) {
      writer.print(" HTTP/" + requestContext.getMajorVersion() + "."
          + requestContext.getMinorVersion() + "\r\n");
      requestContext.getHeaders().print(writer);
    } else {
      writer.print("\r\n\r\n");
    }
    if (!config.dontPrintPostBody()) {
      writer.print(getPostBody());
    }
  }

  void writeIOBuffer(IOBuffer buf) throws IOException {
    // write the method and headers
    buf.writeBytes((requestContext.getMethod() + " ").getBytes());
    if (requestContext.getURI() != null) {
      buf.writeBytes(requestContext.getURI().toASCIIString().getBytes());
    } else {
      buf.writeBytes("<null>".getBytes());
    }
    if (requestContext.getMajorVersion() >= 1) {
      buf.writeBytes((" HTTP/" + requestContext.getMajorVersion() + "."
          + requestContext.getMinorVersion() + "\r\n").getBytes());
      requestContext.getHeaders().writeIOBuffer(buf);     // write headers
    } else {
      buf.writeBytes("\r\n\r\n".getBytes());
    }

    // write the contents of the post body (if applicable) without
    // consuming the post body
    if (post_body_ != null) {
      post_body_.flush();
      buf.append(post_body_);
    }
  }

  private String getCharacterEncoding() {
    // WARNING: Do NOT call this function if the return value is used
    //          to set the encoding scheme of an i/o buffer or query string
    //          from the incoming request for decoding purpose.
    //          Use getInternalCharacterEncoding() instead.
    getContentType();
    return char_encoding_;
  }

  private String getContentType() {
    String hdr = getHeader("Content-Type");
    if (ctype_ == null) {
      ctype_ = ContentType.parse(hdr);
      content_type_ = ctype_.getType(null);
      // use the encoding specified in the content type only if not already set
      if (char_encoding_ == null) {
        char_encoding_ = ctype_.getParameter(
            "charset", null);
      }
    }
    return content_type_;
  }

  /**
   * Returns a copy of the i/o buffer containing the post body.
   */
  private IOBuffer getPostBody() {
    if (post_body_ != null) {
      return new IOBuffer(post_body_);
    } else {
      return new IOBuffer();
    }
  }

  /**
   * Sets the post body i/o buffer to the provided string
   */
  void setPostBody(final String body)
      throws CharacterCodingException, IOException {
    _setPostBody(new IOBuffer(body, "US-ASCII"), body.length());
  }

  /**
   * Sets the post body i/o buffer to a copy of the provided i/o buffer. Any
   * parameters in the given post body will be parsed the next time parameters
   * are requested from this object.
   */
  void _setPostBody(final IOBuffer buf, final int length) throws IOException {
    post_body_ = new IOBuffer();
    buf.flush();
    post_body_.transfer(buf, length);
    post_body_.flush();
    parsed_params_ = null// Reparse the parameters when needed.
  }

  /**
   * Parses (or continues parsing) a chunked transfer-encoded request body
   * according to RFC 2616, section 3.6.1. This method should be called
   * repeatedly, as new data is available in buf, until it returns
   * <code>true</code>.
   *
   * @param line_buf a buffer to hold the contents of a chunk header between
   *                 calls to this method, for cases where the available bytes
   *                 in buf are not sufficient to hold the entire line.
   * @param buf      iobuffer containing bytes to parse.
   * @return <code>true</code> if the parsing is complete.
   * @throws java.io.IOException in case of badly formed input.
   */
  boolean _parseChunkedBody(
      final ByteArrayOutputStream line_buf, final IOBuffer buf)
      throws IOException {

    while (true) {
      // If a chunk header has been parsed, attempt to read chunk data.
      if (chunk_header_ != null) {
        final int chunkSize = chunk_header_.getChunkSize();
        // We check that we have sufficient bytes in the input buffer to
        // satisfy the size reported with the chunk header plus the trailing
        // CRLF.
        if (buf.availableBytes() >= chunkSize + 2) {
          // As long as this isn't the last chunk (0 length), transfer bytes.
          if (chunkSize > 0) {
            if (post_body_ == null) {
              post_body_ = new IOBuffer();
            }
            post_body_.transfer(buf, chunkSize);
          }
          // Pull off trailing CRLF.
          if (!buf.readLine(line_buf, READLINE_LIMIT)) {
            throw new IOException("chunk-data not succeeded by CRLF");
          } else if (line_buf.size() > 2) {
            throw new IOException("extra characters found in data chunk: " +
                line_buf.toString());
          }
          chunk_header_ = null;
          // We're done if this was the last chunk (chunk_size == 0).
          if (chunkSize == 0) {
            // Set the content length header so request forwarding will work.
            requestContext.getHeaders().removeHeader("Transfer-Encoding");
            requestContext.getHeaders().setHeader("Content-Length",
                    Integer.toString(post_body_.availableBytes()));

            post_body_.flush();
            return true;
          }
        } else {
          return false;
        }
      } else if (buf.readLine(line_buf, READLINE_LIMIT)) {
        // We've managed to read the next chunk header; parse it.
        chunk_header_ =
            ChunkHeader.parse(line_buf.toString("ISO-8859-1").trim());
        line_buf.reset();
      } else {
        return false;
      }
    }
  }

  /**
   * Test whether or not the body of this request is currently gzipped. If so,
   * then the request may be unzipped by calling
   * {@link #_decodeGzippedRequest()}, after which this method will return
   * false.
   */
  boolean _isGzippedRequest() {
    return GSEConstants.CONTENT_ENCODING_GZIP.equals(
        this.getHeader("Content-Encoding"));
  }

  /**
   * When content-encoding indicates the request is gzipped, then decode the
   * post-body.  The post_body_ IOBuffer is modified in place.
   *
   * We also strip the Content-Encoding:gzip header from the request and adjust
   * the size of the content, to prevent confusion in the servlet and make it
   * possible to forward this connection without recompression.
   */
  void _decodeGzippedRequest() throws IOException {
    final String contentEncoding = getHeader("Content-Encoding");
    if (!GSEConstants.CONTENT_ENCODING_GZIP.equals(contentEncoding)) {
      LOGGER.log(Level.WARNING, "cannot unzip body when content-encoding " +
          "is not 'gzip'; Content-Encoding: " +
          (contentEncoding == null ? "null" : contentEncoding));
      return;
    }

    // wrap post body in an inflator stream
    post_body_.flush();
    InputStream inflator_input_stream =
        new GZIPInputStream(new IOBufferInputStream(post_body_), 1024);

    // decompress by writing to a new IO buffer, update body
    IOBuffer inflatedBodyBuffer = new IOBuffer();
    final byte[] byteBuffer = new byte[1024];
    int numBytes;
    while (-1 != (numBytes = inflator_input_stream.read(byteBuffer))) {
      inflatedBodyBuffer.writeBytes(byteBuffer, 0, numBytes);
    }
    inflatedBodyBuffer.flush();

    int inflated_body_length = inflatedBodyBuffer.totalBytes();
    _setPostBody(inflatedBodyBuffer, inflated_body_length);

    // Remove content encoding, set new length, so request forwarding will work
    requestContext.getHeaders().removeHeader("Content-Encoding");
    requestContext.getHeaders().setIntHeader("Content-Length",
        inflated_body_length);
  }

  /**
   * Gives raw byte access to post body. Unlike getReader(), the byte stream is
   * not decoded into characters.
   *
   * @return input stream of the post body in the request.
   * @throws IllegalStateException if {@link #getReader()} has been previously
   *                               called.
   */
  public ServletInputStream getInputStream() {
    if (input_reader_ != null) {
      throw new IllegalStateException("getReader() called previously");
    }
    if (input_stream_ == null) {
      input_stream_ = new IOBufferInputStream(getPostBody());
    }
    return input_stream_;
  }

  /**
   * Called after getInputStream(), to allow body of request to be re-read by
   * throwing away the existing input_stream_. One use case is to modify the
   * bytes in post body using a new InputStream from getInputStream(). Then
   * replace post body with modified body using setPostBody(). But in order to
   * re-parse the new post body, we must call resetInputStream().
   */
  void resetInputStream() {
    input_stream_ = null;
  }

  public Locale getLocale() {
    Enumeration<Locale> e = getLocales();
    if (e.hasMoreElements()) {
      return e.nextElement();
    }
    return DEFAULT_LOCALE;
  }

  public Enumeration<Locale> getLocales() {
    if (locales_ == null) {
      locales_ = Locales.parse(getHeaders("Accept-Language"));
      if (locales_.isEmpty()) {
        locales_.add(DEFAULT_LOCALE);
      }
    }
    // utilize the server's locales hook if available
    return new IteratorEnumeration<Locale>(locales_.iterator());
  }

  /**
   * Return a sorted list of {@link com.google.opengse.httputil.AcceptHeader}
   * values from the "Accept-Encoding" HTTP request header. If no such header is
   * present, 'identity;q=1' is assumed.
   *
   * @return a list of codings, as specified by the AcceptHeader HTTP header
   */
  List<AcceptHeader> getCodings() {
    if (codings_ == null) {
      codings_ = AcceptHeader.parse(getHeaders("Accept-Encoding"));
      if (codings_.isEmpty()) {
        // create a new list, as the list returned via AcceptHeader is
        // fixed size and cannot be added to.
        codings_ = new ArrayList<AcceptHeader>(1);
        codings_.add(new AcceptHeader("identity", 1.0));
      }
    }
    return codings_;
  }

  /**
   * Return a sorted list of {@link com.google.opengse.httputil.AcceptHeader}
   * values from the "Accept-Charset" HTTP request header. If no such header is
   * present, "*;q=1" * is assumed.
   *
   * @return a list of charsets, as specified by the AcceptCharset HTTP header.
   */
  List<AcceptHeader> _getCharsets() {
    if (charsets_ == null) {
      charsets_ = AcceptHeader.parse(getHeaders("Accept-Charset"));
      if (charsets_.isEmpty()) {
        // create a new list, as the list returned via AcceptHeader is
        // fixed size and cannot be added to.
        charsets_ = new ArrayList<AcceptHeader>(1);
        charsets_.add(new AcceptHeader("*", 1.0));
      }
    }
    return charsets_;
  }

  /**
   * Returns the named {@link com.google.opengse.httputil.AcceptHeader} value.
   * If no Accept-Encoding header was present in the request, only
   * coding=="identity" will be available.
   *
   * @param coding the coding to return
   * @return the coding or <code>null</code> if not specified
   */
  AcceptHeader _getAcceptEncoding(String coding) {
    List<AcceptHeader> codings = getCodings();
    for (AcceptHeader ae : codings) {
      if (ae.getType().equals(coding)) {
        return ae;
      }
    }
    return null;
  }

  /**
   * Determines whether the specified charset is acceptable.<p>
   * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2
   *
   * @param charset the charset to test
   * @return {@code true} if the client will accept the specified charset;
   *         {@code false} otherwise.
   */
  boolean _acceptsCharset(String charset) {
    return acceptsType(charset, _getCharsets());
  }

  /**
   * Determines whether the specified coding is acceptable.<p>
   * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
   *
   * @param coding the coding to test
   * @return <code>true</code> if the client will accept the specified coding;
   *         <code>false</code> otherwise.
   */
  boolean _acceptsEncoding(String coding) {
    return acceptsType(coding, getCodings());
  }

  private static boolean acceptsType(String type,
      List<AcceptHeader> acceptHeaders) {
    double star_q = 0.0;

    for (AcceptHeader ae : acceptHeaders) {
      if (ae.getType().equalsIgnoreCase(type)) {
        if (ae.getQuality() > 0.0) {
          return true;
        } else {
          return false;
        }
      } else if (ae.getType().equals("*")) {
        star_q = ae.getQuality();
      }
    }

    return star_q > 0.0;
  }

  // Changes to the returned map won't affect the underlying parameters
  // for this request.
  public Map<String, String[]> getParameterMap() {
    initParsedParameters();
    Map<String, String[]> copy = parsed_params_.toMap();
    return copy;
  }

  private void initParsedParameters() {
    if (parsed_params_ == null) {
      parsed_params_ = parseParameters(getInternalCharacterEncoding());
    }
  }

  /**
   * Parses the parameters associated with the request using the given {@code
   * encoding}.
   */
  protected ParamMap parseParameters(String encoding) {
    ParamMap map = new ParamMap();
    FormUrlDecoder.parse(getQueryString(), map, encoding);

    if (post_body_ != null) {
      IOBuffer params = getPostBody();
      if ("application/x-www-form-urlencoded".equals(getContentType())) {
        // the character encoding on the post body is guaranteed to be US-ASCII
        params.setCharacterEncoding("US-ASCII");

        StringBuilder sb = new StringBuilder();
        char[] char_buf = new char[1024];
        int res;

        while ((res = params.read(char_buf)) > 0) {
          sb.append(char_buf, 0, res);
        }

        FormUrlDecoder.parse(sb.toString(), map, encoding);
      }
    }
    return map;
  }

  public String getProtocol() {
    return "HTTP/" + requestContext.getMajorVersion() + "."
        + requestContext.getMinorVersion();
  }

  public BufferedReader getReader() {
    if (input_stream_ != null) {
      throw new IllegalStateException("getInputStream() called previously");
    }
    if (input_reader_ == null) {
      IOBuffer body = getPostBody();
      body.setCharacterEncoding(getInternalCharacterEncoding());
      input_reader_ = new BufferedReader(new IOBufferReader(body));
    }
    return input_reader_;
  }

  public ConnectionInformation getConnectionInformation() {
    return this;
  }

  public int getLocalPort() {
    Socket socket = conn_.getSocket();
    // can socket ever be null?
    return socket.getLocalPort();
  }

  public String getLocalAddr() {
    Socket socket = conn_.getSocket();
    // can socket ever be null?
    InetAddress inet = socket.getLocalAddress();
    return inet.getHostAddress();
  }

  public String getLocalName() {
    Socket socket = conn_.getSocket();
    // can socket ever be null?
    InetAddress inet = socket.getLocalAddress();
    return inet.getHostName();
  }

  public int getRemotePort() {
    Socket socket = conn_.getSocket();
    // can socket ever be null?
    return socket.getPort();
  }

  protected String getUserIPHeader() {
    return (server_ == null) ? null : server_.getUserIPHeader();
  }

  /**
   * Returns a string representation of the remote host address as a
   * dot-separated IP address. If GSE was instructed to use a request header to
   * divine the user's IP address (useful when behind a proxy), this method will
   * inspect the appropriate header(s) and report the supplied value. This
   * feature must be specifically enabled, as it can be a big security problem
   * if the proxy does not correctly set the header (because the remote client
   * can set it arbitrarily).
   *
   * @return the string representation of the remote IP address or null if it
   *         isn't available
   */
  public String getRemoteAddr() {
    // use an optional header to derive user IP. This is necessary when behind
    // a proxy. In these cases, the getHostAddress() method will incorrectly
    // return the proxy's IP instead of the user's.
    if (getUserIPHeader() != null) {
      Enumeration<String> userIpHeaders = getHeaders(getUserIPHeader());
      String lastUserIp = null;
      String firstDebugIp = null;
      // search through the available user ip headers and report the last
      // header value which IS NOT a debug IP, or the first header value
      // which IS a debug IP.
      while (userIpHeaders != null && userIpHeaders.hasMoreElements()) {
        String ip = userIpHeaders.nextElement();
        if (!StringUtil.isEmpty(ip)) {
          lastUserIp = ip;
        } else {
          LOGGER.warning("encountered empty user IP header");
        }
      }
      if (lastUserIp != null) {
        return lastUserIp;
      } else if (firstDebugIp != null) {
        return firstDebugIp;
      }
      // otherwise, fall through and report connected IP
    }
    return getSocketRemoteAddr();
  }

  /**
   * Returns thre remote address the socket is connected to.
   *
   * @return the string representation of the remote IP address the socket is
   *         connected to or null if socket is not connected or available.
   */
  String getSocketRemoteAddr() {
    Socket socket = conn_.getSocket();
    if (socket != null && socket.isConnected()) {
      InetAddress remoteInetAddr = socket.getInetAddress();
      if (remoteInetAddr != null) {
        return remoteInetAddr.getHostAddress();
      }
    }
    return null;
  }

  public String getRemoteHost() {
    String ip = getRemoteAddr();
    if (ip == null) {
      return null;
    } else {
      try {
        return InetAddress.getByName(ip).getHostName();
      } catch (UnknownHostException e) {
        LOGGER.info("hostname lookup failed for: " + ip);
        return null;
      }
    }
  }

  public String getScheme() {
    String scheme = requestContext.getURI().getScheme();
    if (scheme == null) {
      if (isSecure()) {
        scheme = "https";
      } else {
        scheme = "http";
      }
    }
    return scheme;
  }

  public String getServerName() {
    String host = getHost();
    int index = host.indexOf(':');
    if (index != -1) {
      host = host.substring(0, index);
    }
    Matcher m = CANONICAL_HOST_PATTERN.matcher(host);
    if (!m.find()) {
      LOGGER.warning("bad host header format: " + host);
      return null;
    }
    return host;
  }

  public int getServerPort() {
    String host = getHost();
    int index = host.indexOf(':');
    if (index != -1) {
      try {
        return Integer.parseInt(host.substring(index + 1));
      } catch (NumberFormatException ignored) {
        LOGGER.warning("bad host header format: " + host);
      }
    }
    // no port specified in the host header or through the default
    // host set in the HttpServer. In this case, we assume the client
    // browser is connecting to the default ports (80 for http:// and
    // 443 for https://). This assumption will be true if there is
    // a host header without a :port suffix. It's a just a 'good'
    // guess if there is no host header and we're relying on the default
    // HttpServer host (server_.getname()).
    return isSecure() ? 443 : 80;
  }

  /**
   * Overrides the name of the character encoding used in the body of this
   * request. If parameters have already been parsed, they will be reparsed with
   * the new encoding when needed.
   */
  public void setCharacterEncoding(String env)
      throws UnsupportedEncodingException {
    try {
      Charset.forName(env);
    } catch (IllegalArgumentException unsupported) {
      throw new UnsupportedEncodingException(env);
    }

    if (!env.equalsIgnoreCase(char_encoding_)) {
      if (input_reader_ != null) {
        LOGGER.warning("Resetting character encoding from " + char_encoding_ +
            " to " + env + "will not affect the contents of the " +
            "post body reader already fetched via getReader(), " +
            "which has been initialized using " + char_encoding_);
      }
      if (parsed_params_ != null) {
        LOGGER.info("Parameters (originally encoded with " +
            char_encoding_ + ") will be reparsed with " + env);
        parsed_params_ = null;
      }
    }
    char_encoding_ = env;
  }

  public String getMethod() {
    return requestContext.getMethod();
  }

  public String getPathTranslated() {
    return null;
  }

  public String getQueryString() {
    return requestContext.getURI().getRawQuery();
  }

  public String getRequestURI() {
    // According to the javadoc 1.4 api, the web container does not decode
    // this string, so we return the raw path.
    final URI uri = requestContext.getURI();
    return (uri == null) ? null : uri.getRawPath();
  }

  public StringBuffer getRequestURL() {
    final StringBuffer buf = getRequestURLPrefix();

    // append the raw path
    buf.append(requestContext.getURI().getRawPath());
    return buf;
  }

  StringBuffer getRequestURLPrefix() {
    StringBuffer buf = new StringBuffer();

    // add in the scheme
    String scheme = getScheme();
    buf.append(scheme);
    buf.append("://");

    // add in the authority (username, password, host, port)
    URI requestUri = RequestUtils.getURI(this);
    String authority = requestUri.getRawAuthority();
    if (authority != null) {
      buf.append(authority);
    } else {
      // if the URI has no authority info, construct from the host header
      buf.append(getHost());
    }

    return buf;
  }


  /**
   * Returns whether or not the request was received via a secure channel. This
   * value is determined by the incoming port or through an optional header, if
   * present.
   */
  public boolean isSecure() {
    if (server_.getSecureHeader() != null) {
      String secure = getHeader(server_.getSecureHeader());
      if (secure != null) {
        return ("true".equals(secure) || "yes".equals(secure));
      }
    }
    return conn_.getSecure();
  }

  /**
   * Returns this request's {@link HttpServer}. This is used by the {@link
   * HttpResponseImpl} class.
   */
  HttpServer getServer() {
    return server_;
  }


  /**
   * Get the actual character encoding that will be used to parse input. This
   * may differ from the encoding returned by {@link #getCharacterEncoding}
   *
   *
   * NOTE: Use this instead of <code>getCharacterEncoding</code> to set the
   * encoding scheme of an i/o buffer or query string from the incoming request,
   * for decoding.
   */
  private String getInternalCharacterEncoding() {
    String defaultEncoding = getCharacterEncoding();
    return defaultEncoding;
  }

  /**
   * ParameterSet of only the query string.
   */
  private class QueryStringParameterSet implements ParameterSet {

    private final ParamMap paramMap;

    private QueryStringParameterSet() {
      String queryString = getQueryString();
      paramMap = (queryString == null) ? new ParamMap() :
          FormUrlDecoder.parse(queryString, null,
              getInternalCharacterEncoding());
    }

    public Iterator<String> getParameterNames() {
      return paramMap.toMap().keySet().iterator();
    }

    public String getParameter(String name) {
      String[] vals = getParameterValues(name);
      if (vals == null) {
        return null;
      }
      return vals[0];
    }

    public String[] getParameterValues(String name) {
      return paramMap.get(name);
    }
  }

  /**
   * Returns a ParameterSet consisting of the parameters in the query string.
   */
  ParameterSet getQueryParameterSet() {
    if (queryStringParams == null) {
      queryStringParams = new QueryStringParameterSet();
    }
    return queryStringParams;
  }

  public String getHeader(String name) {
    return requestContext.getHeaders().getHeader(name);
  }

  public Enumeration<String> getHeaders(String name) {
    return requestContext.getHeaders().getHeaders(name);
  }

  public Enumeration<String> getHeaderNames() {
    return requestContext.getHeaders().getHeaderNames();
  }

  public int getIntHeader(String name) {
    return requestContext.getHeaders().getIntHeader(name);
  }
}
TOP

Related Classes of com.google.opengse.core.HttpRequestImpl$QueryStringParameterSet

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.