uhttpd:
[project/luci.git] / contrib / package / uhttpd / src / uhttpd-file.c
1 #define _XOPEN_SOURCE 500       /* strptime() ... */
2 #define _BSD_SOURCE                     /* scandir() ... */
3
4 #include "uhttpd.h"
5 #include "uhttpd-utils.h"
6 #include "uhttpd-file.h"
7
8 #include "uhttpd-mimetypes.h"
9
10
11 static const char * uh_file_mime_lookup(const char *path)
12 {
13         struct mimetype *m = &uh_mime_types[0];
14         char *p, *pd, *ps;
15
16         ps = strrchr(path, '/');
17         pd = strrchr(path, '.');
18
19         /* use either slash or dot as separator, whatever comes last */
20         p = (ps && pd && (ps > pd)) ? ps : pd;
21
22         if( (p != NULL) && (*(++p) != 0) )
23         {
24                 while( m->extn )
25                 {
26                         if( ! strcasecmp(p, m->extn) )
27                                 return m->mime;
28
29                         m++;
30                 }
31         }
32
33         return "application/octet-stream";
34 }
35
36 static const char * uh_file_mktag(struct stat *s)
37 {
38         static char tag[128];
39
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
44         );
45
46         return tag;
47 }
48
49 static time_t uh_file_date2unix(const char *date)
50 {
51         struct tm t;
52
53         memset(&t, 0, sizeof(t));
54
55         if( strptime(date, "%a, %d %b %Y %H:%M:%S %Z", &t) != NULL )
56                 return timegm(&t);
57
58         return 0;
59 }
60
61 static char * uh_file_unix2date(time_t ts)
62 {
63         static char str[128];
64         struct tm *t = gmtime(&ts);
65
66         strftime(str, sizeof(str), "%a, %d %b %Y %H:%M:%S GMT", t);
67
68         return str;
69 }
70
71 static char * uh_file_header_lookup(struct http_request *req, const char *name)
72 {
73         int i;
74
75         foreach_header(i, req->headers)
76         {
77                 if( ! strcasecmp(req->headers[i], name) )
78                         return req->headers[i+1];
79         }
80
81         return NULL;
82 }
83
84 static void uh_file_response_ok_hdrs(struct client *cl, struct http_request *req, struct stat *s)
85 {
86         if( s )
87         {
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));
90         }
91
92         uh_http_sendf(cl, NULL, "Date: %s\r\n", uh_file_unix2date(time(NULL)));
93 }
94
95 static void uh_file_response_200(struct client *cl, struct http_request *req, struct stat *s)
96 {
97         uh_http_sendf(cl, NULL, "HTTP/%.1f 200 OK\r\n", req->version);
98         uh_file_response_ok_hdrs(cl, req, s);
99 }
100
101 static void uh_file_response_304(struct client *cl, struct http_request *req, struct stat *s)
102 {
103         uh_http_sendf(cl, NULL, "HTTP/%.1f 304 Not Modified\r\n", req->version);
104         uh_file_response_ok_hdrs(cl, req, s);
105 }
106
107 static void uh_file_response_412(struct client *cl, struct http_request *req)
108 {
109         uh_http_sendf(cl, NULL, "HTTP/%.1f 412 Precondition Failed\r\n",
110                 req->version);
111 }
112
113 static int uh_file_if_match(struct client *cl, struct http_request *req, struct stat *s)
114 {
115         const char *tag = uh_file_mktag(s);
116         char *hdr = uh_file_header_lookup(req, "If-Match");
117         char *p;
118         int i;
119
120         if( hdr )
121         {
122                 p = &hdr[0];
123
124                 for( i = 0; i < strlen(hdr); i++ )
125                 {
126                         if( (hdr[i] == ' ') || (hdr[i] == ',') )
127                         {
128                                 hdr[i++] = 0;
129                                 p = &hdr[i];
130                         }
131                         else if( !strcmp(p, "*") || !strcmp(p, tag) )
132                         {
133                                 return 1;
134                         }
135                 }
136
137                 uh_file_response_412(cl, req);
138                 return 0;
139         }
140
141         return 1;
142 }
143
144 static int uh_file_if_modified_since(struct client *cl, struct http_request *req, struct stat *s)
145 {
146         char *hdr = uh_file_header_lookup(req, "If-Modified-Since");
147
148         if( hdr )
149         {
150                 if( uh_file_date2unix(hdr) < s->st_mtime )
151                 {
152                         return 1;
153                 }
154                 else
155                 {
156                         uh_file_response_304(cl, req, s);
157                         return 0;
158                 }
159         }
160
161         return 1;
162 }
163
164 static int uh_file_if_none_match(struct client *cl, struct http_request *req, struct stat *s)
165 {
166         const char *tag = uh_file_mktag(s);
167         char *hdr = uh_file_header_lookup(req, "If-None-Match");
168         char *p;
169         int i;
170
171         if( hdr )
172         {
173                 p = &hdr[0];
174
175                 for( i = 0; i < strlen(hdr); i++ )
176                 {
177                         if( (hdr[i] == ' ') || (hdr[i] == ',') )
178                         {
179                                 hdr[i++] = 0;
180                                 p = &hdr[i];
181                         }
182                         else if( !strcmp(p, "*") || !strcmp(p, tag) )
183                         {
184                                 if( (req->method == UH_HTTP_MSG_GET) ||
185                                     (req->method == UH_HTTP_MSG_HEAD) )
186                                         uh_file_response_304(cl, req, s);
187                                 else
188                                         uh_file_response_412(cl, req);
189
190                                 return 0;
191                         }
192                 }
193         }
194
195         return 1;
196 }
197
198 static int uh_file_if_range(struct client *cl, struct http_request *req, struct stat *s)
199 {
200         char *hdr = uh_file_header_lookup(req, "If-Range");
201
202         if( hdr )
203         {
204                 uh_file_response_412(cl, req);
205                 return 0;
206         }
207
208         return 1;
209 }
210
211 static int uh_file_if_unmodified_since(struct client *cl, struct http_request *req, struct stat *s)
212 {
213         char *hdr = uh_file_header_lookup(req, "If-Unmodified-Since");
214
215         if( hdr )
216         {
217                 if( uh_file_date2unix(hdr) <= s->st_mtime )
218                 {
219                         uh_file_response_412(cl, req);
220                         return 0;
221                 }
222         }
223
224         return 1;
225 }
226
227
228 static int uh_file_scandir_filter_dir(const struct dirent *e)
229 {
230         return strcmp(e->d_name, ".") ? 1 : 0;
231 }
232
233 static void uh_file_dirlist(struct client *cl, struct http_request *req, struct uh_path_info *pi)
234 {
235         int i, count;
236         char filename[PATH_MAX];
237         char *pathptr;
238         struct dirent **files = NULL;
239         struct stat s;
240
241         uh_http_sendf(cl, req,
242                 "<html><head><title>Index of %s</title></head>"
243                 "<body><h1>Index of %s</h1><hr /><ol>",
244                         pi->name, pi->name
245         );
246
247         if( (count = scandir(pi->phys, &files, uh_file_scandir_filter_dir, alphasort)) > 0 )
248         {
249                 memset(filename, 0, sizeof(filename));
250                 memcpy(filename, pi->phys, sizeof(filename));
251                 pathptr = &filename[strlen(filename)];
252
253                 /* list subdirs */
254                 for( i = 0; i < count; i++ )
255                 {
256                         strncat(filename, files[i]->d_name,
257                                 sizeof(filename) - strlen(files[i]->d_name));
258
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
266                                 );
267
268                         *pathptr = 0;
269                 }
270
271                 /* list files */
272                 for( i = 0; i < count; i++ )
273                 {
274                         strncat(filename, files[i]->d_name,
275                                 sizeof(filename) - strlen(files[i]->d_name));
276
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
285                                 );
286
287                         *pathptr = 0;
288                         free(files[i]);
289                 }
290         }
291
292         free(files);
293
294         uh_http_sendf(cl, req, "</ol><hr /></body></html>");
295         uh_http_sendf(cl, req, "");
296 }
297
298
299 void uh_file_request(struct client *cl, struct http_request *req, struct uh_path_info *pi)
300 {
301         int fd, rlen;
302         char buf[UH_LIMIT_MSGHEAD];
303
304         /* we have a file */
305         if( (pi->stat.st_mode & S_IFREG) && ((fd = open(pi->phys, O_RDONLY)) > 0) )
306         {
307                 /* test preconditions */
308                 if(
309                         uh_file_if_modified_since(cl, req, &pi->stat)   &&
310                         uh_file_if_match(cl, req, &pi->stat)            &&
311                         uh_file_if_range(cl, req, &pi->stat)            &&
312                         uh_file_if_unmodified_since(cl, req, &pi->stat) &&
313                         uh_file_if_none_match(cl, req, &pi->stat)
314                 ) {
315                         /* write status */
316                         uh_file_response_200(cl, req, &pi->stat);
317
318                         uh_http_sendf(cl, NULL, "Content-Type: %s\r\n", uh_file_mime_lookup(pi->name));
319                         uh_http_sendf(cl, NULL, "Content-Length: %i\r\n", pi->stat.st_size);
320
321                         /* if request was HTTP 1.1 we'll respond chunked */
322                         if( (req->version > 1.0) && (req->method != UH_HTTP_MSG_HEAD) )
323                                 uh_http_send(cl, NULL, "Transfer-Encoding: chunked\r\n", -1);
324
325                         /* close header */
326                         uh_http_send(cl, NULL, "\r\n", -1);
327
328                         /* send body */
329                         if( req->method != UH_HTTP_MSG_HEAD )
330                         {
331                                 /* pump file data */
332                                 while( (rlen = read(fd, buf, sizeof(buf))) > 0 )
333                                 {
334                                         uh_http_send(cl, req, buf, rlen);
335                                 }
336
337                                 /* send trailer in chunked mode */
338                                 uh_http_send(cl, req, "", 0);
339                         }
340                 }
341
342                 /* one of the preconditions failed, terminate opened header and exit */
343                 else
344                 {
345                         uh_http_send(cl, NULL, "\r\n", -1);
346                 }
347
348                 close(fd);
349         }
350
351         /* directory */
352         else if( pi->stat.st_mode & S_IFDIR )
353         {
354                 /* write status */
355                 uh_file_response_200(cl, req, NULL);
356
357                 if( req->version > 1.0 )
358                         uh_http_send(cl, NULL, "Transfer-Encoding: chunked\r\n", -1);
359
360                 uh_http_send(cl, NULL, "Content-Type: text/html\r\n\r\n", -1);
361
362                 /* content */
363                 uh_file_dirlist(cl, req, pi);
364         }
365
366         /* 403 */
367         else
368         {
369                 uh_http_sendhf(cl, 403, "Forbidden",
370                         "Access to this resource is forbidden");
371         }
372 }
373