Libraryless. Click here for Pure Java version (12714L/78K).
| 1 | ifdef NoNanoHTTPD | 
| 2 | !include once #1028763 | 
| 3 | endifdef | 
| 4 | |
| 5 | // TODO: implement If-Modified-Since? | 
| 6 | |
| 7 | sclass HttpFromFileSystem {
 | 
| 8 | File baseDir; | 
| 9 | bool serveDirectoryListings; // default to false | 
| 10 | bool serveIndexHTML = true; | 
| 11 | bool maxCache; // treat files as immutable | 
| 12 | bool debug; | 
| 13 | bool sendLastModified = true; | 
| 14 | settable bool serveDirectoriesAsZipFiles; | 
| 15 | SS params, headers; | 
| 16 | |
| 17 | S baseLink = ""; | 
| 18 | |
| 19 |   *() {}
 | 
| 20 |   *(File *baseDir) {}
 | 
| 21 |   *(File *baseDir, bool *serveDirectoryListings) {}
 | 
| 22 | |
| 23 |   O serve(S uri, SS params, SS headers default null) {
 | 
| 24 |     if (eq("1", params.get("maxCache"))) maxCache = true;
 | 
| 25 | this.params = params; | 
| 26 | this.headers = headers; | 
| 27 | ret serve(uri); | 
| 28 | } | 
| 29 | |
| 30 |   O serve(S uri) {
 | 
| 31 | S originalURI = uri; | 
| 32 | uri = urldecode(uri); | 
| 33 | LS parts = nempties(splitAtSlash(uri)); | 
| 34 | |
| 35 |     if (baseDir == null) ret serve404("No base directory set");
 | 
| 36 | |
| 37 | // process . and .. | 
| 38 | for i over parts: | 
| 39 | if (eq(parts.get(i), ".")) parts.remove(i); | 
| 40 | else if (eq(parts.get(i), "..")) | 
| 41 |         if (i == 0) fail(".. leading nowhere: " + uri);
 | 
| 42 |         else {
 | 
| 43 | parts.remove(i); | 
| 44 | parts.remove(--i); | 
| 45 | } | 
| 46 | |
| 47 |     if (!all validFileName(parts)) {
 | 
| 48 |       print("Parts: " + quoteAll(parts));
 | 
| 49 |       fail("Bad path: " + uri);
 | 
| 50 | } | 
| 51 | |
| 52 | File f = baseDir; | 
| 53 |     if (!isDirectory(baseDir)) print("Base dir not found: " + f2s(f));
 | 
| 54 |     for (S part : parts) {
 | 
| 55 | f = newFile(f, part); | 
| 56 | if (!fileExists(f)) | 
| 57 |         ret serve404("Path not found: " + uri);
 | 
| 58 | } | 
| 59 |     print("Trying to serve: " + f2s(f));
 | 
| 60 | if (isDirectory(f)) | 
| 61 |       if (eq(params.get("asZip"), "1"))
 | 
| 62 | ret serveDirAsZip(f); | 
| 63 | else | 
| 64 | ret serveListingOrIndexHTML(uri, f, originalURI); | 
| 65 | else | 
| 66 | ret serveFile(f); | 
| 67 | } | 
| 68 | |
| 69 |   swappable O addHeaders(O response) {
 | 
| 70 | if (maxCache) | 
| 71 | ret subBot_maxCacheHeaders(response); | 
| 72 | ret response; | 
| 73 | } | 
| 74 | |
| 75 |   swappable O serveFile(File f) {
 | 
| 76 | O response; | 
| 77 | S range = mapGet(headers, "range"); | 
| 78 | S ct = contentTypeForFile(f); | 
| 79 |     if (nempty(range)) {
 | 
| 80 |       print("Serving range query " + f + ": " + range);
 | 
| 81 | response = subBot_serveFileRange(f, ct, range); | 
| 82 | } | 
| 83 |     else {
 | 
| 84 | response = main serveFile(f, ct); | 
| 85 | if (sendLastModified) | 
| 86 |         addHeader("Last-Modified", formatDateForLastModifiedHeader(f.lastModified()), response);
 | 
| 87 | } | 
| 88 | ret addHeaders(response); | 
| 89 | } | 
| 90 | |
| 91 |   O serveListingOrIndexHTML(S uri, File dir, S originalURI) {
 | 
| 92 |     if (serveIndexHTML) {
 | 
| 93 | File index = newFile(dir, "index.html"); | 
| 94 |       if (fileExists(index)) {
 | 
| 95 | /*if (nempty(originalURI) && !endsWithSlash(originalURI)) | 
| 96 | ret subBot_serveRedirect(baseLink + originalURI + "/");*/ | 
| 97 | ret serveFile(index); | 
| 98 | } | 
| 99 | } | 
| 100 | |
| 101 | ret serveListing(uri, dir, originalURI); | 
| 102 | } | 
| 103 | |
| 104 |   O serveListing(S uri, File dir, S originalURI) {
 | 
| 105 |     if (!serveDirectoryListings) fail("Can't serve directory listings");
 | 
| 106 | |
| 107 | // take care of directory listing without trailing slash in URI | 
| 108 |     // XXX broken? S local = addSlashIfNempty(afterLast("/" + uri, '/'));
 | 
| 109 | S local = addSlashIfNempty(uri); | 
| 110 | |
| 111 | var names = listFileNames(dir); | 
| 112 |     sortInPlaceByCalculatedField(names, name -> {
 | 
| 113 | File f = newFile(dir, name); | 
| 114 | ret f.isDirectory() ? | 
| 115 | pair(false, lower(name)) | 
| 116 | : pair(true, modificationTime(f)); | 
| 117 | }); | 
| 118 | |
| 119 | S parent = dropAfterLastSlashOrEmpty(dropTrailingSlash(uri)); | 
| 120 | |
| 121 | S title = "Directory listing of " + htmlEncode2(dropTrailingSlash(baseLink + uri)); | 
| 122 | |
| 123 | S debugText = !debug ? "" | 
| 124 | : htmlEncode2(renderVars(+baseLink, +uri, +local, +parent)); | 
| 125 | O body = postProcessDirListing(h3(title) | 
| 126 | + pUnlessEmpty(debugText) | 
| 127 | + (l(parent) >= l(uri) ? "" : p(ahref(addSlashSuffix(baseLink + parent), "Parent directory"))) | 
| 128 |       + ul(map(names, name -> {
 | 
| 129 | File f = newFile(dir, name); | 
| 130 | bool isDir = isDirectory(f); | 
| 131 | if (isDir) name += "/"; | 
| 132 | new LS l; | 
| 133 | |
| 134 | // make the actual link to file/directroy | 
| 135 | l.add(ahref(baseLink + local + name, htmlEncode2(name))); | 
| 136 | |
| 137 | if (isDir && serveDirectoriesAsZipFiles) | 
| 138 | l.add(ahref(appendParamsToURL(baseLink + local + name, asZip := 1), "[download zip]")); | 
| 139 | |
| 140 | if (!isDir) | 
| 141 | l.add(bracketed(joinNemptiesWithComma(fileInfoHintsWithDate(f)))); | 
| 142 | ret hcombine(l); | 
| 143 | }))); | 
| 144 | |
| 145 | ret hhtml( | 
| 146 | hhead(hcombine( | 
| 147 | htitle_decode(title), | 
| 148 | hsansserif(), | 
| 149 | hmobilefix(), | 
| 150 | ) | 
| 151 | + hbody(body)); | 
| 152 | } | 
| 153 | |
| 154 |   swappable S contentTypeForFile(File f) {
 | 
| 155 | ret or2(extensionToMimeType(fileExtension(f)), binaryMimeType()); | 
| 156 | } | 
| 157 | |
| 158 |   swappable bool validFileName(S name) { ret isValidFileName(name); }
 | 
| 159 | |
| 160 |   swappable O postProcessDirListing(S html) { ret html; }
 | 
| 161 | |
| 162 |   O serveDirAsZip(File dir) ctex {
 | 
| 163 | new ByteArrayOutputStream baos; | 
| 164 | ZipOutputStream zipOut = new(baos); | 
| 165 | dir2zip_recurse(dir, zipOut, fileName(dir) + "/", 0); | 
| 166 | zipOut.close(); | 
| 167 | byte[] data = baos.toByteArray(); | 
| 168 | S zipName = dir.getName() + "-" + ymdMinusHMS() + ".zip"; | 
| 169 | ret addFilenameHeader(zipName, | 
| 170 | subBot_serveInputStream(byteArrayInputStream(data), zipMimeType())); | 
| 171 | } | 
| 172 | } | 
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: | 671 / 1483 | 
| Version history: | 74 change(s) | 
| Referenced in: | [show references] |