1 /*
2 * File: file.c :)
3 *
4 * Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 */
11
12 /*
13 * Directory scanning is no longer streamed, but it gets sorted instead!
14 * Directory entries on top, files next.
15 * With new HTML layout.
16 */
17
18 #include <ctype.h> /* for isspace */
19 #include <errno.h> /* for errno */
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24 #include <sys/select.h>
25 #include <sys/socket.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <sys/time.h>
29 #include <sys/un.h>
30 #include <dirent.h>
31 #include <fcntl.h>
32 #include <time.h>
33 #include <signal.h>
34 #include <netinet/in.h>
35
36 #include "../dpip/dpip.h"
37 #include "dpiutil.h"
38 #include "d_size.h"
39
40 /*
41 * Debugging macros
42 */
43 #define _MSG(...)
44 #define MSG(...) printf("[file dpi]: " __VA_ARGS__)
45 #define _MSG_RAW(...)
46 #define MSG_RAW(...) printf(__VA_ARGS__)
47
48
49 #define MAXNAMESIZE 30
50 #define HIDE_DOTFILES TRUE
51
52 /*
53 * Communication flags
54 */
55 #define FILE_AUTH_OK 1 /* Authentication done */
56 #define FILE_READ 2 /* Waiting data */
57 #define FILE_WRITE 4 /* Sending data */
58 #define FILE_DONE 8 /* Operation done */
59 #define FILE_ERR 16 /* Operation error */
60
61
62 typedef enum {
63 st_start = 10,
64 st_dpip,
65 st_http,
66 st_content,
67 st_done,
68 st_err
69 } FileState;
70
71 typedef struct {
72 char *full_path;
73 const char *filename;
74 off_t size;
75 mode_t mode;
76 time_t mtime;
77 } FileInfo;
78
79 typedef struct {
80 char *dirname;
81 Dlist *flist; /* List of files and subdirectories (for sorting) */
82 } DilloDir;
83
84 typedef struct {
85 Dsh *sh;
86 char *orig_url;
87 char *filename;
88 int file_fd;
89 off_t file_sz;
90 DilloDir *d_dir;
91 FileState state;
92 int err_code;
93 int flags;
94 int old_style;
95 } ClientInfo;
96
97 /*
98 * Forward references
99 */
100 static const char *File_content_type(const char *filename);
101
102 /*
103 * Global variables
104 */
105 static int DPIBYE = 0;
106 static int OLD_STYLE = 0;
107 /* A list for the clients we are serving */
108 static Dlist *Clients;
109 /* Set of filedescriptors we're working on */
110 fd_set read_set, write_set;
111
112
113 /*
114 * Close a file descriptor, but handling EINTR
115 */
File_close(int fd)116 static void File_close(int fd)
117 {
118 while (fd >= 0 && close(fd) < 0 && errno == EINTR)
119 ;
120 }
121
122 /*
123 * Detects 'Content-Type' when the server does not supply one.
124 * It uses the magic(5) logic from file(1). Currently, it
125 * only checks the few mime types that Dillo supports.
126 *
127 * 'Data' is a pointer to the first bytes of the raw data.
128 * (this is based on a_Misc_get_content_type_from_data())
129 */
File_get_content_type_from_data(void * Data,size_t Size)130 static const char *File_get_content_type_from_data(void *Data, size_t Size)
131 {
132 static const char *Types[] = {
133 "application/octet-stream",
134 "text/html", "text/plain",
135 "image/gif", "image/png", "image/jpeg",
136 };
137 int Type = 0;
138 char *p = Data;
139 size_t i, non_ascci;
140
141 _MSG("File_get_content_type_from_data:: Size = %d\n", Size);
142
143 /* HTML try */
144 for (i = 0; i < Size && dIsspace(p[i]); ++i);
145 if ((Size - i >= 5 && !dStrnAsciiCasecmp(p+i, "<html", 5)) ||
146 (Size - i >= 5 && !dStrnAsciiCasecmp(p+i, "<head", 5)) ||
147 (Size - i >= 6 && !dStrnAsciiCasecmp(p+i, "<title", 6)) ||
148 (Size - i >= 14 && !dStrnAsciiCasecmp(p+i, "<!doctype html", 14)) ||
149 /* this line is workaround for FTP through the Squid proxy */
150 (Size - i >= 17 && !dStrnAsciiCasecmp(p+i, "<!-- HTML listing", 17))) {
151
152 Type = 1;
153
154 /* Images */
155 } else if (Size >= 4 && !strncmp(p, "GIF8", 4)) {
156 Type = 3;
157 } else if (Size >= 4 && !strncmp(p, "\x89PNG", 4)) {
158 Type = 4;
159 } else if (Size >= 2 && !strncmp(p, "\xff\xd8", 2)) {
160 /* JPEG has the first 2 bytes set to 0xffd8 in BigEndian - looking
161 * at the character representation should be machine independent. */
162 Type = 5;
163
164 /* Text */
165 } else {
166 /* We'll assume "text/plain" if the set of chars above 127 is <= 10
167 * in a 256-bytes sample. Better heuristics are welcomed! :-) */
168 non_ascci = 0;
169 Size = MIN (Size, 256);
170 for (i = 0; i < Size; i++)
171 if ((uchar_t) p[i] > 127)
172 ++non_ascci;
173 if (Size == 256) {
174 Type = (non_ascci > 10) ? 0 : 2;
175 } else {
176 Type = (non_ascci > 0) ? 0 : 2;
177 }
178 }
179
180 return (Types[Type]);
181 }
182
183 /*
184 * Compare two FileInfo pointers
185 * This function is used for sorting directories
186 */
File_comp(const FileInfo * f1,const FileInfo * f2)187 static int File_comp(const FileInfo *f1, const FileInfo *f2)
188 {
189 if (S_ISDIR(f1->mode)) {
190 if (S_ISDIR(f2->mode)) {
191 return strcmp(f1->filename, f2->filename);
192 } else {
193 return -1;
194 }
195 } else {
196 if (S_ISDIR(f2->mode)) {
197 return 1;
198 } else {
199 return strcmp(f1->filename, f2->filename);
200 }
201 }
202 }
203
204 /*
205 * Allocate a DilloDir structure, set safe values in it and sort the entries.
206 */
File_dillodir_new(char * dirname)207 static DilloDir *File_dillodir_new(char *dirname)
208 {
209 struct stat sb;
210 struct dirent *de;
211 DIR *dir;
212 DilloDir *Ddir;
213 FileInfo *finfo;
214 char *fname;
215 int dirname_len;
216
217 if (!(dir = opendir(dirname)))
218 return NULL;
219
220 Ddir = dNew(DilloDir, 1);
221 Ddir->dirname = dStrdup(dirname);
222 Ddir->flist = dList_new(512);
223
224 dirname_len = strlen(Ddir->dirname);
225
226 /* Scan every name and sort them */
227 while ((de = readdir(dir)) != 0) {
228 if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
229 continue; /* skip "." and ".." */
230
231 if (HIDE_DOTFILES) {
232 /* Don't add hidden files or backup files to the list */
233 if (de->d_name[0] == '.' ||
234 de->d_name[0] == '#' ||
235 (de->d_name[0] != '\0' &&
236 de->d_name[strlen(de->d_name) - 1] == '~'))
237 continue;
238 }
239
240 fname = dStrconcat(Ddir->dirname, de->d_name, NULL);
241 if (stat(fname, &sb) == -1) {
242 dFree(fname);
243 continue; /* ignore files we can't stat */
244 }
245
246 finfo = dNew(FileInfo, 1);
247 finfo->full_path = fname;
248 finfo->filename = fname + dirname_len;
249 finfo->size = sb.st_size;
250 finfo->mode = sb.st_mode;
251 finfo->mtime = sb.st_mtime;
252
253 dList_append(Ddir->flist, finfo);
254 }
255
256 closedir(dir);
257
258 /* sort the entries */
259 dList_sort(Ddir->flist, (dCompareFunc)File_comp);
260
261 return Ddir;
262 }
263
264 /*
265 * Deallocate a DilloDir structure.
266 */
File_dillodir_free(DilloDir * Ddir)267 static void File_dillodir_free(DilloDir *Ddir)
268 {
269 int i;
270 FileInfo *finfo;
271
272 dReturn_if (Ddir == NULL);
273
274 for (i = 0; i < dList_length(Ddir->flist); ++i) {
275 finfo = dList_nth_data(Ddir->flist, i);
276 dFree(finfo->full_path);
277 dFree(finfo);
278 }
279
280 dList_free(Ddir->flist);
281 dFree(Ddir->dirname);
282 dFree(Ddir);
283 }
284
285 /*
286 * Output the string for parent directory
287 */
File_print_parent_dir(ClientInfo * client,const char * dirname)288 static void File_print_parent_dir(ClientInfo *client, const char *dirname)
289 {
290 if (strcmp(dirname, "/") != 0) { /* Not the root dir */
291 char *p, *parent, *HUparent, *Uparent;
292
293 parent = dStrdup(dirname);
294 /* cut trailing slash */
295 parent[strlen(parent) - 1] = '\0';
296 /* make 'parent' have the parent dir path */
297 if ((p = strrchr(parent, '/')))
298 *(p + 1) = '\0';
299
300 Uparent = Escape_uri_str(parent, NULL);
301 HUparent = Escape_html_str(Uparent);
302 a_Dpip_dsh_printf(client->sh, 0,
303 "<a href='file:%s'>Parent directory</a>", HUparent);
304 dFree(HUparent);
305 dFree(Uparent);
306 dFree(parent);
307 }
308 }
309
310 /*
311 * Given a timestamp, output an HTML-formatted date string.
312 */
File_print_mtime(ClientInfo * client,time_t mtime)313 static void File_print_mtime(ClientInfo *client, time_t mtime)
314 {
315 char *ds = ctime(&mtime);
316
317 /* Month, day and {hour or year} */
318 if (client->old_style) {
319 a_Dpip_dsh_printf(client->sh, 0, " %.3s %.2s", ds + 4, ds + 8);
320 if (time(NULL) - mtime > 15811200) {
321 a_Dpip_dsh_printf(client->sh, 0, " %.4s", ds + 20);
322 } else {
323 a_Dpip_dsh_printf(client->sh, 0, " %.5s", ds + 11);
324 }
325 } else {
326 a_Dpip_dsh_printf(client->sh, 0,
327 "<td>%.3s %.2s %.5s", ds + 4, ds + 8,
328 /* (more than 6 months old) ? year : hour; */
329 (time(NULL) - mtime > 15811200) ? ds + 20 : ds + 11);
330 }
331 }
332
333 /*
334 * Return a HTML-line from file info.
335 */
File_info2html(ClientInfo * client,FileInfo * finfo,int n)336 static void File_info2html(ClientInfo *client, FileInfo *finfo, int n)
337 {
338 int size;
339 char *sizeunits;
340 char namebuf[MAXNAMESIZE + 1];
341 char *Uref, *HUref, *Hname;
342 const char *ref, *filecont, *name = finfo->filename;
343
344 if (finfo->size <= 9999) {
345 size = finfo->size;
346 sizeunits = "bytes";
347 } else if (finfo->size / 1024 <= 9999) {
348 size = finfo->size / 1024 + (finfo->size % 1024 >= 1024 / 2);
349 sizeunits = "KB";
350 } else {
351 size = finfo->size / 1048576 + (finfo->size % 1048576 >= 1048576 / 2);
352 sizeunits = "MB";
353 }
354
355 /* we could note if it's a symlink... */
356 if S_ISDIR (finfo->mode) {
357 filecont = "Directory";
358 } else if (finfo->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
359 filecont = "Executable";
360 } else {
361 filecont = File_content_type(finfo->full_path);
362 if (!filecont || !strcmp(filecont, "application/octet-stream"))
363 filecont = "unknown";
364 }
365
366 ref = name;
367
368 if (strlen(name) > MAXNAMESIZE) {
369 memcpy(namebuf, name, MAXNAMESIZE - 3);
370 strcpy(namebuf + (MAXNAMESIZE - 3), "...");
371 name = namebuf;
372 }
373
374 /* escape problematic filenames */
375 Uref = Escape_uri_str(ref, NULL);
376 HUref = Escape_html_str(Uref);
377 Hname = Escape_html_str(name);
378
379 if (client->old_style) {
380 char *dots = ".. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..";
381 int ndots = MAXNAMESIZE - strlen(name);
382 a_Dpip_dsh_printf(client->sh, 0,
383 "%s<a href='%s'>%s</a>"
384 " %s"
385 " %-11s%4d %-5s",
386 S_ISDIR (finfo->mode) ? ">" : " ", HUref, Hname,
387 dots + 50 - (ndots > 0 ? ndots : 0),
388 filecont, size, sizeunits);
389
390 } else {
391 a_Dpip_dsh_printf(client->sh, 0,
392 "<tr align=center %s><td>%s<td align=left><a href='%s'>%s</a>"
393 "<td>%s<td>%d %s",
394 (n & 1) ? "bgcolor=#dcdcdc" : "",
395 S_ISDIR (finfo->mode) ? ">" : " ", HUref, Hname,
396 filecont, size, sizeunits);
397 }
398 File_print_mtime(client, finfo->mtime);
399 a_Dpip_dsh_write_str(client->sh, 0, "\n");
400
401 dFree(Hname);
402 dFree(HUref);
403 dFree(Uref);
404 }
405
406 /*
407 * Send the HTML directory page in HTTP.
408 */
File_send_dir(ClientInfo * client)409 static void File_send_dir(ClientInfo *client)
410 {
411 int n;
412 char *d_cmd, *Hdirname, *Udirname, *HUdirname;
413 DilloDir *Ddir = client->d_dir;
414
415 if (client->state == st_start) {
416 /* Send DPI command */
417 d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page",
418 client->orig_url);
419 a_Dpip_dsh_write_str(client->sh, 1, d_cmd);
420 dFree(d_cmd);
421 client->state = st_dpip;
422
423 } else if (client->state == st_dpip) {
424 /* send HTTP header and HTML top part */
425
426 /* Send page title */
427 Udirname = Escape_uri_str(Ddir->dirname, NULL);
428 HUdirname = Escape_html_str(Udirname);
429 Hdirname = Escape_html_str(Ddir->dirname);
430
431 a_Dpip_dsh_printf(client->sh, 0,
432 "HTTP/1.1 200 OK\r\n"
433 "Content-Type: text/html\r\n"
434 "\r\n"
435 "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n"
436 "<HTML>\n<HEAD>\n <BASE href='file:%s'>\n"
437 " <TITLE>file:%s</TITLE>\n</HEAD>\n"
438 "<BODY><H1>Directory listing of %s</H1>\n",
439 HUdirname, Hdirname, Hdirname);
440 dFree(Hdirname);
441 dFree(HUdirname);
442 dFree(Udirname);
443
444 if (client->old_style) {
445 a_Dpip_dsh_write_str(client->sh, 0, "<pre>\n");
446 }
447
448 /* Output the parent directory */
449 File_print_parent_dir(client, Ddir->dirname);
450
451 /* HTML style toggle */
452 a_Dpip_dsh_write_str(client->sh, 0,
453 " <a href='dpi:/file/toggle'>%</a>\n");
454
455 if (dList_length(Ddir->flist)) {
456 if (client->old_style) {
457 a_Dpip_dsh_write_str(client->sh, 0, "\n\n");
458 } else {
459 a_Dpip_dsh_write_str(client->sh, 0,
460 "<br><br>\n"
461 "<table border=0 cellpadding=1 cellspacing=0"
462 " bgcolor=#E0E0E0 width=100%>\n"
463 "<tr align=center>\n"
464 "<td>\n"
465 "<td width=60%><b>Filename</b>"
466 "<td><b>Type</b>"
467 "<td><b>Size</b>"
468 "<td><b>Modified at</b>\n");
469 }
470 } else {
471 a_Dpip_dsh_write_str(client->sh, 0, "<br><br>Directory is empty...");
472 }
473 client->state = st_http;
474
475 } else if (client->state == st_http) {
476 /* send directories as HTML contents */
477 for (n = 0; n < dList_length(Ddir->flist); ++n) {
478 File_info2html(client, dList_nth_data(Ddir->flist,n), n+1);
479 }
480
481 if (client->old_style) {
482 a_Dpip_dsh_write_str(client->sh, 0, "</pre>\n");
483 } else if (dList_length(Ddir->flist)) {
484 a_Dpip_dsh_write_str(client->sh, 0, "</table>\n");
485 }
486
487 a_Dpip_dsh_write_str(client->sh, 1, "</BODY></HTML>\n");
488 client->state = st_content;
489 client->flags |= FILE_DONE;
490 }
491 }
492
493 /*
494 * Return a content type based on the extension of the filename.
495 */
File_ext(const char * filename)496 static const char *File_ext(const char *filename)
497 {
498 char *e;
499
500 if (!(e = strrchr(filename, '.')))
501 return NULL;
502
503 e++;
504
505 if (!dStrAsciiCasecmp(e, "gif")) {
506 return "image/gif";
507 } else if (!dStrAsciiCasecmp(e, "jpg") ||
508 !dStrAsciiCasecmp(e, "jpeg")) {
509 return "image/jpeg";
510 } else if (!dStrAsciiCasecmp(e, "png")) {
511 return "image/png";
512 } else if (!dStrAsciiCasecmp(e, "html") ||
513 !dStrAsciiCasecmp(e, "htm") ||
514 !dStrAsciiCasecmp(e, "shtml")) {
515 return "text/html";
516 } else if (!dStrAsciiCasecmp(e, "txt")) {
517 return "text/plain";
518 } else {
519 return NULL;
520 }
521 }
522
523 /*
524 * Based on the extension, return the content_type for the file.
525 * (if there's no extension, analyze the data and try to figure it out)
526 */
File_content_type(const char * filename)527 static const char *File_content_type(const char *filename)
528 {
529 int fd;
530 struct stat sb;
531 const char *ct;
532 char buf[256];
533 ssize_t buf_size;
534
535 if (!(ct = File_ext(filename))) {
536 /* everything failed, let's analyze the data... */
537 if ((fd = open(filename, O_RDONLY | O_NONBLOCK)) != -1) {
538 if ((buf_size = read(fd, buf, 256)) == 256 ) {
539 ct = File_get_content_type_from_data(buf, (size_t)buf_size);
540
541 } else if (stat(filename, &sb) != -1 &&
542 buf_size > 0 && buf_size == sb.st_size) {
543 ct = File_get_content_type_from_data(buf, (size_t)buf_size);
544 }
545 File_close(fd);
546 }
547 }
548 _MSG("File_content_type: name=%s ct=%s\n", filename, ct);
549 return ct;
550 }
551
552 /*
553 * Send an error page
554 */
File_prepare_send_error_page(ClientInfo * client,int res,const char * orig_url)555 static void File_prepare_send_error_page(ClientInfo *client, int res,
556 const char *orig_url)
557 {
558 client->state = st_err;
559 client->err_code = res;
560 client->orig_url = dStrdup(orig_url);
561 client->flags &= ~FILE_READ;
562 client->flags |= FILE_WRITE;
563 }
564
565 /*
566 * Send an error page
567 */
File_send_error_page(ClientInfo * client)568 static void File_send_error_page(ClientInfo *client)
569 {
570 const char *status;
571 char *d_cmd;
572 Dstr *body = dStr_sized_new(128);
573
574 if (client->err_code == EACCES) {
575 status = "403 Forbidden";
576 } else if (client->err_code == ENOENT) {
577 status = "404 Not Found";
578 } else {
579 /* good enough */
580 status = "500 Internal Server Error";
581 }
582 dStr_append(body, status);
583 dStr_append(body, "\n");
584 dStr_append(body, dStrerror(client->err_code));
585
586 /* Send DPI command */
587 d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page",
588 client->orig_url);
589 a_Dpip_dsh_write_str(client->sh, 0, d_cmd);
590 dFree(d_cmd);
591
592 a_Dpip_dsh_printf(client->sh, 0,
593 "HTTP/1.1 %s\r\n"
594 "Content-Type: text/plain\r\n"
595 "Content-Length: %d\r\n"
596 "\r\n"
597 "%s",
598 status, body->len, body->str);
599 dStr_free(body, TRUE);
600
601 client->flags |= FILE_DONE;
602 }
603
604 /*
605 * Scan the directory, sort and prepare to send it enclosed in HTTP.
606 */
File_prepare_send_dir(ClientInfo * client,const char * DirName,const char * orig_url)607 static int File_prepare_send_dir(ClientInfo *client,
608 const char *DirName, const char *orig_url)
609 {
610 Dstr *ds_dirname;
611 DilloDir *Ddir;
612
613 /* Let's make sure this directory url has a trailing slash */
614 ds_dirname = dStr_new(DirName);
615 if (ds_dirname->str[ds_dirname->len - 1] != '/')
616 dStr_append(ds_dirname, "/");
617
618 /* Let's get a structure ready for transfer */
619 Ddir = File_dillodir_new(ds_dirname->str);
620 dStr_free(ds_dirname, TRUE);
621 if (Ddir) {
622 /* looks ok, set things accordingly */
623 client->orig_url = dStrdup(orig_url);
624 client->d_dir = Ddir;
625 client->state = st_start;
626 client->flags &= ~FILE_READ;
627 client->flags |= FILE_WRITE;
628 return 0;
629 } else
630 return EACCES;
631 }
632
633 /*
634 * Prepare to send HTTP headers and then the file itself.
635 */
File_prepare_send_file(ClientInfo * client,const char * filename,const char * orig_url)636 static int File_prepare_send_file(ClientInfo *client,
637 const char *filename,
638 const char *orig_url)
639 {
640 int fd, res = -1;
641 struct stat sb;
642
643 if (stat(filename, &sb) != 0) {
644 /* prepare a file-not-found error */
645 res = ENOENT;
646 } else if ((fd = open(filename, O_RDONLY | O_NONBLOCK)) < 0) {
647 /* prepare an error message */
648 res = errno;
649 } else {
650 /* looks ok, set things accordingly */
651 client->file_fd = fd;
652 client->file_sz = sb.st_size;
653 client->d_dir = NULL;
654 client->state = st_start;
655 client->filename = dStrdup(filename);
656 client->orig_url = dStrdup(orig_url);
657 client->flags &= ~FILE_READ;
658 client->flags |= FILE_WRITE;
659 res = 0;
660 }
661 return res;
662 }
663
664 /*
665 * Try to stat the file and determine if it's readable.
666 */
File_get(ClientInfo * client,const char * filename,const char * orig_url)667 static void File_get(ClientInfo *client, const char *filename,
668 const char *orig_url)
669 {
670 int res;
671 struct stat sb;
672
673 if (stat(filename, &sb) != 0) {
674 /* stat failed, prepare a file-not-found error. */
675 res = ENOENT;
676 } else if (S_ISDIR(sb.st_mode)) {
677 /* set up for reading directory */
678 res = File_prepare_send_dir(client, filename, orig_url);
679 } else {
680 /* set up for reading a file */
681 res = File_prepare_send_file(client, filename, orig_url);
682 }
683 if (res != 0) {
684 File_prepare_send_error_page(client, res, orig_url);
685 }
686 }
687
688 /*
689 * Send HTTP headers and then the file itself.
690 */
File_send_file(ClientInfo * client)691 static int File_send_file(ClientInfo *client)
692 {
693 //#define LBUF 1
694 #define LBUF 16*1024
695
696 const char *ct;
697 const char *unknown_type = "application/octet-stream";
698 char buf[LBUF], *d_cmd, *name;
699 int st, st2, namelen;
700 bool_t gzipped = FALSE;
701
702 if (client->state == st_start) {
703 /* Send DPI command */
704 d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page",
705 client->orig_url);
706 a_Dpip_dsh_write_str(client->sh, 1, d_cmd);
707 dFree(d_cmd);
708 client->state = st_dpip;
709
710 } else if (client->state == st_dpip) {
711 /* send HTTP header */
712
713 /* Check for gzipped file */
714 namelen = strlen(client->filename);
715 if (namelen > 3 &&
716 !dStrAsciiCasecmp(client->filename + namelen - 3, ".gz")) {
717 gzipped = TRUE;
718 namelen -= 3;
719 }
720 /* Content-Type info is based on filename extension (with ".gz" removed).
721 * If there's no known extension, perform data sniffing.
722 * If this doesn't lead to a conclusion, use "application/octet-stream".
723 */
724 name = dStrndup(client->filename, namelen);
725 if (!(ct = File_content_type(name)))
726 ct = unknown_type;
727 dFree(name);
728
729 /* Send HTTP headers */
730 a_Dpip_dsh_write_str(client->sh, 0, "HTTP/1.1 200 OK\r\n");
731 if (gzipped) {
732 a_Dpip_dsh_write_str(client->sh, 0, "Content-Encoding: gzip\r\n");
733 }
734 if (!gzipped || strcmp(ct, unknown_type)) {
735 a_Dpip_dsh_printf(client->sh, 0, "Content-Type: %s\r\n", ct);
736 } else {
737 /* If we don't know type for gzipped data, let dillo figure it out. */
738 }
739 a_Dpip_dsh_printf(client->sh, 1,
740 "Content-Length: %ld\r\n"
741 "\r\n",
742 client->file_sz);
743 client->state = st_http;
744
745 } else if (client->state == st_http) {
746 /* Send body -- raw file contents */
747 if ((st = a_Dpip_dsh_tryflush(client->sh)) < 0) {
748 client->flags |= (st == -3) ? FILE_ERR : 0;
749 } else {
750 /* no pending data, let's send new data */
751 do {
752 st2 = read(client->file_fd, buf, LBUF);
753 } while (st2 < 0 && errno == EINTR);
754 if (st2 < 0) {
755 MSG("\nERROR while reading from file '%s': %s\n\n",
756 client->filename, dStrerror(errno));
757 client->flags |= FILE_ERR;
758 } else if (st2 == 0) {
759 client->state = st_content;
760 client->flags |= FILE_DONE;
761 } else {
762 /* ok to write */
763 st = a_Dpip_dsh_trywrite(client->sh, buf, st2);
764 client->flags |= (st == -3) ? FILE_ERR : 0;
765 }
766 }
767 }
768
769 return 0;
770 }
771
772 /*
773 * Given a hex octet (e3, 2F, 20), return the corresponding
774 * character if the octet is valid, and -1 otherwise
775 */
File_parse_hex_octet(const char * s)776 static int File_parse_hex_octet(const char *s)
777 {
778 int hex_value;
779 char *tail, hex[3];
780
781 if ((hex[0] = s[0]) && (hex[1] = s[1])) {
782 hex[2] = 0;
783 hex_value = strtol(hex, &tail, 16);
784 if (tail - hex == 2)
785 return hex_value;
786 }
787
788 return -1;
789 }
790
791 /*
792 * Make a file URL into a human (and machine) readable path.
793 * The idea is to always have a path that starts with only one slash.
794 * Embedded slashes are ignored.
795 */
File_normalize_path(const char * orig)796 static char *File_normalize_path(const char *orig)
797 {
798 char *str = (char *) orig, *basename = NULL, *ret = NULL, *p;
799
800 dReturn_val_if (orig == NULL, ret);
801
802 /* Make sure the string starts with "file:/" */
803 if (dStrnAsciiCasecmp(str, "file:/", 5) != 0)
804 return ret;
805 str += 5;
806
807 /* Skip "localhost" */
808 if (dStrnAsciiCasecmp(str, "//localhost/", 12) == 0)
809 str += 11;
810
811 /* Skip packed slashes, and leave just one */
812 while (str[0] == '/' && str[1] == '/')
813 str++;
814
815 {
816 int i, val;
817 Dstr *ds = dStr_sized_new(32);
818 dStr_sprintf(ds, "%s%s%s",
819 basename ? basename : "",
820 basename ? "/" : "",
821 str);
822 dFree(basename);
823
824 /* Parse possible hexadecimal octets in the URI path */
825 for (i = 0; ds->str[i]; ++i) {
826 if (ds->str[i] == '%' &&
827 (val = File_parse_hex_octet(ds->str + i+1)) != -1) {
828 ds->str[i] = val;
829 dStr_erase(ds, i+1, 2);
830 }
831 }
832 /* Remove the fragment if not a part of the filename */
833 if ((p = strrchr(ds->str, '#')) != NULL && access(ds->str, F_OK) != 0)
834 dStr_truncate(ds, p - ds->str);
835 ret = ds->str;
836 dStr_free(ds, 0);
837 }
838
839 return ret;
840 }
841
842 /*
843 * Set the style flag and ask for a reload, so it shows immediately.
844 */
File_toggle_html_style(ClientInfo * client)845 static void File_toggle_html_style(ClientInfo *client)
846 {
847 char *d_cmd;
848
849 OLD_STYLE = !OLD_STYLE;
850 d_cmd = a_Dpip_build_cmd("cmd=%s", "reload_request");
851 a_Dpip_dsh_write_str(client->sh, 1, d_cmd);
852 dFree(d_cmd);
853 }
854
855 /*
856 * Perform any necessary cleanups upon abnormal termination
857 */
termination_handler(int signum)858 static void termination_handler(int signum)
859 {
860 MSG("\nexit(signum), signum=%d\n\n", signum);
861 exit(signum);
862 }
863
864
865 /* Client handling ----------------------------------------------------------*/
866
867 /*
868 * Add a new client to the list.
869 */
File_add_client(int sock_fd)870 static ClientInfo *File_add_client(int sock_fd)
871 {
872 ClientInfo *new_client;
873
874 new_client = dNew(ClientInfo, 1);
875 new_client->sh = a_Dpip_dsh_new(sock_fd, sock_fd, 8*1024);
876 new_client->orig_url = NULL;
877 new_client->filename = NULL;
878 new_client->file_fd = -1;
879 new_client->file_sz = 0;
880 new_client->d_dir = NULL;
881 new_client->state = 0;
882 new_client->err_code = 0;
883 new_client->flags = FILE_READ;
884 new_client->old_style = OLD_STYLE;
885
886 dList_append(Clients, new_client);
887 return new_client;
888 }
889
890 /*
891 * Remove a client from the list.
892 */
File_remove_client(ClientInfo * client)893 static void File_remove_client(ClientInfo *client)
894 {
895 dList_remove(Clients, (void *)client);
896
897 _MSG("Closing Socket Handler\n");
898 a_Dpip_dsh_close(client->sh);
899 a_Dpip_dsh_free(client->sh);
900 File_close(client->file_fd);
901 dFree(client->orig_url);
902 dFree(client->filename);
903 File_dillodir_free(client->d_dir);
904
905 dFree(client);
906 }
907
908 /*
909 * Serve this client.
910 */
File_serve_client(void * data,int f_write)911 static void File_serve_client(void *data, int f_write)
912 {
913 char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *path;
914 ClientInfo *client = data;
915 int st;
916
917 while (1) {
918 _MSG("File_serve_client %p, flags=%d state=%d\n",
919 client, client->flags, client->state);
920 if (client->flags & (FILE_DONE | FILE_ERR))
921 break;
922 if (client->flags & FILE_READ) {
923 dpip_tag = a_Dpip_dsh_read_token(client->sh, 0);
924 _MSG("dpip_tag={%s}\n", dpip_tag);
925 if (!dpip_tag)
926 break;
927 }
928
929 if (client->flags & FILE_READ) {
930 if (!(client->flags & FILE_AUTH_OK)) {
931 /* Authenticate our client... */
932 st = a_Dpip_check_auth(dpip_tag);
933 _MSG("a_Dpip_check_auth returned %d\n", st);
934 client->flags |= (st == 1) ? FILE_AUTH_OK : FILE_ERR;
935 } else {
936 /* Get file request */
937 cmd = a_Dpip_get_attr(dpip_tag, "cmd");
938 url = a_Dpip_get_attr(dpip_tag, "url");
939 path = File_normalize_path(url);
940 if (cmd) {
941 if (strcmp(cmd, "DpiBye") == 0) {
942 DPIBYE = 1;
943 MSG("(pid %d): Got DpiBye.\n", (int)getpid());
944 client->flags |= FILE_DONE;
945 } else if (url && dStrnAsciiCasecmp(url, "dpi:", 4) == 0 &&
946 strcmp(url+4, "/file/toggle") == 0) {
947 File_toggle_html_style(client);
948 } else if (path) {
949 File_get(client, path, url);
950 } else {
951 client->flags |= FILE_ERR;
952 MSG("ERROR: URL was %s\n", url);
953 }
954 }
955 dFree(path);
956 dFree(url);
957 dFree(cmd);
958 dFree(dpip_tag);
959 break;
960 }
961 dFree(dpip_tag);
962
963 } else if (f_write) {
964 /* send our answer */
965 if (client->state == st_err)
966 File_send_error_page(client);
967 else if (client->d_dir)
968 File_send_dir(client);
969 else
970 File_send_file(client);
971 break;
972 }
973 } /*while*/
974
975 client->flags |= (client->sh->status & DPIP_ERROR) ? FILE_ERR : 0;
976 client->flags |= (client->sh->status & DPIP_EOF) ? FILE_DONE : 0;
977 }
978
979 /*
980 * Serve the client queue.
981 */
File_serve_clients()982 static void File_serve_clients()
983 {
984 int i, f_read, f_write;
985 ClientInfo *client;
986
987 for (i = 0; (client = dList_nth_data(Clients, i)); ++i) {
988 f_read = FD_ISSET(client->sh->fd_in, &read_set);
989 f_write = FD_ISSET(client->sh->fd_out, &write_set);
990 if (!f_read && !f_write)
991 continue;
992 File_serve_client(client, f_write);
993 if (client->flags & (FILE_DONE | FILE_ERR)) {
994 File_remove_client(client);
995 --i;
996 }
997 }
998 }
999
1000 /* --------------------------------------------------------------------------*/
1001
1002 /*
1003 * Check the fd sets for activity, with a max timeout.
1004 * return value: 0 if timeout, 1 if input available, -1 if error.
1005 */
File_check_fds(uint_t seconds)1006 static int File_check_fds(uint_t seconds)
1007 {
1008 int i, st;
1009 ClientInfo *client;
1010 struct timeval timeout;
1011
1012 /* initialize observed file descriptors */
1013 FD_ZERO (&read_set);
1014 FD_ZERO (&write_set);
1015 FD_SET (STDIN_FILENO, &read_set);
1016 for (i = 0; (client = dList_nth_data(Clients, i)); ++i) {
1017 if (client->flags & FILE_READ)
1018 FD_SET (client->sh->fd_in, &read_set);
1019 if (client->flags & FILE_WRITE)
1020 FD_SET (client->sh->fd_out, &write_set);
1021 }
1022 _MSG("Watching %d fds\n", dList_length(Clients) + 1);
1023
1024 /* Initialize the timeout data structure. */
1025 timeout.tv_sec = seconds;
1026 timeout.tv_usec = 0;
1027
1028 do {
1029 st = select(FD_SETSIZE, &read_set, &write_set, NULL, &timeout);
1030 } while (st == -1 && errno == EINTR);
1031 /*
1032 MSG_RAW(" (%d%s%s)", STDIN_FILENO,
1033 FD_ISSET(STDIN_FILENO, &read_set) ? "R" : "",
1034 FD_ISSET(STDIN_FILENO, &write_set) ? "W" : "");
1035 for (i = 0; (client = dList_nth_data(Clients, i)); ++i) {
1036 MSG_RAW(" (%d%s%s)", client->sh->fd_in,
1037 FD_ISSET(client->sh->fd_in, &read_set) ? "R" : "",
1038 FD_ISSET(client->sh->fd_out, &write_set) ? "W" : "");
1039 }
1040 MSG_RAW("\n");
1041 */
1042 return st;
1043 }
1044
1045
main(void)1046 int main(void)
1047 {
1048 struct sockaddr_in sin;
1049 socklen_t sin_sz;
1050 int sock_fd, c_st, st = 1;
1051
1052 /* Arrange the cleanup function for abnormal terminations */
1053 if (signal (SIGINT, termination_handler) == SIG_IGN)
1054 signal (SIGINT, SIG_IGN);
1055 if (signal (SIGHUP, termination_handler) == SIG_IGN)
1056 signal (SIGHUP, SIG_IGN);
1057 if (signal (SIGTERM, termination_handler) == SIG_IGN)
1058 signal (SIGTERM, SIG_IGN);
1059
1060 MSG("(v.2) accepting connections...\n");
1061 //sleep(20);
1062
1063 /* initialize observed file descriptors */
1064 FD_ZERO (&read_set);
1065 FD_ZERO (&write_set);
1066 FD_SET (STDIN_FILENO, &read_set);
1067
1068 /* Set STDIN socket nonblocking (to ensure accept() never blocks) */
1069 fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK | fcntl(STDIN_FILENO, F_GETFL));
1070
1071 /* initialize Clients list */
1072 Clients = dList_new(512);
1073
1074 /* some OSes may need this... */
1075 sin_sz = sizeof(sin);
1076
1077 /* start the service loop */
1078 while (!DPIBYE) {
1079 /* wait for activity */
1080 do {
1081 c_st = File_check_fds(10);
1082 } while (c_st == 0 && !DPIBYE);
1083 if (c_st < 0) {
1084 MSG(" select() %s\n", dStrerror(errno));
1085 break;
1086 }
1087 if (DPIBYE)
1088 break;
1089
1090 if (FD_ISSET(STDIN_FILENO, &read_set)) {
1091 /* accept the incoming connection */
1092 do {
1093 sock_fd = accept(STDIN_FILENO, (struct sockaddr *)&sin, &sin_sz);
1094 } while (sock_fd < 0 && errno == EINTR);
1095 if (sock_fd == -1) {
1096 if (errno == EAGAIN)
1097 continue;
1098 MSG(" accept() %s\n", dStrerror(errno));
1099 break;
1100 } else {
1101 _MSG(" accept() fd=%d\n", sock_fd);
1102 /* Set nonblocking */
1103 fcntl(sock_fd, F_SETFL, O_NONBLOCK | fcntl(sock_fd, F_GETFL));
1104 /* Create and initialize a new client */
1105 File_add_client(sock_fd);
1106 }
1107 continue;
1108 }
1109
1110 File_serve_clients();
1111 }
1112
1113 if (DPIBYE)
1114 st = 0;
1115 return st;
1116 }
1117
1118