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