aboutsummaryrefslogtreecommitdiff
path: root/dpi
diff options
context:
space:
mode:
authorjcid <devnull@localhost>2007-10-07 00:36:34 +0200
committerjcid <devnull@localhost>2007-10-07 00:36:34 +0200
commit93715c46a99c96d6c866968312691ec9ab0f6a03 (patch)
tree573f19ec6aa740844f53a7c0eb7114f04096bf64 /dpi
Initial revision
Diffstat (limited to 'dpi')
-rw-r--r--dpi/Makefile.am37
-rw-r--r--dpi/bookmarks.c1716
-rw-r--r--dpi/cookies.c1466
-rw-r--r--dpi/datauri.c323
-rw-r--r--dpi/downloads.cc1133
-rw-r--r--dpi/dpiutil.c270
-rw-r--r--dpi/dpiutil.h97
-rw-r--r--dpi/file.c975
-rw-r--r--dpi/ftp.c301
-rw-r--r--dpi/hello.c161
-rw-r--r--dpi/https.c713
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>&nbsp;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>&nbsp;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&nbsp;an&nbsp;operation&nbsp;</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>,&nbsp;mark&nbsp;its&nbsp;operands,&nbsp;and&nbsp;</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"
+" &nbsp;&nbsp;&nbsp;%s&nbsp;&nbsp;&nbsp;</b></font></td>\n"
+" <td bgcolor='white' width='100%%'>&nbsp;</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"
+" &nbsp;&nbsp;&nbsp;%s&nbsp;&nbsp;&nbsp;</b></font></td>\n"
+" <td bgcolor='white' width='100%%'>&nbsp;</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'>&nbsp;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&nbsp;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'>&nbsp;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'>&nbsp;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&nbsp;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 &nbsp;
+ */
+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, "&nbsp;");
+ 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&nbsp;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&nbsp;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[] =
+ { "&amp;", "&lt;", "&gt;", "&quot;", "&#39;" };
+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&nbsp;%.2s&nbsp;%.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&nbsp;%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,
+ "&nbsp;&nbsp;<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&nbsp;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;
+}
+