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