Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

172
LINES

< > BotCompany Repo | #1027709 // HttpFromFileSystem [serve files and directories over HTTP]

JavaX fragment (include) [tags: use-pretranspiled]

Libraryless. Click here for Pure Java version (12714L/78K).

ifdef NoNanoHTTPD
  !include once #1028763
endifdef

// TODO: implement If-Modified-Since?

sclass HttpFromFileSystem {
  File baseDir;
  bool serveDirectoryListings; // default to false
  bool serveIndexHTML = true;
  bool maxCache; // treat files as immutable
  bool debug;
  bool sendLastModified = true;
  settable bool serveDirectoriesAsZipFiles;
  SS params, headers;
  
  S baseLink = "";
  
  *() {}
  *(File *baseDir) {}
  *(File *baseDir, bool *serveDirectoryListings) {}
  
  O serve(S uri, SS params, SS headers default null) {
    if (eq("1", params.get("maxCache"))) maxCache = true;
    this.params = params;
    this.headers = headers;
    ret serve(uri);
  }
  
  O serve(S uri) {
    S originalURI = uri;
    uri = urldecode(uri);
    LS parts = nempties(splitAtSlash(uri));
    
    if (baseDir == null) ret serve404("No base directory set");
    
    // process . and ..
    for i over parts:
      if (eq(parts.get(i), ".")) parts.remove(i);
      else if (eq(parts.get(i), ".."))
        if (i == 0) fail(".. leading nowhere: " + uri);
        else {
          parts.remove(i);
          parts.remove(--i);
        }
      
    if (!all validFileName(parts)) {
      print("Parts: " + quoteAll(parts));
      fail("Bad path: " + uri);
    }
    
    File f = baseDir;
    if (!isDirectory(baseDir)) print("Base dir not found: " + f2s(f));
    for (S part : parts) {
      f = newFile(f, part);
      if (!fileExists(f))
        ret serve404("Path not found: " + uri);
    }
    print("Trying to serve: " + f2s(f));
    if (isDirectory(f))
      if (eq(params.get("asZip"), "1"))
        ret serveDirAsZip(f);
      else
        ret serveListingOrIndexHTML(uri, f, originalURI);
    else
      ret serveFile(f);
  }
  
  swappable O addHeaders(O response) {
    if (maxCache)
      ret subBot_maxCacheHeaders(response);
    ret response;
  }
  
  swappable O serveFile(File f) {
    O response;
    S range = mapGet(headers, "range");
    S ct = contentTypeForFile(f);
    if (nempty(range)) {
      print("Serving range query " + f + ": " + range);
      response = subBot_serveFileRange(f, ct, range);
    }
    else {
      response = main serveFile(f, ct);
      if (sendLastModified)
        addHeader("Last-Modified", formatDateForLastModifiedHeader(f.lastModified()), response);
    }
    ret addHeaders(response);
  }
  
  O serveListingOrIndexHTML(S uri, File dir, S originalURI) {
    if (serveIndexHTML) {
      File index = newFile(dir, "index.html");
      if (fileExists(index)) {
        /*if (nempty(originalURI) && !endsWithSlash(originalURI))
          ret subBot_serveRedirect(baseLink + originalURI + "/");*/
        ret serveFile(index);
      }
    }
    
    ret serveListing(uri, dir, originalURI);
  }
    
  O serveListing(S uri, File dir, S originalURI) {
    if (!serveDirectoryListings) fail("Can't serve directory listings");
    
    // take care of directory listing without trailing slash in URI
    // XXX broken? S local = addSlashIfNempty(afterLast("/" + uri, '/'));
    S local = addSlashIfNempty(uri);
    
    var names = listFileNames(dir);
    sortInPlaceByCalculatedField(names, name -> {
      File f = newFile(dir, name);
      ret f.isDirectory() ?
          pair(false, lower(name))
        : pair(true, modificationTime(f));
    });
    
    S parent = dropAfterLastSlashOrEmpty(dropTrailingSlash(uri));
    
    S title = "Directory listing of " + htmlEncode2(dropTrailingSlash(baseLink + uri));
    
    S debugText = !debug ? ""
      : htmlEncode2(renderVars(+baseLink, +uri, +local, +parent));
    O body = postProcessDirListing(h3(title)
      + pUnlessEmpty(debugText)
      + (l(parent) >= l(uri) ? "" : p(ahref(addSlashSuffix(baseLink + parent), "Parent directory")))
      + ul(map(names, name -> {
        File f = newFile(dir, name);
        bool isDir = isDirectory(f);
        if (isDir) name += "/";
        new LS l;
        
        // make the actual link to file/directroy
        l.add(ahref(baseLink + local + name, htmlEncode2(name)));
        
        if (isDir && serveDirectoriesAsZipFiles)
          l.add(ahref(appendParamsToURL(baseLink + local + name, asZip := 1), "[download zip]"));
        
        if (!isDir)
          l.add(bracketed(joinNemptiesWithComma(fileInfoHintsWithDate(f))));
        ret hcombine(l);
      })));
      
    ret hhtml(
      hhead(hcombine(
        htitle_decode(title),
        hsansserif(),
        hmobilefix(),
      )
      + hbody(body));
  }
  
  swappable S contentTypeForFile(File f) {
    ret or2(extensionToMimeType(fileExtension(f)), binaryMimeType());
  }
  
  swappable bool validFileName(S name) { ret isValidFileName(name); }
  
  swappable O postProcessDirListing(S html) { ret html; }

  O serveDirAsZip(File dir) ctex {
    new ByteArrayOutputStream baos;
    ZipOutputStream zipOut = new(baos);
    dir2zip_recurse(dir, zipOut, fileName(dir) + "/", 0);
    zipOut.close();
    byte[] data = baos.toByteArray();
    S zipName = dir.getName() + "-" + ymdMinusHMS() + ".zip";
    ret addFilenameHeader(zipName,
      subBot_serveInputStream(byteArrayInputStream(data), zipMimeType()));
  }
}

download  show line numbers  debug dex  old transpilations   

Travelled to 9 computer(s): bhatertpkbcr, ekrmjmnbrukm, elmgxqgtpvxh, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv

No comments. add comment

Snippet ID: #1027709
Snippet name: HttpFromFileSystem [serve files and directories over HTTP]
Eternal ID of this version: #1027709/75
Text MD5: 4baa7a792a1f28b5be016fe347ef80bb
Transpilation MD5: c21a42da563d55f85ab19b96efc0f6be
Author: stefan
Category: javax / http
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2022-05-22 14:33:15
Source code size: 5307 bytes / 172 lines
Pitched / IR pitched: No / No
Views / Downloads: 358 / 1088
Version history: 74 change(s)
Referenced in: [show references]