// FIXME - normalize further
  public Response serveFile(
      String uri,
      Map<String, String> header,
      File homeDir,
      boolean allowDirectoryListing,
      boolean isCustomFile) {

    String username;
    String password;

    //
    if (webgui.requiresSecurity()) { // && !authorized) {
      try {
        if (header.containsKey("authorization")) {
          String up = header.get("authorization");
          int pos = up.indexOf("Basic ");
          if (pos != -1) {
            up = up.substring(pos + 6);
          }
          // FIXME - depends on commons !!!!
          String usernameAndPassword = new String(Base64.decode(up)); // SHWEET
          // CURRENTLY
          // USING
          // WEBSOCKETS
          // VERSION
          // !!!
          // :P
          username = usernameAndPassword.substring(0, usernameAndPassword.lastIndexOf(":"));
          password = usernameAndPassword.substring(usernameAndPassword.lastIndexOf(":") + 1);
          String token = BasicSecurity.authenticate(username, password);

          if (token != null) {
            // authorized = true;
          } else {
            throw new Exception(String.format("no token for user %s", username));
          }
          log.info(usernameAndPassword);
        } else {
          throw new Exception("no authorization in header");
        }

      } catch (Exception e) {
        Logging.logException(e);
        Response r = new Response(e.getMessage());
        r.setStatus(Response.Status.FORBIDDEN);
        r.addHeader("WWW-Authenticate", "Basic realm=\"MyRobotLab\"");
        return r;
      }
    }

    // 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) {
      return new Response(
          Response.Status.FORBIDDEN,
          WSServer.MIME_PLAINTEXT,
          "FORBIDDEN: Won't serve ../ for security reasons.");
    }

    File f;
    if (!isCustomFile) {
      // ---- begin resource ----------------
      // TODO ? hard coded /resource - are they going to pull up
      // WebGUI.class how useful is that ??
      String resoucePath = String.format("/resource%s", uri);
      InputStream fis = null;
      if (resoucePath.endsWith("/")) {
        // ends with a slash .. might be a directory - try index.html
        String documentIndex = String.format("%sindex.html", resoucePath);
        fis = FileIO.class.getResourceAsStream(documentIndex);
        if (fis == null) { // couldn't find document index try directory
          // listing, if allowed
          fis = FileIO.class.getResourceAsStream(resoucePath);
        } else {
          uri = documentIndex;
        }
      } else {
        fis = FileIO.class.getResourceAsStream(resoucePath);
      }

      if (fis == null)
        return new Response(
            Response.Status.NOT_FOUND, WSServer.MIME_PLAINTEXT, "Error 404, file not found.");

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

        ByteArrayOutputStream buffer = new ByteArrayOutputStream();

        int nRead;
        byte[] data = new byte[16384];

        while ((nRead = fis.read(data, 0, data.length)) != -1) {
          buffer.write(data, 0, nRead);
        }

        fis.close();
        buffer.flush();

        byte[] content = null;

        // if ("/resource/WebGUI/myrobotlab.html".equals(uri)) {
        if (uri.endsWith(".mrl")) {
          content = filter(new String(buffer.toByteArray()), header);
          mime = NanoHTTPD.MIME_HTML;
        } else {
          content = buffer.toByteArray();
        }

        Response r = new Response(Response.Status.OK, mime, new ByteArrayInputStream(content));

        r.addHeader("Content-length", "" + content.length);
        return r;
      } catch (IOException ioe) {
        return new Response(
            Response.Status.FORBIDDEN, WSServer.MIME_PLAINTEXT, "FORBIDDEN: Reading file failed.");
      }
      // ---- end resource -------------------
    } else {
      // ---- begin file -----------------------
      f = new File(homeDir, uri);
      if (!f.exists())
        return new Response(
            Response.Status.NOT_FOUND, WSServer.MIME_PLAINTEXT, "Error 404, file not found.");

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

        // 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
        else if (allowDirectoryListing) {
          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/>";
          }

          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=\"" + NanoHTTPD.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 += curFile.length() + " bytes";
              else if (len < 1024 * 1024)
                msg += curFile.length() / 1024 + "." + (curFile.length() % 1024 / 10 % 100) + " KB";
              else
                msg +=
                    curFile.length() / (1024 * 1024)
                        + "."
                        + curFile.length() % (1024 * 1024) / 10 % 100
                        + " MB";

              msg += ")</font>";
            }
            msg += "<br/>";
            if (dir) msg += "</b>";
          }
          return new Response(Response.Status.OK, NanoHTTPD.MIME_HTML, msg);
        } else {
          return new Response(
              Response.Status.FORBIDDEN,
              WSServer.MIME_PLAINTEXT,
              "FORBIDDEN: No directory listing.");
        }
      }

      // ----- end file ------------------------
    }
    // }
    try {
      // Get MIME type from file name extension, if possible
      String mime = null;
      // int dot = f.getCanonicalPath().lastIndexOf('.');
      int dot = uri.lastIndexOf('.');
      if (dot >= 0) {
        mime = (String) NanoHTTPD.theMimeTypes.get(uri.substring(dot + 1).toLowerCase());
      }
      if (mime == null) {
        mime = WSServer.MIME_PLAINTEXT;
      }

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

      InputStream fis;
      if (!isCustomFile) {
        fis = FileIO.class.getResourceAsStream(uri);
        if (fis == null) {
          return new Response(
              Response.Status.NOT_FOUND, WSServer.MIME_PLAINTEXT, "Error 404, file not found.");
        }

        ByteArrayOutputStream buffer = new ByteArrayOutputStream();

        int nRead;
        byte[] data = new byte[16384];

        while ((nRead = fis.read(data, 0, data.length)) != -1) {
          buffer.write(data, 0, nRead);
        }

        fis.close();
        buffer.flush();

        byte[] content = null;
        // FIXME - this is not normalized - because the code around it
        // is
        // not normalized
        if (uri.endsWith(".mrl")) {
          content = filter(new String(buffer.toByteArray()), header);
          mime = NanoHTTPD.MIME_HTML;
        } else {
          content = buffer.toByteArray();
        }

        Response r = new Response(Response.Status.OK, mime, new ByteArrayInputStream(content));

        r.addHeader("Content-length", "" + content.length);
        return r;
      } else {
        fis = new FileInputStream(f);
        // fis.skip(startFrom); // TODO support skip in the future
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();

        int nRead;
        byte[] data = new byte[16384];

        while ((nRead = fis.read(data, 0, data.length)) != -1) {
          buffer.write(data, 0, nRead);
        }

        fis.close();
        buffer.flush();

        byte[] content = null;
        // FIXME - this is not normalized - because the code around it
        // is
        // not normalized

        if (uri.endsWith(".mrl")) {
          content = filter(new String(buffer.toByteArray()), header);
          mime = NanoHTTPD.MIME_HTML;
        } else {
          content = buffer.toByteArray();
        }

        Response r = new Response(Response.Status.OK, mime, new ByteArrayInputStream(content));
        r.addHeader("Content-length", "" + content.length);
        // r.addHeader("Content-length", "" + (f.length() - startFrom));
        // r.addHeader("Content-range", "" + startFrom + "-" +
        // (f.length() - 1) + "/" + f.length());
        return r;
      }

    } catch (IOException ioe) {
      return new Response(
          Response.Status.FORBIDDEN, WSServer.MIME_PLAINTEXT, "FORBIDDEN: Reading file failed.");
    }
  }