public void init() throws javax.servlet.ServletException {
    super.init();

    org.slf4j.Logger logServerStartup = org.slf4j.LoggerFactory.getLogger("serverStartup");
    logServerStartup.info(
        getClass().getName() + " initialization start - " + UsageLog.setupNonRequestContext());

    this.ascLimit = ThreddsConfig.getInt("Opendap.ascLimit", ascLimit);
    this.binLimit = ThreddsConfig.getInt("Opendap.binLimit", binLimit);

    this.odapVersionString = ThreddsConfig.get("Opendap.serverVersion", odapVersionString);
    logServerStartup.info(
        getClass().getName()
            + " version= "
            + odapVersionString
            + " ascLimit = "
            + ascLimit
            + " binLimit = "
            + binLimit);

    // debugging actions
    makeDebugActions();

    logServerStartup.info(
        getClass().getName()
            + " initialization done - "
            + UsageLog.closingMessageNonRequestContext());
  }
  /**
   * Send given content string as the HTTP response.
   *
   * @param contents the string to return as the HTTP response.
   * @param res the HttpServletResponse
   * @throws IOException if an I/O error occurs while writing the response.
   */
  public static void returnString(String contents, HttpServletResponse res) throws IOException {

    try {
      ServletOutputStream out = res.getOutputStream();
      IO.copy(new ByteArrayInputStream(contents.getBytes()), out);
      log.info(
          UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_OK, contents.length()));
    } catch (IOException e) {
      log.error(" IOException sending string: ", e);
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0));
      res.sendError(HttpServletResponse.SC_NOT_FOUND, "Problem sending string: " + e.getMessage());
    }
  }
  /**
   * Write a file to the response stream. Handles Range requests.
   *
   * @param servlet called from here
   * @param req the request
   * @param res the response
   * @param file to serve
   * @param contentType content type, if null, will try to guess
   * @throws IOException on write error
   */
  public static void returnFile(
      HttpServlet servlet,
      HttpServletRequest req,
      HttpServletResponse res,
      File file,
      String contentType)
      throws IOException {

    // No file, nothing to view
    if (file == null) {
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0));
      res.sendError(HttpServletResponse.SC_NOT_FOUND);
      return;
    }

    // check that it exists
    if (!file.exists()) {
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0));
      res.sendError(HttpServletResponse.SC_NOT_FOUND);
      return;
    }

    // not a directory
    if (!file.isFile()) {
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_BAD_REQUEST, 0));
      res.sendError(HttpServletResponse.SC_BAD_REQUEST);
      return;
    }

    // Set the type of the file
    String filename = file.getPath();
    if (null == contentType) {
      if (filename.endsWith(".html")) contentType = "text/html; charset=iso-8859-1";
      else if (filename.endsWith(".xml")) contentType = "text/xml; charset=iso-8859-1";
      else if (filename.endsWith(".txt") || (filename.endsWith(".log"))) contentType = CONTENT_TEXT;
      else if (filename.indexOf(".log.") > 0) contentType = CONTENT_TEXT;
      else if (filename.endsWith(".nc")) contentType = "application/x-netcdf";
      else contentType = servlet.getServletContext().getMimeType(filename);

      if (contentType == null) contentType = "application/octet-stream";
    }

    returnFile(req, res, file, contentType);
  }
  /**
   * Write a file to the response stream.
   *
   * @param servlet called from here
   * @param contentPath file root path
   * @param path file path reletive to the root
   * @param req the request
   * @param res the response
   * @param contentType content type, or null
   * @throws IOException on write error
   */
  public static void returnFile(
      HttpServlet servlet,
      String contentPath,
      String path,
      HttpServletRequest req,
      HttpServletResponse res,
      String contentType)
      throws IOException {

    String filename = ServletUtil.formFilename(contentPath, path);

    log.debug("returnFile(): returning file <" + filename + ">.");
    // No file, nothing to view
    if (filename == null) {
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0));
      res.sendError(HttpServletResponse.SC_NOT_FOUND);
      return;
    }

    // dontallow ..
    if (filename.indexOf("..") != -1) {
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_FORBIDDEN, 0));
      res.sendError(HttpServletResponse.SC_FORBIDDEN);
      return;
    }

    // dont allow access to WEB-INF or META-INF
    String upper = filename.toUpperCase();
    if (upper.indexOf("WEB-INF") != -1 || upper.indexOf("META-INF") != -1) {
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_FORBIDDEN, 0));
      res.sendError(HttpServletResponse.SC_FORBIDDEN);
      return;
    }

    returnFile(servlet, req, res, new File(filename), contentType);
  }
  public static boolean saveFile(
      HttpServlet servlet,
      String contentPath,
      String path,
      HttpServletRequest req,
      HttpServletResponse res) {

    // @todo Need to use logServerAccess() below here.
    boolean debugRequest = Debug.isSet("SaveFile");
    if (debugRequest) log.debug(" saveFile(): path= " + path);

    String filename = contentPath + path; // absolute path
    File want = new File(filename);

    // backup current version if it exists
    int version = getBackupVersion(want.getParent(), want.getName());
    String fileSave = filename + "~" + version;
    File file = new File(filename);
    if (file.exists()) {
      try {
        IO.copyFile(filename, fileSave);
      } catch (IOException e) {
        log.error(
            "saveFile(): Unable to save copy of file "
                + filename
                + " to "
                + fileSave
                + "\n"
                + e.getMessage());
        return false;
      }
    }

    // save new file
    try {
      OutputStream out = new BufferedOutputStream(new FileOutputStream(filename));
      IO.copy(req.getInputStream(), out);
      out.close();
      if (debugRequest) log.debug("saveFile(): ok= " + filename);
      res.setStatus(HttpServletResponse.SC_CREATED);
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_CREATED, -1));
      return true;
    } catch (IOException e) {
      log.error(
          "saveFile(): Unable to PUT file " + filename + " to " + fileSave + "\n" + e.getMessage());
      return false;
    }
  }
  private void sendErrorResponse(HttpServletResponse response, int errorCode, String errorMessage) {
    try {
      log.info(UsageLog.closingMessageForRequestContext(errorCode, -1));
      response.setStatus(errorCode);
      response.setHeader("Content-Description", "dods-error");
      response.setContentType("text/plain");

      PrintWriter pw = new PrintWriter(response.getOutputStream());
      pw.println("Error {");
      pw.println("    code = " + errorCode + ";");
      pw.println("    message = \"" + errorMessage + "\";");

      pw.println("};");
      pw.flush();
    } catch (Exception e) {
      System.err.println("sendErrorResponse: " + e);
    }
  }
  /**
   * ************************************************************************ Sends an error to the
   * client.
   *
   * @param t The exception that caused the problem.
   * @param res The <code>HttpServletResponse</code> for the client.
   */
  public static void handleException(Throwable t, HttpServletResponse res) {
    try {
      String message = t.getMessage();
      if (message == null) message = "NULL message " + t.getClass().getName();
      if (Debug.isSet("trustedMode")) { // security issue: only show stack if trusted
        ByteArrayOutputStream bs = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(bs);
        t.printStackTrace(ps);
        message = new String(bs.toByteArray());
      }
      log.info(
          UsageLog.closingMessageForRequestContext(
              HttpServletResponse.SC_BAD_REQUEST, message.length()));
      log.error("handleException", t);
      t.printStackTrace(); // debugging - log.error not showing stack trace !!
      if (!res.isCommitted()) res.sendError(HttpServletResponse.SC_BAD_REQUEST, message);

    } catch (Throwable e) {
      log.error("handleException() had problem reporting Exception", e);
      t.printStackTrace();
    }
  }
  public void doGet(HttpServletRequest request, HttpServletResponse response) {
    log.info("doGet(): " + UsageLog.setupRequestContext(request));
    // System.out.printf("opendap doGet: req=%s%n%s%n", ServletUtil.getRequest(request),
    // ServletUtil.showRequestDetail(this, request));

    String path = null;

    ReqState rs = getRequestState(request, response);

    try {
      path = request.getPathInfo();
      log.debug("doGet path={}", path);

      if (thredds.servlet.Debug.isSet("showRequestDetail"))
        log.debug(ServletUtil.showRequestDetail(this, request));

      if (path == null) {
        log.info(
            "doGet(): "
                + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, -1));
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
      }

      if (baseURI == null) { // first time, set baseURI
        URI reqURI = ServletUtil.getRequestURI(request);
        // Build base URI from request (rather than hard-coding "/thredds/dodsC/").
        String baseUriString = request.getContextPath() + request.getServletPath() + "/";
        baseURI = reqURI.resolve(baseUriString);
        log.debug("doGet(): baseURI was set = {}", baseURI);
      }

      if (path.endsWith("latest.xml")) {
        DataRootHandler.getInstance().processReqForLatestDataset(this, request, response);
        return;
      }

      // Redirect all catalog requests at the root level.
      if (path.equals("/") || path.equals("/catalog.html") || path.equals("/catalog.xml")) {
        ServletUtil.sendPermanentRedirect(ServletUtil.getContextPath() + path, request, response);
        return;
      }

      // Make sure catalog requests match a dataRoot before trying to handle.
      if (path.endsWith("/") || path.endsWith("/catalog.html") || path.endsWith("/catalog.xml")) {
        if (!DataRootHandler.getInstance().hasDataRootMatch(path)) {
          log.info(
              "doGet(): "
                  + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, -1));
          response.sendError(HttpServletResponse.SC_NOT_FOUND);
          return;
        }

        if (!DataRootHandler.getInstance().processReqForCatalog(request, response))
          log.error(
              "doGet(): "
                  + UsageLog.closingMessageForRequestContext(
                      ServletUtil.STATUS_FORWARD_FAILURE, -1));

        return;
      }

      if (rs != null) {
        String dataSet = rs.getDataSet();
        String requestSuffix = rs.getRequestSuffix();

        if ((dataSet == null) || dataSet.equals("/") || dataSet.equals("")) {
          doGetDIR(rs);
        } else if (requestSuffix.equalsIgnoreCase("blob")) {
          doGetBLOB(rs);
        } else if (requestSuffix.equalsIgnoreCase("close")) {
          doClose(rs);
        } else if (requestSuffix.equalsIgnoreCase("dds")) {
          doGetDDS(rs);
        } else if (requestSuffix.equalsIgnoreCase("das")) {
          doGetDAS(rs);
        } else if (requestSuffix.equalsIgnoreCase("ddx")) {
          doGetDDX(rs);
        } else if (requestSuffix.equalsIgnoreCase("dods")) {
          doGetDAP2Data(rs);
        } else if (requestSuffix.equalsIgnoreCase("asc")
            || requestSuffix.equalsIgnoreCase("ascii")) {
          doGetASC(rs);
        } else if (requestSuffix.equalsIgnoreCase("info")) {
          doGetINFO(rs);
        } else if (requestSuffix.equalsIgnoreCase("html")
            || requestSuffix.equalsIgnoreCase("htm")) {
          doGetHTML(rs);
        } else if (requestSuffix.equalsIgnoreCase("ver")
            || requestSuffix.equalsIgnoreCase("version")
            || dataSet.equalsIgnoreCase("/version")
            || dataSet.equalsIgnoreCase("/version/")) {
          doGetVER(rs);
        } else if (dataSet.equalsIgnoreCase("/help")
            || dataSet.equalsIgnoreCase("/help/")
            || dataSet.equalsIgnoreCase("/" + requestSuffix)
            || requestSuffix.equalsIgnoreCase("help")) {
          doGetHELP(rs);
        } else {
          sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST, "Unrecognized request");
          return;
        }

      } else {
        sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST, "Unrecognized request");
        return;
      }

      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_OK, -1));

      // plain ol' 404
    } catch (FileNotFoundException e) {
      sendErrorResponse(response, HttpServletResponse.SC_NOT_FOUND, e.getMessage());

      // DAP2Exception bad url
    } catch (BadURLException e) {
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_BAD_REQUEST, -1));
      response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
      dap2ExceptionHandler(e, rs);

      // all other DAP2Exception
    } catch (DAP2Exception de) {
      int status =
          (de.getErrorCode() == DAP2Exception.NO_SUCH_FILE)
              ? HttpServletResponse.SC_NOT_FOUND
              : HttpServletResponse.SC_BAD_REQUEST;
      if ((de.getErrorCode() != DAP2Exception.NO_SUCH_FILE) && (de.getErrorMessage() != null))
        log.debug(de.getErrorMessage());
      log.info(UsageLog.closingMessageForRequestContext(status, -1));
      response.setStatus(status);
      dap2ExceptionHandler(de, rs);

      // parsing, usually the CE
    } catch (ParseException pe) {
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_BAD_REQUEST, -1));
      response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
      parseExceptionHandler(pe, response);

      // 403 - request too big
    } catch (UnsupportedOperationException e) {
      sendErrorResponse(response, HttpServletResponse.SC_FORBIDDEN, e.getMessage());

    } catch (java.net.SocketException e) {
      log.info("SocketException: " + e.getMessage(), e);
      log.info(UsageLog.closingMessageForRequestContext(ServletUtil.STATUS_CLIENT_ABORT, -1));

    } catch (IOException e) {
      String eName =
          e.getClass().getName(); // dont want compile time dependency on ClientAbortException
      if (eName.equals("org.apache.catalina.connector.ClientAbortException")) {
        log.debug("ClientAbortException: " + e.getMessage());
        log.info(UsageLog.closingMessageForRequestContext(ServletUtil.STATUS_CLIENT_ABORT, -1));
        return;
      }

      log.error("path= " + path, e);
      sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());

      // everything else
    } catch (Throwable t) {
      log.error("path= " + path, t);
      t.printStackTrace();
      sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, t.getMessage());
    }
  }
  /**
   * Write a file to the response stream. Handles Range requests.
   *
   * @param req request
   * @param res response
   * @param file must exists and not be a directory
   * @param contentType must not be null
   * @throws IOException or error
   */
  public static void returnFile(
      HttpServletRequest req, HttpServletResponse res, File file, String contentType)
      throws IOException {
    res.setContentType(contentType);

    // see if its a Range Request
    boolean isRangeRequest = false;
    long startPos = 0, endPos = Long.MAX_VALUE;
    String rangeRequest = req.getHeader("Range");
    if (rangeRequest != null) { // bytes=12-34 or bytes=12-
      int pos = rangeRequest.indexOf("=");
      if (pos > 0) {
        int pos2 = rangeRequest.indexOf("-");
        if (pos2 > 0) {
          String startString = rangeRequest.substring(pos + 1, pos2);
          String endString = rangeRequest.substring(pos2 + 1);
          startPos = Long.parseLong(startString);
          if (endString.length() > 0) endPos = Long.parseLong(endString) + 1;
          isRangeRequest = true;
        }
      }
    }

    // set content length
    long fileSize = file.length();
    long contentLength = fileSize;
    if (isRangeRequest) {
      endPos = Math.min(endPos, fileSize);
      contentLength = endPos - startPos;
    }

    if (contentLength > Integer.MAX_VALUE)
      res.addHeader(
          "Content-Length", Long.toString(contentLength)); // allow content length > MAX_INT
    else res.setContentLength((int) contentLength); // note HEAD only allows this

    String filename = file.getPath();
    boolean debugRequest = Debug.isSet("returnFile");
    if (debugRequest)
      log.debug(
          "returnFile(): filename = "
              + filename
              + " contentType = "
              + contentType
              + " contentLength = "
              + contentLength);

    // indicate we allow Range Requests
    res.addHeader("Accept-Ranges", "bytes");

    if (req.getMethod().equals("HEAD")) {
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_OK, 0));
      return;
    }

    try {

      if (isRangeRequest) {
        // set before content is sent
        res.addHeader("Content-Range", "bytes " + startPos + "-" + (endPos - 1) + "/" + fileSize);
        res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

        FileCacheRaf.Raf craf = null;
        try {
          craf = fileCacheRaf.acquire(filename);
          IO.copyRafB(
              craf.getRaf(), startPos, contentLength, res.getOutputStream(), new byte[60000]);
          log.info(
              "returnFile(): "
                  + UsageLog.closingMessageForRequestContext(
                      HttpServletResponse.SC_PARTIAL_CONTENT, contentLength));
          return;
        } finally {
          if (craf != null) fileCacheRaf.release(craf);
        }
      }

      // Return the file
      ServletOutputStream out = res.getOutputStream();
      IO.copyFileB(file, out, 60000);
      res.flushBuffer();
      out.close();
      if (debugRequest) log.debug("returnFile(): returnFile ok = " + filename);
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_OK, contentLength));
    }

    // @todo Split up this exception handling: those from file access vs those from dealing with
    // response
    //       File access: catch and res.sendError()
    //       response: don't catch (let bubble up out of doGet() etc)
    catch (FileNotFoundException e) {
      log.error("returnFile(): FileNotFoundException= " + filename);
      log.info(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0));
      if (!res.isCommitted()) res.sendError(HttpServletResponse.SC_NOT_FOUND);
    } catch (java.net.SocketException e) {
      log.info("returnFile(): SocketException sending file: " + filename + " " + e.getMessage());
      log.info("returnFile(): " + UsageLog.closingMessageForRequestContext(STATUS_CLIENT_ABORT, 0));
    } catch (IOException e) {
      String eName =
          e.getClass().getName(); // dont want compile time dependency on ClientAbortException
      if (eName.equals("org.apache.catalina.connector.ClientAbortException")) {
        log.info(
            "returnFile(): ClientAbortException while sending file: "
                + filename
                + " "
                + e.getMessage());
        log.info(
            "returnFile(): " + UsageLog.closingMessageForRequestContext(STATUS_CLIENT_ABORT, 0));
        return;
      }

      log.error("returnFile(): IOException (" + e.getClass().getName() + ") sending file ", e);
      log.error(
          "returnFile(): "
              + UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, 0));
      if (!res.isCommitted())
        res.sendError(HttpServletResponse.SC_NOT_FOUND, "Problem sending file: " + e.getMessage());
    }
  }
  /**
   * Send a permanent redirect (HTTP status 301 "Moved Permanently") response with the given target
   * path.
   *
   * <p>The given target path may be relative or absolute. If it is relative, it will be resolved
   * against the request URL.
   *
   * @param targetPath the path to which the client is redirected.
   * @param req the HttpServletRequest
   * @param res the HttpServletResponse
   * @throws IOException if can't write the response.
   */
  public static void sendPermanentRedirect(
      String targetPath, HttpServletRequest req, HttpServletResponse res) throws IOException {
    // Absolute URL needed so resolve the target path against the request URL.
    URI uri;
    try {
      uri = new URI(req.getRequestURL().toString());
    } catch (URISyntaxException e) {
      log.error(
          "sendPermanentRedirect(): Bad syntax on request URL <" + req.getRequestURL() + ">.", e);
      log.info(
          "sendPermanentRedirect(): "
              + UsageLog.closingMessageForRequestContext(
                  HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 0));
      if (!res.isCommitted()) res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
      return;
    }
    String absolutePath = uri.resolve(targetPath).toString();
    absolutePath = res.encodeRedirectURL(absolutePath);

    res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
    res.addHeader("Location", absolutePath);

    String title = "Permanently Moved - 301";
    String body =
        new StringBuilder()
            .append("<p>")
            .append("The requested URL <")
            .append(req.getRequestURL())
            .append("> has been permanently moved (HTTP status code 301).")
            .append(" Instead, please use the following URL: <a href=\"")
            .append(absolutePath)
            .append("\">")
            .append(absolutePath)
            .append("</a>.")
            .append("</p>")
            .toString();
    String htmlResp =
        new StringBuilder()
            .append(HtmlWriter.getInstance().getHtmlDoctypeAndOpenTag())
            .append("<head><title>")
            .append(title)
            .append("</title></head><body>")
            .append("<h1>")
            .append(title)
            .append("</h1>")
            .append(body)
            .append("</body></html>")
            .toString();

    log.info("sendPermanentRedirect(): redirect to " + absolutePath);
    log.info(
        "sendPermanentRedirect(): "
            + UsageLog.closingMessageForRequestContext(
                HttpServletResponse.SC_MOVED_PERMANENTLY, htmlResp.length()));

    // Write the catalog out.
    PrintWriter out = res.getWriter();
    res.setContentType("text/html");
    out.print(htmlResp);
    out.flush();
  }
  /**
   * Convenience routine used by handleRequestForContentFile() and handleRequestForRootFile().
   *
   * @param pathPrefix
   * @param path
   * @param servlet
   * @param req request
   * @param res response
   * @throws IOException on IO error
   */
  private static void handleRequestForContentOrRootFile(
      String pathPrefix,
      String path,
      HttpServlet servlet,
      HttpServletRequest req,
      HttpServletResponse res)
      throws IOException {
    if (!pathPrefix.equals("/content/") && !pathPrefix.equals("/root/")) {
      log.error(
          "handleRequestForContentFile(): The path prefix <"
              + pathPrefix
              + "> must be \"/content/\" or \"/root/\".");
      throw new IllegalArgumentException("Path prefix must be \"/content/\" or \"/root/\".");
    }

    if (!path.startsWith(pathPrefix)) {
      log.error(
          "handleRequestForContentFile(): path <"
              + path
              + "> must start with \""
              + pathPrefix
              + "\".");
      throw new IllegalArgumentException("Path must start with \"" + pathPrefix + "\".");
    }

    // Don't allow ".." directories in path.
    if (path.indexOf("/../") != -1
        || path.equals("..")
        || path.startsWith("../")
        || path.endsWith("/..")) {
      res.sendError(HttpServletResponse.SC_FORBIDDEN, "Path cannot contain \"..\" directory.");
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_FORBIDDEN, -1));
      return;
    }

    // Find the requested file.
    File file =
        new File(
            ServletUtil.formFilename(getContentPath(), path.substring(pathPrefix.length() - 1)));
    if (file.exists()) {
      // Do not allow request for a directory.
      if (file.isDirectory()) {
        if (!path.endsWith("/")) {
          String redirectPath = req.getRequestURL().append("/").toString();
          ServletUtil.sendPermanentRedirect(redirectPath, req, res);
          return;
        }

        int i = HtmlWriter.getInstance().writeDirectory(res, file, path);
        int status = i == 0 ? HttpServletResponse.SC_NOT_FOUND : HttpServletResponse.SC_OK;
        log.info(UsageLog.closingMessageForRequestContext(status, i));

        return;
      }

      // Return the requested file.
      ServletUtil.returnFile(servlet, req, res, file, null);
    } else {
      // Requested file not found.
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, -1));
      res.sendError(HttpServletResponse.SC_NOT_FOUND); // 404
    }
  }
  /**
   * Handle a request for a raw/static file (i.e., not a catalog or dataset request).
   *
   * <p>Look in the content (user) directory then the root (distribution) directory for a file that
   * matches the given path and, if found, return it as the content of the HttpServletResponse. If
   * the file is forbidden (i.e., the path contains a "..", "WEB-INF", or "META-INF" directory),
   * send a HttpServletResponse.SC_FORBIDDEN response. If no file matches the request (including an
   * "index.html" file if the path ends in "/"), send an HttpServletResponse.SC_NOT_FOUND..
   *
   * <p>
   *
   * <ol>
   *   <li>Make sure the path does not contain ".." directories.
   *   <li>Make sure the path does not contain "WEB-INF" or "META-INF".
   *   <li>Check for requested file in the content directory (if the path is a directory, make sure
   *       the path ends with "/" and check for an "index.html" file).
   *   <li>Check for requested file in the root directory (if the path is a directory, make sure the
   *       path ends with "/" and check for an "index.html" file). </ol
   *
   * @param path the requested path
   * @param servlet the servlet handling the request
   * @param req the HttpServletRequest
   * @param res the HttpServletResponse
   * @throws IOException if can't complete request due to IO problems.
   */
  public static void handleRequestForRawFile(
      String path, HttpServlet servlet, HttpServletRequest req, HttpServletResponse res)
      throws IOException {
    // Don't allow ".." directories in path.
    if (path.indexOf("/../") != -1
        || path.equals("..")
        || path.startsWith("../")
        || path.endsWith("/..")) {
      res.sendError(HttpServletResponse.SC_FORBIDDEN, "Path cannot contain \"..\" directory.");
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_FORBIDDEN, -1));
      return;
    }

    // Don't allow access to WEB-INF or META-INF directories.
    String upper = path.toUpperCase();
    if (upper.indexOf("WEB-INF") != -1 || upper.indexOf("META-INF") != -1) {
      res.sendError(
          HttpServletResponse.SC_FORBIDDEN, "Path cannot contain \"WEB-INF\" or \"META-INF\".");
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_FORBIDDEN, -1));
      return;
    }

    // Find a regular file
    File regFile = null;
    // Look in content directory for regular file.
    File cFile = new File(ServletUtil.formFilename(getContentPath(), path));
    if (cFile.exists()) {
      if (cFile.isDirectory()) {
        if (!path.endsWith("/")) {
          String newPath = req.getRequestURL().append("/").toString();
          ServletUtil.sendPermanentRedirect(newPath, req, res);
        }
        // If request path is a directory, check for index.html file.
        cFile = new File(cFile, "index.html");
        if (cFile.exists() && !cFile.isDirectory()) regFile = cFile;
      }
      // If not a directory, use this file.
      else regFile = cFile;
    }

    if (regFile == null) {
      // Look in root directory.
      File rFile = new File(ServletUtil.formFilename(getRootPath(), path));
      if (rFile.exists()) {
        if (rFile.isDirectory()) {
          if (!path.endsWith("/")) {
            String newPath = req.getRequestURL().append("/").toString();
            ServletUtil.sendPermanentRedirect(newPath, req, res);
          }
          rFile = new File(rFile, "index.html");
          if (rFile.exists() && !rFile.isDirectory()) regFile = rFile;
        } else regFile = rFile;
      }
    }

    if (regFile == null) {
      res.sendError(HttpServletResponse.SC_NOT_FOUND); // 404
      log.info(UsageLog.closingMessageForRequestContext(HttpServletResponse.SC_NOT_FOUND, -1));
      return;
    }

    ServletUtil.returnFile(servlet, req, res, regFile, null);
  }