/**
   * Start this Engine component.
   *
   * @exception org.apache.catalina.LifecycleException if a startup error occurs
   */
  public void start() throws LifecycleException {

    // Log our server identification information
    System.out.println(ServerInfo.getServerInfo());

    // Standard container startup
    super.start();
  }
 /** Open the new log file for the date specified by <code>dateStamp</code>. */
 @Override
 protected synchronized void open() {
   super.open();
   if (currentLogFile.length() == 0) {
     writer.println("#Fields: " + pattern);
     writer.println("#Version: 2.0");
     writer.println("#Software: " + ServerInfo.getServerInfo());
   }
 }
 /** Open the new log file for the date specified by <code>dateStamp</code>. */
 @Override
 protected synchronized void open() {
   super.open();
   if (getCurrentLogFile().length() == 0) {
     getWriter().println("#Fields: " + getPattern());
     getWriter().println("#Version: 2.0");
     getWriter().println("#Software: " + ServerInfo.getServerInfo());
   }
 }
  /**
   * Render a HTML list of the currently active Contexts in our virtual host, and memory and server
   * status information.
   *
   * @param request The request
   * @param response The response
   * @param message a message to display
   */
  public void list(
      HttpServletRequest request,
      HttpServletResponse response,
      String message,
      StringManager smClient)
      throws IOException {

    if (debug >= 1) {
      log(sm.getString("hostManagerServlet.list", engine.getName()));
    }

    PrintWriter writer = response.getWriter();

    // HTML Header Section
    writer.print(org.apache.catalina.manager.Constants.HTML_HEADER_SECTION);

    // Body Header Section
    Object[] args = new Object[2];
    args[0] = request.getContextPath();
    args[1] = smClient.getString("htmlHostManagerServlet.title");
    writer.print(MessageFormat.format(Constants.BODY_HEADER_SECTION, args));

    // Message Section
    args = new Object[3];
    args[0] = smClient.getString("htmlHostManagerServlet.messageLabel");
    if (message == null || message.length() == 0) {
      args[1] = "OK";
    } else {
      args[1] = RequestUtil.filter(message);
    }
    writer.print(MessageFormat.format(Constants.MESSAGE_SECTION, args));

    // Manager Section
    args = new Object[9];
    args[0] = smClient.getString("htmlHostManagerServlet.manager");
    args[1] = response.encodeURL(request.getContextPath() + "/html/list");
    args[2] = smClient.getString("htmlHostManagerServlet.list");
    args[3] =
        response.encodeURL(
            request.getContextPath()
                + "/"
                + smClient.getString("htmlHostManagerServlet.helpHtmlManagerFile"));
    args[4] = smClient.getString("htmlHostManagerServlet.helpHtmlManager");
    args[5] =
        response.encodeURL(
            request.getContextPath()
                + "/"
                + smClient.getString("htmlHostManagerServlet.helpManagerFile"));
    args[6] = smClient.getString("htmlHostManagerServlet.helpManager");
    args[7] = response.encodeURL("/manager/status");
    args[8] = smClient.getString("statusServlet.title");
    writer.print(MessageFormat.format(Constants.MANAGER_SECTION, args));

    // Hosts Header Section
    args = new Object[3];
    args[0] = smClient.getString("htmlHostManagerServlet.hostName");
    args[1] = smClient.getString("htmlHostManagerServlet.hostAliases");
    args[2] = smClient.getString("htmlHostManagerServlet.hostTasks");
    writer.print(MessageFormat.format(HOSTS_HEADER_SECTION, args));

    // Hosts Row Section
    // Create sorted map of host names.
    Container[] children = engine.findChildren();
    String hostNames[] = new String[children.length];
    for (int i = 0; i < children.length; i++) hostNames[i] = children[i].getName();

    TreeMap<String, String> sortedHostNamesMap = new TreeMap<String, String>();

    for (int i = 0; i < hostNames.length; i++) {
      String displayPath = hostNames[i];
      sortedHostNamesMap.put(displayPath, hostNames[i]);
    }

    String hostsStart = smClient.getString("htmlHostManagerServlet.hostsStart");
    String hostsStop = smClient.getString("htmlHostManagerServlet.hostsStop");
    String hostsRemove = smClient.getString("htmlHostManagerServlet.hostsRemove");

    Iterator<Map.Entry<String, String>> iterator = sortedHostNamesMap.entrySet().iterator();
    while (iterator.hasNext()) {
      Map.Entry<String, String> entry = iterator.next();
      String hostName = entry.getKey();
      Host host = (Host) engine.findChild(hostName);

      if (host != null) {
        args = new Object[2];
        args[0] = RequestUtil.filter(hostName);
        String[] aliases = host.findAliases();
        StringBuilder buf = new StringBuilder();
        if (aliases.length > 0) {
          buf.append(aliases[0]);
          for (int j = 1; j < aliases.length; j++) {
            buf.append(", ").append(aliases[j]);
          }
        }

        if (buf.length() == 0) {
          buf.append("&nbsp;");
          args[1] = buf.toString();
        } else {
          args[1] = RequestUtil.filter(buf.toString());
        }

        writer.print(MessageFormat.format(HOSTS_ROW_DETAILS_SECTION, args));

        args = new Object[4];
        if (host.getState().isAvailable()) {
          args[0] =
              response.encodeURL(
                  request.getContextPath()
                      + "/html/stop?name="
                      + URLEncoder.encode(hostName, "UTF-8"));
          args[1] = hostsStop;
        } else {
          args[0] =
              response.encodeURL(
                  request.getContextPath()
                      + "/html/start?name="
                      + URLEncoder.encode(hostName, "UTF-8"));
          args[1] = hostsStart;
        }
        args[2] =
            response.encodeURL(
                request.getContextPath()
                    + "/html/remove?name="
                    + URLEncoder.encode(hostName, "UTF-8"));
        args[3] = hostsRemove;
        if (host == this.installedHost) {
          writer.print(MessageFormat.format(MANAGER_HOST_ROW_BUTTON_SECTION, args));
        } else {
          writer.print(MessageFormat.format(HOSTS_ROW_BUTTON_SECTION, args));
        }
      }
    }

    // Add Section
    args = new Object[6];
    args[0] = smClient.getString("htmlHostManagerServlet.addTitle");
    args[1] = smClient.getString("htmlHostManagerServlet.addHost");
    args[2] = response.encodeURL(request.getContextPath() + "/html/add");
    args[3] = smClient.getString("htmlHostManagerServlet.addName");
    args[4] = smClient.getString("htmlHostManagerServlet.addAliases");
    args[5] = smClient.getString("htmlHostManagerServlet.addAppBase");
    writer.print(MessageFormat.format(ADD_SECTION_START, args));

    args = new Object[3];
    args[0] = smClient.getString("htmlHostManagerServlet.addAutoDeploy");
    args[1] = "autoDeploy";
    args[2] = "checked";
    writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
    args[0] = smClient.getString("htmlHostManagerServlet.addDeployOnStartup");
    args[1] = "deployOnStartup";
    args[2] = "checked";
    writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
    args[0] = smClient.getString("htmlHostManagerServlet.addDeployXML");
    args[1] = "deployXML";
    args[2] = "checked";
    writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
    args[0] = smClient.getString("htmlHostManagerServlet.addUnpackWARs");
    args[1] = "unpackWARs";
    args[2] = "checked";
    writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));

    args[0] = smClient.getString("htmlHostManagerServlet.addManager");
    args[1] = "manager";
    args[2] = "checked";
    writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));

    args = new Object[1];
    args[0] = smClient.getString("htmlHostManagerServlet.addButton");
    writer.print(MessageFormat.format(ADD_SECTION_END, args));

    // Server Header Section
    args = new Object[7];
    args[0] = smClient.getString("htmlHostManagerServlet.serverTitle");
    args[1] = smClient.getString("htmlHostManagerServlet.serverVersion");
    args[2] = smClient.getString("htmlHostManagerServlet.serverJVMVersion");
    args[3] = smClient.getString("htmlHostManagerServlet.serverJVMVendor");
    args[4] = smClient.getString("htmlHostManagerServlet.serverOSName");
    args[5] = smClient.getString("htmlHostManagerServlet.serverOSVersion");
    args[6] = smClient.getString("htmlHostManagerServlet.serverOSArch");
    writer.print(MessageFormat.format(Constants.SERVER_HEADER_SECTION, args));

    // Server Row Section
    args = new Object[6];
    args[0] = ServerInfo.getServerInfo();
    args[1] = System.getProperty("java.runtime.version");
    args[2] = System.getProperty("java.vm.vendor");
    args[3] = System.getProperty("os.name");
    args[4] = System.getProperty("os.version");
    args[5] = System.getProperty("os.arch");
    writer.print(MessageFormat.format(Constants.SERVER_ROW_SECTION, args));

    // HTML Tail Section
    writer.print(Constants.HTML_TAIL_SECTION);

    // Finish up the response
    writer.flush();
    writer.close();
  }
Example #5
0
/**
 * Implementation of a request processor which delegates the processing to a Coyote processor.
 *
 * @author Craig R. McClanahan
 * @author Remy Maucherat
 */
public class CoyoteAdapter implements Adapter {

  private static final Log log = LogFactory.getLog(CoyoteAdapter.class);

  // -------------------------------------------------------------- Constants

  private static final String POWERED_BY =
      "Servlet/3.0 JSP/2.2 "
          + "("
          + ServerInfo.getServerInfo()
          + " Java/"
          + System.getProperty("java.vm.vendor")
          + "/"
          + System.getProperty("java.runtime.version")
          + ")";

  private static final EnumSet<SessionTrackingMode> SSL_ONLY = EnumSet.of(SessionTrackingMode.SSL);

  public static final int ADAPTER_NOTES = 1;

  protected static final boolean ALLOW_BACKSLASH =
      Boolean.valueOf(
              System.getProperty(
                  "org.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH", "false"))
          .booleanValue();

  // ----------------------------------------------------------- Constructors

  /**
   * Construct a new CoyoteProcessor associated with the specified connector.
   *
   * @param connector CoyoteConnector that owns this processor
   */
  public CoyoteAdapter(Connector connector) {

    super();
    this.connector = connector;
  }

  // ----------------------------------------------------- Instance Variables

  /** The CoyoteConnector with which this processor is associated. */
  private Connector connector = null;

  /** The string manager for this package. */
  protected static final StringManager sm = StringManager.getManager(Constants.Package);

  /** Encoder for the Location URL in HTTP redirects. */
  protected static URLEncoder urlEncoder;

  // ----------------------------------------------------- Static Initializer

  /** The safe character set. */
  static {
    urlEncoder = new URLEncoder();
    urlEncoder.addSafeCharacter('-');
    urlEncoder.addSafeCharacter('_');
    urlEncoder.addSafeCharacter('.');
    urlEncoder.addSafeCharacter('*');
    urlEncoder.addSafeCharacter('/');
  }

  // -------------------------------------------------------- Adapter Methods

  /**
   * Event method.
   *
   * @return false to indicate an error, expected or not
   */
  @Override
  public boolean event(
      org.apache.coyote.Request req, org.apache.coyote.Response res, SocketStatus status) {

    Request request = (Request) req.getNote(ADAPTER_NOTES);
    Response response = (Response) res.getNote(ADAPTER_NOTES);

    if (request.getWrapper() == null) {
      return false;
    }

    boolean error = false;
    boolean read = false;
    try {
      if (status == SocketStatus.OPEN_READ) {
        if (response.isClosed()) {
          // The event has been closed asynchronously, so call end instead of
          // read to cleanup the pipeline
          request.getEvent().setEventType(CometEvent.EventType.END);
          request.getEvent().setEventSubType(null);
        } else {
          try {
            // Fill the read buffer of the servlet layer
            if (request.read()) {
              read = true;
            }
          } catch (IOException e) {
            error = true;
          }
          if (read) {
            request.getEvent().setEventType(CometEvent.EventType.READ);
            request.getEvent().setEventSubType(null);
          } else if (error) {
            request.getEvent().setEventType(CometEvent.EventType.ERROR);
            request.getEvent().setEventSubType(CometEvent.EventSubType.CLIENT_DISCONNECT);
          } else {
            request.getEvent().setEventType(CometEvent.EventType.END);
            request.getEvent().setEventSubType(null);
          }
        }
      } else if (status == SocketStatus.DISCONNECT) {
        request.getEvent().setEventType(CometEvent.EventType.ERROR);
        request.getEvent().setEventSubType(CometEvent.EventSubType.CLIENT_DISCONNECT);
        error = true;
      } else if (status == SocketStatus.ERROR) {
        request.getEvent().setEventType(CometEvent.EventType.ERROR);
        request.getEvent().setEventSubType(CometEvent.EventSubType.IOEXCEPTION);
        error = true;
      } else if (status == SocketStatus.STOP) {
        request.getEvent().setEventType(CometEvent.EventType.END);
        request.getEvent().setEventSubType(CometEvent.EventSubType.SERVER_SHUTDOWN);
      } else if (status == SocketStatus.TIMEOUT) {
        if (response.isClosed()) {
          // The event has been closed asynchronously, so call end instead of
          // read to cleanup the pipeline
          request.getEvent().setEventType(CometEvent.EventType.END);
          request.getEvent().setEventSubType(null);
        } else {
          request.getEvent().setEventType(CometEvent.EventType.ERROR);
          request.getEvent().setEventSubType(CometEvent.EventSubType.TIMEOUT);
        }
      }

      req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());

      // Calling the container
      connector
          .getService()
          .getContainer()
          .getPipeline()
          .getFirst()
          .event(request, response, request.getEvent());

      if (!error
          && !response.isClosed()
          && (request.getAttribute(RequestDispatcher.ERROR_EXCEPTION) != null)) {
        // An unexpected exception occurred while processing the event, so
        // error should be called
        request.getEvent().setEventType(CometEvent.EventType.ERROR);
        request.getEvent().setEventSubType(null);
        error = true;
        connector
            .getService()
            .getContainer()
            .getPipeline()
            .getFirst()
            .event(request, response, request.getEvent());
      }
      if (response.isClosed() || !request.isComet()) {
        if (status == SocketStatus.OPEN_READ
            && request.getEvent().getEventType() != EventType.END) {
          // CometEvent.close was called during an event other than END
          request.getEvent().setEventType(CometEvent.EventType.END);
          request.getEvent().setEventSubType(null);
          error = true;
          connector
              .getService()
              .getContainer()
              .getPipeline()
              .getFirst()
              .event(request, response, request.getEvent());
        }
        res.action(ActionCode.COMET_END, null);
      } else if (!error && read && request.getAvailable()) {
        // If this was a read and not all bytes have been read, or if no data
        // was read from the connector, then it is an error
        request.getEvent().setEventType(CometEvent.EventType.ERROR);
        request.getEvent().setEventSubType(CometEvent.EventSubType.IOEXCEPTION);
        error = true;
        connector
            .getService()
            .getContainer()
            .getPipeline()
            .getFirst()
            .event(request, response, request.getEvent());
      }
      return (!error);
    } catch (Throwable t) {
      ExceptionUtils.handleThrowable(t);
      if (!(t instanceof IOException)) {
        log.error(sm.getString("coyoteAdapter.service"), t);
      }
      error = true;
      return false;
    } finally {
      req.getRequestProcessor().setWorkerThreadName(null);
      // Recycle the wrapper request and response
      if (error || response.isClosed() || !request.isComet()) {
        ((Context) request.getMappingData().context)
            .logAccess(request, response, System.currentTimeMillis() - req.getStartTime(), false);
        request.recycle();
        request.setFilterChain(null);
        response.recycle();
      }
    }
  }

  @Override
  public boolean asyncDispatch(
      org.apache.coyote.Request req, org.apache.coyote.Response res, SocketStatus status)
      throws Exception {
    Request request = (Request) req.getNote(ADAPTER_NOTES);
    Response response = (Response) res.getNote(ADAPTER_NOTES);

    if (request == null) {
      throw new IllegalStateException("Dispatch may only happen on an existing request.");
    }
    boolean comet = false;
    boolean success = true;
    AsyncContextImpl asyncConImpl = (AsyncContextImpl) request.getAsyncContext();
    req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
    try {
      if (!request.isAsync() && !comet) {
        // Error or timeout - need to tell listeners the request is over
        // Have to test this first since state may change while in this
        // method and this is only required if entering this method in
        // this state
        Context ctxt = (Context) request.getMappingData().context;
        if (ctxt != null) {
          ctxt.fireRequestDestroyEvent(request);
        }
        // Lift any suspension (e.g. if sendError() was used by an async
        // request) to allow the response to be written to the client
        response.setSuspended(false);
      }

      if (status == SocketStatus.TIMEOUT) {
        if (!asyncConImpl.timeout()) {
          asyncConImpl.setErrorState(null, false);
        }
      }
      // Has an error occurred during async processing that needs to be
      // processed by the application's error page mechanism (or Tomcat's
      // if the application doesn't define one)?
      if (!request.isAsyncDispatching() && request.isAsync() && response.isErrorReportRequired()) {
        connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
      }

      if (request.isAsyncDispatching()) {
        connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
        Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
        if (t != null) {
          asyncConImpl.setErrorState(t, true);
        }
      }

      if (request.isComet()) {
        if (!response.isClosed() && !response.isError()) {
          if (request.getAvailable()
              || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
            // Invoke a read event right away if there are available bytes
            if (event(req, res, SocketStatus.OPEN_READ)) {
              comet = true;
              res.action(ActionCode.COMET_BEGIN, null);
            }
          } else {
            comet = true;
            res.action(ActionCode.COMET_BEGIN, null);
          }
        } else {
          // Clear the filter chain, as otherwise it will not be reset elsewhere
          // since this is a Comet request
          request.setFilterChain(null);
        }
      }
      if (!request.isAsync() && !comet) {
        request.finishRequest();
        response.finishResponse();
        req.action(ActionCode.POST_REQUEST, null);
        ((Context) request.getMappingData().context)
            .logAccess(request, response, System.currentTimeMillis() - req.getStartTime(), false);
      }

      // Check to see if the processor is in an error state. If it is,
      // bail out now.
      AtomicBoolean error = new AtomicBoolean(false);
      res.action(ActionCode.IS_ERROR, error);
      if (error.get()) {
        success = false;
      }
    } catch (IOException e) {
      success = false;
      // Ignore
    } catch (Throwable t) {
      ExceptionUtils.handleThrowable(t);
      success = false;
      log.error(sm.getString("coyoteAdapter.service"), t);
    } finally {
      req.getRequestProcessor().setWorkerThreadName(null);
      // Recycle the wrapper request and response
      if (!success || (!comet && !request.isAsync())) {
        request.recycle();
        response.recycle();
      } else {
        // Clear converters so that the minimum amount of memory
        // is used by this processor
        request.clearEncoders();
        response.clearEncoders();
      }
    }
    return success;
  }

  /** Service method. */
  @Override
  public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
      throws Exception {

    Request request = (Request) req.getNote(ADAPTER_NOTES);
    Response response = (Response) res.getNote(ADAPTER_NOTES);

    if (request == null) {

      // Create objects
      request = connector.createRequest();
      request.setCoyoteRequest(req);
      response = connector.createResponse();
      response.setCoyoteResponse(res);

      // Link objects
      request.setResponse(response);
      response.setRequest(request);

      // Set as notes
      req.setNote(ADAPTER_NOTES, request);
      res.setNote(ADAPTER_NOTES, response);

      // Set query string encoding
      req.getParameters().setQueryStringEncoding(connector.getURIEncoding());
    }

    if (connector.getXpoweredBy()) {
      response.addHeader("X-Powered-By", POWERED_BY);
    }

    boolean comet = false;
    boolean async = false;

    try {

      // Parse and set Catalina and configuration specific
      // request parameters
      req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
      boolean postParseSuccess = postParseRequest(req, request, res, response);
      if (postParseSuccess) {
        // check valves if we support async
        request.setAsyncSupported(
            connector.getService().getContainer().getPipeline().isAsyncSupported());
        // Calling the container
        connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

        if (request.isComet()) {
          if (!response.isClosed() && !response.isError()) {
            if (request.getAvailable()
                || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
              // Invoke a read event right away if there are available bytes
              if (event(req, res, SocketStatus.OPEN_READ)) {
                comet = true;
                res.action(ActionCode.COMET_BEGIN, null);
              }
            } else {
              comet = true;
              res.action(ActionCode.COMET_BEGIN, null);
            }
          } else {
            // Clear the filter chain, as otherwise it will not be reset elsewhere
            // since this is a Comet request
            request.setFilterChain(null);
          }
        }
      }
      AsyncContextImpl asyncConImpl = (AsyncContextImpl) request.getAsyncContext();
      if (asyncConImpl != null) {
        async = true;
      } else if (!comet) {
        request.finishRequest();
        response.finishResponse();
        if (postParseSuccess && request.getMappingData().context != null) {
          // Log only if processing was invoked.
          // If postParseRequest() failed, it has already logged it.
          // If context is null this was the start of a comet request
          // that failed and has already been logged.
          ((Context) request.getMappingData().context)
              .logAccess(request, response, System.currentTimeMillis() - req.getStartTime(), false);
        }
        req.action(ActionCode.POST_REQUEST, null);
      }

    } catch (IOException e) {
      // Ignore
    } finally {
      req.getRequestProcessor().setWorkerThreadName(null);
      AtomicBoolean error = new AtomicBoolean(false);
      res.action(ActionCode.IS_ERROR, error);
      // Recycle the wrapper request and response
      if (!comet && !async || error.get()) {
        request.recycle();
        response.recycle();
      } else {
        // Clear converters so that the minimum amount of memory
        // is used by this processor
        request.clearEncoders();
        response.clearEncoders();
      }
    }
  }

  @Override
  public void errorDispatch(org.apache.coyote.Request req, org.apache.coyote.Response res) {
    Request request = (Request) req.getNote(ADAPTER_NOTES);
    Response response = (Response) res.getNote(ADAPTER_NOTES);

    if (request != null && request.getMappingData().context != null) {
      ((Context) request.getMappingData().context)
          .logAccess(request, response, System.currentTimeMillis() - req.getStartTime(), false);
    } else {
      log(req, res, System.currentTimeMillis() - req.getStartTime());
    }

    if (request != null) {
      request.recycle();
    }

    if (response != null) {
      response.recycle();
    }

    req.recycle();
    res.recycle();
  }

  @Override
  public void log(org.apache.coyote.Request req, org.apache.coyote.Response res, long time) {

    Request request = (Request) req.getNote(ADAPTER_NOTES);
    Response response = (Response) res.getNote(ADAPTER_NOTES);

    if (request == null) {
      // Create objects
      request = connector.createRequest();
      request.setCoyoteRequest(req);
      response = connector.createResponse();
      response.setCoyoteResponse(res);

      // Link objects
      request.setResponse(response);
      response.setRequest(request);

      // Set as notes
      req.setNote(ADAPTER_NOTES, request);
      res.setNote(ADAPTER_NOTES, response);

      // Set query string encoding
      req.getParameters().setQueryStringEncoding(connector.getURIEncoding());
    }

    try {
      // Log at the lowest level available. logAccess() will be
      // automatically called on parent containers.
      boolean logged = false;
      if (request.mappingData != null) {
        if (request.mappingData.context != null) {
          logged = true;
          ((Context) request.mappingData.context).logAccess(request, response, time, true);
        } else if (request.mappingData.host != null) {
          logged = true;
          ((Host) request.mappingData.host).logAccess(request, response, time, true);
        }
      }
      if (!logged) {
        connector.getService().getContainer().logAccess(request, response, time, true);
      }
    } catch (Throwable t) {
      ExceptionUtils.handleThrowable(t);
      log.warn(sm.getString("coyoteAdapter.accesslogFail"), t);
    } finally {
      request.recycle();
      response.recycle();
    }
  }

  private static class RecycleRequiredException extends Exception {
    private static final long serialVersionUID = 1L;
  }

  @Override
  public void checkRecycled(org.apache.coyote.Request req, org.apache.coyote.Response res) {
    Request request = (Request) req.getNote(ADAPTER_NOTES);
    Response response = (Response) res.getNote(ADAPTER_NOTES);
    String messageKey = null;
    if (request != null && request.getHost() != null) {
      messageKey = "coyoteAdapter.checkRecycled.request";
    } else if (response != null && response.getContentWritten() != 0) {
      messageKey = "coyoteAdapter.checkRecycled.response";
    }
    if (messageKey != null) {
      // Log this request, as it has probably skipped the access log.
      // The log() method will take care of recycling.
      log(req, res, 0L);

      if (connector.getState().isAvailable()) {
        if (log.isInfoEnabled()) {
          log.info(sm.getString(messageKey), new RecycleRequiredException());
        }
      } else {
        // There may be some aborted requests.
        // When connector shuts down, the request and response will not
        // be reused, so there is no issue to warn about here.
        if (log.isDebugEnabled()) {
          log.debug(sm.getString(messageKey), new RecycleRequiredException());
        }
      }
    }
  }

  @Override
  public String getDomain() {
    return connector.getDomain();
  }

  // ------------------------------------------------------ Protected Methods

  /** Parse additional request parameters. */
  protected boolean postParseRequest(
      org.apache.coyote.Request req,
      Request request,
      org.apache.coyote.Response res,
      Response response)
      throws Exception {

    // XXX the processor may have set a correct scheme and port prior to this point,
    // in ajp13 protocols dont make sense to get the port from the connector...
    // otherwise, use connector configuration
    if (!req.scheme().isNull()) {
      // use processor specified scheme to determine secure state
      request.setSecure(req.scheme().equals("https"));
    } else {
      // use connector scheme and secure configuration, (defaults to
      // "http" and false respectively)
      req.scheme().setString(connector.getScheme());
      request.setSecure(connector.getSecure());
    }

    // FIXME: the code below doesnt belongs to here,
    // this is only have sense
    // in Http11, not in ajp13..
    // At this point the Host header has been processed.
    // Override if the proxyPort/proxyHost are set
    String proxyName = connector.getProxyName();
    int proxyPort = connector.getProxyPort();
    if (proxyPort != 0) {
      req.setServerPort(proxyPort);
    }
    if (proxyName != null) {
      req.serverName().setString(proxyName);
    }

    // Copy the raw URI to the decodedURI
    MessageBytes decodedURI = req.decodedURI();
    decodedURI.duplicate(req.requestURI());

    // Parse the path parameters. This will:
    //   - strip out the path parameters
    //   - convert the decodedURI to bytes
    parsePathParameters(req, request);

    // URI decoding
    // %xx decoding of the URL
    try {
      req.getURLDecoder().convert(decodedURI, false);
    } catch (IOException ioe) {
      res.setStatus(400);
      res.setMessage("Invalid URI: " + ioe.getMessage());
      connector.getService().getContainer().logAccess(request, response, 0, true);
      return false;
    }
    // Normalization
    if (!normalize(req.decodedURI())) {
      res.setStatus(400);
      res.setMessage("Invalid URI");
      connector.getService().getContainer().logAccess(request, response, 0, true);
      return false;
    }
    // Character decoding
    convertURI(decodedURI, request);
    // Check that the URI is still normalized
    if (!checkNormalize(req.decodedURI())) {
      res.setStatus(400);
      res.setMessage("Invalid URI character encoding");
      connector.getService().getContainer().logAccess(request, response, 0, true);
      return false;
    }

    // Request mapping.
    MessageBytes serverName;
    if (connector.getUseIPVHosts()) {
      serverName = req.localName();
      if (serverName.isNull()) {
        // well, they did ask for it
        res.action(ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, null);
      }
    } else {
      serverName = req.serverName();
    }
    if (request.isAsyncStarted()) {
      // TODO SERVLET3 - async
      // reset mapping data, should prolly be done elsewhere
      request.getMappingData().recycle();
    }

    // Version for the second mapping loop and
    // Context that we expect to get for that version
    String version = null;
    Context versionContext = null;
    boolean mapRequired = true;

    while (mapRequired) {
      // This will map the the latest version by default
      connector.getMapper().map(serverName, decodedURI, version, request.getMappingData());
      request.setContext((Context) request.getMappingData().context);
      request.setWrapper((Wrapper) request.getMappingData().wrapper);

      // If there is no context at this point, it is likely no ROOT context
      // has been deployed
      if (request.getContext() == null) {
        res.setStatus(404);
        res.setMessage("Not found");
        // No context, so use host
        Host host = request.getHost();
        // Make sure there is a host (might not be during shutdown)
        if (host != null) {
          host.logAccess(request, response, 0, true);
        }
        return false;
      }

      // Now we have the context, we can parse the session ID from the URL
      // (if any). Need to do this before we redirect in case we need to
      // include the session id in the redirect
      String sessionID;
      if (request
          .getServletContext()
          .getEffectiveSessionTrackingModes()
          .contains(SessionTrackingMode.URL)) {

        // Get the session ID if there was one
        sessionID =
            request.getPathParameter(SessionConfig.getSessionUriParamName(request.getContext()));
        if (sessionID != null) {
          request.setRequestedSessionId(sessionID);
          request.setRequestedSessionURL(true);
        }
      }

      // Look for session ID in cookies and SSL session
      parseSessionCookiesId(req, request);
      parseSessionSslId(request);

      sessionID = request.getRequestedSessionId();

      mapRequired = false;
      if (version != null && request.getContext() == versionContext) {
        // We got the version that we asked for. That is it.
      } else {
        version = null;
        versionContext = null;

        Object[] contexts = request.getMappingData().contexts;
        // Single contextVersion means no need to remap
        // No session ID means no possibility of remap
        if (contexts != null && sessionID != null) {
          // Find the context associated with the session
          for (int i = (contexts.length); i > 0; i--) {
            Context ctxt = (Context) contexts[i - 1];
            if (ctxt.getManager().findSession(sessionID) != null) {
              // We found a context. Is it the one that has
              // already been mapped?
              if (!ctxt.equals(request.getMappingData().context)) {
                // Set version so second time through mapping
                // the correct context is found
                version = ctxt.getWebappVersion();
                versionContext = ctxt;
                // Reset mapping
                request.getMappingData().recycle();
                mapRequired = true;
              }
              break;
            }
          }
        }
      }

      if (!mapRequired && request.getContext().getPaused()) {
        // Found a matching context but it is paused. Mapping data will
        // be wrong since some Wrappers may not be registered at this
        // point.
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          // Should never happen
        }
        // Reset mapping
        request.getMappingData().recycle();
        mapRequired = true;
      }
    }

    // Possible redirect
    MessageBytes redirectPathMB = request.getMappingData().redirectPath;
    if (!redirectPathMB.isNull()) {
      String redirectPath = urlEncoder.encode(redirectPathMB.toString());
      String query = request.getQueryString();
      if (request.isRequestedSessionIdFromURL()) {
        // This is not optimal, but as this is not very common, it
        // shouldn't matter
        redirectPath =
            redirectPath
                + ";"
                + SessionConfig.getSessionUriParamName(request.getContext())
                + "="
                + request.getRequestedSessionId();
      }
      if (query != null) {
        // This is not optimal, but as this is not very common, it
        // shouldn't matter
        redirectPath = redirectPath + "?" + query;
      }
      response.sendRedirect(redirectPath);
      request.getContext().logAccess(request, response, 0, true);
      return false;
    }

    // Filter trace method
    if (!connector.getAllowTrace() && req.method().equalsIgnoreCase("TRACE")) {
      Wrapper wrapper = request.getWrapper();
      String header = null;
      if (wrapper != null) {
        String[] methods = wrapper.getServletMethods();
        if (methods != null) {
          for (int i = 0; i < methods.length; i++) {
            if ("TRACE".equals(methods[i])) {
              continue;
            }
            if (header == null) {
              header = methods[i];
            } else {
              header += ", " + methods[i];
            }
          }
        }
      }
      res.setStatus(405);
      res.addHeader("Allow", header);
      res.setMessage("TRACE method is not allowed");
      request.getContext().logAccess(request, response, 0, true);
      return false;
    }

    doConnectorAuthenticationAuthorization(req, request);

    return true;
  }

  private void doConnectorAuthenticationAuthorization(
      org.apache.coyote.Request req, Request request) {
    // Set the remote principal
    String username = req.getRemoteUser().toString();
    if (username != null) {
      if (log.isDebugEnabled()) {
        log.debug(sm.getString("coyoteAdapter.authenticate", username));
      }
      if (req.getRemoteUserNeedsAuthorization()) {
        Authenticator authenticator = request.getContext().getAuthenticator();
        if (authenticator == null) {
          // No security constraints configured for the application so
          // no need to authorize the user. Use the CoyotePrincipal to
          // provide the authenticated user.
          request.setUserPrincipal(new CoyotePrincipal(username));
        } else if (!(authenticator instanceof AuthenticatorBase)) {
          if (log.isDebugEnabled()) {
            log.debug(sm.getString("coyoteAdapter.authorize", username));
          }
          // Custom authenticator that may not trigger authorization.
          // Do the authorization here to make sure it is done.
          request.setUserPrincipal(request.getContext().getRealm().authenticate(username));
        }
        // If the Authenticator is an instance of AuthenticatorBase then
        // it will check req.getRemoteUserNeedsAuthorization() and
        // trigger authorization as necessary. It will also cache the
        // result preventing excessive calls to the Realm.
      } else {
        // The connector isn't configured for authorization. Create a
        // user without any roles using the supplied user name.
        request.setUserPrincipal(new CoyotePrincipal(username));
      }
    }

    // Set the authorization type
    String authtype = req.getAuthType().toString();
    if (authtype != null) {
      request.setAuthType(authtype);
    }
  }

  /**
   * Extract the path parameters from the request. This assumes parameters are of the form
   * /path;name=value;name2=value2/ etc. Currently only really interested in the session ID that
   * will be in this form. Other parameters can safely be ignored.
   *
   * @param req
   * @param request
   */
  protected void parsePathParameters(org.apache.coyote.Request req, Request request) {

    // Process in bytes (this is default format so this is normally a NO-OP
    req.decodedURI().toBytes();

    ByteChunk uriBC = req.decodedURI().getByteChunk();
    int semicolon = uriBC.indexOf(';', 0);

    // What encoding to use? Some platforms, eg z/os, use a default
    // encoding that doesn't give the expected result so be explicit
    String enc = connector.getURIEncoding();
    if (enc == null) {
      enc = "ISO-8859-1";
    }
    Charset charset = null;
    try {
      charset = B2CConverter.getCharset(enc);
    } catch (UnsupportedEncodingException e1) {
      log.warn(sm.getString("coyoteAdapter.parsePathParam", enc));
    }

    if (log.isDebugEnabled()) {
      log.debug(sm.getString("coyoteAdapter.debug", "uriBC", uriBC.toString()));
      log.debug(sm.getString("coyoteAdapter.debug", "semicolon", String.valueOf(semicolon)));
      log.debug(sm.getString("coyoteAdapter.debug", "enc", enc));
    }

    while (semicolon > -1) {
      // Parse path param, and extract it from the decoded request URI
      int start = uriBC.getStart();
      int end = uriBC.getEnd();

      int pathParamStart = semicolon + 1;
      int pathParamEnd =
          ByteChunk.findBytes(
              uriBC.getBuffer(), start + pathParamStart, end, new byte[] {';', '/'});

      String pv = null;

      if (pathParamEnd >= 0) {
        if (charset != null) {
          pv =
              new String(
                  uriBC.getBuffer(),
                  start + pathParamStart,
                  pathParamEnd - pathParamStart,
                  charset);
        }
        // Extract path param from decoded request URI
        byte[] buf = uriBC.getBuffer();
        for (int i = 0; i < end - start - pathParamEnd; i++) {
          buf[start + semicolon + i] = buf[start + i + pathParamEnd];
        }
        uriBC.setBytes(buf, start, end - start - pathParamEnd + semicolon);
      } else {
        if (charset != null) {
          pv =
              new String(
                  uriBC.getBuffer(),
                  start + pathParamStart,
                  (end - start) - pathParamStart,
                  charset);
        }
        uriBC.setEnd(start + semicolon);
      }

      if (log.isDebugEnabled()) {
        log.debug(
            sm.getString("coyoteAdapter.debug", "pathParamStart", String.valueOf(pathParamStart)));
        log.debug(
            sm.getString("coyoteAdapter.debug", "pathParamEnd", String.valueOf(pathParamEnd)));
        log.debug(sm.getString("coyoteAdapter.debug", "pv", pv));
      }

      if (pv != null) {
        int equals = pv.indexOf('=');
        if (equals > -1) {
          String name = pv.substring(0, equals);
          String value = pv.substring(equals + 1);
          request.addPathParameter(name, value);
          if (log.isDebugEnabled()) {
            log.debug(sm.getString("coyoteAdapter.debug", "equals", String.valueOf(equals)));
            log.debug(sm.getString("coyoteAdapter.debug", "name", name));
            log.debug(sm.getString("coyoteAdapter.debug", "value", value));
          }
        }
      }

      semicolon = uriBC.indexOf(';', semicolon);
    }
  }

  /**
   * Look for SSL session ID if required. Only look for SSL Session ID if it is the only tracking
   * method enabled.
   */
  protected void parseSessionSslId(Request request) {
    if (request.getRequestedSessionId() == null
        && SSL_ONLY.equals(request.getServletContext().getEffectiveSessionTrackingModes())
        && request.connector.secure) {
      // TODO Is there a better way to map SSL sessions to our sesison ID?
      // TODO The request.getAttribute() will cause a number of other SSL
      //      attribute to be populated. Is this a performance concern?
      request.setRequestedSessionId(request.getAttribute(SSLSupport.SESSION_ID_KEY).toString());
      request.setRequestedSessionSSL(true);
    }
  }

  /** Parse session id in URL. */
  protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) {

    // If session tracking via cookies has been disabled for the current
    // context, don't go looking for a session ID in a cookie as a cookie
    // from a parent context with a session ID may be present which would
    // overwrite the valid session ID encoded in the URL
    Context context = (Context) request.getMappingData().context;
    if (context != null
        && !context
            .getServletContext()
            .getEffectiveSessionTrackingModes()
            .contains(SessionTrackingMode.COOKIE)) {
      return;
    }

    // Parse session id from cookies
    Cookies serverCookies = req.getCookies();
    int count = serverCookies.getCookieCount();
    if (count <= 0) {
      return;
    }

    String sessionCookieName = SessionConfig.getSessionCookieName(context);

    for (int i = 0; i < count; i++) {
      ServerCookie scookie = serverCookies.getCookie(i);
      if (scookie.getName().equals(sessionCookieName)) {
        // Override anything requested in the URL
        if (!request.isRequestedSessionIdFromCookie()) {
          // Accept only the first session id cookie
          convertMB(scookie.getValue());
          request.setRequestedSessionId(scookie.getValue().toString());
          request.setRequestedSessionCookie(true);
          request.setRequestedSessionURL(false);
          if (log.isDebugEnabled()) {
            log.debug(" Requested cookie session id is " + request.getRequestedSessionId());
          }
        } else {
          if (!request.isRequestedSessionIdValid()) {
            // Replace the session id until one is valid
            convertMB(scookie.getValue());
            request.setRequestedSessionId(scookie.getValue().toString());
          }
        }
      }
    }
  }

  /** Character conversion of the URI. */
  protected void convertURI(MessageBytes uri, Request request) throws Exception {

    ByteChunk bc = uri.getByteChunk();
    int length = bc.getLength();
    CharChunk cc = uri.getCharChunk();
    cc.allocate(length, -1);

    String enc = connector.getURIEncoding();
    if (enc != null) {
      B2CConverter conv = request.getURIConverter();
      try {
        if (conv == null) {
          conv = new B2CConverter(enc, true);
          request.setURIConverter(conv);
        } else {
          conv.recycle();
        }
      } catch (IOException e) {
        log.error("Invalid URI encoding; using HTTP default");
        connector.setURIEncoding(null);
      }
      if (conv != null) {
        try {
          conv.convert(bc, cc, true);
          uri.setChars(cc.getBuffer(), cc.getStart(), cc.getLength());
          return;
        } catch (IOException ioe) {
          // Should never happen as B2CConverter should replace
          // problematic characters
          request.getResponse().sendError(HttpServletResponse.SC_BAD_REQUEST);
        }
      }
    }

    // Default encoding: fast conversion for ISO-8859-1
    byte[] bbuf = bc.getBuffer();
    char[] cbuf = cc.getBuffer();
    int start = bc.getStart();
    for (int i = 0; i < length; i++) {
      cbuf[i] = (char) (bbuf[i + start] & 0xff);
    }
    uri.setChars(cbuf, 0, length);
  }

  /** Character conversion of the a US-ASCII MessageBytes. */
  protected void convertMB(MessageBytes mb) {

    // This is of course only meaningful for bytes
    if (mb.getType() != MessageBytes.T_BYTES) {
      return;
    }

    ByteChunk bc = mb.getByteChunk();
    CharChunk cc = mb.getCharChunk();
    int length = bc.getLength();
    cc.allocate(length, -1);

    // Default encoding: fast conversion
    byte[] bbuf = bc.getBuffer();
    char[] cbuf = cc.getBuffer();
    int start = bc.getStart();
    for (int i = 0; i < length; i++) {
      cbuf[i] = (char) (bbuf[i + start] & 0xff);
    }
    mb.setChars(cbuf, 0, length);
  }

  /**
   * Normalize URI.
   *
   * <p>This method normalizes "\", "//", "/./" and "/../". This method will return false when
   * trying to go above the root, or if the URI contains a null byte.
   *
   * @param uriMB URI to be normalized
   */
  public static boolean normalize(MessageBytes uriMB) {

    ByteChunk uriBC = uriMB.getByteChunk();
    final byte[] b = uriBC.getBytes();
    final int start = uriBC.getStart();
    int end = uriBC.getEnd();

    // An empty URL is not acceptable
    if (start == end) {
      return false;
    }

    // URL * is acceptable
    if ((end - start == 1) && b[start] == (byte) '*') {
      return true;
    }

    int pos = 0;
    int index = 0;

    // Replace '\' with '/'
    // Check for null byte
    for (pos = start; pos < end; pos++) {
      if (b[pos] == (byte) '\\') {
        if (ALLOW_BACKSLASH) {
          b[pos] = (byte) '/';
        } else {
          return false;
        }
      }
      if (b[pos] == (byte) 0) {
        return false;
      }
    }

    // The URL must start with '/'
    if (b[start] != (byte) '/') {
      return false;
    }

    // Replace "//" with "/"
    for (pos = start; pos < (end - 1); pos++) {
      if (b[pos] == (byte) '/') {
        while ((pos + 1 < end) && (b[pos + 1] == (byte) '/')) {
          copyBytes(b, pos, pos + 1, end - pos - 1);
          end--;
        }
      }
    }

    // If the URI ends with "/." or "/..", then we append an extra "/"
    // Note: It is possible to extend the URI by 1 without any side effect
    // as the next character is a non-significant WS.
    if (((end - start) >= 2) && (b[end - 1] == (byte) '.')) {
      if ((b[end - 2] == (byte) '/')
          || ((b[end - 2] == (byte) '.') && (b[end - 3] == (byte) '/'))) {
        b[end] = (byte) '/';
        end++;
      }
    }

    uriBC.setEnd(end);

    index = 0;

    // Resolve occurrences of "/./" in the normalized path
    while (true) {
      index = uriBC.indexOf("/./", 0, 3, index);
      if (index < 0) {
        break;
      }
      copyBytes(b, start + index, start + index + 2, end - start - index - 2);
      end = end - 2;
      uriBC.setEnd(end);
    }

    index = 0;

    // Resolve occurrences of "/../" in the normalized path
    while (true) {
      index = uriBC.indexOf("/../", 0, 4, index);
      if (index < 0) {
        break;
      }
      // Prevent from going outside our context
      if (index == 0) {
        return false;
      }
      int index2 = -1;
      for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos--) {
        if (b[pos] == (byte) '/') {
          index2 = pos;
        }
      }
      copyBytes(b, start + index2, start + index + 3, end - start - index - 3);
      end = end + index2 - index - 3;
      uriBC.setEnd(end);
      index = index2;
    }

    return true;
  }

  /**
   * Check that the URI is normalized following character decoding.
   *
   * <p>This method checks for "\", 0, "//", "/./" and "/../". This method will return false if
   * sequences that are supposed to be normalized are still present in the URI.
   *
   * @param uriMB URI to be checked (should be chars)
   */
  public static boolean checkNormalize(MessageBytes uriMB) {

    CharChunk uriCC = uriMB.getCharChunk();
    char[] c = uriCC.getChars();
    int start = uriCC.getStart();
    int end = uriCC.getEnd();

    int pos = 0;

    // Check for '\' and 0
    for (pos = start; pos < end; pos++) {
      if (c[pos] == '\\') {
        return false;
      }
      if (c[pos] == 0) {
        return false;
      }
    }

    // Check for "//"
    for (pos = start; pos < (end - 1); pos++) {
      if (c[pos] == '/') {
        if (c[pos + 1] == '/') {
          return false;
        }
      }
    }

    // Check for ending with "/." or "/.."
    if (((end - start) >= 2) && (c[end - 1] == '.')) {
      if ((c[end - 2] == '/') || ((c[end - 2] == '.') && (c[end - 3] == '/'))) {
        return false;
      }
    }

    // Check for "/./"
    if (uriCC.indexOf("/./", 0, 3, 0) >= 0) {
      return false;
    }

    // Check for "/../"
    if (uriCC.indexOf("/../", 0, 4, 0) >= 0) {
      return false;
    }

    return true;
  }

  // ------------------------------------------------------ Protected Methods

  /** Copy an array of bytes to a different position. Used during normalization. */
  protected static void copyBytes(byte[] b, int dest, int src, int len) {
    for (int pos = 0; pos < len; pos++) {
      b[pos + dest] = b[pos + src];
    }
  }
}
  public void setWrapper(Wrapper wrapper) {
    if (tomcatContainer == null) {

      synchronized (lock) {
        if (tomcatContainer == null) {

          String serverInfo = ServerInfo.getServerInfo();
          logger.info("Server info: " + serverInfo);
          for (int i = 0; i < adaptorClasses.size(); i++) {
            String className = (String) adaptorClasses.get(i);
            try {
              Object o = Class.forName(className).newInstance();
              logger.debug("Testing container adaptor: " + className);
              if (o instanceof TomcatContainer) {
                if (forceFirstAdaptor || ((TomcatContainer) o).canBoundTo(serverInfo)) {
                  logger.info("Using " + className);
                  tomcatContainer = (TomcatContainer) o;
                  tomcatContainer.setWrapper(wrapper);
                  break;
                } else {
                  logger.debug("Cannot bind " + className + " to " + serverInfo);
                }
              } else {
                logger.error(className + " does not implement " + TomcatContainer.class.getName());
              }
            } catch (Throwable e) {
              if (logger.isDebugEnabled()) {
                logger.debug("Failed to load " + className, e);
              } else {
                logger.info("Failed to load " + className);
              }
              //
              // make sure we always re-throw ThreadDeath
              //
              if (e instanceof ThreadDeath) {
                throw (ThreadDeath) e;
              }
            }
          }

          if (tomcatContainer == null) {
            logger.fatal("No suitable container adaptor found!");
          }
        }
      }
    }

    try {
      if (tomcatContainer != null && wrapper == null) {
        logger.info("Unregistering container adaptor");
        tomcatContainer.setWrapper(null);
      }
    } catch (Throwable e) {
      logger.error("Could not unregister container adaptor", e);
      //
      // make sure we always re-throw ThreadDeath
      //
      if (e instanceof ThreadDeath) {
        throw (ThreadDeath) e;
      }
    }
  }
Example #7
0
  /**
   * Prints out an error report.
   *
   * @param request The request being processed
   * @param response The response being generated
   * @param throwable The exception that occurred (which possibly wraps a root cause exception
   */
  protected void report(Request request, Response response, Throwable throwable) {

    // Do nothing on non-HTTP responses
    int statusCode = response.getStatus();

    // Do nothing on a 1xx, 2xx and 3xx status
    // Do nothing if anything has been written already
    if (statusCode < 400 || response.getContentWritten() > 0 || !response.isError()) {
      return;
    }

    String message = RequestUtil.filter(response.getMessage());
    if (message == null) {
      if (throwable != null) {
        String exceptionMessage = throwable.getMessage();
        if (exceptionMessage != null && exceptionMessage.length() > 0) {
          message = RequestUtil.filter((new Scanner(exceptionMessage)).nextLine());
        }
      }
      if (message == null) {
        message = "";
      }
    }

    // Do nothing if there is no report for the specified status code
    String report = null;
    try {
      report = sm.getString("http." + statusCode);
    } catch (Throwable t) {
      ExceptionUtils.handleThrowable(t);
    }
    if (report == null) {
      return;
    }

    StringBuilder sb = new StringBuilder();

    sb.append("<html><head><title>");
    sb.append(ServerInfo.getServerInfo()).append(" - ");
    sb.append(sm.getString("errorReportValve.errorReport"));
    sb.append("</title>");
    sb.append("<style><!--");
    sb.append(org.apache.catalina.util.TomcatCSS.TOMCAT_CSS);
    sb.append("--></style> ");
    sb.append("</head><body>");
    sb.append("<h1>");
    sb.append(sm.getString("errorReportValve.statusHeader", "" + statusCode, message))
        .append("</h1>");
    sb.append("<HR size=\"1\" noshade=\"noshade\">");
    sb.append("<p><b>type</b> ");
    if (throwable != null) {
      sb.append(sm.getString("errorReportValve.exceptionReport"));
    } else {
      sb.append(sm.getString("errorReportValve.statusReport"));
    }
    sb.append("</p>");
    sb.append("<p><b>");
    sb.append(sm.getString("errorReportValve.message"));
    sb.append("</b> <u>");
    sb.append(message).append("</u></p>");
    sb.append("<p><b>");
    sb.append(sm.getString("errorReportValve.description"));
    sb.append("</b> <u>");
    sb.append(report);
    sb.append("</u></p>");

    if (throwable != null) {

      String stackTrace = getPartialServletStackTrace(throwable);
      sb.append("<p><b>");
      sb.append(sm.getString("errorReportValve.exception"));
      sb.append("</b> <pre>");
      sb.append(RequestUtil.filter(stackTrace));
      sb.append("</pre></p>");

      int loops = 0;
      Throwable rootCause = throwable.getCause();
      while (rootCause != null && (loops < 10)) {
        stackTrace = getPartialServletStackTrace(rootCause);
        sb.append("<p><b>");
        sb.append(sm.getString("errorReportValve.rootCause"));
        sb.append("</b> <pre>");
        sb.append(RequestUtil.filter(stackTrace));
        sb.append("</pre></p>");
        // In case root cause is somehow heavily nested
        rootCause = rootCause.getCause();
        loops++;
      }

      sb.append("<p><b>");
      sb.append(sm.getString("errorReportValve.note"));
      sb.append("</b> <u>");
      sb.append(sm.getString("errorReportValve.rootCauseInLogs", ServerInfo.getServerInfo()));
      sb.append("</u></p>");
    }

    sb.append("<HR size=\"1\" noshade=\"noshade\">");
    sb.append("<h3>").append(ServerInfo.getServerInfo()).append("</h3>");
    sb.append("</body></html>");

    try {
      try {
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");
      } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        if (container.getLogger().isDebugEnabled()) {
          container.getLogger().debug("status.setContentType", t);
        }
      }
      Writer writer = response.getReporter();
      if (writer != null) {
        // If writer is null, it's an indication that the response has
        // been hard committed already, which should never happen
        writer.write(sb.toString());
      }
    } catch (IOException e) {
      // Ignore
    } catch (IllegalStateException e) {
      // Ignore
    }
  }
Example #8
0
  /**
   * Report the current Tomcat Server Release number
   *
   * @return Tomcat release identifier
   */
  public String getServerInfo() {

    return ServerInfo.getServerInfo();
  }
/**
 * Implementation of a request processor (and its associated thread) that may be used by an
 * HttpConnector to process individual requests. The connector will allocate a processor from its
 * pool, assign a particular socket to it, and the processor will then execute the processing
 * required to complete the request. When the processor is completed, it will recycle itself.
 *
 * @author Craig R. McClanahan
 * @author Remy Maucherat
 * @version $Revision: 1.46 $ $Date: 2002/04/04 17:50:34 $
 * @deprecated
 */
final class HttpProcessor implements Lifecycle, Runnable {

  // ----------------------------------------------------- Manifest Constants

  /** Server information string for this server. */
  private static final String SERVER_INFO = ServerInfo.getServerInfo() + " (HTTP/1.1 Connector)";

  // ----------------------------------------------------------- Constructors

  /**
   * Construct a new HttpProcessor associated with the specified connector.
   *
   * @param connector HttpConnector that owns this processor
   * @param id Identifier of this HttpProcessor (unique per connector)
   */
  public HttpProcessor(HttpConnector connector, int id) {

    super();
    this.connector = connector;
    this.debug = connector.getDebug();
    this.id = id;
    this.proxyName = connector.getProxyName();
    this.proxyPort = connector.getProxyPort();
    this.request = (HttpRequestImpl) connector.createRequest();
    this.response = (HttpResponseImpl) connector.createResponse();
    this.serverPort = connector.getPort();
    this.threadName = "HttpProcessor[" + connector.getPort() + "][" + id + "]";
  }

  // ----------------------------------------------------- Instance Variables

  /** Is there a new socket available? */
  private boolean available = false;

  /** The HttpConnector with which this processor is associated. */
  private HttpConnector connector = null;

  /** The debugging detail level for this component. */
  private int debug = 0;

  /** The identifier of this processor, unique per connector. */
  private int id = 0;

  /** The lifecycle event support for this component. */
  private LifecycleSupport lifecycle = new LifecycleSupport(this);

  /** The match string for identifying a session ID parameter. */
  private static final String match = ";" + Globals.SESSION_PARAMETER_NAME + "=";

  /** The match string for identifying a session ID parameter. */
  private static final char[] SESSION_ID = match.toCharArray();

  /** The string parser we will use for parsing request lines. */
  private StringParser parser = new StringParser();

  /** The proxy server name for our Connector. */
  private String proxyName = null;

  /** The proxy server port for our Connector. */
  private int proxyPort = 0;

  /** The HTTP request object we will pass to our associated container. */
  private HttpRequestImpl request = null;

  /** The HTTP response object we will pass to our associated container. */
  private HttpResponseImpl response = null;

  /** The actual server port for our Connector. */
  private int serverPort = 0;

  /** The string manager for this package. */
  protected StringManager sm = StringManager.getManager(Constants.Package);

  /**
   * The socket we are currently processing a request for. This object is used for inter-thread
   * communication only.
   */
  private Socket socket = null;

  /** Has this component been started yet? */
  private boolean started = false;

  /** The shutdown signal to our background thread */
  private boolean stopped = false;

  /** The background thread. */
  private Thread thread = null;

  /** The name to register for the background thread. */
  private String threadName = null;

  /** The thread synchronization object. */
  private Object threadSync = new Object();

  /** Keep alive indicator. */
  private boolean keepAlive = false;

  /** HTTP/1.1 client. */
  private boolean http11 = true;

  /**
   * True if the client has asked to recieve a request acknoledgement. If so the server will send a
   * preliminary 100 Continue response just after it has successfully parsed the request headers,
   * and before starting reading the request entity body.
   */
  private boolean sendAck = false;

  /** Ack string when pipelining HTTP requests. */
  private static final byte[] ack = (new String("HTTP/1.1 100 Continue\r\n\r\n")).getBytes();

  /** CRLF. */
  private static final byte[] CRLF = (new String("\r\n")).getBytes();

  /** Line buffer. */
  // private char[] lineBuffer = new char[4096];

  /** Request line buffer. */
  private HttpRequestLine requestLine = new HttpRequestLine();

  /** Processor state */
  private int status = Constants.PROCESSOR_IDLE;

  // --------------------------------------------------------- Public Methods

  /** Return a String value representing this object. */
  public String toString() {

    return (this.threadName);
  }

  // -------------------------------------------------------- Package Methods

  /**
   * Process an incoming TCP/IP connection on the specified socket. Any exception that occurs during
   * processing must be logged and swallowed. <b>NOTE</b>: This method is called from our
   * Connector's thread. We must assign it to our own thread so that multiple simultaneous requests
   * can be handled.
   *
   * @param socket TCP socket to process
   */
  synchronized void assign(Socket socket) {

    // Wait for the Processor to get the previous Socket
    while (available) {
      try {
        wait();
      } catch (InterruptedException e) {
      }
    }

    // Store the newly available Socket and notify our thread
    this.socket = socket;
    available = true;
    notifyAll();

    if ((debug >= 1) && (socket != null)) log(" An incoming request is being assigned");
  }

  // -------------------------------------------------------- Private Methods

  /**
   * Await a newly assigned Socket from our Connector, or <code>null</code> if we are supposed to
   * shut down.
   */
  private synchronized Socket await() {

    // Wait for the Connector to provide a new Socket
    while (!available) {
      try {
        wait();
      } catch (InterruptedException e) {
      }
    }

    // Notify the Connector that we have received this Socket
    Socket socket = this.socket;
    available = false;
    notifyAll();

    if ((debug >= 1) && (socket != null)) log("  The incoming request has been awaited");

    return (socket);
  }

  /**
   * Log a message on the Logger associated with our Container (if any)
   *
   * @param message Message to be logged
   */
  private void log(String message) {

    Logger logger = connector.getContainer().getLogger();
    if (logger != null) logger.log(threadName + " " + message);
  }

  /**
   * Log a message on the Logger associated with our Container (if any)
   *
   * @param message Message to be logged
   * @param throwable Associated exception
   */
  private void log(String message, Throwable throwable) {

    Logger logger = connector.getContainer().getLogger();
    if (logger != null) logger.log(threadName + " " + message, throwable);
  }

  /**
   * Parse the value of an <code>Accept-Language</code> header, and add the corresponding Locales to
   * the current request.
   *
   * @param value The value of the <code>Accept-Language</code> header.
   */
  private void parseAcceptLanguage(String value) {

    // Store the accumulated languages that have been requested in
    // a local collection, sorted by the quality value (so we can
    // add Locales in descending order).  The values will be ArrayLists
    // containing the corresponding Locales to be added
    TreeMap locales = new TreeMap();

    // Preprocess the value to remove all whitespace
    int white = value.indexOf(' ');
    if (white < 0) white = value.indexOf('\t');
    if (white >= 0) {
      StringBuffer sb = new StringBuffer();
      int len = value.length();
      for (int i = 0; i < len; i++) {
        char ch = value.charAt(i);
        if ((ch != ' ') && (ch != '\t')) sb.append(ch);
      }
      value = sb.toString();
    }

    // Process each comma-delimited language specification
    parser.setString(value); // ASSERT: parser is available to us
    int length = parser.getLength();
    while (true) {

      // Extract the next comma-delimited entry
      int start = parser.getIndex();
      if (start >= length) break;
      int end = parser.findChar(',');
      String entry = parser.extract(start, end).trim();
      parser.advance(); // For the following entry

      // Extract the quality factor for this entry
      double quality = 1.0;
      int semi = entry.indexOf(";q=");
      if (semi >= 0) {
        try {
          quality = Double.parseDouble(entry.substring(semi + 3));
        } catch (NumberFormatException e) {
          quality = 0.0;
        }
        entry = entry.substring(0, semi);
      }

      // Skip entries we are not going to keep track of
      if (quality < 0.00005) continue; // Zero (or effectively zero) quality factors
      if ("*".equals(entry)) continue; // FIXME - "*" entries are not handled

      // Extract the language and country for this entry
      String language = null;
      String country = null;
      String variant = null;
      int dash = entry.indexOf('-');
      if (dash < 0) {
        language = entry;
        country = "";
        variant = "";
      } else {
        language = entry.substring(0, dash);
        country = entry.substring(dash + 1);
        int vDash = country.indexOf('-');
        if (vDash > 0) {
          String cTemp = country.substring(0, vDash);
          variant = country.substring(vDash + 1);
          country = cTemp;
        } else {
          variant = "";
        }
      }

      // Add a new Locale to the list of Locales for this quality level
      Locale locale = new Locale(language, country, variant);
      Double key = new Double(-quality); // Reverse the order
      ArrayList values = (ArrayList) locales.get(key);
      if (values == null) {
        values = new ArrayList();
        locales.put(key, values);
      }
      values.add(locale);
    }

    // Process the quality values in highest->lowest order (due to
    // negating the Double value when creating the key)
    Iterator keys = locales.keySet().iterator();
    while (keys.hasNext()) {
      Double key = (Double) keys.next();
      ArrayList list = (ArrayList) locales.get(key);
      Iterator values = list.iterator();
      while (values.hasNext()) {
        Locale locale = (Locale) values.next();
        if (debug >= 1) log(" Adding locale '" + locale + "'");
        request.addLocale(locale);
      }
    }
  }

  /**
   * Parse and record the connection parameters related to this request.
   *
   * @param socket The socket on which we are connected
   * @exception IOException if an input/output error occurs
   * @exception ServletException if a parsing error occurs
   */
  private void parseConnection(Socket socket) throws IOException, ServletException {

    if (debug >= 2)
      log(
          "  parseConnection: address="
              + socket.getInetAddress()
              + ", port="
              + connector.getPort());
    ((HttpRequestImpl) request).setInet(socket.getInetAddress());
    if (proxyPort != 0) request.setServerPort(proxyPort);
    else request.setServerPort(serverPort);
    request.setSocket(socket);
  }

  /**
   * Parse the incoming HTTP request headers, and set the appropriate request headers.
   *
   * @param input The input stream connected to our socket
   * @exception IOException if an input/output error occurs
   * @exception ServletException if a parsing error occurs
   */
  private void parseHeaders(SocketInputStream input) throws IOException, ServletException {

    while (true) {

      HttpHeader header = request.allocateHeader();

      // Read the next header
      input.readHeader(header);
      if (header.nameEnd == 0) {
        if (header.valueEnd == 0) {
          return;
        } else {
          throw new ServletException(sm.getString("httpProcessor.parseHeaders.colon"));
        }
      }

      String value = new String(header.value, 0, header.valueEnd);
      if (debug >= 1) log(" Header " + new String(header.name, 0, header.nameEnd) + " = " + value);

      // Set the corresponding request headers
      if (header.equals(DefaultHeaders.AUTHORIZATION_NAME)) {
        request.setAuthorization(value);
      } else if (header.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {
        parseAcceptLanguage(value);
      } else if (header.equals(DefaultHeaders.COOKIE_NAME)) {
        Cookie cookies[] = RequestUtil.parseCookieHeader(value);
        for (int i = 0; i < cookies.length; i++) {
          if (cookies[i].getName().equals(Globals.SESSION_COOKIE_NAME)) {
            // Override anything requested in the URL
            if (!request.isRequestedSessionIdFromCookie()) {
              // Accept only the first session id cookie
              request.setRequestedSessionId(cookies[i].getValue());
              request.setRequestedSessionCookie(true);
              request.setRequestedSessionURL(false);
              if (debug >= 1)
                log(
                    " Requested cookie session id is "
                        + ((HttpServletRequest) request.getRequest()).getRequestedSessionId());
            }
          }
          if (debug >= 1)
            log(" Adding cookie " + cookies[i].getName() + "=" + cookies[i].getValue());
          request.addCookie(cookies[i]);
        }
      } else if (header.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {
        int n = -1;
        try {
          n = Integer.parseInt(value);
        } catch (Exception e) {
          throw new ServletException(sm.getString("httpProcessor.parseHeaders.contentLength"));
        }
        request.setContentLength(n);
      } else if (header.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {
        request.setContentType(value);
      } else if (header.equals(DefaultHeaders.HOST_NAME)) {
        int n = value.indexOf(':');
        if (n < 0) {
          if (connector.getScheme().equals("http")) {
            request.setServerPort(80);
          } else if (connector.getScheme().equals("https")) {
            request.setServerPort(443);
          }
          if (proxyName != null) request.setServerName(proxyName);
          else request.setServerName(value);
        } else {
          if (proxyName != null) request.setServerName(proxyName);
          else request.setServerName(value.substring(0, n).trim());
          if (proxyPort != 0) request.setServerPort(proxyPort);
          else {
            int port = 80;
            try {
              port = Integer.parseInt(value.substring(n + 1).trim());
            } catch (Exception e) {
              throw new ServletException(sm.getString("httpProcessor.parseHeaders.portNumber"));
            }
            request.setServerPort(port);
          }
        }
      } else if (header.equals(DefaultHeaders.CONNECTION_NAME)) {
        if (header.valueEquals(DefaultHeaders.CONNECTION_CLOSE_VALUE)) {
          keepAlive = false;
          response.setHeader("Connection", "close");
        }
        // request.setConnection(header);
        /*
          if ("keep-alive".equalsIgnoreCase(value)) {
          keepAlive = true;
          }
        */
      } else if (header.equals(DefaultHeaders.EXPECT_NAME)) {
        if (header.valueEquals(DefaultHeaders.EXPECT_100_VALUE)) sendAck = true;
        else
          throw new ServletException(sm.getString("httpProcessor.parseHeaders.unknownExpectation"));
      } else if (header.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {
        // request.setTransferEncoding(header);
      }

      request.nextHeader();
    }
  }

  /**
   * Parse the incoming HTTP request and set the corresponding HTTP request properties.
   *
   * @param input The input stream attached to our socket
   * @param output The output stream of the socket
   * @exception IOException if an input/output error occurs
   * @exception ServletException if a parsing error occurs
   */
  private void parseRequest(SocketInputStream input, OutputStream output)
      throws IOException, ServletException {

    // Parse the incoming request line
    input.readRequestLine(requestLine);

    // When the previous method returns, we're actually processing a
    // request
    status = Constants.PROCESSOR_ACTIVE;

    String method = new String(requestLine.method, 0, requestLine.methodEnd);
    String uri = null;
    String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);

    // System.out.println(" Method:" + method + "_ Uri:" + uri
    //                   + "_ Protocol:" + protocol);

    if (protocol.length() == 0) protocol = "HTTP/0.9";

    // Now check if the connection should be kept alive after parsing the
    // request.
    if (protocol.equals("HTTP/1.1")) {
      http11 = true;
      sendAck = false;
    } else {
      http11 = false;
      sendAck = false;
      // For HTTP/1.0, connection are not persistent by default,
      // unless specified with a Connection: Keep-Alive header.
      keepAlive = false;
    }

    // Validate the incoming request line
    if (method.length() < 1) {
      throw new ServletException(sm.getString("httpProcessor.parseRequest.method"));
    } else if (requestLine.uriEnd < 1) {
      throw new ServletException(sm.getString("httpProcessor.parseRequest.uri"));
    }

    // Parse any query parameters out of the request URI
    int question = requestLine.indexOf("?");
    if (question >= 0) {
      request.setQueryString(
          new String(requestLine.uri, question + 1, requestLine.uriEnd - question - 1));
      if (debug >= 1)
        log(" Query string is " + ((HttpServletRequest) request.getRequest()).getQueryString());
      uri = new String(requestLine.uri, 0, question);
    } else {
      request.setQueryString(null);
      uri = new String(requestLine.uri, 0, requestLine.uriEnd);
    }

    // Checking for an absolute URI (with the HTTP protocol)
    if (!uri.startsWith("/")) {
      int pos = uri.indexOf("://");
      // Parsing out protocol and host name
      if (pos != -1) {
        pos = uri.indexOf('/', pos + 3);
        if (pos == -1) {
          uri = "";
        } else {
          uri = uri.substring(pos);
        }
      }
    }

    // Parse any requested session ID out of the request URI
    int semicolon = uri.indexOf(match);
    if (semicolon >= 0) {
      String rest = uri.substring(semicolon + match.length());
      int semicolon2 = rest.indexOf(';');
      if (semicolon2 >= 0) {
        request.setRequestedSessionId(rest.substring(0, semicolon2));
        rest = rest.substring(semicolon2);
      } else {
        request.setRequestedSessionId(rest);
        rest = "";
      }
      request.setRequestedSessionURL(true);
      uri = uri.substring(0, semicolon) + rest;
      if (debug >= 1)
        log(
            " Requested URL session id is "
                + ((HttpServletRequest) request.getRequest()).getRequestedSessionId());
    } else {
      request.setRequestedSessionId(null);
      request.setRequestedSessionURL(false);
    }

    // Normalize URI (using String operations at the moment)
    String normalizedUri = normalize(uri);
    if (debug >= 1) log("Normalized: '" + uri + "' to '" + normalizedUri + "'");

    // Set the corresponding request properties
    ((HttpRequest) request).setMethod(method);
    request.setProtocol(protocol);
    if (normalizedUri != null) {
      ((HttpRequest) request).setRequestURI(normalizedUri);
    } else {
      ((HttpRequest) request).setRequestURI(uri);
    }
    request.setSecure(connector.getSecure());
    request.setScheme(connector.getScheme());

    if (normalizedUri == null) {
      log(" Invalid request URI: '" + uri + "'");
      throw new ServletException("Invalid URI: " + uri + "'");
    }

    if (debug >= 1)
      log(" Request is '" + method + "' for '" + uri + "' with protocol '" + protocol + "'");
  }

  /**
   * Return a context-relative path, beginning with a "/", that represents the canonical version of
   * the specified path after ".." and "." elements are resolved out. If the specified path attempts
   * to go outside the boundaries of the current context (i.e. too many ".." path elements are
   * present), return <code>null</code> instead.
   *
   * @param path Path to be normalized
   */
  protected String normalize(String path) {

    if (path == null) return null;

    // Create a place for the normalized path
    String normalized = path;

    // Normalize "/%7E" and "/%7e" at the beginning to "/~"
    if (normalized.startsWith("/%7E") || normalized.startsWith("/%7e"))
      normalized = "/~" + normalized.substring(4);

    // Prevent encoding '%', '/', '.' and '\', which are special reserved
    // characters
    if ((normalized.indexOf("%25") >= 0)
        || (normalized.indexOf("%2F") >= 0)
        || (normalized.indexOf("%2E") >= 0)
        || (normalized.indexOf("%5C") >= 0)
        || (normalized.indexOf("%2f") >= 0)
        || (normalized.indexOf("%2e") >= 0)
        || (normalized.indexOf("%5c") >= 0)) {
      return null;
    }

    if (normalized.equals("/.")) return "/";

    // Normalize the slashes and add leading slash if necessary
    if (normalized.indexOf('\\') >= 0) normalized = normalized.replace('\\', '/');
    if (!normalized.startsWith("/")) normalized = "/" + normalized;

    // Resolve occurrences of "//" in the normalized path
    while (true) {
      int index = normalized.indexOf("//");
      if (index < 0) break;
      normalized = normalized.substring(0, index) + normalized.substring(index + 1);
    }

    // Resolve occurrences of "/./" in the normalized path
    while (true) {
      int index = normalized.indexOf("/./");
      if (index < 0) break;
      normalized = normalized.substring(0, index) + normalized.substring(index + 2);
    }

    // Resolve occurrences of "/../" in the normalized path
    while (true) {
      int index = normalized.indexOf("/../");
      if (index < 0) break;
      if (index == 0) return (null); // Trying to go outside our context
      int index2 = normalized.lastIndexOf('/', index - 1);
      normalized = normalized.substring(0, index2) + normalized.substring(index + 3);
    }

    // Declare occurrences of "/..." (three or more dots) to be invalid
    // (on some Windows platforms this walks the directory tree!!!)
    if (normalized.indexOf("/...") >= 0) return (null);

    // Return the normalized path that we have completed
    return (normalized);
  }

  /**
   * Send a confirmation that a request has been processed when pipelining. HTTP/1.1 100 Continue is
   * sent back to the client.
   *
   * @param output Socket output stream
   */
  private void ackRequest(OutputStream output) throws IOException {
    if (sendAck) output.write(ack);
  }

  /**
   * Process an incoming HTTP request on the Socket that has been assigned to this Processor. Any
   * exceptions that occur during processing must be swallowed and dealt with.
   *
   * @param socket The socket on which we are connected to the client
   */
  private void process(Socket socket) {

    boolean ok = true;
    boolean finishResponse = true;
    SocketInputStream input = null;
    OutputStream output = null;

    // Construct and initialize the objects we will need
    try {
      input = new SocketInputStream(socket.getInputStream(), connector.getBufferSize());
    } catch (Exception e) {
      log("process.create", e);
      ok = false;
    }

    keepAlive = true;

    while (!stopped && ok && keepAlive) {

      finishResponse = true;

      try {
        request.setStream(input);
        request.setResponse(response);
        output = socket.getOutputStream();
        response.setStream(output);
        response.setRequest(request);
        ((HttpServletResponse) response.getResponse()).setHeader("Server", SERVER_INFO);
      } catch (Exception e) {
        log("process.create", e);
        ok = false;
      }

      // Parse the incoming request
      try {
        if (ok) {
          parseConnection(socket);
          parseRequest(input, output);
          if (!request.getRequest().getProtocol().startsWith("HTTP/0")) parseHeaders(input);
          if (http11) {
            // Sending a request acknowledge back to the client if
            // requested.
            ackRequest(output);
            // If the protocol is HTTP/1.1, chunking is allowed.
            if (connector.isChunkingAllowed()) response.setAllowChunking(true);
          }
        }
      } catch (EOFException e) {
        // It's very likely to be a socket disconnect on either the
        // client or the server
        ok = false;
        finishResponse = false;
      } catch (ServletException e) {
        ok = false;
        try {
          ((HttpServletResponse) response.getResponse())
              .sendError(HttpServletResponse.SC_BAD_REQUEST);
        } catch (Exception f) {;
        }
      } catch (InterruptedIOException e) {
        if (debug > 1) {
          try {
            log("process.parse", e);
            ((HttpServletResponse) response.getResponse())
                .sendError(HttpServletResponse.SC_BAD_REQUEST);
          } catch (Exception f) {;
          }
        }
        ok = false;
      } catch (Exception e) {
        try {
          log("process.parse", e);
          ((HttpServletResponse) response.getResponse())
              .sendError(HttpServletResponse.SC_BAD_REQUEST);
        } catch (Exception f) {;
        }
        ok = false;
      }

      // Ask our Container to process this request
      try {
        ((HttpServletResponse) response).setHeader("Date", FastHttpDateFormat.getCurrentDate());
        if (ok) {
          connector.getContainer().invoke(request, response);
        }
      } catch (ServletException e) {
        log("process.invoke", e);
        try {
          ((HttpServletResponse) response.getResponse())
              .sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (Exception f) {;
        }
        ok = false;
      } catch (InterruptedIOException e) {
        ok = false;
      } catch (Throwable e) {
        log("process.invoke", e);
        try {
          ((HttpServletResponse) response.getResponse())
              .sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (Exception f) {;
        }
        ok = false;
      }

      // Finish up the handling of the request
      if (finishResponse) {
        try {
          response.finishResponse();
        } catch (IOException e) {
          ok = false;
        } catch (Throwable e) {
          log("process.invoke", e);
          ok = false;
        }
        try {
          request.finishRequest();
        } catch (IOException e) {
          ok = false;
        } catch (Throwable e) {
          log("process.invoke", e);
          ok = false;
        }
        try {
          if (output != null) output.flush();
        } catch (IOException e) {
          ok = false;
        }
      }

      // We have to check if the connection closure has been requested
      // by the application or the response stream (in case of HTTP/1.0
      // and keep-alive).
      if ("close".equals(response.getHeader("Connection"))) {
        keepAlive = false;
      }

      // End of request processing
      status = Constants.PROCESSOR_IDLE;

      // Recycling the request and the response objects
      request.recycle();
      response.recycle();
    }

    try {
      shutdownInput(input);
      socket.close();
    } catch (IOException e) {;
    } catch (Throwable e) {
      log("process.invoke", e);
    }
    socket = null;
  }

  protected void shutdownInput(InputStream input) {
    try {
      int available = input.available();
      // skip any unread (bogus) bytes
      if (available > 0) {
        input.skip(available);
      }
    } catch (Throwable e) {;
    }
  }

  // ---------------------------------------------- Background Thread Methods

  /**
   * The background thread that listens for incoming TCP/IP connections and hands them off to an
   * appropriate processor.
   */
  public void run() {

    // Process requests until we receive a shutdown signal
    while (!stopped) {

      // Wait for the next socket to be assigned
      Socket socket = await();
      if (socket == null) continue;

      // Process the request from this socket
      try {
        process(socket);
      } catch (Throwable t) {
        log("process.invoke", t);
      }

      // Finish up this request
      connector.recycle(this);
    }

    // Tell threadStop() we have shut ourselves down successfully
    synchronized (threadSync) {
      threadSync.notifyAll();
    }
  }

  /** Start the background processing thread. */
  private void threadStart() {

    log(sm.getString("httpProcessor.starting"));

    thread = new Thread(this, threadName);
    thread.setDaemon(true);
    thread.start();

    if (debug >= 1) log(" Background thread has been started");
  }

  /** Stop the background processing thread. */
  private void threadStop() {

    log(sm.getString("httpProcessor.stopping"));

    stopped = true;
    assign(null);

    if (status != Constants.PROCESSOR_IDLE) {
      // Only wait if the processor is actually processing a command
      synchronized (threadSync) {
        try {
          threadSync.wait(5000);
        } catch (InterruptedException e) {;
        }
      }
    }
    thread = null;
  }

  // ------------------------------------------------------ Lifecycle Methods

  /**
   * Add a lifecycle event listener to this component.
   *
   * @param listener The listener to add
   */
  public void addLifecycleListener(LifecycleListener listener) {

    lifecycle.addLifecycleListener(listener);
  }

  /**
   * Get the lifecycle listeners associated with this lifecycle. If this Lifecycle has no listeners
   * registered, a zero-length array is returned.
   */
  public LifecycleListener[] findLifecycleListeners() {

    return lifecycle.findLifecycleListeners();
  }

  /**
   * Remove a lifecycle event listener from this component.
   *
   * @param listener The listener to add
   */
  public void removeLifecycleListener(LifecycleListener listener) {

    lifecycle.removeLifecycleListener(listener);
  }

  /**
   * Start the background thread we will use for request processing.
   *
   * @exception LifecycleException if a fatal startup error occurs
   */
  public void start() throws LifecycleException {

    if (started) throw new LifecycleException(sm.getString("httpProcessor.alreadyStarted"));
    lifecycle.fireLifecycleEvent(START_EVENT, null);
    started = true;

    threadStart();
  }

  /**
   * Stop the background thread we will use for request processing.
   *
   * @exception LifecycleException if a fatal shutdown error occurs
   */
  public void stop() throws LifecycleException {

    if (!started) throw new LifecycleException(sm.getString("httpProcessor.notStarted"));
    lifecycle.fireLifecycleEvent(STOP_EVENT, null);
    started = false;

    threadStop();
  }
}