public Response serveFile(String uri, Properties header, File homeDir,
      boolean allowDirectoryListing) {
    Response res = null;

    // Make sure we won't die of an exception later
    if (!homeDir.isDirectory())
      res =
          new Response(HTTP_INTERNALERROR, MIME_PLAINTEXT,
              "INTERNAL ERRROR: serveFile(): given homeDir is not a directory.");

    if (res == null) {
      // Remove URL arguments
      uri = uri.trim().replace(File.separatorChar, '/');
      if (uri.indexOf('?') >= 0) uri = uri.substring(0, uri.indexOf('?'));

      // Prohibit getting out of current directory
      if (uri.startsWith("..") || uri.endsWith("..") || uri.indexOf("../") >= 0)
        res =
            new Response(HTTP_FORBIDDEN, MIME_PLAINTEXT,
                "FORBIDDEN: Won't serve ../ for security reasons.");
    }

    File f = new File(homeDir, uri);
    if (res == null && !f.exists())
      res = new Response(HTTP_NOTFOUND, MIME_PLAINTEXT, "Error 404, file not found.");

    // List the directory, if necessary
    if (res == null && f.isDirectory()) {
      // Browsers get confused without '/' after the
      // directory, send a redirect.
      if (!uri.endsWith("/")) {
        uri += "/";
        res =
            new Response(HTTP_REDIRECT, MIME_HTML, "<html><body>Redirected: <a href=\"" + uri
                + "\">" + uri + "</a></body></html>");
        res.addHeader("Location", uri);
      }

      if (res == null) {
        // First try index.html and index.htm
        if (new File(f, "index.html").exists())
          f = new File(homeDir, uri + "/index.html");
        else if (new File(f, "index.htm").exists())
          f = new File(homeDir, uri + "/index.htm");
        // No index file, list the directory if it is readable
        else if (allowDirectoryListing && f.canRead()) {
          String[] files = f.list();
          String msg = "<html><body><h1>Directory " + uri + "</h1><br/>";

          if (uri.length() > 1) {
            String u = uri.substring(0, uri.length() - 1);
            int slash = u.lastIndexOf('/');
            if (slash >= 0 && slash < u.length())
              msg += "<b><a href=\"" + uri.substring(0, slash + 1) + "\">..</a></b><br/>";
          }

          if (files != null) {
            for (int i = 0; i < files.length; ++i) {
              File curFile = new File(f, files[i]);
              boolean dir = curFile.isDirectory();
              if (dir) {
                msg += "<b>";
                files[i] += "/";
              }

              msg += "<a href=\"" + encodeUri(uri + files[i]) + "\">" + files[i] + "</a>";

              // Show file size
              if (curFile.isFile()) {
                long len = curFile.length();
                msg += " &nbsp;<font size=2>(";
                if (len < 1024)
                  msg += len + " bytes";
                else if (len < 1024 * 1024)
                  msg += len / 1024 + "." + (len % 1024 / 10 % 100) + " KB";
                else
                  msg += len / (1024 * 1024) + "." + len % (1024 * 1024) / 10 % 100 + " MB";

                msg += ")</font>";
              }
              msg += "<br/>";
              if (dir) msg += "</b>";
            }
          }
          msg += "</body></html>";
          res = new Response(HTTP_OK, MIME_HTML, msg);
        } else {
          res = new Response(HTTP_FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: No directory listing.");
        }
      }
    }

    try {
      if (res == null) {
        // Get MIME type from file name extension, if possible
        String mime = null;
        int dot = f.getCanonicalPath().lastIndexOf('.');
        if (dot >= 0)
          mime = (String) theMimeTypes.get(f.getCanonicalPath().substring(dot + 1).toLowerCase());
        if (mime == null) mime = MIME_DEFAULT_BINARY;

        // Calculate etag
        String etag =
            Integer.toHexString((f.getAbsolutePath() + f.lastModified() + "" + f.length())
                .hashCode());

        // Support (simple) skipping:
        long startFrom = 0;
        long endAt = -1;
        String range = header.getProperty("range");
        if (range != null) {
          if (range.startsWith("bytes=")) {
            range = range.substring("bytes=".length());
            int minus = range.indexOf('-');
            try {
              if (minus > 0) {
                startFrom = Long.parseLong(range.substring(0, minus));
                endAt = Long.parseLong(range.substring(minus + 1));
              }
            } catch (NumberFormatException nfe) {}
          }
        }

        // Change return code and add Content-Range header when skipping is requested
        long fileLen = f.length();
        if (range != null && startFrom >= 0) {
          if (startFrom >= fileLen) {
            res = new Response(HTTP_RANGE_NOT_SATISFIABLE, MIME_PLAINTEXT, "");
            res.addHeader("Content-Range", "bytes 0-0/" + fileLen);
            res.addHeader("ETag", etag);
          } else {
            if (endAt < 0) endAt = fileLen - 1;
            long newLen = endAt - startFrom + 1;
            if (newLen < 0) newLen = 0;

            final long dataLen = newLen;
            FileInputStream fis = new FileInputStream(f) {
              public int available() throws IOException {
                return (int) dataLen;
              }
            };
            fis.skip(startFrom);

            res = new Response(HTTP_PARTIALCONTENT, mime, fis);
            res.addHeader("Content-Length", "" + dataLen);
            res.addHeader("Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen);
            res.addHeader("ETag", etag);
          }
        } else {
          if (etag.equals(header.getProperty("if-none-match")))
            res = new Response(HTTP_NOTMODIFIED, mime, "");
          else {
            res = new Response(HTTP_OK, mime, new FileInputStream(f));
            res.addHeader("Content-Length", "" + fileLen);
            res.addHeader("ETag", etag);
          }
        }
      }
    } catch (IOException ioe) {
      res = new Response(HTTP_FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: Reading file failed.");
    }

    res.addHeader("Accept-Ranges", "bytes"); // Announce that the file server accepts partial
                                             // content requestes
    return res;
  }
Serves file from homeDir and its' subdirectories (only). Uses only URI, ignores all headers and
HTTP parameters.
selendroid-test-app/src/main/java/io/selendroid/testapp/server/NanoHTTPD.java