[package] uhttpd:
[openwrt.git] / package / uhttpd / src / uhttpd-file.c
1 /*
2  * uhttpd - Tiny single-threaded httpd - Static file handler
3  *
4  *   Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */
18
19 #define _XOPEN_SOURCE 500       /* strptime() */
20 #define _BSD_SOURCE                     /* scandir(), timegm() */
21
22 #include "uhttpd.h"
23 #include "uhttpd-utils.h"
24 #include "uhttpd-file.h"
25
26 #include "uhttpd-mimetypes.h"
27
28
29 static const char * uh_file_mime_lookup(const char *path)
30 {
31         struct mimetype *m = &uh_mime_types[0];
32         const char *e;
33
34         while (m->extn)
35         {
36                 e = &path[strlen(path)-1];
37
38                 while (e >= path)
39                 {
40                         if ((*e == '.' || *e == '/') && !strcasecmp(&e[1], m->extn))
41                                 return m->mime;
42
43                         e--;
44                 }
45
46                 m++;
47         }
48
49         return "application/octet-stream";
50 }
51
52 static const char * uh_file_mktag(struct stat *s)
53 {
54         static char tag[128];
55
56         snprintf(tag, sizeof(tag), "\"%x-%x-%x\"",
57                          (unsigned int) s->st_ino,
58                          (unsigned int) s->st_size,
59                          (unsigned int) s->st_mtime);
60
61         return tag;
62 }
63
64 static time_t uh_file_date2unix(const char *date)
65 {
66         struct tm t;
67
68         memset(&t, 0, sizeof(t));
69
70         if (strptime(date, "%a, %d %b %Y %H:%M:%S %Z", &t) != NULL)
71                 return timegm(&t);
72
73         return 0;
74 }
75
76 static char * uh_file_unix2date(time_t ts)
77 {
78         static char str[128];
79         struct tm *t = gmtime(&ts);
80
81         strftime(str, sizeof(str), "%a, %d %b %Y %H:%M:%S GMT", t);
82
83         return str;
84 }
85
86 static char * uh_file_header_lookup(struct client *cl, const char *name)
87 {
88         int i;
89
90         foreach_header(i, cl->request.headers)
91         {
92                 if (!strcasecmp(cl->request.headers[i], name))
93                         return cl->request.headers[i+1];
94         }
95
96         return NULL;
97 }
98
99
100 static int uh_file_response_ok_hdrs(struct client *cl, struct stat *s)
101 {
102         ensure_ret(uh_http_sendf(cl, NULL, "Connection: close\r\n"));
103
104         if (s)
105         {
106                 ensure_ret(uh_http_sendf(cl, NULL, "ETag: %s\r\n", uh_file_mktag(s)));
107                 ensure_ret(uh_http_sendf(cl, NULL, "Last-Modified: %s\r\n",
108                                                                  uh_file_unix2date(s->st_mtime)));
109         }
110
111         return uh_http_sendf(cl, NULL, "Date: %s\r\n", uh_file_unix2date(time(NULL)));
112 }
113
114 static int uh_file_response_200(struct client *cl, struct stat *s)
115 {
116         ensure_ret(uh_http_sendf(cl, NULL, "HTTP/%.1f 200 OK\r\n",
117                                                          cl->request.version));
118
119         return uh_file_response_ok_hdrs(cl, s);
120 }
121
122 static int uh_file_response_304(struct client *cl, struct stat *s)
123 {
124         ensure_ret(uh_http_sendf(cl, NULL, "HTTP/%.1f 304 Not Modified\r\n",
125                                                          cl->request.version));
126
127         return uh_file_response_ok_hdrs(cl, s);
128 }
129
130 static int uh_file_response_412(struct client *cl)
131 {
132         return uh_http_sendf(cl, NULL,
133                                                  "HTTP/%.1f 412 Precondition Failed\r\n"
134                                                  "Connection: close\r\n", cl->request.version);
135 }
136
137 static int uh_file_if_match(struct client *cl, struct stat *s, int *ok)
138 {
139         const char *tag = uh_file_mktag(s);
140         char *hdr = uh_file_header_lookup(cl, "If-Match");
141         char *p;
142         int i;
143
144         if (hdr)
145         {
146                 p = &hdr[0];
147
148                 for (i = 0; i < strlen(hdr); i++)
149                 {
150                         if ((hdr[i] == ' ') || (hdr[i] == ','))
151                         {
152                                 hdr[i++] = 0;
153                                 p = &hdr[i];
154                         }
155                         else if (!strcmp(p, "*") || !strcmp(p, tag))
156                         {
157                                 *ok = 1;
158                                 return *ok;
159                         }
160                 }
161
162                 *ok = 0;
163                 ensure_ret(uh_file_response_412(cl));
164                 return *ok;
165         }
166
167         *ok = 1;
168         return *ok;
169 }
170
171 static int uh_file_if_modified_since(struct client *cl, struct stat *s, int *ok)
172 {
173         char *hdr = uh_file_header_lookup(cl, "If-Modified-Since");
174         *ok = 1;
175
176         if (hdr)
177         {
178                 if (uh_file_date2unix(hdr) >= s->st_mtime)
179                 {
180                         *ok = 0;
181                         ensure_ret(uh_file_response_304(cl, s));
182                 }
183         }
184
185         return *ok;
186 }
187
188 static int uh_file_if_none_match(struct client *cl, struct stat *s, int *ok)
189 {
190         const char *tag = uh_file_mktag(s);
191         char *hdr = uh_file_header_lookup(cl, "If-None-Match");
192         char *p;
193         int i;
194         *ok = 1;
195
196         if (hdr)
197         {
198                 p = &hdr[0];
199
200                 for (i = 0; i < strlen(hdr); i++)
201                 {
202                         if ((hdr[i] == ' ') || (hdr[i] == ','))
203                         {
204                                 hdr[i++] = 0;
205                                 p = &hdr[i];
206                         }
207                         else if (!strcmp(p, "*") || !strcmp(p, tag))
208                         {
209                                 *ok = 0;
210
211                                 if ((cl->request.method == UH_HTTP_MSG_GET) ||
212                                     (cl->request.method == UH_HTTP_MSG_HEAD))
213                                 {
214                                         ensure_ret(uh_file_response_304(cl, s));
215                                 }
216                                 else
217                                 {
218                                         ensure_ret(uh_file_response_412(cl));
219                                 }
220
221                                 break;
222                         }
223                 }
224         }
225
226         return *ok;
227 }
228
229 static int uh_file_if_range(struct client *cl, struct stat *s, int *ok)
230 {
231         char *hdr = uh_file_header_lookup(cl, "If-Range");
232         *ok = 1;
233
234         if (hdr)
235         {
236                 *ok = 0;
237                 ensure_ret(uh_file_response_412(cl));
238         }
239
240         return *ok;
241 }
242
243 static int uh_file_if_unmodified_since(struct client *cl, struct stat *s,
244                                                                            int *ok)
245 {
246         char *hdr = uh_file_header_lookup(cl, "If-Unmodified-Since");
247         *ok = 1;
248
249         if (hdr)
250         {
251                 if (uh_file_date2unix(hdr) <= s->st_mtime)
252                 {
253                         *ok = 0;
254                         ensure_ret(uh_file_response_412(cl));
255                 }
256         }
257
258         return *ok;
259 }
260
261
262 static int uh_file_scandir_filter_dir(const struct dirent *e)
263 {
264         return strcmp(e->d_name, ".") ? 1 : 0;
265 }
266
267 static void uh_file_dirlist(struct client *cl, struct path_info *pi)
268 {
269         int i;
270         int count = 0;
271         char filename[PATH_MAX];
272         char *pathptr;
273         struct dirent **files = NULL;
274         struct stat s;
275
276         ensure_out(uh_http_sendf(cl, &cl->request,
277                                                          "<html><head><title>Index of %s</title></head>"
278                                                          "<body><h1>Index of %s</h1><hr /><ol>",
279                                                          pi->name, pi->name));
280
281         if ((count = scandir(pi->phys, &files, uh_file_scandir_filter_dir,
282                                                  alphasort)) > 0)
283         {
284                 memset(filename, 0, sizeof(filename));
285                 memcpy(filename, pi->phys, sizeof(filename));
286                 pathptr = &filename[strlen(filename)];
287
288                 /* list subdirs */
289                 for (i = 0; i < count; i++)
290                 {
291                         strncat(filename, files[i]->d_name,
292                                         sizeof(filename) - strlen(files[i]->d_name));
293
294                         if (!stat(filename, &s) &&
295                                 (s.st_mode & S_IFDIR) && (s.st_mode & S_IXOTH))
296                         {
297                                 ensure_out(uh_http_sendf(cl, &cl->request,
298                                                                                  "<li><strong><a href='%s%s'>%s</a>/"
299                                                                                  "</strong><br /><small>modified: %s"
300                                                                                  "<br />directory - %.02f kbyte<br />"
301                                                                                  "<br /></small></li>",
302                                                                                  pi->name, files[i]->d_name,
303                                                                                  files[i]->d_name,
304                                                                                  uh_file_unix2date(s.st_mtime),
305                                                                                  s.st_size / 1024.0));
306                         }
307
308                         *pathptr = 0;
309                 }
310
311                 /* list files */
312                 for (i = 0; i < count; i++)
313                 {
314                         strncat(filename, files[i]->d_name,
315                                         sizeof(filename) - strlen(files[i]->d_name));
316
317                         if (!stat(filename, &s) &&
318                                 !(s.st_mode & S_IFDIR) && (s.st_mode & S_IROTH))
319                         {
320                                 ensure_out(uh_http_sendf(cl, &cl->request,
321                                                                                  "<li><strong><a href='%s%s'>%s</a>"
322                                                                                  "</strong><br /><small>modified: %s"
323                                                                                  "<br />%s - %.02f kbyte<br />"
324                                                                                  "<br /></small></li>",
325                                                                                  pi->name, files[i]->d_name,
326                                                                                  files[i]->d_name,
327                                                                                  uh_file_unix2date(s.st_mtime),
328                                                                                  uh_file_mime_lookup(filename),
329                                                                                  s.st_size / 1024.0));
330                         }
331
332                         *pathptr = 0;
333                 }
334         }
335
336         ensure_out(uh_http_sendf(cl, &cl->request, "</ol><hr /></body></html>"));
337         ensure_out(uh_http_sendf(cl, &cl->request, ""));
338
339 out:
340         if (files)
341         {
342                 for (i = 0; i < count; i++)
343                         free(files[i]);
344
345                 free(files);
346         }
347 }
348
349
350 bool uh_file_request(struct client *cl, struct path_info *pi)
351 {
352         int rlen;
353         int ok = 1;
354         int fd = -1;
355         char buf[UH_LIMIT_MSGHEAD];
356
357         /* we have a file */
358         if ((pi->stat.st_mode & S_IFREG) && ((fd = open(pi->phys, O_RDONLY)) > 0))
359         {
360                 /* test preconditions */
361                 if (ok) ensure_out(uh_file_if_modified_since(cl, &pi->stat, &ok));
362                 if (ok) ensure_out(uh_file_if_match(cl, &pi->stat, &ok));
363                 if (ok) ensure_out(uh_file_if_range(cl, &pi->stat, &ok));
364                 if (ok) ensure_out(uh_file_if_unmodified_since(cl, &pi->stat, &ok));
365                 if (ok) ensure_out(uh_file_if_none_match(cl, &pi->stat, &ok));
366
367                 if (ok > 0)
368                 {
369                         /* write status */
370                         ensure_out(uh_file_response_200(cl, &pi->stat));
371
372                         ensure_out(uh_http_sendf(cl, NULL, "Content-Type: %s\r\n",
373                                                                          uh_file_mime_lookup(pi->name)));
374
375                         ensure_out(uh_http_sendf(cl, NULL, "Content-Length: %i\r\n",
376                                                                          pi->stat.st_size));
377
378                         /* if request was HTTP 1.1 we'll respond chunked */
379                         if ((cl->request.version > 1.0) &&
380                                 (cl->request.method != UH_HTTP_MSG_HEAD))
381                         {
382                                 ensure_out(uh_http_send(cl, NULL,
383                                                                                 "Transfer-Encoding: chunked\r\n", -1));
384                         }
385
386                         /* close header */
387                         ensure_out(uh_http_send(cl, NULL, "\r\n", -1));
388
389                         /* send body */
390                         if (cl->request.method != UH_HTTP_MSG_HEAD)
391                         {
392                                 /* pump file data */
393                                 while ((rlen = read(fd, buf, sizeof(buf))) > 0)
394                                         ensure_out(uh_http_send(cl, &cl->request, buf, rlen));
395
396                                 /* send trailer in chunked mode */
397                                 ensure_out(uh_http_send(cl, &cl->request, "", 0));
398                         }
399                 }
400
401                 /* one of the preconditions failed, terminate opened header and exit */
402                 else
403                 {
404                         ensure_out(uh_http_send(cl, NULL, "\r\n", -1));
405                 }
406         }
407
408         /* directory */
409         else if ((pi->stat.st_mode & S_IFDIR) && !cl->server->conf->no_dirlists)
410         {
411                 /* write status */
412                 ensure_out(uh_file_response_200(cl, NULL));
413
414                 if (cl->request.version > 1.0)
415                         ensure_out(uh_http_send(cl, NULL,
416                                                                         "Transfer-Encoding: chunked\r\n", -1));
417
418                 ensure_out(uh_http_send(cl, NULL,
419                                                                 "Content-Type: text/html\r\n\r\n", -1));
420
421                 /* content */
422                 uh_file_dirlist(cl, pi);
423         }
424
425         /* 403 */
426         else
427         {
428                 ensure_out(uh_http_sendhf(cl, 403, "Forbidden",
429                                                                   "Access to this resource is forbidden"));
430         }
431
432 out:
433         if (fd > -1)
434                 close(fd);
435
436         return false;
437 }