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&nbsp;%.2s&nbsp;%.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&nbsp;%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          "&nbsp;&nbsp;<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&nbsp;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