1 #define _XOPEN_SOURCE 500 /* strptime() ... */
2 #define _BSD_SOURCE /* scandir() ... */
5 #include "uhttpd-file.h"
6 #include "uhttpd-utils.h"
8 #include "uhttpd-mimetypes.h"
11 static const char * uh_file_mime_lookup(const char *path)
13 struct mimetype *m = &uh_mime_types[0];
16 ps = strrchr(path, '/');
17 pd = strrchr(path, '.');
19 /* use either slash or dot as separator, whatever comes last */
20 p = (ps && pd && (ps > pd)) ? ps : pd;
22 if( (p != NULL) && (*(++p) != 0) )
26 if( ! strcasecmp(p, m->extn) )
33 return "application/octet-stream";
36 static const char * uh_file_mktag(struct stat *s)
40 snprintf(tag, sizeof(tag), "\"%x-%x-%x\"",
41 (unsigned int) s->st_ino,
42 (unsigned int) s->st_size,
43 (unsigned int) s->st_mtime
49 static time_t uh_file_date2unix(const char *date)
53 memset(&t, 0, sizeof(t));
55 if( strptime(date, "%a, %d %b %Y %H:%M:%S %Z", &t) != NULL )
61 static char * uh_file_unix2date(time_t ts)
64 struct tm *t = localtime(&ts);
66 strftime(str, sizeof(str), "%a, %d %b %Y %H:%M:%S %Z", t);
71 static char * uh_file_header_lookup(struct http_request *req, const char *name)
75 foreach_header(i, req->headers)
77 if( ! strcasecmp(req->headers[i], name) )
78 return req->headers[i+1];
84 static void uh_file_response_ok_hdrs(struct client *cl, struct http_request *req, struct stat *s)
88 uh_http_sendf(cl, NULL, "ETag: %s\r\n", uh_file_mktag(s));
89 uh_http_sendf(cl, NULL, "Last-Modified: %s\r\n", uh_file_unix2date(s->st_mtime));
92 uh_http_sendf(cl, NULL, "Date: %s\r\n", uh_file_unix2date(time(NULL)));
95 static void uh_file_response_200(struct client *cl, struct http_request *req, struct stat *s)
97 uh_http_sendf(cl, NULL, "HTTP/%.1f 200 OK\r\n", req->version);
98 uh_file_response_ok_hdrs(cl, req, s);
101 static void uh_file_response_304(struct client *cl, struct http_request *req, struct stat *s)
103 uh_http_sendf(cl, NULL, "HTTP/%.1f 304 Not Modified\r\n", req->version);
104 uh_file_response_ok_hdrs(cl, req, s);
107 static void uh_file_response_412(struct client *cl, struct http_request *req)
109 uh_http_sendf(cl, NULL, "HTTP/%.1f 412 Precondition Failed\r\n",
113 static int uh_file_if_match(struct client *cl, struct http_request *req, struct stat *s)
115 const char *tag = uh_file_mktag(s);
116 char *hdr = uh_file_header_lookup(req, "If-Match");
124 for( i = 0; i < strlen(hdr); i++ )
126 if( (hdr[i] == ' ') || (hdr[i] == ',') )
131 else if( !strcmp(p, "*") || !strcmp(p, tag) )
137 uh_file_response_412(cl, req);
144 static int uh_file_if_modified_since(struct client *cl, struct http_request *req, struct stat *s)
146 char *hdr = uh_file_header_lookup(req, "If-Modified-Since");
150 if( uh_file_date2unix(hdr) < s->st_mtime )
156 uh_file_response_304(cl, req, s);
164 static int uh_file_if_none_match(struct client *cl, struct http_request *req, struct stat *s)
166 const char *tag = uh_file_mktag(s);
167 char *hdr = uh_file_header_lookup(req, "If-None-Match");
175 for( i = 0; i < strlen(hdr); i++ )
177 if( (hdr[i] == ' ') || (hdr[i] == ',') )
182 else if( !strcmp(p, "*") || !strcmp(p, tag) )
184 if( (req->method == UH_HTTP_MSG_GET) ||
185 (req->method == UH_HTTP_MSG_HEAD) )
186 uh_file_response_304(cl, req, s);
188 uh_file_response_412(cl, req);
198 static int uh_file_if_range(struct client *cl, struct http_request *req, struct stat *s)
200 char *hdr = uh_file_header_lookup(req, "If-Range");
204 uh_file_response_412(cl, req);
211 static int uh_file_if_unmodified_since(struct client *cl, struct http_request *req, struct stat *s)
213 char *hdr = uh_file_header_lookup(req, "If-Unmodified-Since");
217 if( uh_file_date2unix(hdr) <= s->st_mtime )
219 uh_file_response_412(cl, req);
228 static int uh_file_scandir_filter_dir(const struct dirent *e)
230 return strcmp(e->d_name, ".") ? 1 : 0;
233 static void uh_file_dirlist(struct client *cl, struct http_request *req, struct uh_path_info *pi)
236 char filename[PATH_MAX];
238 struct dirent **files = NULL;
241 uh_http_sendf(cl, req,
242 "<html><head><title>Index of %s</title></head>"
243 "<body><h1>Index of %s</h1><hr /><ol>",
247 if( (count = scandir(pi->phys, &files, uh_file_scandir_filter_dir, alphasort)) > 0 )
249 memset(filename, 0, sizeof(filename));
250 memcpy(filename, pi->phys, sizeof(filename));
251 pathptr = &filename[strlen(filename)];
254 for( i = 0; i < count; i++ )
256 strncat(filename, files[i]->d_name,
257 sizeof(filename) - strlen(files[i]->d_name));
259 if( !stat(filename, &s) && (s.st_mode & S_IFDIR) )
260 uh_http_sendf(cl, req,
261 "<li><strong><a href='%s%s'>%s</a>/</strong><br />"
262 "<small>modified: %s<br />directory - %.02f kbyte"
263 "<br /><br /></small></li>",
264 pi->name, files[i]->d_name, files[i]->d_name,
265 uh_file_unix2date(s.st_mtime), s.st_size / 1024.0
272 for( i = 0; i < count; i++ )
274 strncat(filename, files[i]->d_name,
275 sizeof(filename) - strlen(files[i]->d_name));
277 if( !stat(filename, &s) && !(s.st_mode & S_IFDIR) )
278 uh_http_sendf(cl, req,
279 "<li><strong><a href='%s%s'>%s</a></strong><br />"
280 "<small>modified: %s<br />%s - %.02f kbyte<br />"
281 "<br /></small></li>",
282 pi->name, files[i]->d_name, files[i]->d_name,
283 uh_file_unix2date(s.st_mtime),
284 uh_file_mime_lookup(filename), s.st_size / 1024.0
294 uh_http_sendf(cl, req, "</ol><hr /></body></html>");
295 uh_http_sendf(cl, req, "");
299 void uh_file_request(struct client *cl, struct http_request *req)
302 char buf[UH_LIMIT_MSGHEAD];
303 struct uh_path_info *pi;
305 /* obtain path information */
306 if( (pi = uh_path_lookup(cl, req->url)) != NULL )
309 if( (pi->stat.st_mode & S_IFREG) &&
310 ((fd = open(pi->phys, O_RDONLY)) > 0)
312 /* test preconditions */
314 uh_file_if_modified_since(cl, req, &pi->stat) &&
315 uh_file_if_match(cl, req, &pi->stat) &&
316 uh_file_if_range(cl, req, &pi->stat) &&
317 uh_file_if_unmodified_since(cl, req, &pi->stat) &&
318 uh_file_if_none_match(cl, req, &pi->stat)
321 uh_file_response_200(cl, req, &pi->stat);
323 uh_http_sendf(cl, NULL, "Content-Type: %s\r\n", uh_file_mime_lookup(pi->name));
324 uh_http_sendf(cl, NULL, "Content-Length: %i\r\n", pi->stat.st_size);
326 /* if request was HTTP 1.1 we'll respond chunked */
327 if( req->version > 1.0 )
328 uh_http_send(cl, NULL, "Transfer-Encoding: chunked\r\n", -1);
331 uh_http_send(cl, NULL, "\r\n", -1);
334 while( (rlen = read(fd, buf, sizeof(buf))) > 0 )
336 uh_http_send(cl, req, buf, rlen);
339 /* send trailer in chunked mode */
340 uh_http_send(cl, req, "", 0);
343 /* one of the preconditions failed, terminate opened header and exit */
346 uh_http_send(cl, NULL, "\r\n", -1);
353 else if( pi->stat.st_mode & S_IFDIR )
356 uh_file_response_200(cl, req, NULL);
358 if( req->version > 1.0 )
359 uh_http_send(cl, NULL, "Transfer-Encoding: chunked\r\n", -1);
361 uh_http_send(cl, NULL, "Content-Type: text/html\r\n\r\n", -1);
364 uh_file_dirlist(cl, req, pi);
370 uh_http_sendhf(cl, 403, "Forbidden",
371 "Access to this resource is forbidden");
378 uh_http_sendhf(cl, 404, "Not Found",
379 "No such file or directory");