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).

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