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: | 425 / 1181 |
Version history: | 74 change(s) |
Referenced in: | [show references] |