diff options
author | jcid <devnull@localhost> | 2007-10-07 00:36:34 +0200 |
---|---|---|
committer | jcid <devnull@localhost> | 2007-10-07 00:36:34 +0200 |
commit | 93715c46a99c96d6c866968312691ec9ab0f6a03 (patch) | |
tree | 573f19ec6aa740844f53a7c0eb7114f04096bf64 /dpi |
Initial revision
Diffstat (limited to 'dpi')
-rw-r--r-- | dpi/Makefile.am | 37 | ||||
-rw-r--r-- | dpi/bookmarks.c | 1716 | ||||
-rw-r--r-- | dpi/cookies.c | 1466 | ||||
-rw-r--r-- | dpi/datauri.c | 323 | ||||
-rw-r--r-- | dpi/downloads.cc | 1133 | ||||
-rw-r--r-- | dpi/dpiutil.c | 270 | ||||
-rw-r--r-- | dpi/dpiutil.h | 97 | ||||
-rw-r--r-- | dpi/file.c | 975 | ||||
-rw-r--r-- | dpi/ftp.c | 301 | ||||
-rw-r--r-- | dpi/hello.c | 161 | ||||
-rw-r--r-- | dpi/https.c | 713 |
11 files changed, 7192 insertions, 0 deletions
diff --git a/dpi/Makefile.am b/dpi/Makefile.am new file mode 100644 index 00000000..6aba3049 --- /dev/null +++ b/dpi/Makefile.am @@ -0,0 +1,37 @@ +bookmarksdir = $(libdir)/dillo/dpi/bookmarks +downloadsdir = $(libdir)/dillo/dpi/downloads +ftpdir = $(libdir)/dillo/dpi/ftp +httpsdir = $(libdir)/dillo/dpi/https +hellodir = $(libdir)/dillo/dpi/hello +filedir = $(libdir)/dillo/dpi/file +cookiesdir = $(libdir)/dillo/dpi/cookies +datauridir = $(libdir)/dillo/dpi/datauri +bookmarks_PROGRAMS = bookmarks.dpi +downloads_PROGRAMS = downloads.dpi +ftp_PROGRAMS = ftp.filter.dpi +https_PROGRAMS = https.filter.dpi +hello_PROGRAMS = hello.filter.dpi +file_PROGRAMS = file.dpi +cookies_PROGRAMS = cookies.dpi +datauri_PROGRAMS = datauri.filter.dpi + +bookmarks_dpi_LDADD = ../dpip/libDpip.a ../dlib/libDlib.a +downloads_dpi_LDADD = @LIBFLTK_LIBS@ ../dpip/libDpip.a ../dlib/libDlib.a +ftp_filter_dpi_LDADD = ../dpip/libDpip.a ../dlib/libDlib.a +https_filter_dpi_LDADD = @LIBSSL_LIBS@ ../dpip/libDpip.a ../dlib/libDlib.a +hello_filter_dpi_LDADD = ../dpip/libDpip.a ../dlib/libDlib.a +file_dpi_LDADD = @LIBPTHREAD_LIBS@ ../dpip/libDpip.a ../dlib/libDlib.a +cookies_dpi_LDADD = ../dpip/libDpip.a ../dlib/libDlib.a +datauri_filter_dpi_LDADD = ../dpip/libDpip.a ../dlib/libDlib.a + +file_dpi_LDFLAGS = @LIBPTHREAD_LDFLAGS@ + +bookmarks_dpi_SOURCES = bookmarks.c dpiutil.c dpiutil.h +downloads_dpi_SOURCES = downloads.cc dpiutil.c dpiutil.h +ftp_filter_dpi_SOURCES = ftp.c dpiutil.c dpiutil.h +https_filter_dpi_SOURCES = https.c dpiutil.c dpiutil.h +hello_filter_dpi_SOURCES = hello.c dpiutil.c dpiutil.h +file_dpi_SOURCES = file.c dpiutil.c dpiutil.h +cookies_dpi_SOURCES = cookies.c dpiutil.c dpiutil.h +datauri_filter_dpi_SOURCES = datauri.c dpiutil.c dpiutil.h + diff --git a/dpi/bookmarks.c b/dpi/bookmarks.c new file mode 100644 index 00000000..b0f4ca8e --- /dev/null +++ b/dpi/bookmarks.c @@ -0,0 +1,1716 @@ +/* + * Bookmarks server (chat version). + * + * NOTE: this code illustrates how to make a dpi-program. + * + * Copyright 2002-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + */ + +/* Todo: this server is not assembling the received packets. + * This means it currently expects dillo to send full dpi tags + * within the socket; if that fails, everything stops. + * This is not hard to fix, mainly is a matter of expecting the + * final '>' of a tag. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <ctype.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <time.h> +#include <netdb.h> +#include <fcntl.h> +#include <signal.h> +#include "../dpip/dpip.h" +#include "dpiutil.h" + + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[bookmarks dpi]: " __VA_ARGS__) + +/* This one is tricky, some sources state it should include the byte + * for the terminating NULL, and others say it shouldn't. */ +# define D_SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + + strlen ((ptr)->sun_path)) + +#define DOCTYPE \ + "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n" + +/* + * Notes on character escaping: + * - Basically things are saved unescaped and escaped when in memory. + * - &<>"' are escaped in titles and sections and saved unescaped. + * - ' is escaped as %27 in URLs and saved escaped. + */ +typedef struct { + int key; + int section; + char *url; + char *title; +} BmRec; + +typedef struct { + int section; + char *title; + + int o_sec; /* private, for normalization */ +} BmSec; + + +/* + * Local data + */ +static char *Header = "Content-type: text/html\n\n"; +static char *BmFile = NULL; +static time_t BmFileTimeStamp = 0; +static Dlist *B_bms = NULL; +static int bm_key = 0; + +static Dlist *B_secs = NULL; +static int sec_key = 0; + +static int MODIFY_PAGE_NUM = 1; + + +/* + * Forward declarations + */ + + +/* -- HTML templates ------------------------------------------------------- */ + +char *mainpage_header = +DOCTYPE +"<html>\n" +"<head>\n" +"<title>Bookmarks</title>\n" +"</head>\n" +"<body bgcolor='#778899' link='black' vlink='brown'>\n" +"<table border='1' cellpadding='0' width='100%'>\n" +" <tr><td>\n" +" <table width='100%' bgcolor='#b4b4b4'>\n" +" <tr>\n" +" <td> Bookmarks::</td>\n" +" <td width='100%' align='right'>\n" +" [<a href='dpi:/bm/modify'>modify</a>]\n" +" </td></tr>\n" +" </table></td></tr>\n" +"</table>\n" +"<br>\n"; + +char *modifypage_header = +DOCTYPE +"<html>\n" +"<head>\n" +"<title>Bookmarks</title>\n" +"</head>\n" +"<body bgcolor='#778899' link='black' vlink='brown'>\n" +"<table border='1' cellpadding='0' width='100%'>\n" +" <tr><td>\n" +" <table width='100%' bgcolor='#b4b4b4'>\n" +" <tr>\n" +" <td> Bookmarks :: modify</td></tr>\n" +" </table></td></tr> \n" +"</table> \n" +"\n" +"<form>\n" +"<table width='100%' border='1' cellpadding='0'>\n" +" <tr><td>\n" +" <table width='100%' bgcolor='teal'>\n" +" <tr>\n" +" <td><b>Select an operation </b></td>\n" +" <td><select name='operation'>\n" +" <option value='none' selected>--\n" +" <option value='delete'>Delete\n" +" <option value='move'>Move\n" +" <option value='modify'>Modify\n" +" <option value='add_sec'>Add Section\n" +" <option value='add_url'>Add URL\n" +" </select></td>\n" +" <td><b>, mark its operands, and </b></td>\n" +" <td><input type='submit' name='submit' value='submit.'></td>\n" +" <td width='100%'></td>\n" +" </tr>\n" +" </table></td></tr>\n" +"</table>\n"; + +char *mainpage_sections_header = +"<table border='1' cellpadding='0' cellspacing='20' width='100%'>\n" +" <tr valign='top'>\n" +" <td>\n" +" <table bgcolor='#b4b4b4' border='2' cellpadding='4' cellspacing='1'>\n" +" <tr><td>\n" +" <table width='100%' bgcolor='#b4b4b4'>\n" +" <tr><td><small>Sections:</small></td></tr></table></td></tr>\n"; + +char *modifypage_sections_header = +"<table border='1' cellpadding='0' cellspacing='20' width='100%'>\n" +" <tr valign='top'>\n" +" <td>\n" +" <table bgcolor='#b4b4b4' border='1'>\n" +" <tr><td>\n" +" <table width='100%' bgcolor='#b4b4b4'>\n" +" <tr><td><small>Sections:</small></td></tr></table></td></tr>\n"; + +char *mainpage_sections_item = +" <tr><td align='center'>\n" +" <a href='#s%d'>%s</a></td></tr>\n"; + +char *modifypage_sections_item = +" <tr><td>\n" +" <table width='100%%' bgcolor='#b4b4b4'cellspacing='0' cellpadding='0'>\n" +" <tr align='center'>" +" <td width='1%%'><input type='checkbox' name='s%d'></td>\n" +" <td><a href='#s%d'>%s</a></td></tr></table></td></tr>\n"; + +char *mainpage_sections_footer = +" </table>\n"; + +char *modifypage_sections_footer = +" </table>\n"; + +char *mainpage_middle1 = +" </td>\n" +" <td width='100%'>\n"; + +char *modifypage_middle1 = +" </td>\n" +" <td width='100%'>\n"; + +char *mainpage_section_card_header = +" <a name='s%d'></a>\n" +" <table bgcolor='#bfbfbf' width='100%%' cellspacing='2'>\n" +" <tr>\n" +" <td bgcolor='#bf0c0c'><font color='white'><b>\n" +" %s </b></font></td>\n" +" <td bgcolor='white' width='100%%'> </td></tr>\n"; + +char *modifypage_section_card_header = +" <a name='s%d'></a>\n" +" <table bgcolor='#bfbfbf' width='100%%' cellspacing='2'>\n" +" <tr>\n" +" <td bgcolor='#bf0c0c'><font color='white'><b>\n" +" %s </b></font></td>\n" +" <td bgcolor='white' width='100%%'> </td></tr>\n"; + +char *mainpage_section_card_item = +" <tr><td colspan='2'>\n" +" <a href='%s'>%s</a> </td></tr>\n"; + +char *modifypage_section_card_item = +" <tr>\n" +" <td colspan='2'><input type='checkbox' name='url%d'>\n" +" <a href='%s'>%s</a></td></tr>\n"; + +char *mainpage_section_card_footer = +" </table>\n" +" <hr>\n"; + +char *modifypage_section_card_footer = +" </table>\n" +" <hr>\n"; + +char *mainpage_footer = +" </td>\n" +" </tr>\n" +"</table>\n" +"</body>\n" +"</html>\n"; + +char *modifypage_footer = +" </td>\n" +" </tr>\n" +"</table>\n" +"</form>\n" +"</body>\n" +"</html>\n"; + +/* ------------------------------------------------------------------------- */ +char *modifypage_add_section_page = +DOCTYPE +"<html>\n" +"<head>\n" +"<title>Bookmarks</title>\n" +"</head>\n" +"<body bgcolor='#778899' link='black' vlink='brown'>\n" +"<table border='1' cellpadding='0' width='100%'>\n" +" <tr><td colspan='2'>\n" +" <table bgcolor='#b4b4b4' width='100%'>\n" +" <tr><td bgcolor='#b4b4b4'> Modify bookmarks:: add section\n" +" </td></tr></table></td></tr>\n" +"</table>\n" +"<br>\n" +"<form>\n" +" <input type='hidden' name='operation' value='add_section'>\n" +"<table border='1' width='100%'>\n" +" <tr>\n" +" <td bgcolor='olive'><b>New section:</b></td>\n" +" <td bgcolor='white' width='100%'></td></tr>\n" +"</table>\n" +"<table width='100%' cellpadding='10'>\n" +"<tr><td>\n" +" <table width='100%' bgcolor='teal'>\n" +" <tr>\n" +" <td>Title:</td>\n" +" <td><input type='text' name='title' size='64'></td></tr>\n" +" </table>\n" +" </td></tr>\n" +"</table>\n" +"<table width='100%' cellpadding='4' border='0'>\n" +"<tr><td bgcolor='#a0a0a0'>\n" +" <input type='submit' name='submit' value='submit.'></td></tr>\n" +"</table>\n" +"</form>\n" +"</body>\n" +"</html>\n" +"\n"; + +/* ------------------------------------------------------------------------- */ +char *modifypage_update_header = +DOCTYPE +"<html>\n" +"<head>\n" +"<title>Bookmarks</title>\n" +"</head>\n" +"<body bgcolor='#778899' link='black' vlink='brown'>\n" +"<table border='1' cellpadding='0' width='100%'>\n" +" <tr><td colspan='2'>\n" +" <table bgcolor='#b4b4b4' width='100%'>\n" +" <tr><td bgcolor='#b4b4b4'> Modify bookmarks:: update\n" +" </td></tr></table></td></tr>\n" +"</table>\n" +"<br>\n" +"<form>\n" +"<input type='hidden' name='operation' value='modify2'>\n"; + +char *modifypage_update_title = +"<table border='1' width='100%%'>\n" +" <tr>\n" +" <td bgcolor='olive'><b>%s</b></td>\n" +" <td bgcolor='white' width='100%%'></td></tr>\n" +"</table>\n"; + +char *modifypage_update_item_header = +"<table width='100%' cellpadding='10'>\n"; + +char *modifypage_update_item = +"<tr><td>\n" +" <table width='100%%' bgcolor='teal'>\n" +" <tr>\n" +" <td>Title:</td>\n" +" <td><input type='text' name='title%d' size='64'\n" +" value='%s'></td></tr>\n" +" <tr>\n" +" <td>URL:</td>\n" +" <td>%s</td></tr>\n" +" </table>\n" +" </td></tr>\n"; + +char *modifypage_update_item2 = +"<tr><td>\n" +" <table width='100%%' bgcolor='teal'>\n" +" <tr>\n" +" <td>Title:</td>\n" +" <td><input type='text' name='s%d' size='64'\n" +" value='%s'></td></tr>\n" +" </table>\n" +" </td></tr>\n"; + +char *modifypage_update_item_footer = +"</table>\n"; + +char *modifypage_update_footer = +"<table width='100%' cellpadding='4' border='0'>\n" +"<tr><td bgcolor='#a0a0a0'>\n" +" <input type='submit' name='submit' value='submit.'></td></tr>\n" +"</table>\n" +"</form>\n" +"</body>\n" +"</html>\n"; + +/* ------------------------------------------------------------------------- */ +char *modifypage_add_url = +DOCTYPE +"<html>\n" +"<head>\n" +"<title>Bookmarks</title>\n" +"</head>\n" +"<body bgcolor='#778899' link='black' vlink='brown'>\n" +"<table border='1' cellpadding='0' width='100%'>\n" +" <tr><td colspan='2'>\n" +" <table bgcolor='#b4b4b4' width='100%'>\n" +" <tr><td bgcolor='#b4b4b4'> Modify bookmarks:: add url\n" +" </td></tr></table></td></tr>\n" +"</table>\n" +"<br>\n" +"<form>\n" +"<input type='hidden' name='operation' value='add_url2'>\n" +"<table border='1' width='100%'>\n" +" <tr>\n" +" <td bgcolor='olive'><b>Add url:</b></td>\n" +" <td bgcolor='white' width='100%'></td></tr>\n" +"</table>\n" +"<table width='100%' cellpadding='10'>\n" +"<tr><td>\n" +" <table width='100%' bgcolor='teal'>\n" +" <tr>\n" +" <td>Title:</td>\n" +" <td><input type='text' name='title' size='64'></td></tr>\n" +" <tr>\n" +" <td>URL:</td>\n" +" <td><input type='text' name='url' size='64'></td></tr>\n" +" </table>\n" +" </td></tr>\n" +"</table>\n" +"<table width='100%' cellpadding='4' border='0'>\n" +"<tr><td bgcolor='#a0a0a0'>\n" +" <input type='submit' name='submit' value='submit.'></td></tr>\n" +"</table>\n" +"</form>\n" +"</body>\n" +"</html>\n"; + + +/* ------------------------------------------------------------------------- */ + +/* + * Return a new string with spaces changed with + */ +static char *make_one_line_str(char *str) +{ + char *new_str; + int i, j, n; + + for (i = 0, n = 0; str[i]; ++i) + if (str[i] == ' ') + ++n; + + new_str = dNew(char, strlen(str) + 6*n + 1); + new_str[0] = 0; + + for (i = 0, j = 0; str[i]; ++i) { + if (str[i] == ' ') { + strcpy(new_str + j, " "); + j += 6; + } else { + new_str[j] = str[i]; + new_str[++j] = 0; + } + } + + return new_str; +} + +/* + * Given an urlencoded string, return it to the original version. + */ +static void Unencode_str(char *e_str) +{ + char *p, *e; + + for (p = e = e_str; *e; e++, p++) { + if (*e == '+') { + *p = ' '; + } else if (*e == '%') { + if (dStrncasecmp(e, "%0D%0A", 6) == 0) { + *p = '\n'; + e += 5; + } else { + *p = (isdigit(e[1]) ? (e[1] - '0') : (e[1] - 'A' + 10)) * 16 + + (isdigit(e[2]) ? (e[2] - '0') : (e[2] - 'A' + 10)); + e += 2; + } + } else { + *p = *e; + } + } + *p = 0; +} + +/* + * Send a short message to dillo's status bar. + */ +static int Bmsrv_dpi_send_status_msg(SockHandler *sh, char *str) +{ + int st; + char *d_cmd; + + d_cmd = a_Dpip_build_cmd("cmd=%s msg=%s", "send_status_message", str); + st = sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + return st; +} + +/* -- ADT for bookmarks ---------------------------------------------------- */ +/* + * Compare function for searching a bookmark by its key + */ +static int Bms_node_by_key_cmp(const void *node, const void *key) +{ + return ((BmRec *)node)->key - VOIDP2INT(key); +} + +/* + * Compare function for searching a bookmark by section + */ +static int Bms_node_by_section_cmp(const void *node, const void *key) +{ + return ((BmRec *)node)->section - VOIDP2INT(key); +} + +/* + * Compare function for searching a section by its number + */ +static int Bms_sec_by_number_cmp(const void *node, const void *key) +{ + return ((BmSec *)node)->section - VOIDP2INT(key); +} + +/* + * Return the Bm record by key + */ +static BmRec *Bms_get(int key) +{ + return dList_find_custom(B_bms, INT2VOIDP(key), Bms_node_by_key_cmp); +} + +/* + * Return the Section record by key + */ +static BmSec *Bms_get_sec(int key) +{ + return dList_find_custom(B_secs, INT2VOIDP(key), Bms_sec_by_number_cmp); +} + +/* + * Add a bookmark + */ +static void Bms_add(int section, char *url, char *title) +{ + BmRec *bm_node; + + bm_node = dNew(BmRec, 1); + bm_node->key = ++bm_key; + bm_node->section = section; + bm_node->url = Escape_uri_str(url, "'"); + bm_node->title = Escape_html_str(title); + dList_append(B_bms, bm_node); +} + +/* + * Add a section + */ +static void Bms_sec_add(char *title) +{ + BmSec *sec_node; + + sec_node = dNew(BmSec, 1); + sec_node->section = sec_key++; + sec_node->title = Escape_html_str(title); + dList_append(B_secs, sec_node); +} + +/* + * Delete a bookmark by its key + */ +static void Bms_del(int key) +{ + BmRec *bm_node; + + bm_node = dList_find_custom(B_bms, INT2VOIDP(key), Bms_node_by_key_cmp); + if (bm_node) { + dList_remove(B_bms, bm_node); + dFree(bm_node->title); + dFree(bm_node->url); + dFree(bm_node); + } + if (dList_length(B_bms) == 0) + bm_key = 0; +} + +/* + * Delete a section and its bookmarks by section number + */ +static void Bms_sec_del(int section) +{ + BmSec *sec_node; + BmRec *bm_node; + + sec_node = dList_find_custom(B_secs, INT2VOIDP(section), + Bms_sec_by_number_cmp); + if (sec_node) { + dList_remove(B_secs, sec_node); + dFree(sec_node->title); + dFree(sec_node); + + /* iterate B_bms and remove those that match the section */ + while ((bm_node = dList_find_custom(B_bms, INT2VOIDP(section), + Bms_node_by_section_cmp))) { + Bms_del(bm_node->key); + } + } + if (dList_length(B_secs) == 0) + sec_key = 0; +} + +/* + * Move a bookmark to another section + */ +static void Bms_move(int key, int target_section) +{ + BmRec *bm_node; + + bm_node = dList_find_custom(B_bms, INT2VOIDP(key), Bms_node_by_key_cmp); + if (bm_node) { + bm_node->section = target_section; + } +} + +/* + * Update a bookmark title by key + */ +static void Bms_update_title(int key, char *n_title) +{ + BmRec *bm_node; + + bm_node = dList_find_custom(B_bms, INT2VOIDP(key), Bms_node_by_key_cmp); + if (bm_node) { + dFree(bm_node->title); + bm_node->title = Escape_html_str(n_title); + } +} + +/* + * Update a section title by key + */ +static void Bms_update_sec_title(int key, char *n_title) +{ + BmSec *sec_node; + + sec_node = dList_find_custom(B_secs, INT2VOIDP(key), Bms_sec_by_number_cmp); + if (sec_node) { + dFree(sec_node->title); + sec_node->title = Escape_html_str(n_title); + } +} + +/* + * Free all the bookmarks data (bookmarks and sections) + */ +static void Bms_free(void) +{ + BmRec *bm_node; + BmSec *sec_node; + + /* free B_bms */ + while ((bm_node = dList_nth_data(B_bms, 0))) { + Bms_del(bm_node->key); + } + /* free B_secs */ + while ((sec_node = dList_nth_data(B_secs, 0))) { + Bms_sec_del(sec_node->section); + } +} + +/* + * Enforce increasing correlative section numbers with no jumps. + */ +static void Bms_normalize(void) +{ + BmRec *bm_node; + BmSec *sec_node; + int i, j; + + /* we need at least one section */ + if (dList_length(B_secs) == 0) + Bms_sec_add("Unclassified"); + + /* make correlative section numbers */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + sec_node->o_sec = sec_node->section; + sec_node->section = i; + } + + /* iterate B_secs and make the changes in B_bms */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + if (sec_node->section != sec_node->o_sec) { + /* update section numbers */ + for (j = 0; (bm_node = dList_nth_data(B_bms, j)); ++j) { + if (bm_node->section == sec_node->o_sec) + bm_node->section = sec_node->section; + } + } + } +} + +/* -- Load bookmarks file -------------------------------------------------- */ + +/* + * If there's no "bm.txt", create one from "bookmarks.html". + */ +static void Bms_check_import(void) +{ + char *OldBmFile; + char *cmd1 = + "echo \":s0: Unclassified\" > %s"; + char *cmd2 = + "grep -i \"href\" %s | " + "sed -e 's/<li><A HREF=\"/s0 /' -e 's/\">/ /' -e 's/<.*$//' >> %s"; + Dstr *dstr = dStr_new(""); + + + if (access(BmFile, F_OK) != 0) { + OldBmFile = dStrconcat(dGethomedir(), "/.dillo/bookmarks.html", NULL); + if (access(OldBmFile, F_OK) == 0) { + dStr_sprintf(dstr, cmd1, BmFile); + system(dstr->str); + dStr_sprintf(dstr, cmd2, OldBmFile, BmFile); + system(dstr->str); + dStr_free(dstr, TRUE); + dFree(OldBmFile); + } + } +} + +/* + * Load bookmarks data from a file + */ +static int Bms_load(void) +{ + FILE *BmTxt; + char *buf, *p, *url, *title, *u_title; + int section; + struct stat TimeStamp; + + /* clear current bookmarks */ + Bms_free(); + + /* open bm file */ + if (!(BmTxt = fopen(BmFile, "r"))) { + perror("[fopen]"); + return 1; + } + + /* load bm file into memory */ + while ((buf = dGetline(BmTxt)) != NULL) { + if (buf[0] == 's') { + /* get section, url and title */ + section = strtol(buf + 1, NULL, 10); + p = strchr(buf, ' '); *p = 0; + url = ++p; + p = strchr(p, ' '); *p = 0; + title = ++p; + p = strchr(p, '\n'); *p = 0; + u_title = Unescape_html_str(title); + Bms_add(section, url, u_title); + dFree(u_title); + + } else if (buf[0] == ':' && buf[1] == 's') { + /* section = strtol(buf + 2, NULL, 10); */ + p = strchr(buf + 2, ' '); + title = ++p; + p = strchr(p, '\n'); *p = 0; + Bms_sec_add(title); + + } else { + MSG("Syntax error in bookmarks file:\n %s", buf); + } + dFree(buf); + } + fclose(BmTxt); + + /* keep track of the timestamp */ + stat(BmFile, &TimeStamp); + BmFileTimeStamp = TimeStamp.st_mtime; + + return 0; +} + +/* + * Load bookmarks data if: + * - file timestamp is newer than ours or + * - we haven't loaded anything yet :) + */ +static int Bms_cond_load(void) +{ + int st = 0; + struct stat TimeStamp; + + if (stat(BmFile, &TimeStamp) != 0) { + /* try to import... */ + Bms_check_import(); + if (stat(BmFile, &TimeStamp) != 0) + TimeStamp.st_mtime = 0; + } + + if (!BmFileTimeStamp || !dList_length(B_bms) || !dList_length(B_secs) || + BmFileTimeStamp < TimeStamp.st_mtime) { + Bms_load(); + st = 1; + } + return st; +} + +/* -- Save bookmarks file -------------------------------------------------- */ + +/* + * Update the bookmarks file from memory contents + * Return code: { 0:OK, 1:Abort } + */ +static int Bms_save(void) +{ + FILE *BmTxt; + BmRec *bm_node; + BmSec *sec_node; + struct stat BmStat; + char *u_title; + int i, j; + Dstr *dstr = dStr_new(""); + + /* make a safety backup */ + if (stat(BmFile, &BmStat) == 0 && BmStat.st_size > 256) { + char *BmFileBak = dStrconcat(BmFile, ".bak", NULL); + rename(BmFile, BmFileBak); + dFree(BmFileBak); + } + + /* open bm file */ + if (!(BmTxt = fopen(BmFile, "w"))) { + perror("[fopen]"); + return 1; + } + + /* normalize */ + Bms_normalize(); + + /* save sections */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + u_title = Unescape_html_str(sec_node->title); + dStr_sprintf(dstr, ":s%d: %s\n", sec_node->section, u_title); + fwrite(dstr->str, (size_t)dstr->len, 1, BmTxt); + dFree(u_title); + } + + /* save bookmarks (section url title) */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + for (j = 0; (bm_node = dList_nth_data(B_bms, j)); ++j) { + if (bm_node->section == sec_node->section) { + u_title = Unescape_html_str(bm_node->title); + dStr_sprintf(dstr, "s%d %s %s\n", + bm_node->section, bm_node->url, u_title); + fwrite(dstr->str, (size_t)dstr->len, 1, BmTxt); + dFree(u_title); + } + } + } + + dStr_free(dstr, TRUE); + fclose(BmTxt); + + /* keep track of the timestamp */ + stat(BmFile, &BmStat); + BmFileTimeStamp = BmStat.st_mtime; + + return 0; +} + +/* -- Add bookmark --------------------------------------------------------- */ + +/* + * Add a new bookmark to DB :) + */ +static int Bmsrv_add_bm(SockHandler *sh, char *url, char *title) +{ + char *u_title; + char *msg="Added bookmark!"; + int section = 0; + + /* Add in memory */ + u_title = Unescape_html_str(title); + Bms_add(section, url, u_title); + dFree(u_title); + + /* Write to file */ + Bms_save(); + + if (Bmsrv_dpi_send_status_msg(sh, msg)) + return 1; + + return 0; +} + +/* -- Modify --------------------------------------------------------------- */ + +/* + * Count how many sections and urls were marked in a request + */ +static void Bmsrv_count_urls_and_sections(char *url, int *n_sec, int *n_url) +{ + char *p, *q; + int i; + + /* Check marked urls and sections */ + *n_sec = *n_url = 0; + if ((p = strchr(url, '?'))) { + for (q = p; (q = strstr(q, "&url")); ++q) { + for (i = 0; isdigit(q[4+i]); ++i); + *n_url += (q[4+i] == '=') ? 1 : 0; + } + for (q = p; (q = strstr(q, "&s")); ++q) { + for (i = 0; isdigit(q[2+i]); ++i); + *n_sec += (q[2+i] == '=') ? 1 : 0; + } + } +} + +/* + * Send a dpi reload request + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_send_reload_request(SockHandler *sh, char *url) +{ + int st; + char *d_cmd; + + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "reload_request", url); + st = sock_handler_write_str(sh, 1, d_cmd) ? 1 : 0; + dFree(d_cmd); + return st; +} + +/* + * Send the HTML for the modify page + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_send_modify_page(SockHandler *sh) +{ + static Dstr *dstr = NULL; + char *l_title; + BmSec *sec_node; + BmRec *bm_node; + int i, j; + + if (!dstr) + dstr = dStr_new(""); + + /* send modify page header */ + if (sock_handler_write_str(sh, 0, modifypage_header)) + return 1; + + /* write sections header */ + if (sock_handler_write_str(sh, 0, modifypage_sections_header)) + return 1; + /* write sections */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + dStr_sprintf(dstr, modifypage_sections_item, + sec_node->section, sec_node->section, sec_node->title); + if (sock_handler_write_str(sh, 0, dstr->str)) + return 1; + } + /* write sections footer */ + if (sock_handler_write_str(sh, 0, modifypage_sections_footer)) + return 1; + + /* send page middle */ + if (sock_handler_write_str(sh, 0, modifypage_middle1)) + return 1; + + /* send bookmark cards */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + /* send card header */ + l_title = make_one_line_str(sec_node->title); + dStr_sprintf(dstr, modifypage_section_card_header, + sec_node->section, l_title); + dFree(l_title); + if (sock_handler_write_str(sh, 0, dstr->str)) + return 1; + + /* send section's bookmarks */ + for (j = 0; (bm_node = dList_nth_data(B_bms, j)); ++j) { + if (bm_node->section == sec_node->section) { + dStr_sprintf(dstr, modifypage_section_card_item, + bm_node->key, bm_node->url, bm_node->title); + if (sock_handler_write_str(sh, 0, dstr->str)) + return 1; + } + } + + /* send card footer */ + if (sock_handler_write_str(sh, 0, modifypage_section_card_footer)) + return 1; + } + + /* finish page */ + if (sock_handler_write_str(sh, 1, modifypage_footer)) + return 1; + + return 2; +} + +/* + * Send the HTML for the modify page for "add section" + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_send_modify_page_add_section(SockHandler *sh) +{ + /* send modify page2 */ + if (sock_handler_write_str(sh, 1, modifypage_add_section_page)) + return 1; + + return 2; +} + +/* + * Send the HTML for the modify page for "add url" + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_send_modify_page_add_url(SockHandler *sh) +{ + if (sock_handler_write_str(sh, 1, modifypage_add_url)) + return 1; + return 2; +} + +/* + * Parse a modify urls request and either: + * - make a local copy of the url + * or + * - send the modify page for the marked urls and sections + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_send_modify_update(SockHandler *sh, char *url) +{ + static char *url1 = NULL; + static Dstr *dstr = NULL; + char *p, *q; + int i, key, n_sec, n_url; + BmRec *bm_node; + BmSec *sec_node; + + /* bookmarks were loaded before */ + + if (!dstr) + dstr = dStr_new(""); + + if (sh == NULL) { + /* just copy url */ + dFree(url1); + url1 = dStrdup(url); + return 0; + } + + /* send HTML here */ + if (sock_handler_write_str(sh, 0, modifypage_update_header)) + return 1; + + /* Count number of marked urls and sections */ + Bmsrv_count_urls_and_sections(url1, &n_sec, &n_url); + + if (n_sec) { + dStr_sprintf(dstr, modifypage_update_title, "Update sections:"); + sock_handler_write_str(sh, 0, dstr->str); + sock_handler_write_str(sh, 0, modifypage_update_item_header); + /* send items here */ + p = strchr(url1, '?'); + for (q = p; (q = strstr(q, "&s")); ++q) { + for (i = 0; isdigit(q[2+i]); ++i); + if (q[2+i] == '=') { + key = strtol(q + 2, NULL, 10); + if ((sec_node = Bms_get_sec(key))) { + dStr_sprintf(dstr, modifypage_update_item2, + sec_node->section, sec_node->title); + sock_handler_write_str(sh, 0, dstr->str); + } + } + } + sock_handler_write_str(sh, 0, modifypage_update_item_footer); + } + + if (n_url) { + dStr_sprintf(dstr, modifypage_update_title, "Update titles:"); + sock_handler_write_str(sh, 0, dstr->str); + sock_handler_write_str(sh, 0, modifypage_update_item_header); + /* send items here */ + p = strchr(url1, '?'); + for (q = p; (q = strstr(q, "&url")); ++q) { + for (i = 0; isdigit(q[4+i]); ++i); + if (q[4+i] == '=') { + key = strtol(q + 4, NULL, 10); + bm_node = Bms_get(key); + dStr_sprintf(dstr, modifypage_update_item, + bm_node->key, bm_node->title, bm_node->url); + sock_handler_write_str(sh, 0, dstr->str); + } + } + sock_handler_write_str(sh, 0, modifypage_update_item_footer); + } + + sock_handler_write_str(sh, 1, modifypage_update_footer); + + return 2; +} + +/* + * Make the modify-page and send it back + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_send_modify_answer(SockHandler *sh, char *url) +{ + char *d_cmd; + int st; + + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + st = sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + if (st != 0) + return 1; + + /* Send HTTP header */ + if (sock_handler_write_str(sh, 0, Header) != 0) { + return 1; + } + + if (MODIFY_PAGE_NUM == 2) { + MODIFY_PAGE_NUM = 1; + return Bmsrv_send_modify_page_add_section(sh); + } else if (MODIFY_PAGE_NUM == 3) { + MODIFY_PAGE_NUM = 1; + return Bmsrv_send_modify_update(sh, NULL); + } else if (MODIFY_PAGE_NUM == 4) { + MODIFY_PAGE_NUM = 1; + return Bmsrv_send_modify_page_add_url(sh); + } else { + return Bmsrv_send_modify_page(sh); + } +} + + +/* Operations */ + +/* + * Parse a delete bms request, delete them, and update bm file. + * Return code: { 0:OK, 1:Abort } + */ +static int Bmsrv_modify_delete(SockHandler *sh, char *url) +{ + char *p; + int nb, ns, key; + + /* bookmarks were loaded before */ + + /* Remove marked sections */ + p = strchr(url, '?'); + for (ns = 0; (p = strstr(p, "&s")); ++p) { + if (isdigit(p[2])) { + key = strtol(p + 2, NULL, 10); + Bms_sec_del(key); + ++ns; + } + } + + /* Remove marked urls */ + p = strchr(url, '?'); + for (nb = 0; (p = strstr(p, "&url")); ++p) { + if (isdigit(p[4])) { + key = strtol(p + 4, NULL, 10); + Bms_del(key); + ++nb; + } + } + +/* -- This doesn't work because dillo erases the message upon the + * receipt of the first data stream. + * + sprintf(msg, "Deleted %d bookmark%s!>", n, (n > 1) ? "s" : ""); + if (Bmsrv_dpi_send_status_msg(sh, msg)) + return 1; +*/ + + /* Write new bookmarks file */ + if (nb || ns) + Bms_save(); + + return 0; +} + +/* + * Parse a move urls request, move and update bm file. + * Return code: { 0:OK, 1:Abort } + */ +static int Bmsrv_modify_move(SockHandler *sh, char *url) +{ + char *p; + int n, section = 0, key; + + /* bookmarks were loaded before */ + + /* get target section */ + for (p = url; (p = strstr(p, "&s")); ++p) { + if (isdigit(p[2])) { + section = strtol(p + 2, NULL, 10); + break; + } + } + if (!p) + return 1; + + /* move marked urls */ + p = strchr(url, '?'); + for (n = 0; (p = strstr(p, "&url")); ++p) { + if (isdigit(p[4])) { + key = strtol(p + 4, NULL, 10); + Bms_move(key, section); + ++n; + } + } + + /* Write new bookmarks file */ + if (n) { + Bms_save(); + } + + return 0; +} + +/* + * Parse a modify request: update urls and sections, then save. + * Return code: { 0:OK, 1:Abort } + */ +static int Bmsrv_modify_update(SockHandler *sh, char *url) +{ + char *p, *q, *title; + int i, key; + + /* bookmarks were loaded before */ + + p = strchr(url, '?'); + for ( ; (p = strstr(p, "s")); ++p) { + if (p[-1] == '&' || p[-1] == '?' ) { + for (i = 0; isdigit(p[1 + i]); ++i); + if (i && p[1 + i] == '=') { + /* we have a title/key to change */ + key = strtol(p + 1, NULL, 10); + if ((q = strchr(p + 1, '&'))) + title = dStrndup(p + 2 + i, (uint_t)(q - (p + 2 + i))); + else + title = dStrdup(p + 2 + i); + + Unencode_str(title); + Bms_update_sec_title(key, title); + dFree(title); + } + } + } + + p = strchr(url, '?'); + for ( ; (p = strstr(p, "title")); ++p) { + if (p[-1] == '&' || p[-1] == '?' ) { + for (i = 0; isdigit(p[5 + i]); ++i); + if (i && p[5 + i] == '=') { + /* we have a title/key to change */ + key = strtol(p + 5, NULL, 10); + if ((q = strchr(p + 5, '&'))) + title = dStrndup(p + 6 + i, (uint_t)(q - (p + 6 + i))); + else + title = dStrdup(p + 6 + i); + + Unencode_str(title); + Bms_update_title(key, title); + dFree(title); + } + } + } + + /* Write new bookmarks file */ + Bms_save(); + + return 0; +} + +/* + * Parse an "add section" request, and update the bm file. + * Return code: { 0:OK, 1:Abort } + */ +static int Bmsrv_modify_add_section(SockHandler *sh, char *url) +{ + char *p, *title = NULL; + + /* bookmarks were loaded before */ + + /* get new section's title */ + if ((p = strstr(url, "&title="))) { + title = dStrdup (p + 7); + if ((p = strchr(title, '&'))) + *p = 0; + Unencode_str(title); + } else + return 1; + + Bms_sec_add(title); + dFree(title); + + /* Write new bookmarks file */ + Bms_save(); + + return 0; +} + +/* + * Parse an "add url" request, and update the bm file. + * Return code: { 0:OK, 1:Abort } + */ +static int Bmsrv_modify_add_url(SockHandler *sh, char *s_url) +{ + char *p, *q, *title, *u_title, *url; + int i; + static int section = 0; + + /* bookmarks were loaded before */ + + if (sh == NULL) { + /* look for section */ + for (q = s_url; (q = strstr(q, "&s")); ++q) { + for (i = 0; isdigit(q[2+i]); ++i); + if (q[2+i] == '=') + section = strtol(q + 2, NULL, 10); + } + return 1; + } + + if (!(p = strstr(s_url, "&title=")) || + !(q = strstr(s_url, "&url="))) + return 1; + + title = dStrdup (p + 7); + if ((p = strchr(title, '&'))) + *p = 0; + url = dStrdup (q + 5); + if ((p = strchr(url, '&'))) + *p = 0; + if (strlen(title) && strlen(url)) { + Unencode_str(title); + Unencode_str(url); + u_title = Unescape_html_str(title); + Bms_add(section, url, u_title); + dFree(u_title); + } + dFree(title); + dFree(url); + section = 0; + + /* todo: we should send an "Bookmark added" message, but the + msg-after-HTML functionallity is still pending, not hard though. */ + + /* Write new bookmarks file */ + Bms_save(); + + return 0; +} + +/* + * Check the parameters of a modify request, and return an error message + * when it's wrong. + * Return code: { 0:OK, 2:Close } + */ +static int Bmsrv_check_modify_request(SockHandler *sh, char *url) +{ + char *p, *msg; + int n_sec, n_url; + + /* Count number of marked urls and sections */ + Bmsrv_count_urls_and_sections(url, &n_sec, &n_url); + + p = strchr(url, '?'); + if (strstr(p, "operation=delete&")) { + if (n_url || n_sec) + return 0; + msg = "Delete: you must mark what to delete!"; + + } else if (strstr(url, "operation=move&")) { + if (n_url && n_sec) + return 0; + else if (n_url) + msg = "Move: you must mark a target section!"; + else if (n_sec) + msg = "Move: can not move a section (yet)."; + else + msg = "Move: you must mark some urls, and a target section!"; + + } else if (strstr(url, "operation=modify&")) { + if (n_url || n_sec) + return 0; + msg = "Modify: you must mark what to update!"; + + } else if (strstr(url, "operation=modify2&")) { + /* nothing to check here */ + return 0; + + } else if (strstr(url, "operation=add_sec&")) { + /* nothing to check here */ + return 0; + + } else if (strstr(url, "operation=add_section&")) { + /* nothing to check here */ + return 0; + + } else if (strstr(url, "operation=add_url&")) { + if (n_sec <= 1) + return 0; + msg = "Add url: only one target section is allowed!"; + + } else if (strstr(url, "operation=add_url2&")) { + /* nothing to check here */ + return 0; + + } else if (strstr(url, "operation=none&")) { + msg = "No operation, just do nothing!"; + + } else { + msg = "Sorry, not implemented yet."; + } + + Bmsrv_dpi_send_status_msg(sh, msg); + return 2; +} + +/* + * Parse a and process a modify request. + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_process_modify_request(SockHandler *sh, char *url) +{ + /* check the provided parameters */ + if (Bmsrv_check_modify_request(sh, url) != 0) + return 2; + + if (strstr(url, "operation=delete&")) { + if (Bmsrv_modify_delete(sh, url) == 1) + return 1; + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + + } else if (strstr(url, "operation=move&")) { + if (Bmsrv_modify_move(sh, url) == 1) + return 1; + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + + } else if (strstr(url, "operation=modify&")) { + /* make a local copy of 'url' */ + Bmsrv_send_modify_update(NULL, url); + MODIFY_PAGE_NUM = 3; + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + + } else if (strstr(url, "operation=modify2&")) { + if (Bmsrv_modify_update(sh, url) == 1) + return 1; + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + + } else if (strstr(url, "operation=add_sec&")) { + /* this global variable tells which page to send (--hackish...) */ + MODIFY_PAGE_NUM = 2; + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + + } else if (strstr(url, "operation=add_section&")) { + if (Bmsrv_modify_add_section(sh, url) == 1) + return 1; + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + + } else if (strstr(url, "operation=add_url&")) { + /* this global variable tells which page to send (--hackish...) */ + MODIFY_PAGE_NUM = 4; + /* parse section if present */ + Bmsrv_modify_add_url(NULL, url); + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + + } else if (strstr(url, "operation=add_url2&")) { + if (Bmsrv_modify_add_url(sh, url) == 1) + return 1; + if (Bmsrv_send_reload_request(sh, "dpi:/bm/modify") == 1) + return 1; + } + + return 2; +} + +/* -- Bookmarks ------------------------------------------------------------ */ + +/* + * Send the current bookmarks page (in HTML) + */ +static int send_bm_page(SockHandler *sh) +{ + static Dstr *dstr = NULL; + char *l_title; + BmSec *sec_node; + BmRec *bm_node; + int i, j; + + if (!dstr) + dstr = dStr_new(""); + + if (sock_handler_write_str(sh, 0, mainpage_header)) + return 1; + + /* write sections header */ + if (sock_handler_write_str(sh, 0, mainpage_sections_header)) + return 1; + /* write sections */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + dStr_sprintf(dstr, mainpage_sections_item, + sec_node->section, sec_node->title); + if (sock_handler_write_str(sh, 0, dstr->str)) + return 1; + } + /* write sections footer */ + if (sock_handler_write_str(sh, 0, mainpage_sections_footer)) + return 1; + + /* send page middle */ + if (sock_handler_write_str(sh, 0, mainpage_middle1)) + return 1; + + /* send bookmark cards */ + for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) { + /* send card header */ + l_title = make_one_line_str(sec_node->title); + dStr_sprintf(dstr, mainpage_section_card_header, + sec_node->section, l_title); + dFree(l_title); + if (sock_handler_write_str(sh, 0, dstr->str)) + return 1; + + /* send section's bookmarks */ + for (j = 0; (bm_node = dList_nth_data(B_bms, j)); ++j) { + if (bm_node->section == sec_node->section) { + dStr_sprintf(dstr, mainpage_section_card_item, + bm_node->url, bm_node->title); + if (sock_handler_write_str(sh, 0, dstr->str)) + return 1; + } + } + + /* send card footer */ + if (sock_handler_write_str(sh, 0, mainpage_section_card_footer)) + return 1; + } + + /* finish page */ + if (sock_handler_write_str(sh, 1, mainpage_footer)) + return 1; + + return 0; +} + + +/* -- Dpi parser ----------------------------------------------------------- */ + +/* + * Parse a data stream (dpi protocol) + * Note: Buf is a zero terminated string + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int Bmsrv_parse_buf(SockHandler *sh, char *Buf) +{ + static char *msg1=NULL, *msg2=NULL, *msg3=NULL; + char *p, *cmd, *d_cmd, *url, *title, *msg; + size_t BufSize; + int st; + + if (!msg1) { + /* Initialize data for the "chat" command. */ + msg1 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat", "Hi browser"); + msg2 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat", "Is it worth?"); + msg3 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat", "Ok, send it"); + } + + if (!(p = strchr(Buf, '>'))) { + /* Haven't got a full tag */ + MSG("Haven't got a full tag!\n"); + return 1; + } + + BufSize = strlen(Buf); + cmd = a_Dpip_get_attr(Buf, BufSize, "cmd"); + + if (cmd && strcmp(cmd, "chat") == 0) { + dFree(cmd); + msg = a_Dpip_get_attr(Buf, BufSize, "msg"); + if (*msg == 'H') { + /* "Hi server" */ + if (sock_handler_write_str(sh, 1, msg1)) + return 1; + } else if (*msg == 'I') { + /* "I want to set abookmark" */ + if (sock_handler_write_str(sh, 1, msg2)) + return 1; + } else if (*msg == 'S') { + /* "Sure" */ + if (sock_handler_write_str(sh, 1, msg3)) + return 1; + } + dFree(msg); + return 0; + } + + /* sync with the bookmarks file */ + Bms_cond_load(); + + if (cmd && strcmp(cmd, "DpiBye") == 0) { + MSG("(pid %d): Got DpiBye.\n", (int)getpid()); + exit(0); + + } else if (cmd && strcmp(cmd, "add_bookmark") == 0) { + dFree(cmd); + url = a_Dpip_get_attr(Buf, BufSize, "url"); + title = a_Dpip_get_attr(Buf, BufSize, "title"); + if (strlen(title) == 0) { + dFree(title); + title = dStrdup("(Untitled)"); + } + if (url && title) + Bmsrv_add_bm(sh, url, title); + dFree(url); + dFree(title); + return 2; + + } else if (cmd && strcmp(cmd, "open_url") == 0) { + dFree(cmd); + url = a_Dpip_get_attr(Buf, BufSize, "url"); + + if (strcmp(url, "dpi:/bm/modify") == 0) { + st = Bmsrv_send_modify_answer(sh, url); + return st; + + } else if (strncmp(url, "dpi:/bm/modify?", 15) == 0) { + /* process request */ + st = Bmsrv_process_modify_request(sh, url); + return st; + } + + + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + st = sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + if (st != 0) + return 1; + + /* Send HTTP header */ + if (sock_handler_write_str(sh, 1, Header) != 0) { + return 1; + } + + st = send_bm_page(sh); + if (st != 0) { + char *err = + DOCTYPE + "<HTML><body> Error on the bookmarks server...</body></html>"; + if (sock_handler_write_str(sh, 1, err) != 0) { + return 1; + } + } + return 2; + } + + return 0; +} + +/* -- Termination handlers ----------------------------------------------- */ +/* + * (was to delete the local namespace socket), + * but this is handled by 'dpid' now. + */ +static void cleanup(void) +{ + /* no cleanup required */ +} + +/* + * Perform any necessary cleanups upon abnormal termination + */ +static void termination_handler(int signum) +{ + exit(signum); +} + + +/* + * -- MAIN ------------------------------------------------------------------- + */ +int main (void) { + struct sockaddr_un spun; + int temp_sock_descriptor; + socklen_t address_size; + char *buf; + int code; + SockHandler *sh; + + /* Arrange the cleanup function for terminations via exit() */ + atexit(cleanup); + + /* Arrange the cleanup function for abnormal terminations */ + if (signal (SIGINT, termination_handler) == SIG_IGN) + signal (SIGINT, SIG_IGN); + if (signal (SIGHUP, termination_handler) == SIG_IGN) + signal (SIGHUP, SIG_IGN); + if (signal (SIGTERM, termination_handler) == SIG_IGN) + signal (SIGTERM, SIG_IGN); + + /* Initialize local data */ + B_bms = dList_new(512); + B_secs = dList_new(32); + BmFile = dStrconcat(dGethomedir(), "/.dillo/bm.txt", NULL); + /* some OSes may need this... */ + address_size = sizeof(struct sockaddr_un); + + MSG("(v.13): accepting connections...\n"); + + while (1) { + temp_sock_descriptor = + accept(STDIN_FILENO, (struct sockaddr *)&spun, &address_size); + if (temp_sock_descriptor == -1) { + perror("[accept]"); + exit(1); + } + + /* create the SockHandler structure */ + sh = sock_handler_new(temp_sock_descriptor,temp_sock_descriptor,8*1024); + + while (1) { + code = 1; + if ((buf = sock_handler_read(sh)) != NULL) { + /* Let's see what we fished... */ + code = Bmsrv_parse_buf(sh, buf); + } + if (code == 1) + exit(1); + else if (code == 2) + break; + } + + sock_handler_close(sh); + sock_handler_free(sh); + + }/*while*/ +} diff --git a/dpi/cookies.c b/dpi/cookies.c new file mode 100644 index 00000000..f669eb69 --- /dev/null +++ b/dpi/cookies.c @@ -0,0 +1,1466 @@ +/* + * File: cookies.c + * Cookies server. + * + * Copyright 2001 Lars Clausen <lrclause@cs.uiuc.edu> + * Jörgen Viksell <jorgen.viksell@telia.com> + * Copyright 2002-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + */ + +/* Handling of cookies takes place here. + * This implementation aims to follow RFC 2965: + * http://www.cis.ohio-state.edu/cs/Services/rfc/rfc-text/rfc2965.txt + */ + +/* + * TODO: Cleanup this code. Shorten some functions, order things, + * add comments, remove leaks, etc. + */ + +/* Todo: this server is not assembling the received packets. + * This means it currently expects dillo to send full dpi tags + * within the socket; if that fails, everything stops. + */ + +#ifdef DISABLE_COOKIES + +int main(void) +{ + return 0; /* never called */ +} + +#else + + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> /* for time() and time_t */ +#include <ctype.h> +#include <netdb.h> +#include <signal.h> +#include "dpiutil.h" +#include "../dpip/dpip.h" + + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[cookies dpi]: " __VA_ARGS__) + + +/* This one is tricky, some sources state it should include the byte + * for the terminating NULL, and others say it shouldn't. */ +# define D_SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + + strlen ((ptr)->sun_path)) + + +/* + * a_List_add() + * + * Make sure there's space for 'num_items' items within the list + * (First, allocate an 'alloc_step' sized chunk, after that, double the + * list size --to make it faster) + */ +#define a_List_add(list,num_items,alloc_step) \ + if (!list) { \ + list = dMalloc(alloc_step * sizeof((*list))); \ + } \ + if (num_items >= alloc_step){ \ + while ( num_items >= alloc_step ) \ + alloc_step <<= 1; \ + list = dRealloc(list, alloc_step * sizeof((*list))); \ + } + +/* The maximum length of a line in the cookie file */ +#define LINE_MAXLEN 4096 + +typedef enum { + COOKIE_ACCEPT, + COOKIE_ACCEPT_SESSION, + COOKIE_DENY +} CookieControlAction; + +typedef struct { + char *domain; + CookieControlAction action; +} CookieControl; + +typedef struct { + char *domain; + Dlist *dlist; +} CookieNode; + +typedef struct { + char *name; + char *value; + char *domain; + char *path; + time_t expires_at; + uint_t version; + char *comment; + char *comment_url; + bool_t secure; + bool_t session_only; + Dlist *ports; +} CookieData_t; + +/* + * Local data + */ + +/* List of CookieNode. Each node holds a domain and its list of cookies */ +static Dlist *cookies; + +/* Variables for access control */ +static CookieControl *ccontrol = NULL; +static int num_ccontrol = 0; +static int num_ccontrol_max = 1; +static CookieControlAction default_action = COOKIE_DENY; + +static bool_t disabled; +static FILE *file_stream; +static char *cookies_txt_header_str = +"# HTTP Cookie File\n" +"# http://www.netscape.com/newsref/std/cookie_spec.html\n" +"# This is a generated file! Do not edit.\n\n"; + + +/* + * Forward declarations + */ + +static FILE *Cookies_fopen(const char *file, char *init_str); +static CookieControlAction Cookies_control_check_domain(const char *domain); +static int Cookie_control_init(void); +static void Cookies_parse_ports(int url_port, CookieData_t *cookie, + const char *port_str); +static char *Cookies_build_ports_str(CookieData_t *cookie); +static char *Cookies_strip_path(const char *path); +static void Cookies_add_cookie(CookieData_t *cookie); +static void Cookies_remove_cookie(CookieData_t *cookie); +static int Cookies_cmp(const void *a, const void *b); + +/* + * Compare function for searching a cookie node + */ +int Cookie_node_cmp(const void *v1, const void *v2) +{ + const CookieNode *n1 = v1, *n2 = v2; + + return strcmp(n1->domain, n2->domain); +} + +/* + * Compare function for searching a cookie node by domain + */ +int Cookie_node_by_domain_cmp(const void *v1, const void *v2) +{ + const CookieNode *node = v1; + const char *domain = v2; + + return strcmp(node->domain, domain); +} + +/* + * Return a file pointer. If the file doesn't exist, try to create it, + * with the optional 'init_str' as its content. + */ +static FILE *Cookies_fopen(const char *filename, char *init_str) +{ + FILE *F_in; + int fd; + + if ((F_in = fopen(filename, "r+")) == NULL) { + /* Create the file */ + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd != -1) { + if (init_str) + write(fd, init_str, strlen(init_str)); + close(fd); + + MSG("Created file: %s\n", filename); + F_in = Cookies_fopen(filename, NULL); + } else { + MSG("Could not create file: %s!\n", filename); + } + } + + /* set close on exec */ + fcntl(fileno(F_in), F_SETFD, FD_CLOEXEC | fcntl(fileno(F_in), F_GETFD)); + + return F_in; +} + +static void Cookies_free_cookie(CookieData_t *cookie) +{ + dFree(cookie->name); + dFree(cookie->value); + dFree(cookie->domain); + dFree(cookie->path); + dFree(cookie->comment); + dFree(cookie->comment_url); + dList_free(cookie->ports); + dFree(cookie); +} + +/* + * Initialize the cookies module + * (The 'disabled' variable is writable only within Cookies_init) + */ +void Cookies_init() +{ + CookieData_t *cookie; + char *filename; + char line[LINE_MAXLEN]; +#ifndef HAVE_LOCKF + struct flock lck; +#endif + FILE *old_cookies_file_stream; + + /* Default setting */ + disabled = TRUE; + + /* Read and parse the cookie control file (cookiesrc) */ + if (Cookie_control_init() != 0) { + MSG("Disabling cookies.\n"); + return; + } + + /* Get a stream for the cookies file */ + filename = dStrconcat(dGethomedir(), "/.dillo/cookies.txt", NULL); + file_stream = Cookies_fopen(filename, cookies_txt_header_str); + + dFree(filename); + + if (!file_stream) { + MSG("ERROR: Can't open ~/.dillo/cookies.txt, disabling cookies\n"); + return; + } + + /* Try to get a lock from the file descriptor */ +#ifdef HAVE_LOCKF + disabled = (lockf(fileno(file_stream), F_TLOCK, 0) == -1); +#else /* POSIX lock */ + lck.l_start = 0; /* start at beginning of file */ + lck.l_len = 0; /* lock entire file */ + lck.l_type = F_WRLCK; + lck.l_whence = SEEK_SET; /* absolute offset */ + + disabled = (fcntl(fileno(file_stream), F_SETLK, &lck) == -1); +#endif + if (disabled) { + MSG("The cookies file has a file lock: disabling cookies!\n"); + fclose(file_stream); + return; + } + + MSG("Enabling cookies as from cookiesrc...\n"); + + cookies = dList_new(32); + + /* Get all lines in the file */ + while (!feof(file_stream)) { + line[0] = '\0'; + fgets(line, LINE_MAXLEN, file_stream); + + /* Remove leading and trailing whitespaces */ + dStrstrip(line); + + if ((line[0] != '\0') && (line[0] != '#')) { + /* + * Split the row into pieces using a tab as the delimiter. + * pieces[0] The domain name + * pieces[1] TRUE/FALSE: is the domain a suffix, or a full domain? + * pieces[2] The path + * pieces[3] Is the cookie unsecure or secure (TRUE/FALSE) + * pieces[4] Timestamp of expire date + * pieces[5] Name of the cookie + * pieces[6] Value of the cookie + */ + CookieControlAction action; + char *piece; + char *line_marker = line; + + cookie = dNew0(CookieData_t, 1); + + cookie->session_only = FALSE; + cookie->version = 0; + cookie->domain = dStrdup(dStrsep(&line_marker, "\t")); + dStrsep(&line_marker, "\t"); /* we use domain always as sufix */ + cookie->path = dStrdup(dStrsep(&line_marker, "\t")); + piece = dStrsep(&line_marker, "\t"); + if (piece != NULL && piece[0] == 'T') + cookie->secure = TRUE; + piece = dStrsep(&line_marker, "\t"); + if (piece != NULL) + cookie->expires_at = (time_t) strtol(piece, NULL, 10); + cookie->name = dStrdup(dStrsep(&line_marker, "\t")); + cookie->value = dStrdup(dStrsep(&line_marker, "\t")); + + if (!cookie->domain || cookie->domain[0] == '\0' || + !cookie->path || cookie->path[0] != '/' || + !cookie->name || cookie->name[0] == '\0' || + !cookie->value) { + MSG("Malformed line in cookies.txt file!\n"); + Cookies_free_cookie(cookie); + continue; + } + + action = Cookies_control_check_domain(cookie->domain); + if (action == COOKIE_DENY) { + Cookies_free_cookie(cookie); + continue; + } else if (action == COOKIE_ACCEPT_SESSION) { + cookie->session_only = TRUE; + } + + /* Save cookie in memory */ + Cookies_add_cookie(cookie); + } + } + + filename = dStrconcat(dGethomedir(), "/.dillo/cookies", NULL); + if ((old_cookies_file_stream = fopen(filename, "r")) != NULL) { + dFree(filename); + MSG("WARNING: Reading old cookies file ~/.dillo/cookies too\n"); + + /* Get all lines in the file */ + while (!feof(old_cookies_file_stream)) { + line[0] = '\0'; + fgets(line, LINE_MAXLEN, old_cookies_file_stream); + + /* Remove leading and trailing whitespaces */ + dStrstrip(line); + + if (line[0] != '\0') { + /* + * Split the row into pieces using a tab as the delimiter. + * pieces[0] The version this cookie was set as (0 / 1) + * pieces[1] The domain name + * pieces[2] A comma separated list of accepted ports + * pieces[3] The path + * pieces[4] Is the cookie unsecure or secure (0 / 1) + * pieces[5] Timestamp of expire date + * pieces[6] Name of the cookie + * pieces[7] Value of the cookie + * pieces[8] Comment + * pieces[9] Comment url + */ + CookieControlAction action; + char *piece; + char *line_marker = line; + + cookie = dNew0(CookieData_t, 1); + + cookie->session_only = FALSE; + piece = dStrsep(&line_marker, "\t"); + if (piece != NULL) + cookie->version = strtol(piece, NULL, 10); + cookie->domain = dStrdup(dStrsep(&line_marker, "\t")); + Cookies_parse_ports(0, cookie, dStrsep(&line_marker, "\t")); + cookie->path = dStrdup(dStrsep(&line_marker, "\t")); + piece = dStrsep(&line_marker, "\t"); + if (piece != NULL && piece[0] == '1') + cookie->secure = TRUE; + piece = dStrsep(&line_marker, "\t"); + if (piece != NULL) + cookie->expires_at = (time_t) strtol(piece, NULL, 10); + cookie->name = dStrdup(dStrsep(&line_marker, "\t")); + cookie->value = dStrdup(dStrsep(&line_marker, "\t")); + cookie->comment = dStrdup(dStrsep(&line_marker, "\t")); + cookie->comment_url = dStrdup(dStrsep(&line_marker, "\t")); + + if (!cookie->domain || cookie->domain[0] == '\0' || + !cookie->path || cookie->path[0] != '/' || + !cookie->name || cookie->name[0] == '\0' || + !cookie->value) { + MSG("Malformed line in cookies file!\n"); + Cookies_free_cookie(cookie); + continue; + } + + action = Cookies_control_check_domain(cookie->domain); + if (action == COOKIE_DENY) { + Cookies_free_cookie(cookie); + continue; + } else if (action == COOKIE_ACCEPT_SESSION) { + cookie->session_only = TRUE; + } + + /* Save cookie in memory */ + Cookies_add_cookie(cookie); + } + } + fclose(old_cookies_file_stream); + } else { + dFree(filename); + } + +} + +/* + * Flush cookies to disk and free all the memory allocated. + */ +void Cookies_save_and_free() +{ + int i, fd; + CookieNode *node; + CookieData_t *cookie; + +#ifndef HAVE_LOCKF + struct flock lck; +#endif + + if (disabled) + return; + + rewind(file_stream); + fd = fileno(file_stream); + ftruncate(fd, 0); + fprintf(file_stream, cookies_txt_header_str); + + /* Iterate cookies per domain, saving and freeing */ + while ((node = dList_nth_data(cookies, 0))) { + for (i = 0; (cookie = dList_nth_data(node->dlist, i)); ++i) { + if (!cookie->session_only) { + /* char * ports_str = Cookies_build_ports_str(cookie); */ + fprintf(file_stream, "%s\tTRUE\t%s\t%s\t%ld\t%s\t%s\n", + cookie->domain, + cookie->path, + cookie->secure ? "TRUE" : "FALSE", + (long)cookie->expires_at, + cookie->name, + cookie->value); + /* dFree(ports_str); */ + } + + Cookies_free_cookie(cookie); + } + dList_remove(cookies, node); + dFree(node->domain); + dList_free(node->dlist); + dFree(node); + } + +#ifdef HAVE_LOCKF + lockf(fd, F_ULOCK, 0); +#else /* POSIX file lock */ + lck.l_start = 0; /* start at beginning of file */ + lck.l_len = 0; /* lock entire file */ + lck.l_type = F_UNLCK; + lck.l_whence = SEEK_SET; /* absolute offset */ + + fcntl(fileno(file_stream), F_SETLKW, &lck); +#endif + fclose(file_stream); +} + +static char *months[] = +{ "", + "Jan", "Feb", "Mar", + "Apr", "May", "Jun", + "Jul", "Aug", "Sep", + "Oct", "Nov", "Dec" +}; + +/* + * Take a months name and return a number between 1-12. + * E.g. 'April' -> 4 + */ +static int Cookies_get_month(const char *month_name) +{ + int i; + + for (i = 1; i <= 12; i++) { + if (!dStrncasecmp(months[i], month_name, 3)) + return i; + } + return 0; +} + +/* + * Return a local timestamp from a GMT date string + * Accept: RFC-1123 | RFC-850 | ANSI asctime | Old Netscape format. + * + * Wdy, DD-Mon-YY HH:MM:SS GMT + * Wdy, DD-Mon-YYYY HH:MM:SS GMT + * Weekday, DD-Mon-YY HH:MM:SS GMT + * Weekday, DD-Mon-YYYY HH:MM:SS GMT + * Tue May 21 13:46:22 1991\n + * Tue May 21 13:46:22 1991 + * + * (return 0 on malformed date string syntax) + */ +static time_t Cookies_create_timestamp(const char *expires) +{ + time_t ret; + int day, month, year, hour, minutes, seconds; + char *cp; + char *E_msg = + "Expire date is malformed!\n" + " (should be RFC-1123 | RFC-850 | ANSI asctime)\n" + " Ignoring cookie: "; + + cp = strchr(expires, ','); + if (!cp && (strlen(expires) == 24 || strlen(expires) == 25)) { + /* Looks like ANSI asctime format... */ + cp = (char *)expires; + day = strtol(cp + 8, NULL, 10); /* day */ + month = Cookies_get_month(cp + 4); /* month */ + year = strtol(cp + 20, NULL, 10); /* year */ + hour = strtol(cp + 11, NULL, 10); /* hour */ + minutes = strtol(cp + 14, NULL, 10); /* minutes */ + seconds = strtol(cp + 17, NULL, 10); /* seconds */ + + } else if (cp && (cp - expires == 3 || cp - expires > 5) && + (strlen(cp) == 24 || strlen(cp) == 26)) { + /* RFC-1123 | RFC-850 format | Old Netscape format */ + day = strtol(cp + 2, NULL, 10); + month = Cookies_get_month(cp + 5); + year = strtol(cp + 9, &cp, 10); + /* todo: tricky, because two digits for year IS ambiguous! */ + year += (year < 70) ? 2000 : ((year < 100) ? 1900 : 0); + hour = strtol(cp + 1, NULL, 10); + minutes = strtol(cp + 4, NULL, 10); + seconds = strtol(cp + 7, NULL, 10); + + } else { + MSG("%s%s\n", E_msg, expires); + return (time_t) 0; + } + + /* Error checks --this may be overkill */ + if (!(day > 0 && day < 32 && month > 0 && month < 13 && year > 1970 && + hour >= 0 && hour < 24 && minutes >= 0 && minutes < 60 && + seconds >= 0 && seconds < 60)) { + MSG("%s%s\n", E_msg, expires); + return (time_t) 0; + } + + /* Calculate local timestamp. + * [stolen from Lynx... (http://lynx.browser.org)] */ + month -= 3; + if (month < 0) { + month += 12; + year--; + } + + day += (year - 1968) * 1461 / 4; + day += ((((month * 153) + 2) / 5) - 672); + ret = (time_t)((day * 60 * 60 * 24) + + (hour * 60 * 60) + + (minutes * 60) + + seconds); + + MSG("Expires in %ld seconds, at %s", + (long)ret - time(NULL), ctime(&ret)); + + return ret; +} + +/* + * Parse a string containing a list of port numbers. + */ +static void Cookies_parse_ports(int url_port, CookieData_t *cookie, + const char *port_str) +{ + if ((!port_str || !port_str[0]) && url_port != 0) { + /* There was no list, so only the calling urls port should be allowed. */ + if (!cookie->ports) + cookie->ports = dList_new(1); + dList_append(cookie->ports, INT2VOIDP(url_port)); + } else if (port_str[0] == '"' && port_str[1] != '"') { + char *tok, *str; + int port; + + str = dStrdup(port_str + 1); + while ((tok = dStrsep(&str, ","))) { + port = strtol(tok, NULL, 10); + if (port > 0) { + if (!cookie->ports) + cookie->ports = dList_new(1); + dList_append(cookie->ports, INT2VOIDP(port)); + } + } + dFree(str); + } +} + +/* + * Build a string of the ports in 'cookie'. + */ +static char *Cookies_build_ports_str(CookieData_t *cookie) +{ + Dstr *dstr; + char *ret; + void *data; + int i; + + dstr = dStr_new("\""); + for (i = 0; (data = dList_nth_data(cookie->ports, i)); ++i) { + dStr_sprintfa(dstr, "%d,", VOIDP2INT(data)); + } + /* Remove any trailing comma */ + if (dstr->len > 1) + dStr_erase(dstr, dstr->len - 1, 1); + dStr_append(dstr, "\""); + + ret = dstr->str; + dStr_free(dstr, FALSE); + + return ret; +} + +static void Cookies_add_cookie(CookieData_t *cookie) +{ + Dlist *domain_cookies; + CookieData_t *c; + CookieNode *node; + + /* Don't add an expired cookie */ + if (!cookie->session_only && cookie->expires_at < time(NULL)) { + Cookies_free_cookie(cookie); + return; + } + + node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp); + domain_cookies = (node) ? node->dlist : NULL; + + if (domain_cookies) { + /* Respect the limit of 20 cookies per domain */ + if (dList_length(domain_cookies) >= 20) { + MSG("There are too many cookies for this domain (%s)\n", + cookie->domain); + Cookies_free_cookie(cookie); + return; + } + + /* Remove any cookies with the same name and path */ + while ((c = dList_find_custom(domain_cookies, cookie, Cookies_cmp))){ + Cookies_remove_cookie(c); + } + } + + /* add the cookie into the respective domain list */ + node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp); + domain_cookies = (node) ? node->dlist : NULL; + if (!domain_cookies) { + domain_cookies = dList_new(5); + dList_append(domain_cookies, cookie); + node = dNew(CookieNode, 1); + node->domain = dStrdup(cookie->domain); + node->dlist = domain_cookies; + dList_insert_sorted(cookies, node, Cookie_node_cmp); + } else { + dList_append(domain_cookies, cookie); + } +} + +/* + * Remove the cookie from the domain list. + * If the domain list is empty, remove the node too. + * Free the cookie. + */ +static void Cookies_remove_cookie(CookieData_t *cookie) +{ + CookieNode *node; + + node = dList_find_sorted(cookies, cookie->domain,Cookie_node_by_domain_cmp); + if (node) { + dList_remove(node->dlist, cookie); + if (dList_length(node->dlist) == 0) { + dList_remove(cookies, node); + dFree(node->domain); + dList_free(node->dlist); + } + } else { + MSG("Attempting to remove a cookie that doesn't exist!\n"); + } + + Cookies_free_cookie(cookie); +} + +/* + * Return the attribute that is present at *cookie_str. This function + * will also attempt to advance cookie_str past any equal-sign. + */ +static char *Cookies_parse_attr(char **cookie_str) +{ + char *str = *cookie_str; + uint_t i, end = 0; + bool_t got_attr = FALSE; + + for (i = 0; ; i++) { + switch (str[i]) { + case ' ': + case '\t': + case '=': + case ';': + got_attr = TRUE; + if (end == 0) + end = i; + break; + case ',': + *cookie_str = str + i; + return dStrndup(str, i); + break; + case '\0': + if (!got_attr) { + end = i; + got_attr = TRUE; + } + /* fall through! */ + default: + if (got_attr) { + *cookie_str = str + i; + return dStrndup(str, end); + } + break; + } + } + + return NULL; +} + +/* + * Get the value starting at *cookie_str. + * broken_syntax: watch out for stupid syntax (comma in unquoted string...) + */ +static char *Cookies_parse_value(char **cookie_str, + bool_t broken_syntax, + bool_t keep_quotes) +{ + uint_t i, end; + char *str = *cookie_str; + + for (i = end = 0; !end; ++i) { + switch (str[i]) { + case ' ': + case '\t': + if (!broken_syntax && str[0] != '\'' && str[0] != '"') { + *cookie_str = str + i + 1; + end = 1; + } + break; + case '\'': + case '"': + if (i != 0 && str[i] == str[0]) { + char *tmp = str + i; + + while (*tmp != '\0' && *tmp != ';' && *tmp != ',') + tmp++; + + *cookie_str = (*tmp == ';') ? tmp + 1 : tmp; + + if (keep_quotes) + i++; + end = 1; + } + break; + case '\0': + *cookie_str = str + i; + end = 1; + break; + case ',': + if (str[0] != '\'' && str[0] != '"' && !broken_syntax) { + /* A new cookie starts here! */ + *cookie_str = str + i; + end = 1; + } + break; + case ';': + if (str[0] != '\'' && str[0] != '"') { + *cookie_str = str + i + 1; + end = 1; + } + break; + default: + break; + } + } + /* keep i as an index to the last char */ + --i; + + if ((str[0] == '\'' || str[0] == '"') && !keep_quotes) { + return i > 1 ? dStrndup(str + 1, i - 1) : NULL; + } else { + return dStrndup(str, i); + } +} + +/* + * Parse one cookie... + */ +static CookieData_t *Cookies_parse_one(int url_port, char **cookie_str) +{ + CookieData_t *cookie; + char *str = *cookie_str; + char *attr; + char *value; + int num_attr = 0; + bool_t max_age = FALSE; + bool_t discard = FALSE; + + cookie = dNew0(CookieData_t, 1); + cookie->session_only = TRUE; + + /* Iterate until there is nothing left of the string OR we come + * across a comma representing the start of another cookie */ + while (*str != '\0' && *str != ',') { + /* Skip whitespace */ + while (isspace(*str)) + str++; + + /* Get attribute */ + attr = Cookies_parse_attr(&str); + if (!attr) { + MSG("Failed to parse cookie attribute!\n"); + Cookies_free_cookie(cookie); + return NULL; + } + + /* Get the value for the attribute and store it */ + if (num_attr == 0) { + /* The first attr, which always is the user supplied attr, may + * have the same name as an ordinary attr. Hence this workaround. */ + cookie->name = dStrdup(attr); + cookie->value = Cookies_parse_value(&str, FALSE, TRUE); + } else if (dStrcasecmp(attr, "Path") == 0) { + value = Cookies_parse_value(&str, FALSE, FALSE); + cookie->path = value; + } else if (dStrcasecmp(attr, "Domain") == 0) { + value = Cookies_parse_value(&str, FALSE, FALSE); + cookie->domain = value; + } else if (dStrcasecmp(attr, "Discard") == 0) { + cookie->session_only = TRUE; + discard = TRUE; + } else if (dStrcasecmp(attr, "Max-Age") == 0) { + if (!discard) { + value = Cookies_parse_value(&str, FALSE, FALSE); + + if (value) { + cookie->expires_at = time(NULL) + strtol(value, NULL, 10); + cookie->session_only = FALSE; + max_age = TRUE; + dFree(value); + } else { + MSG("Failed to parse cookie value!\n"); + Cookies_free_cookie(cookie); + return NULL; + } + } + } else if (dStrcasecmp(attr, "Expires") == 0) { + if (!max_age && !discard) { + MSG("Old netscape-style cookie...\n"); + value = Cookies_parse_value(&str, TRUE, FALSE); + if (value) { + cookie->expires_at = Cookies_create_timestamp(value); + cookie->session_only = FALSE; + dFree(value); + } else { + MSG("Failed to parse cookie value!\n"); + Cookies_free_cookie(cookie); + return NULL; + } + } + } else if (dStrcasecmp(attr, "Port") == 0) { + value = Cookies_parse_value(&str, FALSE, TRUE); + Cookies_parse_ports(url_port, cookie, value); + dFree(value); + } else if (dStrcasecmp(attr, "Comment") == 0) { + value = Cookies_parse_value(&str, FALSE, FALSE); + cookie->comment = value; + } else if (dStrcasecmp(attr, "CommentURL") == 0) { + value = Cookies_parse_value(&str, FALSE, FALSE); + cookie->comment_url = value; + } else if (dStrcasecmp(attr, "Version") == 0) { + value = Cookies_parse_value(&str, FALSE, FALSE); + + if (value) { + cookie->version = strtol(value, NULL, 10); + dFree(value); + } else { + MSG("Failed to parse cookie value!\n"); + Cookies_free_cookie(cookie); + return NULL; + } + } else if (dStrcasecmp(attr, "Secure") == 0) { + cookie->secure = TRUE; + } else { + /* Oops! this can't be good... */ + dFree(attr); + Cookies_free_cookie(cookie); + MSG("Cookie contains illegal attribute!\n"); + return NULL; + } + + dFree(attr); + num_attr++; + } + + *cookie_str = (*str == ',') ? str + 1 : str; + + if (cookie->name && cookie->value) { + return cookie; + } else { + MSG("Cookie missing name and/or value!\n"); + Cookies_free_cookie(cookie); + return NULL; + } +} + +/* + * Iterate the cookie string until we catch all cookies. + * Return Value: a list with all the cookies! (or NULL upon error) + */ +static Dlist *Cookies_parse_string(int url_port, char *cookie_string) +{ + CookieData_t *cookie; + Dlist *ret = NULL; + char *str = cookie_string; + + /* The string may contain several cookies separated by comma. + * We'll iterate until we've catched them all */ + while (*str) { + cookie = Cookies_parse_one(url_port, &str); + + if (cookie) { + if (!ret) + ret = dList_new(4); + dList_append(ret, cookie); + } else { + MSG("Malformed cookie field, ignoring cookie: %s\n", cookie_string); + } + } + + return ret; +} + +/* + * Compare cookies by name and path (return 0 if equal) + */ +static int Cookies_cmp(const void *a, const void *b) +{ + const CookieData_t *ca = a, *cb = b; + int ret; + + if (!(ret = strcmp(ca->name, cb->name))) + ret = strcmp(ca->path, cb->path); + return ret; +} + +/* + * Validate cookies domain against some security checks. + */ +static bool_t Cookies_validate_domain(CookieData_t *cookie, char *host, + char *url_path) +{ + int dots, diff, i; + bool_t is_ip; + + /* Make sure that the path is set to something */ + if (!cookie->path || cookie->path[0] != '/') { + dFree(cookie->path); + cookie->path = Cookies_strip_path(url_path); + } + + /* If the server never set a domain, or set one without a leading + * dot (which isn't allowed), we use the calling URL's hostname. */ + if (cookie->domain == NULL || cookie->domain[0] != '.') { + dFree(cookie->domain); + cookie->domain = dStrdup(host); + return TRUE; + } + + /* Count the number of dots and also find out if it is an IP-address */ + is_ip = TRUE; + for (i = 0, dots = 0; cookie->domain[i] != '\0'; i++) { + if (cookie->domain[i] == '.') + dots++; + else if (!isdigit(cookie->domain[i])) + is_ip = FALSE; + } + + /* A valid domain must have at least two dots in it */ + /* NOTE: this breaks cookies on localhost... */ + if (dots < 2) { + return FALSE; + } + + /* Now see if the url matches the domain */ + diff = strlen(host) - i; + if (diff > 0) { + if (dStrcasecmp(host + diff, cookie->domain)) + return FALSE; + + if (!is_ip) { + /* "x.y.test.com" is not allowed to set cookies for ".test.com"; + * only an url of the form "y.test.com" would be. */ + while ( diff-- ) + if (host[diff] == '.') + return FALSE; + } + } + + return TRUE; +} + +/* + * Strip of the filename from a full path + */ +static char *Cookies_strip_path(const char *path) +{ + char *ret; + uint_t len; + + if (path) { + len = strlen(path); + + while (len && path[len] != '/') + len--; + ret = dStrndup(path, len + 1); + } else { + ret = dStrdup("/"); + } + + return ret; +} + +/* + * Set the value corresponding to the cookie string + */ +void Cookies_set(char *cookie_string, char *url_host, + char *url_path, int url_port) +{ + CookieControlAction action; + CookieData_t *cookie; + Dlist *list; + int i; + + if (disabled) + return; + + action = Cookies_control_check_domain(url_host); + if (action == COOKIE_DENY) { + MSG("denied SET for %s\n", url_host); + return; + } + + if ((list = Cookies_parse_string(url_port, cookie_string))) { + for (i = 0; (cookie = dList_nth_data(list, i)); ++i) { + if (Cookies_validate_domain(cookie, url_host, url_path)) { + if (action == COOKIE_ACCEPT_SESSION) + cookie->session_only = TRUE; + Cookies_add_cookie(cookie); + } else { + MSG("Rejecting cookie for %s from host %s path %s\n", + cookie->domain, url_host, url_path); + Cookies_free_cookie(cookie); + } + } + dList_free(list); + } +} + +/* + * Compare the cookie with the supplied data to see if it matches + */ +static bool_t Cookies_match(CookieData_t *cookie, int port, + const char *path, bool_t is_ssl) +{ + void *data; + int i; + + /* Insecure cookies matches both secure and insecure urls, secure + cookies matches only secure urls */ + if (cookie->secure && !is_ssl) + return FALSE; + + /* Check that the cookie path is a subpath of the current path */ + if (strncmp(cookie->path, path, strlen(cookie->path)) != 0) + return FALSE; + + /* Check if the port of the request URL matches any + * of those set in the cookie */ + if (cookie->ports) { + for (i = 0; (data = dList_nth_data(cookie->ports, i)); ++i) { + if (VOIDP2INT(data) == port) + return TRUE; + } + return FALSE; + } + + /* It's a match */ + return TRUE; +} + +/* + * Return a string that contains all relevant cookies as headers. + */ +char *Cookies_get(char *url_host, char *url_path, + char *url_scheme, int url_port) +{ + char *domain_str, *q, *str, *path; + CookieData_t *cookie; + Dlist *matching_cookies; + CookieNode *node; + Dlist *domain_cookies; + bool_t is_ssl; + Dstr *cookie_dstring; + int i; + + if (disabled) + return dStrdup(""); + + matching_cookies = dList_new(8); + + path = Cookies_strip_path(url_path); + + /* Check if the protocol is secure or not */ + is_ssl = (!dStrcasecmp(url_scheme, "https")); + + for (domain_str = (char *) url_host; + domain_str != NULL && *domain_str; + domain_str = strchr(domain_str+1, '.')) { + + node = dList_find_sorted(cookies, domain_str, Cookie_node_by_domain_cmp); + domain_cookies = (node) ? node->dlist : NULL; + + for (i = 0; (cookie = dList_nth_data(domain_cookies, i)); ++i) { + /* Remove expired cookie. */ + if (!cookie->session_only && cookie->expires_at < time(NULL)) { + Cookies_remove_cookie(cookie); + --i; continue; + } + /* Check if the cookie matches the requesting URL */ + if (Cookies_match(cookie, url_port, path, is_ssl)) { + dList_append(matching_cookies, cookie); + } + } + } + + /* Found the cookies, now make the string */ + cookie_dstring = dStr_new(""); + if (dList_length(matching_cookies) > 0) { + CookieData_t *first_cookie = dList_nth_data(matching_cookies, 0); + + dStr_sprintfa(cookie_dstring, "Cookie: "); + + if (first_cookie->version != 0) + dStr_sprintfa(cookie_dstring, "$Version=\"%d\"; ", + first_cookie->version); + + + for (i = 0; (cookie = dList_nth_data(matching_cookies, i)); ++i) { + q = (cookie->version == 0 ? "" : "\""); + dStr_sprintfa(cookie_dstring, + "%s=%s; $Path=%s%s%s; $Domain=%s%s%s", + cookie->name, cookie->value, + q, cookie->path, q, q, cookie->domain, q); + if (cookie->ports) { + char *ports_str = Cookies_build_ports_str(cookie); + dStr_sprintfa(cookie_dstring, "; $Port=%s", ports_str); + dFree(ports_str); + } + + dStr_append(cookie_dstring, + dList_length(matching_cookies) > i + 1 ? "; " : "\r\n"); + } + } + + dList_free(matching_cookies); + dFree(path); + str = cookie_dstring->str; + dStr_free(cookie_dstring, FALSE); + return str; +} + +/* ------------------------------------------------------------- + * Access control routines + * ------------------------------------------------------------- */ + + +/* + * Get the cookie control rules (from cookiesrc). + * Return value: + * 0 = Parsed OK, with cookies enabled + * 1 = Parsed OK, with cookies disabled + * 2 = Can't open the control file + */ +static int Cookie_control_init(void) +{ + CookieControl cc; + FILE *stream; + char *filename; + char line[LINE_MAXLEN]; + char domain[LINE_MAXLEN]; + char rule[LINE_MAXLEN]; + int i, j; + bool_t enabled = FALSE; + + /* Get a file pointer */ + filename = dStrconcat(dGethomedir(), "/.dillo/cookiesrc", NULL); + stream = Cookies_fopen(filename, "DEFAULT DENY\n"); + dFree(filename); + + if (!stream) + return 2; + + /* Get all lines in the file */ + while (!feof(stream)) { + line[0] = '\0'; + fgets(line, LINE_MAXLEN, stream); + + /* Remove leading and trailing whitespaces */ + dStrstrip(line); + + if (line[0] != '\0' && line[0] != '#') { + i = 0; + j = 0; + + /* Get the domain */ + while (!isspace(line[i])) + domain[j++] = line[i++]; + domain[j] = '\0'; + + /* Skip past whitespaces */ + i++; + while (isspace(line[i])) + i++; + + /* Get the rule */ + j = 0; + while (line[i] != '\0' && !isspace(line[i])) + rule[j++] = line[i++]; + rule[j] = '\0'; + + if (dStrcasecmp(rule, "ACCEPT") == 0) + cc.action = COOKIE_ACCEPT; + else if (dStrcasecmp(rule, "ACCEPT_SESSION") == 0) + cc.action = COOKIE_ACCEPT_SESSION; + else if (dStrcasecmp(rule, "DENY") == 0) + cc.action = COOKIE_DENY; + else { + MSG("Cookies: rule '%s' for domain '%s' is not recognised.\n", + rule, domain); + continue; + } + + cc.domain = dStrdup(domain); + if (dStrcasecmp(cc.domain, "DEFAULT") == 0) { + /* Set the default action */ + default_action = cc.action; + dFree(cc.domain); + } else { + a_List_add(ccontrol, num_ccontrol, num_ccontrol_max); + ccontrol[num_ccontrol++] = cc; + } + + if (cc.action != COOKIE_DENY) + enabled = TRUE; + } + } + + fclose(stream); + + return (enabled ? 0 : 1); +} + +/* + * Check the rules for an appropriate action for this domain + */ +static CookieControlAction Cookies_control_check_domain(const char *domain) +{ + int i, diff; + + for (i = 0; i < num_ccontrol; i++) { + if (ccontrol[i].domain[0] == '.') { + diff = strlen(domain) - strlen(ccontrol[i].domain); + if (diff >= 0) { + if (dStrcasecmp(domain + diff, ccontrol[i].domain) != 0) + continue; + } else { + continue; + } + } else { + if (dStrcasecmp(domain, ccontrol[i].domain) != 0) + continue; + } + + /* If we got here we have a match */ + return( ccontrol[i].action ); + } + + return default_action; +} + +/* -- Dpi parser ----------------------------------------------------------- */ + +/* + * Parse a data stream (dpi protocol) + * Note: Buf is a zero terminated string + * Return code: { 0:OK, 1:Abort, 2:Close } + */ +static int srv_parse_buf(SockHandler *sh, char *Buf, size_t BufSize) +{ + char *p, *cmd, *cookie, *host, *path, *scheme; + int port; + + if (!(p = strchr(Buf, '>'))) { + /* Haven't got a full tag */ + MSG("Haven't got a full tag!\n"); + return 1; + } + + cmd = a_Dpip_get_attr(Buf, BufSize, "cmd"); + + if (cmd && strcmp(cmd, "DpiBye") == 0) { + dFree(cmd); + MSG("Cookies dpi (pid %d): Got DpiBye.\n", (int)getpid()); + exit(0); + + } else if (cmd && strcmp(cmd, "set_cookie") == 0) { + dFree(cmd); + cookie = a_Dpip_get_attr(Buf, BufSize, "cookie"); + host = a_Dpip_get_attr(Buf, BufSize, "host"); + path = a_Dpip_get_attr(Buf, BufSize, "path"); + p = a_Dpip_get_attr(Buf, BufSize, "port"); + port = strtol(p, NULL, 10); + dFree(p); + + Cookies_set(cookie, host, path, port); + + dFree(path); + dFree(host); + dFree(cookie); + return 2; + + } else if (cmd && strcmp(cmd, "get_cookie") == 0) { + dFree(cmd); + scheme = a_Dpip_get_attr(Buf, BufSize, "scheme"); + host = a_Dpip_get_attr(Buf, BufSize, "host"); + path = a_Dpip_get_attr(Buf, BufSize, "path"); + p = a_Dpip_get_attr(Buf, BufSize, "port"); + port = strtol(p, NULL, 10); + dFree(p); + + cookie = Cookies_get(host, path, scheme, port); + dFree(scheme); + dFree(path); + dFree(host); + + cmd = a_Dpip_build_cmd("cmd=%s cookie=%s", "get_cookie_answer", cookie); + + if (sock_handler_write_str(sh, 1, cmd)) { + dFree(cookie); + dFree(cmd); + return 1; + } + dFree(cookie); + dFree(cmd); + + return 2; + } + + return 0; +} + +/* -- Termination handlers ----------------------------------------------- */ +/* + * (was to delete the local namespace socket), + * but this is handled by 'dpid' now. + */ +static void cleanup(void) +{ + Cookies_save_and_free(); + MSG("cleanup\n"); + /* no more cleanup required */ +} + +/* + * Perform any necessary cleanups upon abnormal termination + */ +static void termination_handler(int signum) +{ + exit(signum); +} + + +/* + * -- MAIN ------------------------------------------------------------------- + */ +int main (void) { + struct sockaddr_un spun; + int temp_sock_descriptor; + socklen_t address_size; + char *buf; + int code; + SockHandler *sh; + + /* Arrange the cleanup function for terminations via exit() */ + atexit(cleanup); + + /* Arrange the cleanup function for abnormal terminations */ + if (signal (SIGINT, termination_handler) == SIG_IGN) + signal (SIGINT, SIG_IGN); + if (signal (SIGHUP, termination_handler) == SIG_IGN) + signal (SIGHUP, SIG_IGN); + if (signal (SIGTERM, termination_handler) == SIG_IGN) + signal (SIGTERM, SIG_IGN); + + Cookies_init(); + MSG("(v.1) accepting connections...\n"); + + if (disabled) + exit(1); + + /* some OSes may need this... */ + address_size = sizeof(struct sockaddr_un); + + while (1) { + temp_sock_descriptor = + accept(STDIN_FILENO, (struct sockaddr *)&spun, &address_size); + if (temp_sock_descriptor == -1) { + perror("[accept]"); + exit(1); + } + + /* create the SockHandler structure */ + sh = sock_handler_new(temp_sock_descriptor,temp_sock_descriptor,8*1024); + + while (1) { + code = 1; + if ((buf = sock_handler_read(sh)) != NULL) { + /* Let's see what we fished... */ + code = srv_parse_buf(sh, buf, strlen(buf)); + } + if (code == 1) + exit(1); + else if (code == 2) + break; + } + + _MSG("Closing SockHandler\n"); + sock_handler_close(sh); + sock_handler_free(sh); + + }/*while*/ +} + +#endif /* !DISABLE_COOKIES */ diff --git a/dpi/datauri.c b/dpi/datauri.c new file mode 100644 index 00000000..87afd2d9 --- /dev/null +++ b/dpi/datauri.c @@ -0,0 +1,323 @@ +/* + * File: datauri.c + * + * Copyright (C) 2006 Jorge Arellano Cid <jcid@dillo.org> + * + * Filter dpi for the "data:" URI scheme (RFC 2397). + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../dpip/dpip.h" +#include "dpiutil.h" + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[datauri dpi]: " __VA_ARGS__) + +/* + * Global variables + */ +static SockHandler *sh = NULL; + + + +int b64decode(unsigned char* str) +{ + unsigned char *cur, *start; + int d, dlast, phase; + unsigned char c; + static int table[256] = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 00-0F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 10-1F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, /* 20-2F */ + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, /* 30-3F */ + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, /* 40-4F */ + 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, /* 50-5F */ + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, /* 60-6F */ + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, /* 70-7F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 80-8F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 90-9F */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* A0-AF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* B0-BF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* C0-CF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* D0-DF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* E0-EF */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 /* F0-FF */ + }; + + d = dlast = phase = 0; + start = str; + for (cur = str; *cur != '\0'; ++cur ) + { + // jer: treat line endings as physical breaks. + //if (*cur == '\n' || *cur == '\r'){phase = dlast = 0; continue;} + d = table[(int)*cur]; + if(d != -1) + { + switch(phase) + { + case 0: + ++phase; + break; + case 1: + c = ((dlast << 2) | ((d & 0x30) >> 4)); + *str++ = c; + ++phase; + break; + case 2: + c = (((dlast & 0xf) << 4) | ((d & 0x3c) >> 2)); + *str++ = c; + ++phase; + break; + case 3: + c = (((dlast & 0x03 ) << 6) | d); + *str++ = c; + phase = 0; + break; + } + dlast = d; + } + } + *str = '\0'; + return str - start; +} + +/* Modified from src/url.c --------------------------------------------------*/ + +/* + * Given an hex octet (e.g., e3, 2F, 20), return the corresponding + * character if the octet is valid, and -1 otherwise + */ +static int Url_decode_hex_octet(const char *s) +{ + int hex_value; + char *tail, hex[3]; + + if (s && (hex[0] = s[0]) && (hex[1] = s[1])) { + hex[2] = 0; + hex_value = strtol(hex, &tail, 16); + if (tail - hex == 2) + return hex_value; + } + return -1; +} + +/* + * Parse possible hexadecimal octets in the URI path. + * Returns a new allocated string. + */ +char *a_Url_decode_hex_str(const char *str, size_t *p_sz) +{ + char *new_str, *dest; + int i, val; + + if (!str) { + *p_sz = 0; + return NULL; + } + + dest = new_str = dNew(char, strlen(str) + 1); + for (i = 0; str[i]; i++) { + *dest++ = (str[i] == '%' && (val = Url_decode_hex_octet(str+i+1)) >= 0) ? + i+=2, val : str[i]; + } + *dest = 0; + + new_str = dRealloc(new_str, sizeof(char) * (dest - new_str + 1)); + *p_sz = (size_t)(dest - new_str); + return new_str; +} + +/* end ----------------------------------------------------------------------*/ + +/* + * Send decoded data to dillo in an HTTP envelope. + */ +void send_decoded_data(const char *url, const char *mime_type, + unsigned char *data, size_t data_sz) +{ + char *d_cmd; + + /* Send dpip tag */ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + /* Send HTTP header. */ + sock_handler_write_str(sh, 0, "Content-type: "); + sock_handler_write_str(sh, 0, mime_type); + sock_handler_write_str(sh, 1, "\n\n"); + + /* Send message */ + sock_handler_write(sh, 0, (char *)data, data_sz); +} + +void send_failure_message(const char *url, const char *mime_type, + unsigned char *data, size_t data_sz) +{ + char *d_cmd; + char buf[1024]; + + const char *msg = +"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n" +"<html><body>\n" +"<hr><h1>Datauri dpi</h1><hr>\n" +"<p><b>Can't parse datauri:</b><br>\n"; + const char *msg_mime_type="text/html"; + + /* Send dpip tag */ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + /* Send HTTP header. */ + sock_handler_write_str(sh, 0, "Content-type: "); + sock_handler_write_str(sh, 0, msg_mime_type); + sock_handler_write_str(sh, 1, "\n\n"); + + /* Send message */ + sock_handler_write_str(sh, 0, msg); + + /* send some debug info */ + snprintf(buf, 1024, "mime_type: %s<br>data size: %d<br>data: %s<br>", + mime_type, (int)data_sz, data); + sock_handler_write_str(sh, 0, buf); + + /* close page */ + sock_handler_write_str(sh, 0, "</body></html>"); +} + +/* + * Get mime type from the data URI. + * todo: there's no point in handling "charset" because current dillo + * only handles ISO-LATIN-1. The FLTK2 version (utf-8) could use it in the + * future. + */ +char *datauri_get_mime(char *url) +{ + char buf[256]; + char *mime_type = NULL, *p; + size_t len = 0; + + if (dStrncasecmp(url, "data:", 5) == 0) { + if ((p = strchr(url, ',')) && p - url < 256) { + url += 5; + len = p - url; + strncpy(buf, url, len); + buf[len] = 0; + /* strip ";base64" */ + if (len >= 7 && dStrcasecmp(buf + len - 7, ";base64") == 0) { + len -= 7; + buf[len] = 0; + } + } + + /* that's it, now handle omitted types */ + if (len == 0) { + mime_type = dStrdup("text/plain;charset=US-ASCII"); + } else if (!dStrncasecmp(buf, "charset", 7)) { + mime_type = dStrconcat("text/plain", buf, NULL); + } else { + mime_type = dStrdup(buf); + } + } + + return mime_type; +} + +/* + * Return a decoded data string. + */ +unsigned char *datauri_get_data(char *url, size_t *p_sz) +{ + char *p; + int is_base64 = 0; + unsigned char *data = NULL; + + if ((p = strchr(url, ',')) && p - url >= 12 && /* "data:;base64" */ + dStrncasecmp(p - 7, ";base64", 7) == 0) { + is_base64 = 1; + } + + if (p) { + ++p; + if (is_base64) { + data = (unsigned char *)dStrdup(p); + *p_sz = (size_t) b64decode(data); + } else { + data = (unsigned char *)a_Url_decode_hex_str(p, p_sz); + } + } else { + data = (unsigned char *)dStrdup(""); + *p_sz = 0; + } + + return data; +} + +/* + * + */ +int main(void) +{ + char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *mime_type; + unsigned char *data; + size_t data_size = 0; + + /* Initialize the SockHandler */ + sh = sock_handler_new(STDIN_FILENO, STDOUT_FILENO, 8*1024); + + /* wget may need to write a temporary file... */ + chdir("/tmp"); + + /* Read the dpi command from STDIN */ + dpip_tag = sock_handler_read(sh); + MSG("[%s]\n", dpip_tag); + + cmd = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cmd"); + url = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "url"); + if (!cmd || !url) { + MSG("Error, cmd=%s, url=%s\n", cmd, url); + exit (EXIT_FAILURE); + } + + /* Parse the data URI */ + mime_type = datauri_get_mime(url); + data = datauri_get_data(url, &data_size); + + MSG("mime_type: %s\n", mime_type); + MSG("data_size: %d\n", data_size); + MSG("data: {%s}\n", data); + + if (mime_type && data) { + /* good URI */ + send_decoded_data(url, mime_type, data, data_size); + } else { + /* malformed URI */ + send_failure_message(url, mime_type, data, data_size); + } + + dFree(data); + dFree(mime_type); + dFree(url); + dFree(cmd); + dFree(dpip_tag); + + /* Finish the SockHandler */ + sock_handler_close(sh); + sock_handler_free(sh); + + return 0; +} + diff --git a/dpi/downloads.cc b/dpi/downloads.cc new file mode 100644 index 00000000..b61f4914 --- /dev/null +++ b/dpi/downloads.cc @@ -0,0 +1,1133 @@ +/* + * File: downloads.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * A FLTK2-based GUI for the downloads dpi (dillo plugin). + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <ctype.h> +#include <math.h> +#include <time.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <sys/wait.h> + +#include <fltk/run.h> +#include <fltk/Window.h> +#include <fltk/Widget.h> +#include <fltk/damage.h> +#include <fltk/Box.h> +#include <fltk/draw.h> +#include <fltk/HighlightButton.h> +#include <fltk/PackedGroup.h> +#include <fltk/ScrollGroup.h> +#include <fltk/ask.h> +#include <fltk/file_chooser.h> + +#include "dpiutil.h" +#include "../dpip/dpip.h" + +using namespace fltk; + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[downloads dpi]: " __VA_ARGS__) + +/* + * Internal types + */ +typedef enum { + DL_NEWFILE, + DL_CONTINUE, + DL_RENAME, + DL_OVERWRITE, + DL_ABORT +} DLAction; + +/* + * Class declarations + */ + +// ProgressBar widget -------------------------------------------------------- + +// class FL_API ProgressBar : public Widget { +class ProgressBar : public Widget { +protected: + double mMin; + double mMax; + double mPresent; + double mStep; + bool mShowPct, mShowMsg; + char mMsg[64]; + Color mTextColor; + void draw(); +public: + ProgressBar(int x, int y, int w, int h, const char *lbl = 0); + void range(double min, double max, double step = 1) { + mMin = min; mMax = max; mStep = step; + }; + void step(double step) { mPresent += step; redraw(); }; + void move(double step); + double minimum() { return mMin; } + double maximum() { return mMax; } + void minimum(double nm) { mMin = nm; }; + void maximum(double nm) { mMax = nm; }; + double position () { return mPresent; } + double step() { return mStep; } + void position(double pos) { mPresent = pos; redraw(); } + void showtext(bool st) { mShowPct = st; } + void message(char *msg) { mShowMsg = true; strncpy(mMsg,msg,63); redraw(); } + bool showtext() { return mShowPct; } + void text_color(Color col) { mTextColor = col; } + Color text_color() { return mTextColor; } +}; + +// Download-item class ------------------------------------------------------- + +class DLItem { + enum { + ST_newline, ST_number, ST_discard, ST_copy + }; + + pid_t mPid; + int LogPipe[2]; + char *shortname, *fullname; + char *target_dir; + int log_len, log_max, log_state; + char *log_text; + time_t init_time; + char **dl_argv; + time_t twosec_time, onesec_time; + int twosec_bytesize, onesec_bytesize; + int init_bytesize, curr_bytesize, total_bytesize; + int DataDone, LogDone, ForkDone, UpdatesDone, WidgetDone; + int WgetStatus; + + int gw, gh; + Group *group; + ProgressBar *prBar; + HighlightButton *prButton; + Widget *prTitle, *prGot, *prSize, *prRate, *pr_Rate, *prETA, *prETAt; + +public: + DLItem(const char *full_filename, const char *url, DLAction action); + ~DLItem(); + void child_init(); + void father_init(); + void update_size(int new_sz); + void log_text_add(char *buf, ssize_t st); + void log_text_show(); + void abort_dl(); + void prButton_cb(); + pid_t pid() { return mPid; } + void pid(pid_t p) { mPid = p; } + void child_finished(int status); + void status_msg(char *msg) { prBar->message(msg); } + Widget *get_widget() { return group; } + int widget_done() { return WidgetDone; } + void widget_done(int val) { WidgetDone = val; } + int updates_done() { return UpdatesDone; } + void updates_done(int val) { UpdatesDone = val; } + int fork_done() { return ForkDone; } + void fork_done(int val) { ForkDone = val; } + int log_done() { return LogDone; } + void log_done(int val) { LogDone = val; } + int wget_status() { return WgetStatus; } + void wget_status(int val) { WgetStatus = val; } + void update_prSize(int newsize); + void update(); +}; + +// DLItem List --------------------------------------------------------------- + +/// BUG: make dynamic +class DLItemList { + DLItem *mList[32]; + int mNum, mMax; + +public: + DLItemList() { mNum = 0; mMax = 32; } + ~DLItemList() { } + int num() { return mNum; } + void add(DLItem *i) { if (mNum < mMax) mList[mNum++] = i; } + DLItem *get(int n) { return (n >= 0 && n < mNum) ? mList[n] : NULL; } + void del(int n) { if (n >= 0 && n < mNum) mList[n] = mList[--mNum]; } +}; + +// DLWin --------------------------------------------------------------------- + +class DLWin { + DLItemList *mDList; + Window *mWin; + ScrollGroup *mScroll; + PackedGroup *mPG; + +public: + DLWin(int ww, int wh); + void add(const char *full_filename, const char *url, DLAction action); + void del(int n_item); + int num(); + int num_running(); + void listen(int req_fd); + void show() { mWin->show(); } + void hide() { mWin->hide(); } + void abort_all(); + DLAction check_filename(char **p_dl_dest); +}; + + +/* + * Global variables + */ + +// SIGCHLD mask +sigset_t mask_sigchld; + +// SIGCHLD flag +volatile sig_atomic_t caught_sigchld = 0; + +// The download window object +static class DLWin *dl_win = NULL; + + + +// ProgressBar widget -------------------------------------------------------- + +void ProgressBar::move(double step) +{ + mPresent += step; + if (mPresent > mMax) + mPresent = mMin; + redraw(); +} + +ProgressBar::ProgressBar(int x, int y, int w, int h, const char *lbl) +: Widget(x, y, w, h, lbl) +{ + mMin = mPresent = 0; + mMax = 100; + mShowPct = true; + mShowMsg = false; + box(DOWN_BOX); + selection_color(BLUE); + color(WHITE); + textcolor(RED); +} + +void ProgressBar::draw() +{ + drawstyle(style(), flags()); + if (damage() & DAMAGE_ALL) + draw_box(); + Rectangle r(w(), h()); + box()->inset(r); + if (mPresent > mMax) + mPresent = mMax; + if (mPresent < mMin) + mPresent = mMin; + double pct = (mPresent - mMin) / mMax; + + if (vertical()) { + int barHeight = int (r.h() * pct + .5); + r.y(r.y() + r.h() - barHeight); + r.h(barHeight); + } else { + r.w(int (r.w() * pct + .5)); + } + + setcolor(selection_color()); + + if (mShowPct) { + fillrect(r); + } else { + Rectangle r2(int (r.w() * pct), 0, int (w() * .1), h()); + push_clip(r2); + fillrect(r); + pop_clip(); + } + + if (mShowMsg) { + setcolor(textcolor()); + setfont(this->labelfont(), this->labelsize()); + drawtext(mMsg, Rectangle(w(), h()), ALIGN_CENTER); + } else if (mShowPct) { + char buffer[30]; + sprintf(buffer, "%d%%", int (pct * 100 + .5)); + setcolor(textcolor()); + setfont(this->labelfont(), this->labelsize()); + drawtext(buffer, Rectangle(w(), h()), ALIGN_CENTER); + } +} + + +// Download-item class ------------------------------------------------------- + +static void prButton_scb(Widget *, void *cb_data) +{ + DLItem *i = (DLItem *)cb_data; + + i->prButton_cb(); +} + +DLItem::DLItem(const char *full_filename, const char *url, DLAction action) +{ + struct stat ss; + char *p, *esc_url; + + if (pipe(LogPipe) < 0) { + MSG("pipe, %s\n", strerror(errno)); + return; + } + /* Set FD to background */ + fcntl(LogPipe[0], F_SETFL, + O_NONBLOCK | fcntl(LogPipe[0], F_GETFL)); + + fullname = strdup(full_filename); + p = strrchr(fullname, '/'); + shortname = (p) ? strdup(p + 1) : strdup("??"); + p = strrchr(full_filename, '/'); + target_dir= p ? dStrndup(full_filename,p-full_filename+1) : dStrdup("??"); + + log_len = 0; + log_max = 0; + log_state = ST_newline; + log_text = NULL; + onesec_bytesize = twosec_bytesize = curr_bytesize = init_bytesize = 0; + total_bytesize = -1; + + // Init value. Reset later, upon the first data bytes arrival + init_time = time(NULL); + + // BUG:? test a URL with ' inside. + /* escape "'" character for the shell. Is it necessary? */ + esc_url = Escape_uri_str(url, "'"); + /* avoid malicious SMTP relaying with FTP urls */ + if (dStrncasecmp(esc_url, "ftp:/", 5) == 0) + Filter_smtp_hack(esc_url); + dl_argv = new char*[8]; + int i = 0; + dl_argv[i++] = "wget"; + if (action == DL_CONTINUE) { + if (stat(fullname, &ss) == 0) + init_bytesize = (int)ss.st_size; + dl_argv[i++] = "-c"; + } + dl_argv[i++] = "--load-cookies"; + dl_argv[i++] = dStrconcat(dGethomedir(), "/.dillo/cookies.txt", NULL); + dl_argv[i++] = "-O"; + dl_argv[i++] = fullname; + dl_argv[i++] = esc_url; + dl_argv[i++] = NULL; + + DataDone = 0; + LogDone = 0; + UpdatesDone = 0; + ForkDone = 0; + WidgetDone = 0; + WgetStatus = -1; + + gw = 470, gh = 70; + group = new Group(0,0,gw,gh); + group->begin(); + prTitle = new Widget(24, 7, 290, 23, shortname); + prTitle->box(RSHADOW_BOX); + prTitle->align(ALIGN_LEFT|ALIGN_INSIDE|ALIGN_CLIP); + // Attach this 'log_text' to the tooltip + log_text_add("Target File: ", 13); + log_text_add(fullname, strlen(fullname)); + log_text_add("\n\n", 2); + + prBar = new ProgressBar(24, 40, 92, 20); + prBar->box(BORDER_BOX); // ENGRAVED_BOX + prBar->tooltip("Progress Status"); + + int ix = 122, iy = 36, iw = 50, ih = 14; + Widget *o = new Widget(ix,iy,iw,ih, "Got"); + o->box(RFLAT_BOX); + o->color((Color)0xc0c0c000); + o->tooltip("Downloaded Size"); + prGot = new Widget(ix,iy+14,iw,ih, "0KB"); + prGot->align(ALIGN_CENTER|ALIGN_INSIDE); + prGot->labelcolor((Color)0x6c6cbd00); + prGot->box(NO_BOX); + + ix += iw; + o = new Widget(ix,iy,iw,ih, "Size"); + o->box(RFLAT_BOX); + o->color((Color)0xc0c0c000); + o->tooltip("Total Size"); + prSize = new Widget(ix,iy+14,iw,ih, "??"); + prSize->align(ALIGN_CENTER|ALIGN_INSIDE); + prSize->box(NO_BOX); + + ix += iw; + o = new Widget(ix,iy,iw,ih, "Rate"); + o->box(RFLAT_BOX); + o->color((Color)0xc0c0c000); + o->tooltip("Current transfer Rate (KBytes/sec)"); + prRate = new Widget(ix,iy+14,iw,ih, "??"); + prRate->align(ALIGN_CENTER|ALIGN_INSIDE); + prRate->box(NO_BOX); + + ix += iw; + o = new Widget(ix,iy,iw,ih, "~Rate"); + o->box(RFLAT_BOX); + o->color((Color)0xc0c0c000); + o->tooltip("Average transfer Rate (KBytes/sec)"); + pr_Rate = new Widget(ix,iy+14,iw,ih, "??"); + pr_Rate->align(ALIGN_CENTER|ALIGN_INSIDE); + pr_Rate->box(NO_BOX); + + ix += iw; + prETAt = o = new Widget(ix,iy,iw,ih, "ETA"); + o->box(RFLAT_BOX); + o->color((Color)0xc0c0c000); + o->tooltip("Estimated Time of Arrival"); + prETA = new Widget(ix,iy+14,iw,ih, "??"); + prETA->align(ALIGN_CENTER|ALIGN_INSIDE); + prETA->box(NO_BOX); + + //ix += 50; + //prButton = new HighlightButton(ix, 41, 38, 19, "Stop"); + prButton = new HighlightButton(328, 9, 38, 19, "Stop"); + prButton->tooltip("Stop this transfer"); + prButton->box(UP_BOX); + prButton->clear_tab_to_focus(); + prButton->callback(prButton_scb, this); + + //group->resizable(group); + group->box(ROUND_UP_BOX); + group->end(); +} + +DLItem::~DLItem() +{ + free(shortname); + dFree(fullname); + dFree(target_dir); + free(log_text); + int idx = (strcmp(dl_argv[1], "-c")) ? 2 : 3; + dFree(dl_argv[idx]); + dFree(dl_argv[idx+3]); + delete(dl_argv); + + delete(group); +} + +/* + * Abort a running download + */ +void DLItem::abort_dl() +{ + if (!log_done()) { + close(LogPipe[0]); + remove_fd(LogPipe[0]); + log_done(1); + // Stop wget + if (!fork_done()) + kill(pid(), SIGTERM); + } + widget_done(1); +} + +void DLItem::prButton_cb() +{ + prButton->deactivate(); + abort_dl(); +} + +void DLItem::child_init() +{ + close(0); // stdin + close(1); // stdout + close(LogPipe[0]); + dup2(LogPipe[1], 2); // stderr + // set the locale to C for log parsing + setenv("LC_ALL", "C", 1); + // start wget + execvp(dl_argv[0], dl_argv); +} + +/* + * Update displayed size + */ +void DLItem::update_prSize(int newsize) +{ + char num[64]; + + if (newsize > 1024 * 1024) + snprintf(num, 64, "%.1fMB", (float)newsize / (1024*1024)); + else + snprintf(num, 64, "%.0fKB", (float)newsize / 1024); + prSize->copy_label(num); + prSize->redraw_label(); +} + +void DLItem::log_text_add(char *buf, ssize_t st) +{ + char *p, *q, *d, num[64]; + + // Make room... + if (log_len + st >= log_max) { + log_max = log_len + st + 1024; + log_text = (char *) realloc (log_text, log_max); + log_text[log_len] = 0; + prTitle->tooltip(log_text); + } + + // FSM to remove wget's "dot-progress" (i.e. "^ " || "^[0-9]+K") + q = log_text + log_len; + for (p = buf; (p - buf) < st; ++p) { + switch (log_state) { + case ST_newline: + if (*p == ' ') { + log_state = ST_discard; + } else if (isdigit(*p)) { + *q++ = *p; log_state = ST_number; + } else if (*p == '\n') { + *q++ = *p; + } else { + *q++ = *p; log_state = ST_copy; + } + break; + case ST_number: + if (isdigit(*q++ = *p)) { + // keep here + } else if (*p == 'K') { + for(--q; isdigit(q[-1]); --q); log_state = ST_discard; + } else { + log_state = ST_copy; + } + break; + case ST_discard: + if (*p == '\n') + log_state = ST_newline; + break; + case ST_copy: + if ((*q++ = *p) == '\n') + log_state = ST_newline; + break; + } + } + *q = 0; + log_len = strlen(log_text); + + // Now scan for the length of the file + if (total_bytesize == -1) { + p = strstr(log_text, "\nLength: "); + if (p && isdigit(p[9]) && strchr(p + 9, ' ')) { + for (p += 9, d = &num[0]; *p != ' '; ++p) + if (isdigit(*p)) + *d++ = *p; + *d = 0; + total_bytesize = strtol (num, NULL, 10); + // Update displayed size + update_prSize(total_bytesize); + } + } + + // Show we're connecting... + if (curr_bytesize == 0) { + prTitle->label("Connecting..."); + prTitle->redraw_label(); + } +} + +/// +void DLItem::log_text_show() +{ + MSG("\nStored Log:\n%s", log_text); +} + +void DLItem::update_size(int new_sz) +{ + char buf[64]; + + if (curr_bytesize == 0 && new_sz) { + // Start the timer with the first bytes got + init_time = time(NULL); + // Update the title + prTitle->label(shortname); + prTitle->redraw_label(); + } + + curr_bytesize = new_sz; + if (curr_bytesize > 1024 * 1024) + snprintf(buf, 64, "%.1fMB", (float)curr_bytesize / (1024*1024)); + else + snprintf(buf, 64, "%.0fKB", (float)curr_bytesize / 1024); + prGot->copy_label(buf); + prGot->redraw_label(); + if (total_bytesize == -1) { + prBar->showtext(false); + prBar->move(1); + } else { + prBar->showtext(true); + double pos = 100.0 * (double)curr_bytesize / total_bytesize; + prBar->position(pos); + } +} + +static void read_log_cb(int fd_in, void *data) +{ + DLItem *dl_item = (DLItem *)data; + int BufLen = 4096; + char Buf[BufLen]; + ssize_t st; + int ret = -1; + + do { + st = read(fd_in, Buf, BufLen); + if (st < 0) { + if (errno == EAGAIN) { + ret = 1; + break; + } + perror("read, "); + break; + } else if (st == 0) { + close(fd_in); + remove_fd(fd_in, 1); + dl_item->log_done(1); + ret = 0; + break; + } else { + dl_item->log_text_add(Buf, st); + } + } while (1); +} + +void DLItem::father_init() +{ + close(LogPipe[1]); + add_fd(LogPipe[0], 1, read_log_cb, this); // Read + + // Start the timer after the child is running. + // (this makes a big difference with wget) + //init_time = time(NULL); +} + +/* + * Our wget exited, let's check its status and update the panel. + */ +void DLItem::child_finished(int status) +{ + wget_status(status); + + if (status == 0) { + prButton->label("Done"); + prButton->tooltip("Close this information panel"); + } else { + prButton->label("Close"); + prButton->tooltip("Close this information panel"); + status_msg("ABORTED"); + if (curr_bytesize == 0) { + // Update the title + prTitle->label(shortname); + prTitle->redraw_label(); + } + } + prButton->activate(); + prButton->redraw(); + MSG("wget status %d\n", status); +} + +/* + * Convert seconds into human readable [hour]:[min]:[sec] string. + */ +void secs2timestr(int et, char *str) +{ + int eh, em, es; + + eh = et / 3600; em = (et % 3600) / 60; es = et % 60; + if (eh == 0) { + if (em == 0) + snprintf(str, 8, "%ds", es); + else + snprintf(str, 8, "%dm%ds", em, es); + } else { + snprintf(str, 8, "%dh%dm", eh, em); + } +} + +/* + * Update Got, Rate, ~Rate and ETA + */ +void DLItem::update() +{ + struct stat ss; + time_t curr_time; + float csec, tsec, rate, _rate = 0; + char str[64]; + int et; + + if (updates_done()) + return; + + /* Update curr_size */ + if (stat(fullname, &ss) == -1) { + MSG("stat, %s\n", strerror(errno)); + return; + } + update_size((int)ss.st_size); + + /* Get current time */ + time(&curr_time); + csec = (float) (curr_time - init_time); + + /* Rate */ + if (csec >= 2) { + tsec = (float) (curr_time - twosec_time); + rate = ((float)(curr_bytesize-twosec_bytesize) / 1024) / tsec; + snprintf(str, 64, (rate < 100) ? "%.1fK/s" : "%.0fK/s", rate); + prRate->copy_label(str); + prRate->redraw_label(); + } + /* ~Rate */ + if (csec >= 1) { + _rate = ((float)(curr_bytesize-init_bytesize) / 1024) / csec; + snprintf(str, 64, (_rate < 100) ? "%.1fK/s" : "%.0fK/s", _rate); + pr_Rate->copy_label(str); + pr_Rate->redraw_label(); + } + + /* ETA */ + if (fork_done()) { + updates_done(1); // Last update + prETAt->label("Time"); + prETAt->tooltip("Download Time"); + prETAt->redraw(); + secs2timestr((int)csec, str); + prETA->copy_label(str); + if (total_bytesize == -1) { + update_prSize(curr_bytesize); + if (wget_status() == 0) + status_msg("Done"); + } + } else { + if (_rate > 0 && total_bytesize > 0 && curr_bytesize > 0) { + et = (int)((total_bytesize-curr_bytesize) / (_rate * 1024)); + secs2timestr(et, str); + prETA->copy_label(str); + } + } + prETA->redraw_label(); + + /* Update one and two secs ago times and bytesizes */ + twosec_time = onesec_time; + onesec_time = curr_time; + twosec_bytesize = onesec_bytesize; + onesec_bytesize = curr_bytesize; +} + +// SIGCHLD ------------------------------------------------------------------- + +/*! SIGCHLD handler + */ +void raw_sigchld(int) +{ + caught_sigchld = 1; +} + +/*! Establish SIGCHLD handler */ +void est_sigchld(void) +{ + struct sigaction sigact; + sigset_t set; + + (void) sigemptyset(&set); + sigact.sa_handler = raw_sigchld; + sigact.sa_mask = set; + sigact.sa_flags = SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sigact, NULL) == -1) { + perror("sigaction"); + exit(1); + } +} + +/* + * Timeout function to check wget's exit status. + */ +void cleanup_cb(void *data) +{ + DLItemList *list = (DLItemList *)data; + + sigprocmask(SIG_BLOCK, &mask_sigchld, NULL); + if (caught_sigchld) { + /* Handle SIGCHLD */ + int i, status; + for (i = 0; i < list->num(); ++i) { + if (!list->get(i)->fork_done() && + waitpid(list->get(i)->pid(), &status, WNOHANG) > 0) { + list->get(i)->child_finished(status); + list->get(i)->fork_done(1); + } + } + caught_sigchld = 0; + } + sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL); + + repeat_timeout(1.0,cleanup_cb,data); +} + +/* + * Timeout function to update the widget indicators, + * also remove widgets marked "done". + */ +void update_cb(void *data) +{ + static int cb_used = 0; + + DLItemList *list = (DLItemList *)data; + + /* Update the widgets and remove the ones marked as done */ + for (int i = 0; i < list->num(); ++i) { + if (!list->get(i)->widget_done()) { + list->get(i)->update(); + } else if (list->get(i)->fork_done()) { + // widget_done and fork_done avoid a race condition. + dl_win->del(i); --i; + } + cb_used = 1; + } + + if (cb_used && list->num() == 0) + exit(0); + + repeat_timeout(1.0,update_cb,data); +} + + +// DLWin --------------------------------------------------------------------- + +/* + * Read a single line from a socket and store it in a Dstr. + */ +static ssize_t readline(int socket, Dstr ** msg) +{ + ssize_t st; + char buf[16384]; + + /* can't use fread() */ + do + st = read(socket, buf, 16384); + while (st < 0 && errno == EINTR); + + if (st == -1) + MSG("readline, %s\n", strerror(errno)); + + dStr_truncate(*msg, 0); + if (st > 0) + dStr_append_l(*msg, buf, (int)st); + + return st; +} + +/* + * Make a new name and place it in 'dl_dest'. + */ +static void make_new_name(char **dl_dest, const char *url) +{ + Dstr *gstr = dStr_new(*dl_dest); + int idx = gstr->len; + + if (gstr->str[idx - 1] != '/'){ + dStr_append_c(gstr, '/'); + ++idx; + } + + /* Use a mangled url as name */ + dStr_append(gstr, url); + for ( ; idx < gstr->len; ++idx) + if (!isalnum(gstr->str[idx])) + gstr->str[idx] = '_'; + + /* free memory */ + dFree(*dl_dest); + *dl_dest = gstr->str; + dStr_free(gstr, FALSE); +} + +/* + * Callback function for the request socket. + * Read the request, parse and start a new download. + */ +static void read_req_cb(int req_fd, void *) +{ + Dstr *tag; + struct sockaddr_un clnt_addr; + int new_socket; + socklen_t csz; + struct stat sb; + char *cmd = NULL, *url = NULL, *dl_dest = NULL; + DLAction action = DL_ABORT; /* compiler happiness */ + + /* Initialize the value-result parameter */ + csz = sizeof(struct sockaddr_un); + /* accept the request */ + do { + new_socket = accept(req_fd, (struct sockaddr *) &clnt_addr, &csz); + } while (new_socket == -1 && errno == EINTR); + if (new_socket == -1) { + MSG("accept, %s fd=%d\n", strerror(errno), req_fd); + return; + } + + //sigprocmask(SIG_BLOCK, &blockSC, NULL); + tag = dStr_sized_new(64); + readline(new_socket, &tag); + close(new_socket); + _MSG("Received tag={%s}\n", tag->str); + + if ((cmd = a_Dpip_get_attr(tag->str, (size_t)tag->len, "cmd")) == NULL) { + MSG("Failed to parse 'cmd' in %s\n", tag->str); + goto end; + } + if (strcmp(cmd, "DpiBye") == 0) { + MSG("got DpiBye, ignoring...\n"); + goto end; + } + if (strcmp(cmd, "download") != 0) { + MSG("unknown command: '%s'. Aborting.\n", cmd); + goto end; + } + if (!(url = a_Dpip_get_attr(tag->str,(size_t)tag->len, "url"))){ + MSG("Failed to parse 'url' in %s\n", tag->str); + goto end; + } + if (!(dl_dest = a_Dpip_get_attr(tag->str,(size_t)tag->len,"destination"))){ + MSG("Failed to parse 'destination' in %s\n", tag->str); + goto end; + } + /* 'dl_dest' may be a directory */ + if (stat(dl_dest, &sb) == 0 && S_ISDIR(sb.st_mode)) { + make_new_name(&dl_dest, url); + } + action = dl_win->check_filename(&dl_dest); + if (action != DL_ABORT) { + // Start the whole thing whithin FLTK. + dl_win->add(dl_dest, url, action); + } else if (dl_win->num() == 0) { + exit(0); + } + +end: + dFree(cmd); + dFree(url); + dFree(dl_dest); + dStr_free(tag, TRUE); +} + +/* + * Callback for close window request (WM or EscapeKey press) + */ +static void dlwin_esc_cb(Widget *, void *) +{ + char *msg = "There are running downloads.\n" + "ABORT them and EXIT anyway?"; + + if (dl_win && dl_win->num_running() > 0) { + int ch = fltk::choice(msg, "Yes", "*No", "Cancel"); + if (ch != 0) + return; + } + + /* abort each download properly */ + dl_win->abort_all(); +} + +/* + * Add a new download request to the main window and + * fork a child to do the job. + */ +void DLWin::add(const char *full_filename, const char *url, DLAction action) +{ + DLItem *dl_item = new DLItem(full_filename, url, action); + mDList->add(dl_item); + //mPG->add(*dl_item->get_widget()); + mPG->insert(*dl_item->get_widget(), 0); + + _MSG("Child index = %d\n", mPG->find(dl_item->get_widget())); + + // Start the child process + pid_t f_pid = fork(); + if (f_pid == 0) { + /* child */ + dl_item->child_init(); + _exit(EXIT_FAILURE); + } else if (f_pid < 0) { + perror("fork, "); + exit(1); + } else { + /* father */ + dl_win->show(); + dl_item->pid(f_pid); + dl_item->father_init(); + } +} + +/* + * Decide what to do when the filename already exists. + * (renaming takes place here when necessary) + */ +DLAction DLWin::check_filename(char **p_fullname) +{ + struct stat ss; + Dstr *ds; + int ch; + DLAction ret = DL_ABORT; + + if (stat(*p_fullname, &ss) == -1) + return DL_NEWFILE; + + ds = dStr_sized_new(128); + dStr_sprintf(ds, + "The file:\n %s (%d Bytes)\nalready exists. What do we do?", + *p_fullname, (int)ss.st_size); + ch = fltk::choice(ds->str, "Rename", "Continue", "Abort"); + dStr_free(ds, 1); + MSG("Choice %d\n", ch); + if (ch == 0) { + const char *p; + p = fltk::file_chooser("Enter a new name:", NULL, *p_fullname); + if (p) { + dFree(*p_fullname); + *p_fullname = dStrdup(p); + ret = check_filename(p_fullname); + } + } else if (ch == 1) { + ret = DL_CONTINUE; + } + return ret; +} + +/* + * Add a new download request to the main window and + * fork a child to do the job. + */ +void DLWin::del(int n_item) +{ + DLItem *dl_item = mDList->get(n_item); + + // Remove the widget from the scroll group + mPG->remove(dl_item->get_widget()); + // Resize the scroll group + mPG->resize(mWin->w(), 1); + + mDList->del(n_item); + delete(dl_item); +} + +/* + * Return number of entries + */ +int DLWin::num() +{ + return mDList->num(); +} + +/* + * Return number of running downloads + */ +int DLWin::num_running() +{ + int i, nr; + + for (i = nr = 0; i < mDList->num(); ++i) + if (!mDList->get(i)->fork_done()) + ++nr; + return nr; +} + +/* + * Set a callback function for the request socket + */ +void DLWin::listen(int req_fd) +{ + add_fd(req_fd, 1, read_req_cb, NULL); // Read +} + +/* + * Abort each download properly, and let the main cycle exit + */ +void DLWin::abort_all() +{ + for (int i = 0; i < mDList->num(); ++i) + mDList->get(i)->abort_dl(); +} + +/* + * Create the main window and an empty list of requests. + */ +DLWin::DLWin(int ww, int wh) { + + // Init an empty list for the download requests + mDList = new DLItemList(); + + // Create the empty main window + mWin = new Window(ww, wh, "Downloads:"); + mWin->begin(); + mScroll = new ScrollGroup(0,0,ww,wh); + mScroll->begin(); + mPG = new PackedGroup(0,0,ww,wh); + mPG->end(); + //mPG->spacing(10); + mScroll->end(); + mWin->resizable(mWin); + mWin->end(); + mWin->callback(dlwin_esc_cb, NULL); + mWin->show(); + + // Set SIGCHLD handlers + sigemptyset(&mask_sigchld); + sigaddset(&mask_sigchld, SIGCHLD); + est_sigchld(); + + // Set the cleanup timeout + add_timeout(1.0, cleanup_cb, mDList); + // Set the update timeout + add_timeout(1.0, update_cb, mDList); +} + + +// --------------------------------------------------------------------------- + + + +//int main(int argc, char **argv) +int main() +{ + int ww = 420, wh = 85; + + lock(); + + // Create the download window + dl_win = new DLWin(ww, wh); + + // Start listening to the request socket + dl_win->listen(STDIN_FILENO); + + MSG("started...\n"); + + return run(); +} + diff --git a/dpi/dpiutil.c b/dpi/dpiutil.c new file mode 100644 index 00000000..c6622850 --- /dev/null +++ b/dpi/dpiutil.c @@ -0,0 +1,270 @@ +/* + * File: dpiutil.c + * + * Copyright 2004 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + */ + +#include <unistd.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <sys/socket.h> + +#include "dpiutil.h" + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[dpiutil.c]: " __VA_ARGS__) + + +/* Escaping/De-escaping ---------------------------------------------------*/ + +/* + * Escape URI characters in 'esc_set' as %XX sequences. + * Return value: New escaped string. + */ +char *Escape_uri_str(const char *str, char *p_esc_set) +{ + static const char *hex = "0123456789ABCDEF"; + char *p, *esc_set; + Dstr *dstr; + int i; + + esc_set = (p_esc_set) ? p_esc_set : "%#:' "; + dstr = dStr_sized_new(64); + for (i = 0; str[i]; ++i) { + if (str[i] <= 0x1F || str[i] == 0x7F || strchr(esc_set, str[i])) { + dStr_append_c(dstr, '%'); + dStr_append_c(dstr, hex[(str[i] >> 4) & 15]); + dStr_append_c(dstr, hex[str[i] & 15]); + } else { + dStr_append_c(dstr, str[i]); + } + } + p = dstr->str; + dStr_free(dstr, FALSE); + + return p; +} + +static const char *unsafe_chars = "&<>\"'"; +static const char *unsafe_rep[] = + { "&", "<", ">", """, "'" }; +static const int unsafe_rep_len[] = { 5, 4, 4, 6, 5 }; + +/* + * Escape unsafe characters as html entities. + * Return value: New escaped string. + */ +char *Escape_html_str(const char *str) +{ + int i; + char *p; + Dstr *dstr = dStr_sized_new(64); + + for (i = 0; str[i]; ++i) { + if ((p = strchr(unsafe_chars, str[i]))) + dStr_append(dstr, unsafe_rep[p - unsafe_chars]); + else + dStr_append_c(dstr, str[i]); + } + p = dstr->str; + dStr_free(dstr, FALSE); + + return p; +} + +/* + * Unescape a few HTML entities (inverse of Escape_html_str) + * Return value: New unescaped string. + */ +char *Unescape_html_str(const char *str) +{ + int i, j, k; + char *u_str = dStrdup(str); + + if (!strchr(str, '&')) + return u_str; + + for (i = 0, j = 0; str[i]; ++i) { + if (str[i] == '&') { + for (k = 0; k < 5; ++k) { + if (!dStrncasecmp(str + i, unsafe_rep[k], unsafe_rep_len[k])) { + i += unsafe_rep_len[k] - 1; + break; + } + } + u_str[j++] = (k < 5) ? unsafe_chars[k] : str[i]; + } else { + u_str[j++] = str[i]; + } + } + u_str[j] = 0; + + return u_str; +} + +/* + * Filter '\n', '\r', "%0D" and "%0A" from the authority part of an FTP url. + * This helps to avoid a SMTP relaying hack. This filtering could be done + * only when port == 25, but if the mail server is listening on another + * port it wouldn't work. + * Note: AFAIS this should be done by wget. + */ +char *Filter_smtp_hack(char *url) +{ + int i; + char c; + + if (strlen(url) > 6) { /* ftp:// */ + for (i = 6; (c = url[i]) && c != '/'; ++i) { + if (c == '\n' || c == '\r') { + memmove(url + i, url + i + 1, strlen(url + i)); + --i; + } else if (c == '%' && url[i+1] == '0' && + (tolower(url[i+2]) == 'a' || tolower(url[i+2]) == 'd')) { + memmove(url + i, url + i + 3, strlen(url + i + 2)); + --i; + } + } + } + return url; +} + + +/* Streamed Sockets API (not mandatory) ----------------------------------*/ + +/* + * Create and initialize the SockHandler structure + */ +SockHandler *sock_handler_new(int fd_in, int fd_out, int flush_sz) +{ + SockHandler *sh = dNew(SockHandler, 1); + + /* init descriptors and streams */ + sh->fd_in = fd_in; + sh->fd_out = fd_out; + sh->out = fdopen(fd_out, "w"); + + /* init buffer */ + sh->buf_max = 8 * 1024; + sh->buf = dNew(char, sh->buf_max); + sh->buf_sz = 0; + sh->flush_sz = flush_sz; + + return sh; +} + +/* + * Streamed write to socket + * Return: 0 on success, 1 on error. + */ +int sock_handler_write(SockHandler *sh, int flush, + const char *Data, size_t DataSize) +{ + int retval = 1; + + /* append to buf */ + while (sh->buf_max < sh->buf_sz + DataSize) { + sh->buf_max <<= 1; + sh->buf = dRealloc(sh->buf, sh->buf_max); + } + memcpy(sh->buf + sh->buf_sz, Data, DataSize); + sh->buf_sz += DataSize; +/* + MSG("sh->buf=%p, sh->buf_sz=%d, sh->buf_max=%d, sh->flush_sz=%d\n", + sh->buf, sh->buf_sz, sh->buf_max, sh->flush_sz); +*/ +/**/ +#if 0 +{ + uint_t i; + /* Test dpip's stream handling by chopping data into characters */ + for (i = 0; i < sh->buf_sz; ++i) { + fputc(sh->buf[i], sh->out); + fflush(sh->out); + usleep(50); + } + if (i == sh->buf_sz) { + sh->buf_sz = 0; + retval = 0; + } +} +#else + /* flush data if necessary */ + if (flush || sh->buf_sz >= sh->flush_sz) { + if (sh->buf_sz && fwrite (sh->buf, sh->buf_sz, 1, sh->out) != 1) { + perror("[sock_handler_write]"); + } else { + fflush(sh->out); + sh->buf_sz = 0; + retval = 0; + } + + } else { + retval = 0; + } +#endif + return retval; +} + +/* + * Convenience function. + */ +int sock_handler_write_str(SockHandler *sh, int flush, const char *str) +{ + return sock_handler_write(sh, flush, str, strlen(str)); +} + +/* + * Return a newlly allocated string with the contents read from the socket. + */ +char *sock_handler_read(SockHandler *sh) +{ + ssize_t st; + char buf[16384]; + + /* can't use fread() */ + do + st = read(sh->fd_in, buf, 16384); + while (st < 0 && errno == EINTR); + + if (st == -1) + perror("[sock_handler_read]"); + + return (st > 0) ? dStrndup(buf, (uint_t)st) : NULL; +} + +/* + * Close this socket for reading and writing. + */ +void sock_handler_close(SockHandler *sh) +{ + /* flush before closing */ + sock_handler_write(sh, 1, "", 0); + + fclose(sh->out); + close(sh->fd_out); +} + +/* + * Free the SockHandler structure + */ +void sock_handler_free(SockHandler *sh) +{ + dFree(sh->buf); + dFree(sh); +} + +/* ------------------------------------------------------------------------ */ + diff --git a/dpi/dpiutil.h b/dpi/dpiutil.h new file mode 100644 index 00000000..b8efbb28 --- /dev/null +++ b/dpi/dpiutil.h @@ -0,0 +1,97 @@ +/* + * File: dpiutil.h + * + * Copyright 2004-2005 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +/* + * This file contains common functions used by dpi programs. + * (i.e. a convenience library). + */ + +#ifndef __DPIUTIL_H__ +#define __DPIUTIL_H__ + +#include <stdio.h> +#include "d_size.h" +#include "../dlib/dlib.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define BUFLEN 256 +#define TOUT 300 + + +/* Streamed Sockets API (not mandatory) ----------------------------------*/ + +typedef struct _SockHandler SockHandler; +struct _SockHandler { + int fd_in; + int fd_out; + /* FILE *in; --Unused. The stream functions block when reading. */ + FILE *out; + + char *buf; /* internal buffer */ + uint_t buf_sz; /* data size */ + uint_t buf_max; /* allocated size */ + uint_t flush_sz; /* max size before flush */ +}; + +SockHandler *sock_handler_new(int fd_in, int fd_out, int flush_sz); +int sock_handler_write(SockHandler *sh, int flush, + const char *Data,size_t DataSize); +int sock_handler_write_str(SockHandler *sh, int flush, const char *str); +char *sock_handler_read(SockHandler *sh); +void sock_handler_close(SockHandler *sh); +void sock_handler_free(SockHandler *sh); + +#define sock_handler_printf(sh, flush, ...) \ + D_STMT_START { \ + Dstr *dstr = dStr_sized_new(128); \ + dStr_sprintf(dstr, __VA_ARGS__); \ + sock_handler_write(sh, flush, dstr->str, dstr->len); \ + dStr_free(dstr, 1); \ + } D_STMT_END + +/* ----------------------------------------------------------------------- */ + +/* + * Escape URI characters in 'esc_set' as %XX sequences. + * Return value: New escaped string. + */ +char *Escape_uri_str(const char *str, char *p_esc_set); + +/* + * Escape unsafe characters as html entities. + * Return value: New escaped string. + */ +char *Escape_html_str(const char *str); + +/* + * Unescape a few HTML entities (inverse of Escape_html_str) + * Return value: New unescaped string. + */ +char *Unescape_html_str(const char *str); + +/* + * Filter an SMTP hack with a FTP URI + */ +char *Filter_smtp_hack(char *url); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __DPIUTIL_H__ */ + diff --git a/dpi/file.c b/dpi/file.c new file mode 100644 index 00000000..e1547c66 --- /dev/null +++ b/dpi/file.c @@ -0,0 +1,975 @@ +/* + * File: file.c :) + * + * Copyright (C) 2000 - 2004 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * Directory scanning is no longer streamed, but it gets sorted instead! + * Directory entries on top, files next. + * With new HTML layout. + */ + +#include <pthread.h> + +#include <ctype.h> /* for tolower */ +#include <errno.h> /* for errno */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/un.h> +#include <dirent.h> +#include <fcntl.h> +#include <time.h> +#include <signal.h> + +#include "../dpip/dpip.h" +#include "dpiutil.h" +#include "d_size.h" + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[file dpi]: " __VA_ARGS__) + + +#define MAXNAMESIZE 30 +#define HIDE_DOTFILES TRUE + +enum { + FILE_OK, + FILE_NOT_FOUND, + FILE_NO_ACCESS +}; + +typedef struct { + char *full_path; + const char *filename; + off_t size; + mode_t mode; + time_t mtime; +} FileInfo; + +typedef struct { + char *dirname; + Dlist *flist; /* List of files and subdirectories (for sorting) */ +} DilloDir; + +typedef struct { + SockHandler *sh; + int status; + int old_style; + pthread_t thrID; + int done; +} ClientInfo; + +/* + * Forward references + */ +static const char *File_content_type(const char *filename); +static int File_get_file(ClientInfo *Client, + const char *filename, + struct stat *sb, + const char *orig_url); +static int File_get_dir(ClientInfo *Client, + const char *DirName, + const char *orig_url); + +/* + * Global variables + */ +static volatile int DPIBYE = 0; +static volatile int ThreadRunning = 0; +static int OLD_STYLE = 0; +/* A list for the clients we are serving */ +static Dlist *Clients; +/* a mutex for operations on clients */ +static pthread_mutex_t ClMut; + +/* + * Close a file descriptor, but handling EINTR + */ +static void File_close(int fd) +{ + while (close(fd) < 0 && errno == EINTR) + ; +} + +/* + * Detects 'Content-Type' when the server does not supply one. + * It uses the magic(5) logic from file(1). Currently, it + * only checks the few mime types that Dillo supports. + * + * 'Data' is a pointer to the first bytes of the raw data. + * (this is based on a_Misc_get_content_type_from_data()) + */ +static const char *File_get_content_type_from_data(void *Data, size_t Size) +{ + static const char *Types[] = { + "application/octet-stream", + "text/html", "text/plain", + "image/gif", "image/png", "image/jpeg", + }; + int Type = 0; + char *p = Data; + size_t i, non_ascci; + + _MSG("File_get_content_type_from_data:: Size = %d\n", Size); + + /* HTML try */ + for (i = 0; i < Size && isspace(p[i]); ++i); + if ((Size - i >= 5 && !dStrncasecmp(p+i, "<html", 5)) || + (Size - i >= 5 && !dStrncasecmp(p+i, "<head", 5)) || + (Size - i >= 6 && !dStrncasecmp(p+i, "<title", 6)) || + (Size - i >= 14 && !dStrncasecmp(p+i, "<!doctype html", 14)) || + /* this line is workaround for FTP through the Squid proxy */ + (Size - i >= 17 && !dStrncasecmp(p+i, "<!-- HTML listing", 17))) { + + Type = 1; + + /* Images */ + } else if (Size >= 4 && !dStrncasecmp(p, "GIF8", 4)) { + Type = 3; + } else if (Size >= 4 && !dStrncasecmp(p, "\x89PNG", 4)) { + Type = 4; + } else if (Size >= 2 && !dStrncasecmp(p, "\xff\xd8", 2)) { + /* JPEG has the first 2 bytes set to 0xffd8 in BigEndian - looking + * at the character representation should be machine independent. */ + Type = 5; + + /* Text */ + } else { + /* We'll assume "text/plain" if the set of chars above 127 is <= 10 + * in a 256-bytes sample. Better heuristics are welcomed! :-) */ + non_ascci = 0; + Size = MIN (Size, 256); + for (i = 0; i < Size; i++) + if ((uchar_t) p[i] > 127) + ++non_ascci; + if (Size == 256) { + Type = (non_ascci > 10) ? 0 : 2; + } else { + Type = (non_ascci > 0) ? 0 : 2; + } + } + + return (Types[Type]); +} + +/* + * Compare two FileInfo pointers + * This function is used for sorting directories + */ +static int File_comp(const FileInfo *f1, const FileInfo *f2) +{ + if (S_ISDIR(f1->mode)) { + if (S_ISDIR(f2->mode)) { + return strcmp(f1->filename, f2->filename); + } else { + return -1; + } + } else { + if (S_ISDIR(f2->mode)) { + return 1; + } else { + return strcmp(f1->filename, f2->filename); + } + } +} + +/* + * Allocate a DilloDir structure, set safe values in it and sort the entries. + */ +static DilloDir *File_dillodir_new(char *dirname) +{ + struct stat sb; + struct dirent *de; + DIR *dir; + DilloDir *Ddir; + FileInfo *finfo; + char *fname; + int dirname_len; + + if (!(dir = opendir(dirname))) + return NULL; + + Ddir = dNew(DilloDir, 1); + Ddir->dirname = dStrdup(dirname); + Ddir->flist = dList_new(512); + + dirname_len = strlen(Ddir->dirname); + + /* Scan every name and sort them */ + while ((de = readdir(dir)) != 0) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; /* skip "." and ".." */ + + if (HIDE_DOTFILES) { + /* Don't add hidden files or backup files to the list */ + if (de->d_name[0] == '.' || + de->d_name[0] == '#' || + (de->d_name[0] != '\0' && + de->d_name[strlen(de->d_name) - 1] == '~')) + continue; + } + + fname = dStrconcat(Ddir->dirname, de->d_name, NULL); + if (stat(fname, &sb) == -1) { + dFree(fname); + continue; /* ignore files we can't stat */ + } + + finfo = dNew(FileInfo, 1); + finfo->full_path = fname; + finfo->filename = fname + dirname_len; + finfo->size = sb.st_size; + finfo->mode = sb.st_mode; + finfo->mtime = sb.st_mtime; + + dList_append(Ddir->flist, finfo); + } + + closedir(dir); + + /* sort the entries */ + dList_sort(Ddir->flist, (dCompareFunc)File_comp); + + return Ddir; +} + +/* + * Deallocate a DilloDir structure. + */ +static void File_dillodir_free(DilloDir *Ddir) +{ + int i; + FileInfo *finfo; + + for (i = 0; i < dList_length(Ddir->flist); ++i) { + finfo = dList_nth_data(Ddir->flist, i); + dFree(finfo->full_path); + dFree(finfo); + } + + dList_free(Ddir->flist); + dFree(Ddir->dirname); + dFree(Ddir); +} + +/* + * Output the string for parent directory + */ +static void File_print_parent_dir(ClientInfo *Client, const char *dirname) +{ + if (strcmp(dirname, "/") != 0) { /* Not the root dir */ + char *p, *parent, *HUparent, *Uparent; + + parent = dStrdup(dirname); + /* cut trailing slash */ + parent[strlen(parent) - 1] = '\0'; + /* make 'parent' have the parent dir path */ + if ((p = strrchr(parent, '/'))) + *(p + 1) = '\0'; + + Uparent = Escape_uri_str(parent, NULL); + HUparent = Escape_html_str(Uparent); + sock_handler_printf(Client->sh, 0, + "<a href='file:%s'>Parent directory</a>", HUparent); + dFree(HUparent); + dFree(Uparent); + dFree(parent); + } +} + +/* + * Given a timestamp, output an HTML-formatted date string. + */ +static void File_print_mtime(ClientInfo *Client, time_t mtime) +{ + char *ds = ctime(&mtime); + + /* Month, day and {hour or year} */ + if (Client->old_style) { + sock_handler_printf(Client->sh, 0, " %.3s %.2s", ds + 4, ds + 8); + if (time(NULL) - mtime > 15811200) { + sock_handler_printf(Client->sh, 0, " %.4s", ds + 20); + } else { + sock_handler_printf(Client->sh, 0, " %.5s", ds + 11); + } + } else { + sock_handler_printf(Client->sh, 0, + "<td>%.3s %.2s %.5s", ds + 4, ds + 8, + /* (more than 6 months old) ? year : hour; */ + (time(NULL) - mtime > 15811200) ? ds + 20 : ds + 11); + } +} + +/* + * Return a HTML-line from file info. + */ +static void File_info2html(ClientInfo *Client, + FileInfo *finfo, const char *dirname, int n) +{ + int size; + char *sizeunits; + char namebuf[MAXNAMESIZE + 1]; + char *Uref, *HUref, *Hname; + const char *ref, *filecont, *name = finfo->filename; + + if (finfo->size <= 9999) { + size = finfo->size; + sizeunits = "bytes"; + } else if (finfo->size / 1024 <= 9999) { + size = finfo->size / 1024 + (finfo->size % 1024 >= 1024 / 2); + sizeunits = "Kb"; + } else { + size = finfo->size / 1048576 + (finfo->size % 1048576 >= 1048576 / 2); + sizeunits = "Mb"; + } + + /* we could note if it's a symlink... */ + if S_ISDIR (finfo->mode) { + filecont = "Directory"; + } else if (finfo->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { + filecont = "Executable"; + } else { + filecont = File_content_type(finfo->full_path); + if (!filecont || !strcmp(filecont, "application/octet-stream")) + filecont = "unknown"; + } + + ref = name; + + if (strlen(name) > MAXNAMESIZE) { + memcpy(namebuf, name, MAXNAMESIZE - 3); + strcpy(namebuf + (MAXNAMESIZE - 3), "..."); + name = namebuf; + } + + /* escape problematic filenames */ + Uref = Escape_uri_str(ref, NULL); + HUref = Escape_html_str(Uref); + Hname = Escape_html_str(name); + + if (Client->old_style) { + char *dots = ".. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .."; + int ndots = MAXNAMESIZE - strlen(name); + sock_handler_printf(Client->sh, 0, + "%s<a href='%s'>%s</a>" + " %s" + " %-11s%4d %-5s", + S_ISDIR (finfo->mode) ? ">" : " ", HUref, Hname, + dots + 50 - (ndots > 0 ? ndots : 0), + filecont, size, sizeunits); + + } else { + sock_handler_printf(Client->sh, 0, + "<tr align=center %s><td>%s<td align=left><a href='%s'>%s</a>" + "<td>%s<td>%d %s", + (n & 1) ? "bgcolor=#dcdcdc" : "", + S_ISDIR (finfo->mode) ? ">" : " ", HUref, Hname, + filecont, size, sizeunits); + } + File_print_mtime(Client, finfo->mtime); + sock_handler_write_str(Client->sh, 0, "\n"); + + dFree(Hname); + dFree(HUref); + dFree(Uref); +} + +/* + * Read a local directory and translate it to html. + */ +static void File_transfer_dir(ClientInfo *Client, + DilloDir *Ddir, const char *orig_url) +{ + int n; + char *d_cmd, *Hdirname, *Udirname, *HUdirname; + + /* Send DPI header */ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", orig_url); + sock_handler_write_str(Client->sh, 1, d_cmd); + dFree(d_cmd); + + /* Send page title */ + Udirname = Escape_uri_str(Ddir->dirname, NULL); + HUdirname = Escape_html_str(Udirname); + Hdirname = Escape_html_str(Ddir->dirname); + + sock_handler_printf(Client->sh, 0, + "Content-Type: text/html\n\n" + "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n" + "<HTML>\n<HEAD>\n <BASE href='file:%s'>\n" + " <TITLE>file:%s</TITLE>\n</HEAD>\n" + "<BODY><H1>Directory listing of %s</H1>\n", + HUdirname, Hdirname, Hdirname); + dFree(Hdirname); + dFree(HUdirname); + dFree(Udirname); + + if (Client->old_style) { + sock_handler_write_str(Client->sh, 0, "<pre>\n"); + } + + /* Output the parent directory */ + File_print_parent_dir(Client, Ddir->dirname); + + /* HTML style toggle */ + sock_handler_write_str(Client->sh, 0, + " <a href='dpi:/file/toggle'>%</a>\n"); + + if (dList_length(Ddir->flist)) { + if (Client->old_style) { + sock_handler_write_str(Client->sh, 0, "\n\n"); + } else { + sock_handler_write_str(Client->sh, 0, + "<br><br>\n" + "<table border=0 cellpadding=1 cellspacing=0" + " bgcolor=#E0E0E0 width=100%>\n" + "<tr align=center>\n" + "<td>\n" + "<td width=60%><b>Filename</b>" + "<td><b>Type</b>" + "<td><b>Size</b>" + "<td><b>Modified at</b>\n"); + } + } else { + sock_handler_write_str(Client->sh, 0, "<br><br>Directory is empty..."); + } + + /* Output entries */ + for (n = 0; n < dList_length(Ddir->flist); ++n) { + File_info2html(Client, dList_nth_data(Ddir->flist,n),Ddir->dirname,n+1); + } + + if (dList_length(Ddir->flist)) { + if (Client->old_style) { + sock_handler_write_str(Client->sh, 0, "</pre>\n"); + } else { + sock_handler_write_str(Client->sh, 0, "</table>\n"); + } + } + + sock_handler_write_str(Client->sh, 0, "</BODY></HTML>\n"); +} + +/* + * Return a content type based on the extension of the filename. + */ +static const char *File_ext(const char *filename) +{ + char *e; + + if (!(e = strrchr(filename, '.'))) + return NULL; + + e++; + + if (!dStrcasecmp(e, "gif")) { + return "image/gif"; + } else if (!dStrcasecmp(e, "jpg") || + !dStrcasecmp(e, "jpeg")) { + return "image/jpeg"; + } else if (!dStrcasecmp(e, "png")) { + return "image/png"; + } else if (!dStrcasecmp(e, "html") || + !dStrcasecmp(e, "htm") || + !dStrcasecmp(e, "shtml")) { + return "text/html"; + } else { + return NULL; + } +} + +/* + * Based on the extension, return the content_type for the file. + * (if there's no extension, analyze the data and try to figure it out) + */ +static const char *File_content_type(const char *filename) +{ + int fd; + struct stat sb; + const char *ct; + char buf[256]; + ssize_t buf_size; + + if (!(ct = File_ext(filename))) { + /* everything failed, let's analyze the data... */ + if ((fd = open(filename, O_RDONLY | O_NONBLOCK)) != -1) { + if ((buf_size = read(fd, buf, 256)) == 256 ) { + ct = File_get_content_type_from_data(buf, (size_t)buf_size); + + } else if (stat(filename, &sb) != -1 && + buf_size > 0 && buf_size == sb.st_size) { + ct = File_get_content_type_from_data(buf, (size_t)buf_size); + } + File_close(fd); + } + } + + return ct; +} + +/* + * Try to stat the file and determine if it's readable. + */ +static void File_get(ClientInfo *Client, const char *filename, + const char *orig_url) +{ + int res; + struct stat sb; + char *d_cmd; + Dstr *ds = NULL; + + if (stat(filename, &sb) != 0) { + /* stat failed, prepare a file-not-found error. */ + res = FILE_NOT_FOUND; + } else if (S_ISDIR(sb.st_mode)) { + /* set up for reading directory */ + res = File_get_dir(Client, filename, orig_url); + } else { + /* set up for reading a file */ + res = File_get_file(Client, filename, &sb, orig_url); + } + + if (res == FILE_NOT_FOUND) { + ds = dStr_sized_new(128); + dStr_sprintf(ds, "%s Not Found: %s", + S_ISDIR(sb.st_mode) ? "Directory" : "File", filename); + } else if (res == FILE_NO_ACCESS) { + ds = dStr_sized_new(128); + dStr_sprintf(ds, "Access denied to %s: %s", + S_ISDIR(sb.st_mode) ? "Directory" : "File", filename); + } + if (ds) { + d_cmd = a_Dpip_build_cmd("cmd=%s msg=%s","send_status_message",ds->str); + sock_handler_write_str(Client->sh, 1, d_cmd); + dFree(d_cmd); + dStr_free(ds, 1); + } +} + +/* + * + */ +static int File_get_dir(ClientInfo *Client, + const char *DirName, const char *orig_url) +{ + Dstr *ds_dirname; + DilloDir *Ddir; + + /* Let's make sure this directory url has a trailing slash */ + ds_dirname = dStr_new(DirName); + if (ds_dirname->str[ds_dirname->len - 1] != '/') + dStr_append(ds_dirname, "/"); + + /* Let's get a structure ready for transfer */ + Ddir = File_dillodir_new(ds_dirname->str); + dStr_free(ds_dirname, TRUE); + if (Ddir) { + File_transfer_dir(Client, Ddir, orig_url); + File_dillodir_free(Ddir); + return FILE_OK; + } else + return FILE_NO_ACCESS; +} + +/* + * Send the MIME content/type and then send the file itself. + */ +static int File_get_file(ClientInfo *Client, + const char *filename, + struct stat *sb, + const char *orig_url) +{ +#define LBUF 16*1024 + + const char *ct; + char buf[LBUF], *d_cmd; + int fd, st; + + if ((fd = open(filename, O_RDONLY | O_NONBLOCK)) < 0) + return FILE_NO_ACCESS; + + /* Content-Type info is based on filename extension. If there's no + * known extension, then we do data sniffing. If this doesn't lead + * to a conclusion, "application/octet-stream" is sent. + */ + if (!(ct = File_content_type(filename))) + ct = "application/octet-stream"; + + /* Send DPI command */ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", orig_url); + sock_handler_write_str(Client->sh, 1, d_cmd); + dFree(d_cmd); + + /* Send HTTP stream */ + sock_handler_printf(Client->sh, 0, + "Content-Type: %s\n" + "Content-length: %ld\n\n", + ct, sb->st_size); + + /* Send raw file contents */ + do { + if ((st = read(fd, buf, LBUF)) > 0) { + if (sock_handler_write(Client->sh, 0, buf, (size_t)st) != 0) + break; + } else if (st < 0) { + perror("[read]"); + if (errno == EINTR || errno == EAGAIN) + continue; + } + } while (st > 0); + + /* todo: It may be better to send an error report to dillo instead of + * calling exit() */ + if (st == -1) { + MSG("ERROR while reading from file \"%s\", error was \"%s\"\n", + filename, strerror(errno)); + exit(1); + } + + File_close(fd); + return FILE_OK; +} + +/* + * Given an hex octet (e3, 2F, 20), return the corresponding + * character if the octet is valid, and -1 otherwise + */ +static int File_parse_hex_octet(const char *s) +{ + int hex_value; + char *tail, hex[3]; + + if ((hex[0] = s[0]) && (hex[1] = s[1])) { + hex[2] = 0; + hex_value = strtol(hex, &tail, 16); + if (tail - hex == 2) + return hex_value; + } + + return -1; +} + +/* + * Make a file URL into a human (and machine) readable path. + * The idea is to always have a path that starts with only one slash. + * Embedded slashes are ignored. + */ +static char *File_normalize_path(const char *orig) +{ + char *str = (char *) orig, *basename = NULL, *ret = NULL, *p; + + /* Make sure the string starts with "file:/" */ + if (strncmp(str, "file:/", 5) != 0) + return ret; + str += 5; + + /* Skip "localhost" */ + if (dStrncasecmp(str, "//localhost/", 12) == 0) + str += 11; + + /* Skip packed slashes, and leave just one */ + while (str[0] == '/' && str[1] == '/') + str++; + + { + int i, val; + Dstr *ds = dStr_sized_new(32); + dStr_sprintf(ds, "%s%s%s", + basename ? basename : "", + basename ? "/" : "", + str); + dFree(basename); + + /* Parse possible hexadecimal octets in the URI path */ + for (i = 0; ds->str[i]; ++i) { + if (ds->str[i] == '%' && + (val = File_parse_hex_octet(ds->str + i+1)) != -1) { + ds->str[i] = val; + dStr_erase(ds, i+1, 2); + } + } + /* Remove the fragment if not a part of the filename */ + if ((p = strrchr(ds->str, '#')) != NULL && access(ds->str, F_OK) != 0) + dStr_truncate(ds, p - ds->str); + ret = ds->str; + dStr_free(ds, 0); + } + + return ret; +} + +/* + * Set the style flag and ask for a reload, so it shows inmediatly. + */ +static void File_toggle_html_style(ClientInfo *Client) +{ + char *d_cmd; + + OLD_STYLE = !OLD_STYLE; + d_cmd = a_Dpip_build_cmd("cmd=%s", "reload_request"); + sock_handler_write_str(Client->sh, 1, d_cmd); + dFree(d_cmd); +} + +/* + * Perform any necessary cleanups upon abnormal termination + */ +static void termination_handler(int signum) +{ + exit(signum); +} + + +/* Client handling ----------------------------------------------------------*/ + +/* + * Add a new client to the list. + */ +static ClientInfo *File_add_client(int sock_fd) +{ + ClientInfo *NewClient; + + NewClient = dNew(ClientInfo, 1); + NewClient->sh = sock_handler_new(sock_fd, sock_fd, 8*1024); + NewClient->status = 0; + NewClient->done = 0; + NewClient->old_style = OLD_STYLE; + pthread_mutex_lock(&ClMut); + dList_append(Clients, NewClient); + pthread_mutex_unlock(&ClMut); + return NewClient; +} + +/* + * Get client record by number + */ +static void *File_get_client_n(uint_t n) +{ + void *client; + + pthread_mutex_lock(&ClMut); + client = dList_nth_data(Clients, n); + pthread_mutex_unlock(&ClMut); + + return client; +} + +/* + * Remove a client from the list. + */ +static void File_remove_client_n(uint_t n) +{ + ClientInfo *Client; + + pthread_mutex_lock(&ClMut); + Client = dList_nth_data(Clients, n); + dList_remove(Clients, (void *)Client); + pthread_mutex_unlock(&ClMut); + + _MSG("Closing Socket Handler\n"); + sock_handler_close(Client->sh); + sock_handler_free(Client->sh); + dFree(Client); +} + +/* + * Return the number of clients. + */ +static int File_num_clients(void) +{ + uint_t n; + + pthread_mutex_lock(&ClMut); + n = dList_length(Clients); + pthread_mutex_unlock(&ClMut); + + return n; +} + +/* + * Serve this client. + * (this function runs on its own thread) + */ +static void *File_serve_client(void *data) +{ + char *dpip_tag, *cmd = NULL, *url = NULL, *path; + ClientInfo *Client = data; + + /* Read the dpi command */ + dpip_tag = sock_handler_read(Client->sh); + MSG("dpip_tag={%s}\n", dpip_tag); + + if (dpip_tag) { + cmd = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cmd"); + if (cmd) { + if (strcmp(cmd, "DpiBye") == 0) { + DPIBYE = 1; + } else { + url = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "url"); + if (!url) + MSG("file.dpi:: Failed to parse 'url'\n"); + } + } + } + dFree(cmd); + dFree(dpip_tag); + + if (!DPIBYE && url) { + _MSG("url = '%s'\n", url); + + path = File_normalize_path(url); + if (path) { + _MSG("path = '%s'\n", path); + File_get(Client, path, url); + } else if (strcmp(url, "dpi:/file/toggle") == 0) { + File_toggle_html_style(Client); + } else { + MSG("ERROR: URL path was %s\n", url); + } + dFree(path); + } + dFree(url); + + /* flag the the transfer finished */ + Client->done = 1; + + return NULL; +} + +/* + * Serve the client queue. + * (this function runs on its own thread) + */ +static void *File_serve_clients(void *client) +{ + /* switch to detached state */ + pthread_detach(pthread_self()); + + while (File_num_clients()) { + client = File_get_client_n((uint_t)0); + File_serve_client(client); + File_remove_client_n((uint_t)0); + } + ThreadRunning = 0; + + return NULL; +} + +/* --------------------------------------------------------------------------*/ + +/* + * Check a fd for activity, with a max timeout. + * return value: 0 if timeout, 1 if input available, -1 if error. + */ +int File_check_fd(int filedes, unsigned int seconds) +{ + int st; + fd_set set; + struct timeval timeout; + + /* Initialize the file descriptor set. */ + FD_ZERO (&set); + FD_SET (filedes, &set); + + /* Initialize the timeout data structure. */ + timeout.tv_sec = seconds; + timeout.tv_usec = 0; + + do { + st = select(FD_SETSIZE, &set, NULL, NULL, &timeout); + } while (st == -1 && errno == EINTR); + + return st; +} + + +int main(void) +{ + ClientInfo *NewClient; + struct sockaddr_un spun; + int temp_sock_descriptor; + socklen_t address_size; + int c_st, st = 1; + uint_t i; + + /* Arrange the cleanup function for abnormal terminations */ + if (signal (SIGINT, termination_handler) == SIG_IGN) + signal (SIGINT, SIG_IGN); + if (signal (SIGHUP, termination_handler) == SIG_IGN) + signal (SIGHUP, SIG_IGN); + if (signal (SIGTERM, termination_handler) == SIG_IGN) + signal (SIGTERM, SIG_IGN); + + MSG("(v.1) accepting connections...\n"); + + /* initialize mutex */ + pthread_mutex_init(&ClMut, NULL); + + /* initialize Clients list */ + Clients = dList_new(512); + + /* some OSes may need this... */ + address_size = sizeof(struct sockaddr_un); + + /* start the service loop */ + while (!DPIBYE) { + /* wait for a connection */ + do { + c_st = File_check_fd(STDIN_FILENO, 1); + } while (c_st == 0 && !DPIBYE); + if (c_st < 0) { + perror("[select]"); + break; + } + if (DPIBYE) + break; + + temp_sock_descriptor = + accept(STDIN_FILENO, (struct sockaddr *)&spun, &address_size); + + if (temp_sock_descriptor == -1) { + perror("[accept]"); + break; + } + + /* Create and initialize a new client */ + NewClient = File_add_client(temp_sock_descriptor); + + if (!ThreadRunning) { + ThreadRunning = 1; + /* Serve the client from a thread (avoids deadlocks) */ + if (pthread_create(&NewClient->thrID, NULL, + File_serve_clients, NewClient) != 0) { + perror("[pthread_create]"); + ThreadRunning = 0; + break; + } + } + } + + /* todo: handle a running thread better. */ + for (i = 0; i < 5 && ThreadRunning; ++i) { + MSG("sleep i=%u", i); + sleep(i); + } + + if (DPIBYE) + st = 0; + return st; +} + diff --git a/dpi/ftp.c b/dpi/ftp.c new file mode 100644 index 00000000..36fe7498 --- /dev/null +++ b/dpi/ftp.c @@ -0,0 +1,301 @@ +/* + * Dpi for FTP. + * + * This server checks the ftp-URL to be a directory (requires wget). + * If true, it sends back an html representation of it, and if not + * a dpip message (which is catched by dillo who redirects the ftp URL + * to the downloads server). + * + * Feel free to polish! + * + * Copyright 2003-2005 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + */ + +/* + * TODO: + * - Send feedback about the FTP login process from wget's stderr. + * i.e. capture our child's stderr, process it, and report back. + * - Handle simultaneous connections. + * If ftp.dpi is implemented with a low level ftp library, it becomes + * possible to keep the connection alive, and thus make browsing of ftp + * directories faster (this avoids one login per page, and forks). Perhaps + * it's not worth, but can be done. + */ + +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <sys/wait.h> +#include <errno.h> +#include <sys/time.h> +#include <ctype.h> + +#include "../dpip/dpip.h" +#include "dpiutil.h" +#include "d_size.h" + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[ftp dpi]: " __VA_ARGS__) + +/* + * Global variables + */ +static SockHandler *sh = NULL; +char **dl_argv = NULL; + +/*---------------------------------------------------------------------------*/ + +/* TODO: could use dStr ADT! */ +typedef struct ContentType_ { + const char *str; + int len; +} ContentType_t; + +static const ContentType_t MimeTypes[] = { + { "application/octet-stream", 24 }, + { "text/html", 9 }, + { "text/plain", 10 }, + { "image/gif", 9 }, + { "image/png", 9 }, + { "image/jpeg", 10 }, + { NULL, 0 } +}; + +/* + * Detects 'Content-Type' from a data stream sample. + * + * It uses the magic(5) logic from file(1). Currently, it + * only checks the few mime types that Dillo supports. + * + * 'Data' is a pointer to the first bytes of the raw data. + * + * Return value: (0 on success, 1 on doubt, 2 on lack of data). + */ +int a_Misc_get_content_type_from_data(void *Data, size_t Size, + const char **PT) +{ + int st = 1; /* default to "doubt' */ + int Type = 0; /* default to "application/octet-stream" */ + char *p = Data; + size_t i, non_ascci; + + /* HTML try */ + for (i = 0; i < Size && isspace(p[i]); ++i); + if ((Size - i >= 5 && !dStrncasecmp(p+i, "<html", 5)) || + (Size - i >= 5 && !dStrncasecmp(p+i, "<head", 5)) || + (Size - i >= 6 && !dStrncasecmp(p+i, "<title", 6)) || + (Size - i >= 14 && !dStrncasecmp(p+i, "<!doctype html", 14)) || + /* this line is workaround for FTP through the Squid proxy */ + (Size - i >= 17 && !dStrncasecmp(p+i, "<!-- HTML listing", 17))) { + + Type = 1; + st = 0; + /* Images */ + } else if (Size >= 4 && !dStrncasecmp(p, "GIF8", 4)) { + Type = 3; + st = 0; + } else if (Size >= 4 && !dStrncasecmp(p, "\x89PNG", 4)) { + Type = 4; + st = 0; + } else if (Size >= 2 && !dStrncasecmp(p, "\xff\xd8", 2)) { + /* JPEG has the first 2 bytes set to 0xffd8 in BigEndian - looking + * at the character representation should be machine independent. */ + Type = 5; + st = 0; + + /* Text */ + } else { + /* We'll assume "text/plain" if the set of chars above 127 is <= 10 + * in a 256-bytes sample. Better heuristics are welcomed! :-) */ + non_ascci = 0; + Size = MIN (Size, 256); + for (i = 0; i < Size; i++) + if ((uchar_t) p[i] > 127) + ++non_ascci; + if (Size == 256) { + Type = (non_ascci > 10) ? 0 : 2; + st = 0; + } else { + Type = (non_ascci > 0) ? 0 : 2; + } + } + + *PT = MimeTypes[Type].str; + return st; +} + +/*---------------------------------------------------------------------------*/ + +/* + * Build a shell command using wget for this URL. + */ +static void make_wget_argv(char *url) +{ + char *esc_url; + + if (dl_argv) { + dFree(dl_argv[2]); + dFree(dl_argv); + } + dl_argv = dNew(char*, 10); + + esc_url = Escape_uri_str(url, "'"); + /* avoid malicious SMTP relaying with FTP urls */ + Filter_smtp_hack(esc_url); + + dl_argv[0] = "wget"; + dl_argv[1] = "-O-"; + dl_argv[2] = esc_url; + dl_argv[3] = NULL; +} + +/* + * Fork, exec command, get its output and send via stdout. + * Return: Number of bytes transfered. + */ +static int try_ftp_transfer(char *url) +{ +#define MinSZ 256 + + ssize_t n; + int nb, minibuf_sz; + const char *mime_type; + char buf[4096], minibuf[MinSZ], *d_cmd; + pid_t ch_pid; + int aborted = 0; + int DataPipe[2]; + + if (pipe(DataPipe) < 0) { + MSG("pipe, %s\n", strerror(errno)); + return 0; + } + + /* Prepare args for execvp() */ + make_wget_argv(url); + + /* Start the child process */ + if ((ch_pid = fork()) == 0) { + /* child */ + /* start wget */ + close(DataPipe[0]); + dup2(DataPipe[1], 1); /* stdout */ + execvp(dl_argv[0], dl_argv); + _exit(1); + } else if (ch_pid < 0) { + perror("fork, "); + exit(1); + } else { + /* father continues below */ + close(DataPipe[1]); + } + + /* Read/Write the real data */ + minibuf_sz = 0; + for (nb = 0; 1; nb += n) { + while ((n = read(DataPipe[0], buf, 4096)) < 0 && errno == EINTR); + if (n <= 0) + break; + + if (minibuf_sz < MinSZ) { + memcpy(minibuf + minibuf_sz, buf, MIN(n, MinSZ - minibuf_sz)); + minibuf_sz += MIN(n, MinSZ - minibuf_sz); + if (minibuf_sz < MinSZ) + continue; + a_Misc_get_content_type_from_data(minibuf, minibuf_sz, &mime_type); + if (strcmp(mime_type, "application/octet-stream") == 0) { + /* abort transfer */ + kill(ch_pid, SIGTERM); + /* The "application/octet-stream" MIME type will be sent and + * Dillo will offer a download dialog */ + aborted = 1; + } + } + + if (nb == 0) { + /* Send dpip tag */ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + /* Send HTTP header. */ + sock_handler_write_str(sh, 0, "Content-type: "); + sock_handler_write_str(sh, 0, mime_type); + sock_handler_write_str(sh, 1, "\n\n"); + } + + if (!aborted) + sock_handler_write(sh, 0, buf, n); + } + + return nb; +} + +/* + * + */ +int main(void) +{ + char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *url2 = NULL; + int nb; + char *p, *d_cmd; + + /* Initialize the SockHandler */ + sh = sock_handler_new(STDIN_FILENO, STDOUT_FILENO, 8*1024); + + /* wget may need to write a temporary file... */ + chdir("/tmp"); + + /* Read the dpi command from STDIN */ + dpip_tag = sock_handler_read(sh); + MSG("tag=[%s]\n", dpip_tag); + + cmd = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cmd"); + url = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "url"); + if (!cmd || !url) { + MSG("ERROR, cmd=%s, url=%s\n", cmd, url); + exit (EXIT_FAILURE); + } + + if ((nb = try_ftp_transfer(url)) == 0) { + /* Transfer failed, the requested file may not exist or be a symlink + * to a directory. Try again... */ + if ((p = strrchr(url, '/')) && p[1]) { + url2 = dStrconcat(url, "/", NULL); + nb = try_ftp_transfer(url2); + } + } + + if (nb == 0) { + /* The transfer failed, let dillo know... */ + d_cmd = a_Dpip_build_cmd("cmd=%s to_cmd=%s msg=%s", + "answer", "open_url", "not a directory"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + } + + dFree(cmd); + dFree(url); + dFree(url2); + dFree(dpip_tag); + + /* Finish the SockHandler */ + sock_handler_close(sh); + sock_handler_free(sh); + + return 0; +} + diff --git a/dpi/hello.c b/dpi/hello.c new file mode 100644 index 00000000..bc316f5e --- /dev/null +++ b/dpi/hello.c @@ -0,0 +1,161 @@ +/* + * Dpi for "Hello World". + * + * This server is an example. Play with it and modify to your taste. + * + * Copyright 2003 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + */ + +#include <unistd.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include "../dpip/dpip.h" +#include "dpiutil.h" + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[hello dpi]: " __VA_ARGS__) + +/*---------------------------------------------------------------------------*/ + + +/* + * + */ +int main(void) +{ + FILE *in_stream; + SockHandler *sh; + char *dpip_tag, *cmd = NULL, *url = NULL, *child_cmd = NULL; + char *esc_tag, *d_cmd; + size_t n; + int ret; + char buf[4096]; + char *choice[] = {"Window was closed", "Yes", "No", + "Could be", "It's OK", "Cancel"}; + /* "Could>be", ">It's OK", "Can'>cel"}; --for testing */ + int choice_num; + + MSG("starting...\n"); + + /* Initialize the SockHandler */ + sh = sock_handler_new(STDIN_FILENO, STDOUT_FILENO, 2*1024); + + /* Read the dpi command from STDIN */ + dpip_tag = sock_handler_read(sh); + MSG("tag = [%s]\n", dpip_tag); + + cmd = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cmd"); + url = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "url"); + +/*-- Dialog part */ +{ + char *dpip_tag2, *dialog_msg; + + /* Let's confirm the request */ + /* NOTE: you can send less alternatives (e.g. only alt1 and alt2) */ + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s alt3=%s alt4=%s alt5=%s", + "dialog", "Do you want to see the hello page?", + choice[1], choice[2], choice[3], choice[4], choice[5]); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + /* Get the answer */ + dpip_tag2 = sock_handler_read(sh); + MSG("tag = [%s]\n", dpip_tag2); + + /* Get "msg" value */ + dialog_msg = a_Dpip_get_attr(dpip_tag2, strlen(dpip_tag2), "msg"); + choice_num = 0; + if (dialog_msg) + choice_num = *dialog_msg - '0'; + + dFree(dialog_msg); + dFree(dpip_tag2); +} +/*-- EOD part */ + + /* Start sending our answer */ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + sock_handler_write_str(sh, 0, d_cmd); + dFree(d_cmd); + + sock_handler_printf(sh, 0, + "Content-type: text/html\n\n" + "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n" + "<html>\n" + "<head><title>Simple dpi test page (hello.dpi)</title></head>\n" + "<body><hr><h1>Hello world!</h1><hr>\n<br><br>\n"); + + /* Show the choice received with the dialog */ + sock_handler_printf(sh, 0, + "<hr>\n" + "<table width='100%%' border='1' bgcolor='burlywood'><tr><td>\n" + "<big><em>Dialog question:</em> Do you want to see the hello page?<br>\n" + "<em>Answer received:</em> <b>%s</b></big> </table>\n" + "<hr>\n", + choice[choice_num]); + + /* Show the dpip tag we received */ + esc_tag = Escape_html_str(dpip_tag); + sock_handler_printf(sh, 0, + "<h3>dpip tag received:</h3>\n" + "<pre>\n%s</pre>\n" + "<br><small>(<b>dpip:</b> dpi protocol)</small><br><br><br>\n", + esc_tag); + dFree(esc_tag); + + + /* Now something more interesting, + * fork a command and show its feedback */ + if (cmd && url) { + child_cmd = dStrdup("date -R"); + MSG("[%s]\n", child_cmd); + + /* Fork, exec command, get its output and answer */ + if ((in_stream = popen(child_cmd, "r")) == NULL) { + perror("popen"); + return EXIT_FAILURE; + } + + sock_handler_printf(sh, 0, "<h3>date:</h3>\n"); + sock_handler_printf(sh, 0, "<pre>\n"); + + /* Read/Write */ + while ((n = fread (buf, 1, 4096, in_stream)) > 0) { + sock_handler_write(sh, 0, buf, n); + } + + sock_handler_printf(sh, 0, "</pre>\n"); + + if ((ret = pclose(in_stream)) != 0) + MSG("popen: [%d]\n", ret); + + dFree(child_cmd); + } + + sock_handler_printf(sh, 1, "</body></html>\n"); + + dFree(cmd); + dFree(url); + dFree(dpip_tag); + + /* Finish the SockHandler */ + sock_handler_close(sh); + sock_handler_free(sh); + + return 0; +} + diff --git a/dpi/https.c b/dpi/https.c new file mode 100644 index 00000000..bbfc7ae4 --- /dev/null +++ b/dpi/https.c @@ -0,0 +1,713 @@ +/* + * Dpi for HTTPS. + * + * + * + * W A R N I N G + * + * One of the important things to have in mind is about whether + * unix domain sockets (UDS) are secure for https. I mean, root can always + * snoop on sockets (regardless of permissions) so he'd be able to "see" all + * the traffic. OTOH, if someone has root access on a machine he can do + * anything, and that includes modifying the binaries, peeking-up in + * memory space, installing a key-grabber, ... + * + * + * Copyright 2003, 2004 Jorge Arellano Cid <jcid@dillo.org> + * Copyright 2004 Garrett Kajmowicz <gkajmowi@tbaytel.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * As a special exception permission is granted to link the code of + * the https dillo plugin with the OpenSSL project's "OpenSSL" + * library, and distribute the linked executables, without including + * the source code for OpenSSL in the source distribution. You must + * obey the GNU General Public License, version 2, in all respects + * for all of the code used other than "OpenSSL". + * + */ + +/* + * TODO: a lot of things, this is just a bare bones example. + * + * For instance: + * - Handle cookies (now that they arrive with the dpip tag, it needs + * testing). + * - Certificate authentication (asking the user in case it can't be verified) + * - Certificate management. + * - Session caching ... + * + */ + +#include <config.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/un.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <sys/wait.h> +#include <errno.h> +#include <sys/time.h> +#include <sys/stat.h> + +#include "../dpip/dpip.h" +#include "dpiutil.h" + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[https dpi]: " __VA_ARGS__) + + + +#define ENABLE_SSL +/* #undef ENABLE_SSL */ +#ifdef ENABLE_SSL + +#include <openssl/ssl.h> +#include <openssl/rand.h> + +static int get_network_connection(char * url); +static int handle_certificate_problem(SSL * ssl_connection); +static int save_certificate_home(X509 * cert); + +#endif + + + +/*---------------------------------------------------------------------------*/ +/* + * Global variables + */ +static char *root_url = NULL; /*Holds the URL we are connecting to*/ +static SockHandler *sh; + + +#ifdef ENABLE_SSL + +/* + * Read the answer dpip tag for a dialog and return the number for + * the user-selected alternative. + * Return: (-1: parse error, 0: window closed, 1-5 alt. number) + */ +static int dialog_get_answer_number(void) +{ + int response_number = -1; + char *dpip_tag, *response; + + /* Read the dpi command from STDIN */ + dpip_tag = sock_handler_read(sh); + response = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "msg"); + response_number = (response) ? strtol (response, NULL, 10) : -1; + dFree(dpip_tag); + dFree(response); + + return response_number; +} + + +/* + * This function does all of the work with SSL + */ +static void yes_ssl_support(void) +{ + /* The following variable will be set to 1 in the event of + * an error and skip any further processing + */ + int exit_error = 0; + SSL_CTX * ssl_context = NULL; + SSL * ssl_connection = NULL; + + char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *http_query = NULL; + char buf[4096]; + int retval = 0; + int network_socket = -1; + + + MSG("{In https.filter.dpi}\n"); + + /*Initialize library*/ + SSL_load_error_strings(); + SSL_library_init(); + if (RAND_status() != 1){ + /*Insufficient entropy. Deal with it?*/ + MSG("Insufficient random entropy\n"); + } + + /*Create context and SSL object*/ + if (exit_error == 0){ + ssl_context = SSL_CTX_new(SSLv23_client_method()); + if (ssl_context == NULL){ + MSG("Error creating SSL context\n"); + exit_error = 1; + } + } + + /*Set directory to load certificates from*/ + /*FIXME - provide for sysconfdir variables and such*/ + if (exit_error == 0){ + if (SSL_CTX_load_verify_locations( + ssl_context, NULL, "/etc/ssl/certs/" ) == 0){ + MSG("Error opening system x509 certificate location\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + snprintf(buf, 4095, "%s/.dillo/certs/", dGethomedir()); + if (SSL_CTX_load_verify_locations(ssl_context, NULL, buf )==0){ + MSG("Error opening user x509 certificate location\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + ssl_connection = SSL_new(ssl_context); + if (ssl_connection == NULL){ + MSG("Error creating SSL connection\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + /* Need to do the following if we want to deal with all + * possible ciphers + */ + SSL_set_cipher_list(ssl_connection, "ALL"); + + /* Need to do this if we want to have the option of dealing + * with self-signed certs + */ + SSL_set_verify(ssl_connection, SSL_VERIFY_NONE, 0); + + /*Get the network address and command to be used*/ + dpip_tag = sock_handler_read(sh); + cmd = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cmd"); + url = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "url"); + http_query = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "query"); + + if (cmd == NULL || url == NULL || http_query == NULL){ + MSG("***Value of cmd, url or http_query is NULL" + " - cannot continue\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + network_socket = get_network_connection(url); + if (network_socket<0){ + MSG("Network socket create error\n"); + exit_error = 1; + } + } + + + if (exit_error == 0){ + /* Configure SSL to use network file descriptor */ + if (SSL_set_fd(ssl_connection, network_socket) == 0){ + MSG("Error connecting network socket to SSL\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + /*Actually do SSL connection handshake*/ + if (SSL_connect(ssl_connection) != 1){ + MSG("SSL_connect failed\n"); + exit_error = 1; + } + } + + /*Use handle error function to decide what to do*/ + if (exit_error == 0){ + if (handle_certificate_problem(ssl_connection) < 0){ + MSG("Certificate verification error\n"); + exit_error = 1; + } + } + + if (exit_error == 0) { + char *d_cmd; + + /*Send query we want*/ + SSL_write(ssl_connection, http_query, (int)strlen(http_query)); + + /*Analyse response from server*/ + + /*Send dpi command*/ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + /*Send remaining data*/ + + while ((retval = SSL_read(ssl_connection, buf, 4096)) > 0 ){ + sock_handler_write(sh, 0, buf, (size_t)retval); + } + } + + /*Begin cleanup of all resources used*/ + dFree(dpip_tag); + dFree(cmd); + dFree(url); + dFree(http_query); + + if (network_socket != -1){ + close(network_socket); + network_socket = -1; + } + if (ssl_connection != NULL){ + SSL_free(ssl_connection); + ssl_connection = NULL; + } + if (ssl_context != NULL){ + SSL_CTX_free(ssl_context); + ssl_context = NULL; + } +} + +/* + * The following function attempts to open up a connection to the + * remote server and return the file descriptor number of the + * socket. Returns -1 in the event of an error + */ +static int get_network_connection(char * url) +{ + struct sockaddr_in address; + struct hostent *hp; + + int s; + int url_offset = 0; + int portnum = 443; + unsigned int url_look_up_length = 0; + char * url_look_up = NULL; + + /*Determine how much of url we chop off as unneeded*/ + if (dStrncasecmp(url, "https://", 8) == 0){ + url_offset = 8; + } + + /*Find end of URL*/ + + if (strpbrk(url+url_offset, ":/") != NULL){ + url_look_up_length = strpbrk(url+url_offset, ":/") - (url+url_offset); + url_look_up = dStrndup(url+url_offset, url_look_up_length); + + /*Check for port number*/ + if (strchr(url+url_offset, ':') == + (url + url_offset + url_look_up_length)){ + portnum = atoi(url + url_offset + url_look_up_length + 1); + } + } else { + url_look_up = url + url_offset; + } + + root_url = dStrdup(url_look_up); + hp=gethostbyname(url_look_up); + + /*url_look_uip no longer needed, so free if neccessary*/ + if (url_look_up_length != 0){ + dFree(url_look_up); + } + + if (hp == NULL){ + MSG("gethostbyname() failed\n"); + return -1; + } + + memset(&address,0,sizeof(address)); + memcpy((char *)&address.sin_addr, hp->h_addr, (size_t)hp->h_length); + address.sin_family=hp->h_addrtype; + address.sin_port= htons((u_short)portnum); + + s = socket(hp->h_addrtype, SOCK_STREAM, 0); + if (connect(s, (struct sockaddr *)&address, sizeof(address)) != 0){ + close(s); + s = -1; + MSG("errno: %i\n", errno); + } + return s; +} + + +/* This function is run only when the certificate cannot + * be completely trusted. This will notify the user and + * allow the user to decide what to do. It may save the + * certificate to the user's .dillo directory if it is + * trusted. + * Return value: -1 on abort, 0 or higher on continue + */ +static int handle_certificate_problem(SSL * ssl_connection) +{ + int response_number; + int retval = -1; + long st; + char *cn, *cn_end; + char buf[4096], *d_cmd, *msg; + + X509 * remote_cert; + + remote_cert = SSL_get_peer_certificate(ssl_connection); + if (remote_cert == NULL){ + /*Inform user that remote system cannot be trusted*/ + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "The remote system is NOT presenting a certificate.\n" + "This site CAN NOT be trusted. Sending data is NOT SAFE.\n" + "What do I do?", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + /*Read the user's response*/ + response_number = dialog_get_answer_number(); + + /*Abort on anything but "Continue"*/ + if (response_number == 1){ + retval = 0; + } + + } else { + /*Figure out if (and why) the remote system can't be trusted*/ + st = SSL_get_verify_result(ssl_connection); + switch (st) { + case X509_V_OK: /*Everything is Kosher*/ + retval = 0; + break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + /*Either self signed and untrusted*/ + /*Extract CN from certificate name information*/ + cn = strstr(remote_cert->name, "/CN=") + 4; + if (cn == NULL) + break; + + if ((cn_end = strstr(cn, "/")) == NULL ) + cn_end = cn + strlen(cn); + + strncpy(buf, cn, (size_t) (cn_end - cn)); + + /*Add terminating NULL*/ + buf[cn_end - cn] = 0; + + msg = dStrconcat("The remote certificate is self-signed and " + "untrusted.\nFor address: ", buf, NULL); + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s alt3=%s", + "dialog", msg, "Continue", "Cancel", "Trust Certificate"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + dFree(msg); + + response_number = dialog_get_answer_number(); + switch (response_number){ + case 1: + retval = 0; + break; + case 2: + break; + case 3: + /*Save certificate to a file here and recheck the chain*/ + /*Potential security problems because we are writing + *to the filesystem*/ + save_certificate_home(remote_cert); + retval = 1; + break; + default: + break; + } + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "The issuer for the remote certificate cannot be found\n" + "The authenticity of the remote certificate cannot be trusted", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + case X509_V_ERR_CRL_SIGNATURE_FAILURE: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "The remote certificate signature could not be read\n" + "or is invalid and should not be trusted", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CRL_NOT_YET_VALID: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "Part of the remote certificate is not yet valid\n" + "Certificates usually have a range of dates over which\n" + "they are to be considered valid, and the certificate\n" + "presented has a starting validity after today's date\n" + "You should be cautious about using this site", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_CRL_HAS_EXPIRED: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "The remote certificate has expired. The certificate\n" + "wasn't designed to last this long. You should avoid \n" + "this site.", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: + case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "There was an error in the certificate presented.\n" + "Some of the certificate data was improperly formatted\n" + "making it impossible to determine if the certificate\n" + "is valid. You should not trust this certificate.", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_INVALID_CA: + case X509_V_ERR_INVALID_PURPOSE: + case X509_V_ERR_CERT_UNTRUSTED: + case X509_V_ERR_CERT_REJECTED: + case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "One of the certificates in the chain is being used\n" + "incorrectly (possibly due to configuration problems\n" + "with the remote system. The connection should not\n" + "be trusted", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: + case X509_V_ERR_AKID_SKID_MISMATCH: + case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "Some of the information presented by the remote system\n" + "does not match other information presented\n" + "This may be an attempt to evesdrop on communications", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + default: /*Need to add more options later*/ + snprintf(buf, 80, + "The remote certificate cannot be verified (code %ld)", st); + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", buf, "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + response_number = dialog_get_answer_number(); + /*abort on anything but "Continue"*/ + if (response_number == 1){ + retval = 0; + } + } + X509_free(remote_cert); + remote_cert = 0; + } + + return retval; +} + +/* + * Save certificate with a hashed filename. + * Return: 0 on success, 1 on failure. + */ +static int save_certificate_home(X509 * cert) +{ + char buf[4096]; + + FILE * fp = NULL; + unsigned int i = 0; + int retval = 1; + + /*Attempt to create .dillo/certs blindly - check later*/ + snprintf(buf,4096,"%s/.dillo/", dGethomedir()); + mkdir(buf, 01777); + snprintf(buf,4096,"%s/.dillo/certs/", dGethomedir()); + mkdir(buf, 01777); + + do{ + snprintf(buf, 4096, "%s/.dillo/certs/%lx.%u", + dGethomedir(), X509_subject_name_hash(cert), i); + + fp=fopen(buf, "r"); + if (fp == NULL){ + /*File name doesn't exist so we can use it safely*/ + fp=fopen(buf, "w"); + if (fp == NULL){ + MSG("Unable to open cert save file in home dir\n"); + break; + } else { + PEM_write_X509(fp, cert); + fclose(fp); + MSG("Wrote certificate\n"); + retval = 0; + break; + } + } else { + fclose(fp); + } + i++; + /*Don't loop too many times - just give up*/ + } while( i < 1024 ); + + return retval; +} + + + +#else + + +/* + * Call this function to display an error message if SSL support + * isn't available for some reason + */ +static void no_ssl_support(void) +{ + char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *http_query = NULL; + char *d_cmd; + + /* Read the dpi command from STDIN */ + dpip_tag = sock_handler_read(sh); + + MSG("{In https.filter.dpi}\n"); + MSG("no_ssl_support version\n"); + + cmd = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cmd"); + url = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "url"); + http_query = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "query"); + + MSG("{ cmd: %s}\n", cmd); + MSG("{ url: %s}\n", url); + MSG("{ http_query:\n%s}\n", http_query); + + MSG("{ sending dpip cmd...}\n"); + + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + MSG("{ dpip cmd sent.}\n"); + + MSG("{ sending HTML...}\n"); + + sock_handler_printf(sh, 1, + "Content-type: text/html\n\n" + "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n" + "<html><body><pre>\n" + "<b>Hi!\n\n" + " This is the https dpi that just got a request to send\n" + " the following HTTP query:\n{</b>\n" + "<code>%s</code>\n" + "<b>}</b>\n\n" + " <b>*** Dillo's prototype plugin for https support" + " is disabled now ***</b>\n\n" + " If you want to test this <b>alpha</b> support code, just remove\n" + " line 65 from https.c, recompile and reinstall.\n\n" + " (beware that this https support is very limited now)\n\n" + " To use https and SSL, you must have \n" + " the OpenSSL development libraries installed. Check your\n" + " O/S distribution provider, or check out\n" + " <a href=\"http://www.openssl.org\">www.openssl.org</a>\n\n" + " --\n" + "</pre></body></html>\n", + http_query + ); + MSG("{ HTML content sent.}\n"); + + dFree(cmd); + dFree(url); + dFree(http_query); + dFree(dpip_tag); + + MSG("{ exiting https.dpi}\n"); + +} + +#endif + + +/*---------------------------------------------------------------------------*/ +int main(void) +{ + /* Initialize the SockHandler for this filter dpi */ + sh = sock_handler_new(STDIN_FILENO, STDOUT_FILENO, 8*1024); + +#ifdef ENABLE_SSL + yes_ssl_support(); +#else + no_ssl_support(); +#endif + + /* Finish the SockHandler */ + sock_handler_close(sh); + sock_handler_free(sh); + + dFree(root_url); + + MSG("{ exiting https.dpi}\n"); + + return 0; +} + |