summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/IO/IO.c412
-rw-r--r--src/IO/IO.h45
-rw-r--r--src/IO/Makefile.am14
-rw-r--r--src/IO/Url.h40
-rw-r--r--src/IO/about.c344
-rw-r--r--src/IO/dpi.c779
-rw-r--r--src/IO/http.c494
-rw-r--r--src/IO/iowatch.cc35
-rw-r--r--src/IO/iowatch.hh25
-rw-r--r--src/IO/mime.c152
-rw-r--r--src/IO/mime.h58
-rw-r--r--src/IO/proto.c13
-rw-r--r--src/Makefile.am87
-rw-r--r--src/binaryconst.h38
-rw-r--r--src/bitvec.c59
-rw-r--r--src/bitvec.h36
-rw-r--r--src/bookmark.c89
-rw-r--r--src/bookmark.h19
-rw-r--r--src/bw.c248
-rw-r--r--src/bw.h96
-rw-r--r--src/cache.c932
-rw-r--r--src/cache.h75
-rw-r--r--src/capi.c587
-rw-r--r--src/capi.h29
-rw-r--r--src/chain.c128
-rw-r--r--src/chain.h69
-rwxr-xr-xsrc/chg28
-rw-r--r--src/colors.c366
-rw-r--r--src/colors.h15
-rw-r--r--src/cookies.c332
-rw-r--r--src/cookies.h24
-rw-r--r--src/debug.h149
-rw-r--r--src/dialog.cc116
-rw-r--r--src/dialog.hh22
-rw-r--r--src/dicache.c451
-rw-r--r--src/dicache.h70
-rw-r--r--src/dillo.cc108
-rw-r--r--src/dir.c48
-rw-r--r--src/dir.h19
-rw-r--r--src/dns.c535
-rw-r--r--src/dns.h31
-rw-r--r--src/dpiapi.c82
-rw-r--r--src/dpiapi.h3
-rw-r--r--src/form.cc98
-rw-r--r--src/form.hh87
-rw-r--r--src/gif.c1054
-rw-r--r--src/history.c125
-rw-r--r--src/history.h24
-rw-r--r--src/html.cc5123
-rw-r--r--src/html.hh279
-rw-r--r--src/image.cc226
-rw-r--r--src/image.hh79
-rw-r--r--src/jpeg.c334
-rw-r--r--src/klist.c118
-rw-r--r--src/klist.h40
-rw-r--r--src/list.h49
-rw-r--r--src/menu.cc358
-rw-r--r--src/menu.hh34
-rw-r--r--src/misc.c271
-rw-r--r--src/misc.h25
-rw-r--r--src/msg.h42
-rw-r--r--src/nav.c427
-rw-r--r--src/nav.h40
-rw-r--r--src/pixmaps.h1652
-rw-r--r--src/plain.cc233
-rw-r--r--src/png.c472
-rw-r--r--src/prefs.c434
-rw-r--r--src/prefs.h130
-rwxr-xr-xsrc/srch33
-rw-r--r--src/timeout.cc46
-rw-r--r--src/timeout.hh20
-rw-r--r--src/ui.cc912
-rw-r--r--src/ui.hh109
-rw-r--r--src/uicmd.cc644
-rw-r--r--src/uicmd.hh62
-rw-r--r--src/url.c632
-rw-r--r--src/url.h144
-rw-r--r--src/web.cc175
-rw-r--r--src/web.hh45
79 files changed, 21878 insertions, 0 deletions
diff --git a/src/IO/IO.c b/src/IO/IO.c
new file mode 100644
index 00000000..060fe200
--- /dev/null
+++ b/src/IO/IO.c
@@ -0,0 +1,412 @@
+/*
+ * File: IO.c
+ *
+ * Copyright (C) 2000-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.
+ */
+
+/*
+ * Dillo's event driven IO engine
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+#include "../msg.h"
+#include "../chain.h"
+#include "../klist.h"
+#include "../list.h"
+#include "IO.h"
+#include "iowatch.hh"
+
+#define DEBUG_LEVEL 5
+//#define DEBUG_LEVEL 1
+#include "../debug.h"
+
+/*
+ * Symbolic defines for shutdown() function
+ * (Not defined in the same header file, for all distros --Jcid)
+ */
+#define IO_StopRd 0
+#define IO_StopWr 1
+#define IO_StopRdWr 2
+
+
+typedef struct {
+ int Key; /* Primary Key (for klist) */
+ int Op; /* IORead | IOWrite */
+ int FD; /* Current File Descriptor */
+ int Flags; /* Flag array (look definitions above) */
+ int Status; /* errno code */
+ Dstr *Buf; /* Internal buffer */
+
+ void *Info; /* CCC Info structure for this IO */
+ int events; /* FLTK events for this IO */
+} IOData_t;
+
+
+/*
+ * Local data
+ */
+static Klist_t *ValidIOs = NULL; /* Active IOs list. It holds pointers to
+ * IOData_t structures. */
+
+/*
+ * Forward declarations
+ */
+void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info,
+ void *Data1, void *Data2);
+
+
+/* IO API - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/*
+ * Return a newly created, and initialized, 'io' struct
+ */
+static IOData_t *IO_new(int op, int fd)
+{
+ IOData_t *io = dNew0(IOData_t, 1);
+ io->Op = op;
+ io->FD = fd;
+ io->Flags = 0;
+ io->Key = 0;
+ io->Buf = dStr_sized_new(IOBufLen);
+
+ return io;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/*
+ * Register an IO in ValidIOs
+ */
+static void IO_ins(IOData_t *io)
+{
+ if (io->Key == 0) {
+ io->Key = a_Klist_insert(&ValidIOs, io);
+ }
+}
+
+/*
+ * Remove an IO from ValidIOs
+ */
+static void IO_del(IOData_t *io)
+{
+ if (io->Key != 0) {
+ a_Klist_remove(ValidIOs, io->Key);
+ }
+ io->Key = 0;
+ _MSG(" -->ValidIOs: %d\n", a_Klist_length(ValidIOs));
+}
+
+/*
+ * Return a io by its Key (NULL if not found)
+ */
+static IOData_t *IO_get(int Key)
+{
+ return (IOData_t *)a_Klist_get_data(ValidIOs, Key);
+}
+
+/*
+ * Free an 'io' struct
+ */
+static void IO_free(IOData_t *io)
+{
+ dStr_free(io->Buf, 1);
+ dFree(io);
+}
+
+/*
+ * Close an open FD, and remove io controls.
+ * (This function can be used for Close and Abort operations)
+ */
+static void IO_close_fd(IOData_t *io, int CloseCode)
+{
+ int st;
+
+ /* With HTTP, if we close the writing part, the reading one also gets
+ * closed! (other clients may set 'IOFlag_ForceClose') */
+ if ((io->Flags & IOFlag_ForceClose) || (CloseCode == IO_StopRdWr)) {
+ do
+ st = close(io->FD);
+ while (st < 0 && errno == EINTR);
+ }
+ /* Remove this IOData_t reference, from our ValidIOs list
+ * We don't deallocate it here, just remove from the list.*/
+ IO_del(io);
+
+ /* Stop the polling on this FD */
+ a_IOwatch_remove_fd(io->FD, io->events);
+}
+
+/*
+ * Read data from a file descriptor into a specific buffer
+ */
+static bool_t IO_read(IOData_t *io)
+{
+ char Buf[IOBufLen];
+ ssize_t St;
+ bool_t ret = FALSE;
+
+ DEBUG_MSG(3, " IO_read\n");
+
+ /* this is a new read-buffer */
+ dStr_truncate(io->Buf, 0);
+ io->Status = 0;
+
+ while (1) {
+ St = read(io->FD, Buf, IOBufLen);
+ if (St > 0) {
+ dStr_append_l(io->Buf, Buf, St);
+ continue;
+ } else if (St < 0) {
+ if (errno == EINTR) {
+ continue;
+ } else if (errno == EAGAIN) {
+ ret = TRUE;
+ break;
+ } else {
+ io->Status = errno;
+ break;
+ }
+ } else { /* St == 0 */
+ break;
+ }
+ }
+
+ if (io->Buf->len > 0) {
+ /* send what we've got so far */
+ a_IO_ccc(OpSend, 2, FWD, io->Info, io, NULL);
+ }
+ if (St == 0) {
+ /* All data read (EOF) */
+ a_IO_ccc(OpEnd, 2, FWD, io->Info, io, NULL);
+ }
+ return ret;
+}
+
+/*
+ * Write data, from a specific buffer, into a file descriptor
+ */
+static bool_t IO_write(IOData_t *io)
+{
+ ssize_t St;
+ bool_t ret = FALSE;
+
+ DEBUG_MSG(3, " IO_write\n");
+ io->Status = 0;
+
+ while (1) {
+ St = write(io->FD, io->Buf->str, io->Buf->len);
+ if (St < 0) {
+ /* Error */
+ if (errno == EINTR) {
+ continue;
+ } else if (errno == EAGAIN) {
+ ret = TRUE;
+ break;
+ } else {
+ io->Status = errno;
+ break;
+ }
+ } else if (St < io->Buf->len) {
+ /* Not all data written */
+ dStr_erase (io->Buf, 0, St);
+ } else {
+ /* All data in buffer written */
+ dStr_truncate(io->Buf, 0);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * Handle background IO for a given FD (reads | writes)
+ * (This function gets called when there's activity in the FD)
+ */
+static int IO_callback(int fd, IOData_t *io)
+{
+ bool_t ret = FALSE;
+
+ _MSG("IO_callback:: (%s) FD = %d\n",
+ (io->Op == IORead) ? "IORead" : "IOWrite", io->FD);
+
+ if (io->Op == IORead) { /* Read */
+ ret = IO_read(io);
+ } else if (io->Op == IOWrite) { /* Write */
+ ret = IO_write(io);
+ }
+ return (ret) ? 1 : 0;
+}
+
+/*
+ * Handle the READ event of a FD.
+ */
+static void IO_fd_read_cb(int fd, void *data)
+{
+ int io_key = (int)data;
+ IOData_t *io = IO_get(io_key);
+
+ /* There should be no more events on already closed FDs --Jcid */
+ if (io == NULL) {
+ MSG_ERR("IO_fd_read_cb: call on already closed io!\n");
+ a_IOwatch_remove_fd(fd, DIO_READ);
+
+ } else {
+ if (IO_callback(fd, io) == 0)
+ a_IOwatch_remove_fd(fd, DIO_READ);
+ }
+}
+
+/*
+ * Handle the WRITE event of a FD.
+ */
+static void IO_fd_write_cb(int fd, void *data)
+{
+ int io_key = (int)data;
+ IOData_t *io = IO_get(io_key);
+
+ if (io == NULL) {
+ /* There must be no more events on already closed FDs --Jcid */
+ MSG_ERR("IO_fd_write_cb: call on already closed io!\n");
+ a_IOwatch_remove_fd(fd, DIO_WRITE);
+
+ } else {
+ if (IO_callback(fd, io) == 0)
+ a_IOwatch_remove_fd(fd, DIO_WRITE);
+ }
+}
+
+/*
+ * Receive an IO request (IORead | IOWrite),
+ * Set a watch for it, and let it flow!
+ */
+static void IO_submit(IOData_t *r_io)
+{
+ /* Insert this IO in ValidIOs */
+ IO_ins(r_io);
+
+ _MSG("IO_submit:: (%s) FD = %d\n",
+ (io->Op == IORead) ? "IORead" : "IOWrite", io->FD);
+
+ /* Set FD to background and to close on exec. */
+ fcntl(r_io->FD, F_SETFL, O_NONBLOCK | fcntl(r_io->FD, F_GETFL));
+ fcntl(r_io->FD, F_SETFD, FD_CLOEXEC | fcntl(r_io->FD, F_GETFD));
+
+ if (r_io->Op == IORead) {
+ r_io->events = DIO_READ;
+ a_IOwatch_add_fd(r_io->FD, r_io->events,
+ IO_fd_read_cb, (void*)(r_io->Key));
+
+ } else if (r_io->Op == IOWrite) {
+ r_io->events = DIO_WRITE;
+ a_IOwatch_add_fd(r_io->FD, r_io->events,
+ IO_fd_write_cb, (void*)(r_io->Key));
+ }
+}
+
+/*
+ * CCC function for the IO module
+ * ( Data1 = IOData_t* ; Data2 = NULL )
+ */
+void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info,
+ void *Data1, void *Data2)
+{
+ IOData_t *io;
+ DataBuf *dbuf;
+
+ a_Chain_debug_msg("a_IO_ccc", Op, Branch, Dir);
+
+ if (Branch == 1) {
+ if (Dir == BCK) {
+ /* Write data using select */
+ switch (Op) {
+ case OpStart:
+ io = IO_new(IOWrite, *(int*)Data1); /* SockFD */
+ Info->LocalKey = io;
+ break;
+ case OpSend:
+ io = Info->LocalKey;
+ dbuf = Data1;
+ dStr_append_l(io->Buf, dbuf->Buf, dbuf->Size);
+ IO_submit(io);
+ break;
+ case OpEnd:
+ case OpAbort:
+ io = Info->LocalKey;
+ if (io->Buf->len > 0) {
+ MSG_WARN("IO_write, closing with pending data not sent\n");
+ MSG_WARN(" \"%s\"\n", io->Buf->str);
+ }
+ /* close FD, remove from ValidIOs and remove its watch */
+ IO_close_fd(io, IO_StopRdWr);
+ IO_free(io);
+ dFree(Info);
+ break;
+ default:
+ MSG_WARN("Unused CCC\n");
+ break;
+ }
+ } else { /* FWD */
+ /* Write-data status */
+ switch (Op) {
+ default:
+ MSG_WARN("Unused CCC\n");
+ break;
+ }
+ }
+
+ } else if (Branch == 2) {
+ if (Dir == BCK) {
+ /* This part catches the reader's messages */
+ switch (Op) {
+ case OpStart:
+ io = IO_new(IORead, *(int*)Data2); /* SockFD */
+ Info->LocalKey = io;
+ io->Info = Info;
+ IO_submit(io);
+ break;
+ case OpAbort:
+ io = Info->LocalKey;
+ IO_close_fd(io, IO_StopRdWr);
+ IO_free(io);
+ dFree(Info);
+ break;
+ default:
+ MSG_WARN("Unused CCC\n");
+ break;
+ }
+ } else { /* FWD */
+ /* Send read-data */
+ io = Data1;
+ switch (Op) {
+ case OpSend:
+ dbuf = a_Chain_dbuf_new(io->Buf->str, io->Buf->len, 0);
+ a_Chain_fcb(OpSend, Info, dbuf, NULL);
+ dFree(dbuf);
+ break;
+ case OpEnd:
+ a_Chain_fcb(OpEnd, Info, NULL, NULL);
+ IO_close_fd(io, IO_StopRdWr);
+ IO_free(io);
+ dFree(Info);
+ break;
+ default:
+ MSG_WARN("Unused CCC\n");
+ break;
+ }
+ }
+ }
+}
+
diff --git a/src/IO/IO.h b/src/IO/IO.h
new file mode 100644
index 00000000..71ed25b4
--- /dev/null
+++ b/src/IO/IO.h
@@ -0,0 +1,45 @@
+#ifndef __IO_H__
+#define __IO_H__
+
+#include <unistd.h>
+#include <sys/uio.h>
+
+#include "d_size.h"
+#include "../../dlib/dlib.h"
+#include "../chain.h"
+#include "iowatch.hh"
+
+/*
+ * IO Operations
+ */
+#define IORead 0
+#define IOWrite 1
+#define IOClose 2
+#define IOAbort 3
+
+/*
+ * IO Flags (unused)
+ */
+#define IOFlag_ForceClose (1 << 1)
+#define IOFlag_SingleWrite (1 << 2)
+
+/*
+ * IO constants
+ */
+#define IOBufLen 8192
+
+
+/*
+ * Exported functions
+ */
+/* Note: a_IO_ccc() is defined in Url.h together with the *_ccc() set */
+
+
+/*
+ * Exported data
+ */
+extern const char *AboutSplash;
+
+
+#endif /* __IO_H__ */
+
diff --git a/src/IO/Makefile.am b/src/IO/Makefile.am
new file mode 100644
index 00000000..bff4667f
--- /dev/null
+++ b/src/IO/Makefile.am
@@ -0,0 +1,14 @@
+noinst_LIBRARIES = libDiof.a
+
+libDiof_a_SOURCES = \
+ mime.c \
+ mime.h \
+ about.c \
+ Url.h \
+ proto.c \
+ http.c \
+ dpi.c \
+ IO.c \
+ iowatch.cc \
+ iowatch.hh \
+ IO.h
diff --git a/src/IO/Url.h b/src/IO/Url.h
new file mode 100644
index 00000000..91a9c1bd
--- /dev/null
+++ b/src/IO/Url.h
@@ -0,0 +1,40 @@
+#ifndef __IO_URL_H__
+#define __IO_URL_H__
+
+#include "../chain.h"
+#include "../url.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * External functions
+ */
+extern void a_Http_freeall(void);
+int a_Http_init(void);
+int a_Http_proxy_auth(void);
+void a_Http_set_proxy_passwd(char *str);
+char *a_Http_make_query_str(const DilloUrl *url, bool_t use_proxy);
+
+void a_Http_ccc (int Op, int Branch, int Dir, ChainLink *Info,
+ void *Data1, void *Data2);
+void a_About_ccc(int Op, int Branch, int Dir, ChainLink *Info,
+ void *Data1, void *Data2);
+void a_IO_ccc (int Op, int Branch, int Dir, ChainLink *Info,
+ void *Data1, void *Data2);
+void a_Dpi_ccc (int Op, int Branch, int Dir, ChainLink *Info,
+ void *Data1, void *Data2);
+
+char *a_Dpi_send_blocking_cmd(const char *server_name, const char *cmd);
+void a_Dpi_bye_dpid(void);
+void a_Dpi_init(void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __IO_URL_H__ */
+
diff --git a/src/IO/about.c b/src/IO/about.c
new file mode 100644
index 00000000..a3a0c420
--- /dev/null
+++ b/src/IO/about.c
@@ -0,0 +1,344 @@
+/*
+ * File: about.c
+ *
+ * Copyright (C) 1999-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.
+ */
+
+#include <config.h>
+
+/*
+ * HTML text for startup screen
+ */
+const char *AboutSplash=
+"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n"
+"<html>\n"
+"<head>\n"
+"<title>Splash screen for dillo-" VERSION "</title>\n"
+"</head>\n"
+"<body bgcolor='#778899' text='#000000' link='#000000' vlink='#000000'>\n"
+"\n"
+"\n"
+"<!-- the head of the page -->\n"
+"\n"
+"<table width='100%' border='0' cellspacing='1' cellpadding='3'>\n"
+" <tr><td>\n"
+" <table border='1' cellspacing='1' cellpadding='0'>\n"
+" <tr>\n"
+" <td bgcolor='#000000'>\n"
+" <table width='100%' border='0' bgcolor='#ffffff'>\n"
+" <tr>\n"
+" <td valign='top' align='left'>\n"
+" <h1>&nbsp;Welcome to Dillo " VERSION "&nbsp;</h1>\n"
+" </table>\n"
+" </table>\n"
+"</table>\n"
+"\n"
+"<br>\n"
+"\n"
+"\n"
+"<!-- the main layout table, definition -->\n"
+"\n"
+"<table width='100%' border='0' cellspacing='0' cellpadding='0'>\n"
+"<tr><td valign='top' width='150' align='center'>\n"
+"\n"
+"\n"
+"<!-- The navigation bar -->\n"
+"\n"
+"<table border='0' cellspacing='0' cellpadding='0' width='140' bgcolor='#000000'>\n"
+"<tr>\n"
+" <td>\n"
+" <table width='100%' border='0' cellspacing='1' cellpadding='3'>\n"
+" <tr>\n"
+" <td colspan='1' bgcolor='#CCCCCC'>Dillo\n"
+" <tr>\n"
+" <td bgcolor='#FFFFFF'>\n"
+" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n"
+" <table border='0' cellspacing='0' cellpadding='2'><tr>\n"
+" <td>\n"
+" <td>\n"
+" <a href='http://www.dillo.org/dillo-help.html'>\n"
+" Help</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.dillo.org/'>Home</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.dillo.org/funding/objectives.html'>\n"
+" Objectives</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.dillo.org/ChangeLog.html'>\n"
+" ChangeLog</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.dillo.org/interview.html'>\n"
+" Interview</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.dillo.org/D_authors.html'>\n"
+" Authors</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.dillo.org/donations.html'>\n"
+" Donate</a>\n"
+" </table>\n"
+" </table>\n"
+" </table>\n"
+"</table>\n"
+"\n"
+"<br>\n"
+"\n"
+"<table border='0' cellspacing='0' cellpadding='0' width='140' bgcolor='#000000'>\n"
+"<tr>\n"
+" <td>\n"
+" <table width='100%' border='0' cellspacing='1' cellpadding='3'>\n"
+" <tr>\n"
+" <td colspan='1' bgcolor='#CCCCCC'>Magazines\n"
+"\n"
+" <tr>\n"
+" <td bgcolor='#FFFFFF'>\n"
+" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n"
+" <table border='0' cellpadding='2'>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://lwn.net/'>LWN</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://slashdot.org/'>Slashdot</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.kuro5hin.org/?op=section;section=__all__'>KuroShin</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.nexusmagazine.com/'>Nexus&nbsp;M.</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.gnu-darwin.org/update.html'>Monster News</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td>\n"
+" <a href='http://www.theregister.co.uk/index.html'>The Register</a>\n"
+" </table>\n"
+" </table>\n"
+" </table>\n"
+"</table>\n"
+"\n"
+"<br>\n"
+"\n"
+"<table border='0' cellspacing='0' cellpadding='0' width='140' bgcolor='#000000'>\n"
+"<tr>\n"
+" <td>\n"
+" <table width='100%' border='0' cellspacing='1' cellpadding='3'>\n"
+" <tr>\n"
+" <td colspan='1' bgcolor='#CCCCCC'>Additional Stuff\n"
+"\n"
+" <tr>\n"
+" <td bgcolor='#FFFFFF'>\n"
+" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n"
+" <table border='0' cellpadding='2'><tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td><a href='http://www.google.com/'>Google</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td><a href='http://www.wikipedia.org/'>Wikipedia</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td><a href='http://www.gutenberg.org/'>P. Gutenberg</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td><a href='http://freshmeat.net/'>FreshMeat</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td><a href='http://www.gnu.org/gnu/thegnuproject.html'>GNU\n"
+" project</a>\n"
+" <tr>\n"
+" <td>&nbsp;&nbsp;\n"
+" <td><a href='http://www.linuxfund.org/'>LinuxFund</a>\n"
+" </table>\n"
+" </table>\n"
+" </table>\n"
+"</table>\n"
+"\n"
+"<br>\n"
+"\n"
+"<table border='0' cellspacing='0' cellpadding='0' width='140' bgcolor='#000000'>\n"
+"<tr>\n"
+" <td>\n"
+" <table width='100%' border='0' cellspacing='1' cellpadding='3'>\n"
+" <tr>\n"
+" <td colspan='1' bgcolor='#CCCCCC'>Essential Readings\n"
+"\n"
+" <tr>\n"
+" <td bgcolor='#FFFFFF'>\n"
+" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n"
+" <table border='0' cellpadding='2'>\n"
+" <tr><td>&nbsp;&nbsp;\n"
+" <td><a href='http://www.violence.de'>Peace&amp;Violence</a>\n"
+" <tr><td>&nbsp;&nbsp;\n"
+" <td><a href='http://www.fsf.org/philosophy/right-to-read.html'>"
+" Right to Read</a>\n"
+" </table>\n"
+" </table>\n"
+" </table>\n"
+"</table>\n"
+"\n"
+"<table border='0' width='100%' cellpadding='0' cellspacing='0'><tr><td height='10'></table>\n"
+"\n"
+"\n"
+"<!-- the main layout table, a small vertical spacer -->\n"
+"\n"
+"<td width='20'><td valign='top'>\n"
+"\n"
+"\n"
+"<!-- Main Part of the page -->\n"
+"\n"
+"<table border='0' cellpadding='0' cellspacing='0' align='center' bgcolor='#000000' width='100%'><tr><td>\n"
+"<table border='0' cellpadding='5' cellspacing='1' width='100%'>\n"
+"<tr>\n"
+" <td bgcolor='#CCCCCC'>\n"
+" <h4>Free Software</h4>\n"
+"<tr>\n"
+" <td bgcolor='#FFFFFF'>\n"
+" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n"
+" <p>\n"
+" Dillo is Free Software in the terms of the GPL.\n"
+" This means you have four basic freedoms:\n"
+" <ul>\n"
+" <li>Freedom to use the program any way you see fit.\n"
+" <li>Freedom to study and modify the source code.\n"
+" <li>Freedom to make backup copies.\n"
+" <li>Freedom to redistribute it.\n"
+" </ul>\n"
+" The <a href='http://www.gnu.org/licenses/gpl.html'>GPL</a>\n"
+" is the legal mechanism that gives you these freedoms.\n"
+" It also protects them from being taken away: any derivative work\n"
+" based on the program must be under the GPL.<br>\n"
+" </table>\n"
+"</table>\n"
+"</table>\n"
+"\n"
+"<br>\n"
+"\n"
+"<table border='0' cellpadding='0' cellspacing='0' align='center' bgcolor='#000000' width='100%'><tr><td>\n"
+"<table border='0' cellpadding='5' cellspacing='1' width='100%'>\n"
+"<tr>\n"
+" <td bgcolor='#CCCCCC'>\n"
+" <h4>Release overview</h4>\n"
+" ??, 2005\n"
+"<tr>\n"
+" <td bgcolor='#FFFFFF'>\n"
+" <table border='0' cellspacing='0' cellpadding='5'>\n"
+" <tr>\n"
+" <td>\n"
+"<p>\n"
+"[...]\n"
+"<p>\n"
+"Remember that dillo project uses a release model where every new\n"
+"browser shall be better than the former.\n"
+"<EM>Keep up with the latest one!</EM>\n"
+" </table>\n"
+"</table>\n"
+"</table>\n"
+"\n"
+"<br>\n"
+"\n"
+"<table border='0' cellpadding='0' cellspacing='0' align='center' bgcolor='#000000' width='100%'><tr><td>\n"
+"<table border='0' cellpadding='5' cellspacing='1' width='100%'>\n"
+"<tr>\n"
+" <td bgcolor='#CCCCCC'>\n"
+" <h4>ChangeLog highlights</h4>\n"
+" (Extracted from the\n"
+" <a href='http://www.dillo.org/ChangeLog.html'>full\n"
+" ChangeLog</a>)\n"
+"<tr>\n"
+" <td bgcolor='#FFFFFF'>\n"
+" <table border='0' cellspacing='0' cellpadding='5'>\n"
+" <tr>\n"
+" <td>\n"
+"<ul>\n"
+"<li>[...]\n"
+"</ul>\n"
+" </table>\n"
+"</table>\n"
+"</table>\n"
+"\n"
+"<br>\n"
+"\n"
+"<table border='0' cellpadding='0' cellspacing='0' align='center' bgcolor='#000000' width='100%'><tr><td>\n"
+"<table border='0' cellpadding='5' cellspacing='1' width='100%'>\n"
+"<tr>\n"
+" <td bgcolor='#CCCCCC'>\n"
+" <h4>Notes</h4>\n"
+"<tr>\n"
+" <td bgcolor='#FFFFFF'>\n"
+" <table border='0' cellspacing='0' cellpadding='5'>\n"
+" <tr>\n"
+" <td>\n"
+"<ul>\n"
+" <li> There's a\n"
+" <a href='http://www.dillo.org/dillorc'>dillorc</a>\n"
+" (readable config) file within the tarball; It is well commented\n"
+" and has plenty of options to customize dillo, so <STRONG>copy\n"
+" it</STRONG> to your <STRONG>~/.dillo/</STRONG> directory, and\n"
+" modify to your taste.\n"
+" <li> There's documentation for developers in the <CODE>/doc</CODE>\n"
+" dir within the tarball; you can find directions on everything\n"
+" else at the home page.\n"
+" <li> Dillo has context sensitive menus using the\n"
+" right mouse button (available on pages, links, images,\n"
+" the Back and Forward buttons, and bug meter).\n"
+" <li> Dillo behaves very nicely when browsing local files, images, and HTML.\n"
+" It's also very good for Internet searching (try Google!).\n"
+" <li> This release is mainly intended <strong>for developers</strong>\n"
+" and <em>advanced users</em>.\n"
+" <li> Frames, Java and Javascript are not supported.\n"
+"</ul>\n"
+"<br>\n"
+" </table>\n"
+"</table>\n"
+"</table>\n"
+"\n"
+"<table border='0' width='100%' cellpadding='0' cellspacing='0'><tr><td height='10'></table>\n"
+"\n"
+"\n"
+"<!-- the main layout table, a small vertical spacer -->\n"
+"\n"
+"<td width='20'>\n"
+"\n"
+"\n"
+"\n"
+"<!-- The right column (info) -->\n"
+"<td valign='top' align='center'>\n"
+"\n"
+"\n"
+"\n"
+"<!-- end of the main layout table -->\n"
+"\n"
+"\n"
+"</table>\n"
+"\n"
+"<!-- footnotes -->\n"
+"\n"
+"<br><br><center>\n"
+"<hr size='2'>\n"
+"<hr size='2'>\n"
+"</center>\n"
+"</body>\n"
+"</html>\n";
+
diff --git a/src/IO/dpi.c b/src/IO/dpi.c
new file mode 100644
index 00000000..13cd1f74
--- /dev/null
+++ b/src/IO/dpi.c
@@ -0,0 +1,779 @@
+/*
+ * File: dpi.c
+ *
+ * Copyright (C) 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.
+ */
+
+/*
+ * Dillo plugins (small programs that interact with dillo)
+ *
+ * Dillo plugins are designed to handle:
+ * bookmarks, cookies, FTP, downloads, files, preferences, https,
+ * datauri and a lot of any-to-html filters.
+ */
+
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h> /* for errno */
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "../msg.h"
+#include "../klist.h"
+#include "IO.h"
+#include "Url.h"
+#include "../misc.h"
+#include "../../dpip/dpip.h"
+
+/* #define DEBUG_LEVEL 2 */
+#define DEBUG_LEVEL 4
+#include "../debug.h"
+
+/* 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))
+
+/* Solaris may not have this one... */
+#ifndef AF_LOCAL
+#define AF_LOCAL AF_UNIX
+#endif
+
+
+typedef struct {
+ int InTag;
+ int Send2EOF;
+
+ int DataTotalSize;
+ int DataRecvSize;
+
+ Dstr *Buf;
+
+ int BufIdx;
+ int TokIdx;
+ int TokSize;
+ int TokIsTag;
+
+ ChainLink *InfoRecv;
+ int Key;
+} dpi_conn_t;
+
+
+/*
+ * Local data
+ */
+static Klist_t *ValidConns = NULL; /* Active connections list. It holds
+ * pointers to dpi_conn_t structures. */
+
+
+/*
+ * Initialize local data
+ */
+void a_Dpi_init(void)
+{
+ /* empty */
+}
+
+/*
+ * Close a FD handling EINTR
+ */
+static void Dpi_close_fd(int fd)
+{
+ int st;
+
+ do
+ st = close(fd);
+ while (st < 0 && errno == EINTR);
+}
+
+/*
+ * Create a new connection data structure
+ */
+static dpi_conn_t *Dpi_conn_new(ChainLink *Info)
+{
+ dpi_conn_t *conn = dNew0(dpi_conn_t, 1);
+
+ conn->Buf = dStr_sized_new(8*1024);
+ conn->InfoRecv = Info;
+ conn->Key = a_Klist_insert(&ValidConns, conn);
+
+ return conn;
+}
+
+/*
+ * Free a connection data structure
+ */
+static void Dpi_conn_free(dpi_conn_t *conn)
+{
+ a_Klist_remove(ValidConns, conn->Key);
+ dStr_free(conn->Buf, 1);
+ dFree(conn);
+}
+
+/*
+ * Check whether a conn is still valid.
+ * Return: 1 if found, 0 otherwise
+ */
+int Dpi_conn_valid(int key)
+{
+ return (a_Klist_get_data(ValidConns, key)) ? 1 : 0;
+}
+
+/*
+ * Append the new buffer in 'dbuf' to Buf in 'conn'
+ */
+static void Dpi_append_dbuf(dpi_conn_t *conn, DataBuf *dbuf)
+{
+ if (dbuf->Code == 0 && dbuf->Size > 0) {
+ dStr_append_l(conn->Buf, dbuf->Buf, dbuf->Size);
+ }
+}
+
+/*
+ * Split the data stream into tokens.
+ * Here, a token is either:
+ * a) a dpi tag
+ * b) a raw data chunk
+ *
+ * Return Value: 0 upon a new token, -1 on not enough data.
+ *
+ * TODO: define an API and move this function into libDpip.a.
+*/
+static int Dpi_get_token(dpi_conn_t *conn)
+{
+ int i, resp = -1;
+ char *buf = conn->Buf->str;
+
+ if (conn->BufIdx == conn->Buf->len) {
+ dStr_truncate(conn->Buf, 0);
+ conn->BufIdx = 0;
+ return resp;
+ }
+
+ if (conn->Send2EOF) {
+ conn->TokIdx = conn->BufIdx;
+ conn->TokSize = conn->Buf->len - conn->BufIdx;
+ conn->BufIdx = conn->Buf->len;
+ return 0;
+ }
+
+ _MSG("conn->BufIdx = %d; conn->Buf->len = %d\nbuf: [%s]\n",
+ conn->BufIdx,conn->Buf->len, conn->Buf->str + conn->BufIdx);
+
+ if (!conn->InTag) {
+ /* search for start of tag */
+ while (conn->BufIdx < conn->Buf->len && buf[conn->BufIdx] != '<')
+ ++conn->BufIdx;
+ if (conn->BufIdx < conn->Buf->len) {
+ /* found */
+ conn->InTag = 1;
+ conn->TokIdx = conn->BufIdx;
+ } else {
+ MSG_ERR("[Dpi_get_token] Can't find token start\n");
+ }
+ }
+
+ if (conn->InTag) {
+ /* search for end of tag (EOT=" '>") */
+ for (i = conn->BufIdx; i < conn->Buf->len; ++i)
+ if (buf[i] == '>' && i >= 2 && buf[i-1] == '\'' && buf[i-2] == ' ')
+ break;
+ conn->BufIdx = i;
+
+ if (conn->BufIdx < conn->Buf->len) {
+ /* found EOT */
+ conn->TokIsTag = 1;
+ conn->TokSize = conn->BufIdx - conn->TokIdx + 1;
+ ++conn->BufIdx;
+ conn->InTag = 0;
+ resp = 0;
+ }
+ }
+
+ return resp;
+}
+
+/*
+ * Parse a dpi tag and take the appropriate actions
+ */
+static void Dpi_parse_token(dpi_conn_t *conn)
+{
+ char *tag, *cmd, *msg, *urlstr;
+ DataBuf *dbuf;
+ char *Tok = conn->Buf->str + conn->TokIdx;
+
+ if (conn->Send2EOF) {
+ /* we're receiving data chunks from a HTML page */
+ dbuf = a_Chain_dbuf_new(Tok, conn->TokSize, 0);
+ a_Chain_fcb(OpSend, conn->InfoRecv, dbuf, "send_page_2eof");
+ dFree(dbuf);
+ return;
+ }
+
+ tag = dStrndup(Tok, (size_t)conn->TokSize);
+ _MSG("Dpi_parse_token: {%s}\n", tag);
+
+ cmd = a_Dpip_get_attr(Tok, conn->TokSize, "cmd");
+ if (strcmp(cmd, "send_status_message") == 0) {
+ msg = a_Dpip_get_attr(Tok, conn->TokSize, "msg");
+ a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd);
+ dFree(msg);
+
+ } else if (strcmp(cmd, "chat") == 0) {
+ msg = a_Dpip_get_attr(Tok, conn->TokSize, "msg");
+ a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd);
+ dFree(msg);
+
+ } else if (strcmp(cmd, "dialog") == 0) {
+ /* For now will send the dpip tag... */
+ a_Chain_fcb(OpSend, conn->InfoRecv, tag, cmd);
+
+ } else if (strcmp(cmd, "start_send_page") == 0) {
+ conn->Send2EOF = 1;
+ urlstr = a_Dpip_get_attr(Tok, conn->TokSize, "url");
+ a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd);
+ dFree(urlstr);
+ /* todo: a_Dpip_get_attr(Tok, conn->TokSize, "send_mode") */
+
+ } else if (strcmp(cmd, "reload_request") == 0) {
+ urlstr = a_Dpip_get_attr(Tok, conn->TokSize, "url");
+ a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd);
+ dFree(urlstr);
+ }
+ dFree(cmd);
+
+ dFree(tag);
+}
+
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/*
+ * Get a new data buffer (within a 'dbuf'), save it into local data,
+ * split in tokens and parse the contents.
+ */
+static void Dpi_process_dbuf(int Op, void *Data1, dpi_conn_t *conn)
+{
+ DataBuf *dbuf = Data1;
+ int key = conn->Key;
+
+ /* Very useful for debugging: show the data stream as received. */
+ /* fwrite(dbuf->Buf, dbuf->Size, 1, stdout); */
+
+ if (Op == IORead) {
+ Dpi_append_dbuf(conn, dbuf);
+ /* 'conn' has to be validated because Dpi_parse_token() MAY call abort */
+ while (Dpi_conn_valid(key) && Dpi_get_token(conn) != -1) {
+ Dpi_parse_token(conn);
+ }
+
+ } else if (Op == IOClose) {
+ /* unused */
+ }
+}
+
+/*
+ * Start dpid.
+ * Return: 0 starting now, 1 Error.
+ */
+static int Dpi_start_dpid(void)
+{
+ pid_t pid;
+ int st_pipe[2], n, ret = 1;
+ char buf[16];
+
+ /* create a pipe to track our child's status */
+ if (pipe(st_pipe))
+ return 1;
+
+ pid = fork();
+ if (pid == 0) {
+ /* This is the child process. Execute the command. */
+ char *path1 = dStrconcat(dGethomedir(), "/.dillo/dpid", NULL);
+ Dpi_close_fd(st_pipe[0]);
+ if (execl(path1, "dpid", NULL) == -1) {
+ dFree(path1);
+ if (execlp("dpid", "dpid", NULL) == -1) {
+ DEBUG_MSG(4, "Dpi_start_dpid (child): %s\n", dStrerror(errno));
+ do
+ n = write(st_pipe[1], "ERROR", 5);
+ while (n == -1 && errno == EINTR);
+ Dpi_close_fd(st_pipe[1]);
+ _exit (EXIT_FAILURE);
+ }
+ }
+ } else if (pid < 0) {
+ /* The fork failed. Report failure. */
+ DEBUG_MSG(4, "Dpi_start_dpid: %s\n", dStrerror(errno));
+ /* close the unused pipe */
+ Dpi_close_fd(st_pipe[0]);
+ Dpi_close_fd(st_pipe[1]);
+
+ } else {
+ /* This is the parent process, check our child status... */
+ Dpi_close_fd(st_pipe[1]);
+ do
+ n = read(st_pipe[0], buf, 16);
+ while (n == -1 && errno == EINTR);
+ DEBUG_MSG(2, "Dpi_start_dpid: n = %d\n", n);
+ if (n != 5) {
+ ret = 0;
+ } else {
+ DEBUG_MSG(4, "Dpi_start_dpid: %s\n", dStrerror(errno));
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * Make a connection test for a UDS.
+ * Return: 0 OK, 1 Not working.
+ */
+static int Dpi_check_uds(char *uds_name)
+{
+ struct sockaddr_un pun;
+ int SockFD, ret = 1;
+
+ if (access(uds_name, W_OK) == 0) {
+ /* socket connection test */
+ memset(&pun, 0, sizeof(struct sockaddr_un));
+ pun.sun_family = AF_LOCAL;
+ strncpy(pun.sun_path, uds_name, sizeof (pun.sun_path));
+
+ if ((SockFD = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1 ||
+ connect(SockFD, (void*)&pun, D_SUN_LEN(&pun)) == -1) {
+ DEBUG_MSG(4, "Dpi_check_uds: %s %s\n", dStrerror(errno), uds_name);
+ } else {
+ Dpi_close_fd(SockFD);
+ ret = 0;
+ }
+ }
+ return ret;
+}
+
+/*
+ * Return the directory where the UDS are in,
+ * NULL if it can't be found.
+ */
+static char *Dpi_get_dpid_uds_dir(void)
+{
+ FILE *in;
+ char *saved_name_filename; /* :) */
+ char dpid_uds_dir[256], *p = NULL;
+
+ saved_name_filename =
+ dStrconcat(dGethomedir(), "/.dillo/dpi_socket_dir", NULL);
+ in = fopen(saved_name_filename, "r");
+ dFree(saved_name_filename);
+
+ if (in != NULL) {
+ fgets(dpid_uds_dir, 256, in);
+ fclose(in);
+ if ((p = strchr(dpid_uds_dir, '\n'))) {
+ *p = 0;
+ }
+ if (access(dpid_uds_dir, F_OK) == 0) {
+ p = dStrdup(dpid_uds_dir);
+ _MSG("Dpi_get_dpid_uds_dir:: %s\n", p);
+ }
+ }
+
+ _MSG("Dpi_get_dpid_uds_dir: %s \n", dStrerror(errno));
+ return p;
+}
+
+/*
+ * Return the dpid's UDS name, NULL on failure.
+ */
+static char *Dpi_get_dpid_uds_name(void)
+{
+ char *dpid_uds_dir, *dpid_uds_name = NULL;
+
+ if ((dpid_uds_dir = Dpi_get_dpid_uds_dir()) != NULL)
+ dpid_uds_name= dStrconcat(dpid_uds_dir, "/", "dpid.srs", NULL);
+
+ dFree(dpid_uds_dir);
+ return dpid_uds_name;
+}
+
+/*
+ * Confirm that the dpid is running. If not, start it.
+ * Return: 0 running OK, 1 starting (EAGAIN), 2 Error.
+ */
+static int Dpi_check_dpid(int num_tries)
+{
+ static int starting = 0;
+ char *dpid_uds_name;
+ int check_st = 1, ret = 2;
+
+ if ((dpid_uds_name = Dpi_get_dpid_uds_name()))
+ check_st = Dpi_check_uds(dpid_uds_name);
+
+ _MSG("Dpi_check_dpid: dpid_uds_name=%s, check_st=%d\n",
+ dpid_uds_name, check_st);
+
+ if (check_st == 0) {
+ /* connection test with dpi server passed */
+ starting = 0;
+ ret = 0;
+ } else if (!dpid_uds_name || check_st) {
+ if (!starting) {
+ /* start dpid */
+ if (Dpi_start_dpid() == 0) {
+ starting = 1;
+ ret = 1;
+ }
+ } else if (++starting < num_tries) {
+ ret = 1;
+ } else {
+ /* we waited too much, report an error... */
+ starting = 0;
+ }
+ }
+
+ dFree(dpid_uds_name);
+ DEBUG_MSG(2, "Dpi_check_dpid:: %s\n",
+ (ret == 0) ? "OK" : (ret == 1 ? "EAGAIN" : "ERROR"));
+ return ret;
+}
+
+/*
+ * Confirm that the dpid is running. If not, start it.
+ * Return: 0 running OK, 2 Error.
+ */
+static int Dpi_blocking_start_dpid(void)
+{
+ int cst, try = 0,
+ n_tries = 12; /* 3 seconds */
+
+ /* test the dpid, and wait a bit for it to start if necessary */
+ while ((cst = Dpi_check_dpid(n_tries)) == 1) {
+ MSG("Dpi_blocking_start_dpid: try %d\n", ++try);
+ usleep(250000); /* 1/4 sec */
+ }
+ return cst;
+}
+
+/*
+ * Return the UDS name of a dpi server.
+ * (A query is sent to dpid and then its answer parsed)
+ * note: as the available servers and/or the dpi socket directory can
+ * change at any time, we'll ask each time. If someday we find
+ * that connecting each time significantly degrades performance,
+ * an optimized approach can be tried.
+ */
+static char *Dpi_get_server_uds_name(const char *server_name)
+{
+ char *dpid_uds_dir, *dpid_uds_name = NULL,
+ *server_uds_name = NULL;
+ int st;
+
+ dReturn_val_if_fail (server_name != NULL, NULL);
+ DEBUG_MSG(2, "Dpi_get_server_uds_name:: server_name = [%s]\n", server_name);
+
+ dpid_uds_dir = Dpi_get_dpid_uds_dir();
+ if (dpid_uds_dir) {
+ struct sockaddr_un dpid;
+ int sock, req_sz, rdlen;
+ char buf[128], *cmd, *request, *rply;
+ size_t buflen;
+
+ /* Get the server's uds name from dpid */
+ sock = socket(AF_LOCAL, SOCK_STREAM, 0);
+ dpid.sun_family = AF_LOCAL;
+ dpid_uds_name = dStrconcat(dpid_uds_dir, "/", "dpid.srs", NULL);
+ _MSG("dpid_uds_name = [%s]\n", dpid_uds_name);
+ strncpy(dpid.sun_path, dpid_uds_name, sizeof(dpid.sun_path));
+
+ if (connect(sock, (struct sockaddr *) &dpid, sizeof(dpid)) == -1)
+ perror("connect");
+ /* ask dpid to check the server plugin and send its UDS name back */
+ request = a_Dpip_build_cmd("cmd=%s msg=%s", "check_server", server_name);
+ DEBUG_MSG(2, "[%s]\n", request);
+ do
+ st = write(sock, request, strlen(request));
+ while (st < 0 && errno == EINTR);
+ if (st < 0 && errno != EINTR)
+ perror("writing request");
+ dFree(request);
+ shutdown(sock, 1); /* signals no more writes to dpid */
+
+ /* Get the reply */
+ rply = NULL;
+ buf[0] = '\0';
+ buflen = sizeof(buf)/sizeof(buf[0]);
+ for (req_sz = 0; (rdlen = read(sock, buf, buflen)) != 0;
+ req_sz += rdlen) {
+ if (rdlen == -1 && errno == EINTR)
+ continue;
+ if (rdlen == -1) {
+ perror(" ** Dpi_get_server_uds_name **");
+ break;
+ }
+ rply = dRealloc(rply, (uint_t)(req_sz + rdlen + 1));
+ if (req_sz == 0)
+ rply[0] = '\0';
+ strncat(rply, buf, (size_t)rdlen);
+ }
+ Dpi_close_fd(sock);
+ DEBUG_MSG(2, "rply = [%s]\n", rply);
+
+ /* Parse reply */
+ if (rdlen == 0 && rply) {
+ cmd = a_Dpip_get_attr(rply, (int)strlen(rply), "cmd");
+ if (strcmp(cmd, "send_data") == 0)
+ server_uds_name = a_Dpip_get_attr(rply, (int)strlen(rply), "msg");
+ dFree(cmd);
+ dFree(rply);
+ }
+ }
+ dFree(dpid_uds_dir);
+ dFree(dpid_uds_name);
+ DEBUG_MSG(2, "Dpi_get_server_uds_name:: %s\n", server_uds_name);
+ return server_uds_name;
+}
+
+
+/*
+ * Connect a socket to a dpi server and return the socket's FD.
+ * We have to ask 'dpid' (dpi daemon) for the UDS of the target dpi server.
+ * Once we have it, then the proper file descriptor is returned (-1 on error).
+ */
+static int Dpi_connect_socket(const char *server_name, int retry)
+{
+ char *server_uds_name;
+ struct sockaddr_un pun;
+ int SockFD, err;
+
+ /* Query dpid for the UDS name for this server */
+ server_uds_name = Dpi_get_server_uds_name(server_name);
+ DEBUG_MSG(2, "server_uds_name = [%s]\n", server_uds_name);
+
+ if (access(server_uds_name, F_OK) != 0) {
+ MSG("server socket was NOT found\n");
+ return -1;
+ }
+
+ /* connect with this server's socket */
+ memset(&pun, 0, sizeof(struct sockaddr_un));
+ pun.sun_family = AF_LOCAL;
+ strncpy(pun.sun_path, server_uds_name, sizeof (pun.sun_path));
+ dFree(server_uds_name);
+
+ if ((SockFD = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1)
+ perror("[dpi::socket]");
+ else if (connect(SockFD, (void*)&pun, D_SUN_LEN(&pun)) == -1) {
+ err = errno;
+ SockFD = -1;
+ MSG("[dpi::connect] errno:%d %s\n", errno, dStrerror(errno));
+ if (retry) {
+ switch (err) {
+ case ECONNREFUSED: case EBADF: case ENOTSOCK: case EADDRNOTAVAIL:
+ /* the server may crash and its socket name survive */
+ unlink(pun.sun_path);
+ SockFD = Dpi_connect_socket(server_name, FALSE);
+ break;
+ }
+ }
+ }
+
+ return SockFD;
+}
+
+
+/*
+ * CCC function for the Dpi module
+ */
+void a_Dpi_ccc(int Op, int Branch, int Dir, ChainLink *Info,
+ void *Data1, void *Data2)
+{
+ dpi_conn_t *conn;
+ int SockFD = -1, st;
+
+ a_Chain_debug_msg("a_Dpi_ccc", Op, Branch, Dir);
+
+ if (Branch == 1) {
+ if (Dir == BCK) {
+ /* Send commands to dpi-server */
+ switch (Op) {
+ case OpStart:
+ if ((st = Dpi_blocking_start_dpid()) == 0) {
+ SockFD = Dpi_connect_socket(Data1, TRUE);
+ if (SockFD != -1) {
+ int *fd = dNew(int, 1);
+ *fd = SockFD;
+ Info->LocalKey = fd;
+ a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 1, 1);
+ a_Chain_bcb(OpStart, Info, Info->LocalKey, NULL);
+ /* tell the capi to start the receiving branch */
+ a_Chain_fcb(OpSend, Info, Info->LocalKey, "SockFD");
+ }
+ }
+
+ if (st == 0 && SockFD != -1) {
+ a_Chain_fcb(OpSend, Info, NULL, "DpidOK");
+ } else {
+ MSG_ERR("dpi.c: can't start dpi daemon\n");
+ a_Dpi_ccc(OpAbort, 1, FWD, Info, NULL, "DpidERROR");
+ }
+ break;
+ case OpSend:
+ a_Chain_bcb(OpSend, Info, Data1, NULL);
+ break;
+ case OpEnd:
+ a_Chain_bcb(OpEnd, Info, NULL, NULL);
+ dFree(Info->LocalKey);
+ dFree(Info);
+ break;
+ default:
+ MSG_WARN("Unused CCC\n");
+ break;
+ }
+ } else { /* FWD */
+ /* Send commands to dpi-server (status) */
+ switch (Op) {
+ case OpAbort:
+ a_Chain_fcb(OpAbort, Info, NULL, Data2);
+ dFree(Info);
+ break;
+ default:
+ MSG_WARN("Unused CCC\n");
+ break;
+ }
+ }
+
+ } else if (Branch == 2) {
+ if (Dir == FWD) {
+ /* Receiving from server */
+ switch (Op) {
+ case OpSend:
+ /* Data1 = dbuf */
+ Dpi_process_dbuf(IORead, Data1, Info->LocalKey);
+ break;
+ case OpEnd:
+ a_Chain_fcb(OpEnd, Info, NULL, NULL);
+ Dpi_conn_free(Info->LocalKey);
+ dFree(Info);
+ break;
+ default:
+ MSG_WARN("Unused CCC\n");
+ break;
+ }
+ } else { /* BCK */
+ switch (Op) {
+ case OpStart:
+ conn = Dpi_conn_new(Info);
+ Info->LocalKey = conn;
+
+ /* Hack: for receiving HTTP through the DPI framework */
+ if (strcmp(Data2, "http") == 0) {
+ conn->Send2EOF = 1;
+ }
+
+ a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 2, 2);
+ a_Chain_bcb(OpStart, Info, NULL, Data1); /* IORead, SockFD */
+ break;
+ case OpAbort:
+ a_Chain_bcb(OpAbort, Info, NULL, NULL);
+ Dpi_conn_free(Info->LocalKey);
+ dFree(Info);
+ break;
+ default:
+ MSG_WARN("Unused CCC\n");
+ break;
+ }
+ }
+ }
+}
+
+/*! Send DpiBye to dpid
+ * Note: currently disabled. Maybe it'd be better to have a
+ * dpid_idle_timeout variable in the config file.
+ */
+void a_Dpi_bye_dpid()
+{
+ char *DpiBye_cmd;
+ struct sockaddr_un sa;
+ size_t sun_path_len, addr_len;
+ char *srs_name;
+ int new_socket;
+
+ srs_name = Dpi_get_dpid_uds_name();
+ sun_path_len = sizeof(sa.sun_path);
+ addr_len = sizeof(sa);
+
+ sa.sun_family = AF_LOCAL;
+
+ if ((new_socket = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) {
+ DEBUG_MSG(4, "a_Dpi_bye_dpid: %s\n", dStrerror(errno));
+ }
+ strncpy(sa.sun_path, srs_name, sizeof (sa.sun_path));
+ if (connect(new_socket, (struct sockaddr *) &sa, addr_len) == -1) {
+ DEBUG_MSG(4, "a_Dpi_bye_dpid: %s\n", dStrerror(errno));
+ MSG("%s\n", sa.sun_path);
+ }
+ DpiBye_cmd = a_Dpip_build_cmd("cmd=%s", "DpiBye");
+ (void) write(new_socket, DpiBye_cmd, strlen(DpiBye_cmd));
+ dFree(DpiBye_cmd);
+ Dpi_close_fd(new_socket);
+}
+
+
+/*
+ * Send a command to a dpi server, and block until the answer is got.
+ * Return value: the dpip tag answer as an string, NULL on error.
+ */
+char *a_Dpi_send_blocking_cmd(const char *server_name, const char *cmd)
+{
+ int cst, SockFD;
+ ssize_t st;
+ char buf[16384], *retval = NULL;
+
+ /* test the dpid, and wait a bit for it to start if necessary */
+ if ((cst = Dpi_blocking_start_dpid()) != 0) {
+ return retval;
+ }
+
+ SockFD = Dpi_connect_socket(server_name, TRUE);
+ if (SockFD != -1) {
+ /* todo: handle the case of (st < strlen(cmd)) */
+ do
+ st = write(SockFD, cmd, strlen(cmd));
+ while (st == -1 && errno == EINTR);
+
+ /* todo: if the answer is too long... */
+ do
+ st = read(SockFD, buf, 16384);
+ while (st < 0 && errno == EINTR);
+
+ if (st == -1)
+ perror("[a_Dpi_send_blocking_cmd]");
+ else if (st > 0)
+ retval = dStrndup(buf, (size_t)st);
+
+ Dpi_close_fd(SockFD);
+
+ } else {
+ perror("[a_Dpi_send_blocking_cmd]");
+ }
+
+ return retval;
+}
+
diff --git a/src/IO/http.c b/src/IO/http.c
new file mode 100644
index 00000000..f9b483fa
--- /dev/null
+++ b/src/IO/http.c
@@ -0,0 +1,494 @@
+/*
+ * File: http.c
+ *
+ * Copyright (C) 2000, 2001 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.
+ */
+
+/*
+ * HTTP connect functions
+ */
+
+
+#include <config.h>
+
+#include <unistd.h>
+#include <errno.h> /* for errno */
+#include <stdlib.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <sys/socket.h> /* for lots of socket stuff */
+#include <netinet/in.h> /* for ntohl and stuff */
+#include <arpa/inet.h> /* for inet_ntop */
+
+#include "IO.h"
+#include "Url.h"
+#include "../msg.h"
+#include "../klist.h"
+#include "../dns.h"
+#include "../cache.h"
+#include "../web.hh"
+#include "../cookies.h"
+#include "../prefs.h"
+#include "../misc.h"
+
+#include "../uicmd.hh"
+
+/* Used to send a message to the bw's status bar */
+#define MSG_BW(web, root, ...) \
+D_STMT_START { \
+ if (a_Web_valid((web)) && (!(root) || (web)->flags & WEB_RootUrl)) \
+ a_UIcmd_set_msg((web)->bw, __VA_ARGS__); \
+} D_STMT_END
+
+#define _MSG_BW(web, root, ...)
+
+#define DEBUG_LEVEL 5
+#include "../debug.h"
+
+
+
+/* 'Url' and 'web' are just references (no need to deallocate them here). */
+typedef struct {
+ int SockFD;
+ const DilloUrl *Url; /* reference to original URL */
+ uint_t port; /* need a separate port in order to support PROXY */
+ bool_t use_proxy; /* indicates whether to use proxy or not */
+ DilloWeb *web; /* reference to client's web structure */
+ Dlist *addr_list; /* Holds the DNS answer */
+ int Err; /* Holds the errno of the connect() call */
+ ChainLink *Info; /* Used for CCC asynchronous operations */
+} SocketData_t;
+
+
+/*
+ * Local data
+ */
+static Klist_t *ValidSocks = NULL; /* Active sockets list. It holds pointers to
+ * SocketData_t structures. */
+
+static DilloUrl *HTTP_Proxy = NULL;
+static char *HTTP_Proxy_Auth_base64 = NULL;
+
+/*
+ * Initialize proxy vars.
+ */
+int a_Http_init(void)
+{
+ char *env_proxy = getenv("http_proxy");
+
+ if (env_proxy && strlen(env_proxy))
+ HTTP_Proxy = a_Url_new(env_proxy, NULL, 0, 0, 0);
+ if (!HTTP_Proxy && prefs.http_proxy)
+ HTTP_Proxy = a_Url_dup(prefs.http_proxy);
+
+/* This allows for storing the proxy password in "user:passwd" format
+ * in dillorc, but as this constitutes a security problem, it was disabled.
+ *
+ if (HTTP_Proxy && prefs.http_proxyuser && strchr(prefs.http_proxyuser, ':'))
+ HTTP_Proxy_Auth_base64 = a_Misc_encode_base64(prefs.http_proxyuser);
+ */
+ return 0;
+}
+
+/*
+ * Tell whether the proxy auth is already set (user:password)
+ * Return: 1 Yes, 0 No
+ */
+int a_Http_proxy_auth(void)
+{
+ return (HTTP_Proxy_Auth_base64 ? 1 : 0);
+}
+
+/*
+ * Activate entered proxy password for HTTP.
+ */
+void a_Http_set_proxy_passwd(char *str)
+{
+ char *http_proxyauth = dStrconcat(prefs.http_proxyuser, ":", str, NULL);
+ HTTP_Proxy_Auth_base64 = a_Misc_encode_base64(http_proxyauth);
+ dFree(http_proxyauth);
+}
+
+/*
+ * Create and init a new SocketData_t struct, insert into ValidSocks,
+ * and return a primary key for it.
+ */
+static int Http_sock_new(void)
+{
+ SocketData_t *S = dNew0(SocketData_t, 1);
+ return a_Klist_insert(&ValidSocks, S);
+}
+
+/*
+ * Free SocketData_t struct
+ */
+static void Http_socket_free(int SKey)
+{
+ SocketData_t *S;
+
+ if ((S = a_Klist_get_data(ValidSocks, SKey))) {
+ a_Klist_remove(ValidSocks, SKey);
+ dFree(S);
+ }
+}
+
+/*
+ * Close the socket's FD
+ */
+static void Http_socket_close(SocketData_t *S)
+{
+ int st;
+ do
+ st = close(S->SockFD);
+ while (st < 0 && errno == EINTR);
+}
+
+/*
+ * Make the http query string
+ */
+char *a_Http_make_query_str(const DilloUrl *url, bool_t use_proxy)
+{
+ char *str, *ptr, *cookies;
+ Dstr *s_port = dStr_new(""),
+ *query = dStr_new(""),
+ *full_path = dStr_new(""),
+ *proxy_auth = dStr_new("");
+
+ /* Sending the default port in the query may cause a 302-answer. --Jcid */
+ if (URL_PORT(url) && URL_PORT(url) != DILLO_URL_HTTP_PORT)
+ dStr_sprintfa(s_port, ":%d", URL_PORT(url));
+
+ if (use_proxy) {
+ dStr_sprintfa(full_path, "%s%s",
+ URL_STR(url),
+ (URL_PATH_(url) || URL_QUERY_(url)) ? "" : "/");
+ if ((ptr = strrchr(full_path->str, '#')))
+ dStr_truncate(full_path, ptr - full_path->str);
+ if (HTTP_Proxy_Auth_base64)
+ dStr_sprintf(proxy_auth, "Proxy-Authorization: Basic %s\r\n",
+ HTTP_Proxy_Auth_base64);
+ } else {
+ dStr_sprintfa(full_path, "%s%s%s%s",
+ URL_PATH(url),
+ URL_QUERY_(url) ? "?" : "",
+ URL_QUERY(url),
+ (URL_PATH_(url) || URL_QUERY_(url)) ? "" : "/");
+ }
+
+ cookies = a_Cookies_get(url);
+ if (URL_FLAGS(url) & URL_Post) {
+ dStr_sprintfa(
+ query,
+ "POST %s HTTP/1.0\r\n"
+ "Accept-Charset: utf-8, iso-8859-1\r\n"
+ "Host: %s%s\r\n"
+ "%s"
+ "User-Agent: Dillo/%s\r\n"
+ "Cookie2: $Version=\"1\"\r\n"
+ "%s"
+ "Content-type: application/x-www-form-urlencoded\r\n"
+ "Content-length: %ld\r\n"
+ "\r\n"
+ "%s",
+ full_path->str, URL_HOST(url), s_port->str,
+ proxy_auth->str, VERSION, cookies,
+ (long)strlen(URL_DATA(url)),
+ URL_DATA(url));
+
+ } else {
+ dStr_sprintfa(
+ query,
+ "GET %s HTTP/1.0\r\n"
+ "%s"
+ "Accept-Charset: utf-8, iso-8859-1\r\n"
+ "Host: %s%s\r\n"
+ "%s"
+ "User-Agent: Dillo/%s\r\n"
+ "Cookie2: $Version=\"1\"\r\n"
+ "%s"
+ "\r\n",
+ full_path->str,
+ (URL_FLAGS(url) & URL_E2EReload) ?
+ "Cache-Control: no-cache\r\nPragma: no-cache\r\n" : "",
+ URL_HOST(url), s_port->str,
+ proxy_auth->str,
+ VERSION,
+ cookies);
+ }
+ dFree(cookies);
+
+ str = query->str;
+ dStr_free(query, FALSE);
+ dStr_free(s_port, TRUE);
+ dStr_free(full_path, TRUE);
+ dStr_free(proxy_auth, TRUE);
+ DEBUG_MSG(4, "Query:\n%s", str);
+ return str;
+}
+
+/*
+ * Create and submit the HTTP query to the IO engine
+ */
+static void Http_send_query(ChainLink *Info, SocketData_t *S)
+{
+ char *query;
+ DataBuf *dbuf;
+
+ /* Create the query */
+ query = a_Http_make_query_str(S->Url, S->use_proxy);
+ dbuf = a_Chain_dbuf_new(query, (int)strlen(query), 0);
+
+ /* actually this message is sent too early.
+ * It should go when the socket is ready for writing (i.e. connected) */
+ _MSG_BW(S->web, 1, "Sending query to %s...", URL_HOST_(S->Url));
+
+ /* send query */
+ a_Chain_link_new(Info, a_Http_ccc, BCK, a_IO_ccc, 1, 1);
+ a_Chain_bcb(OpStart, Info, &S->SockFD, NULL);
+ a_Chain_bcb(OpSend, Info, dbuf, NULL);
+ dFree(dbuf);
+ dFree(query);
+
+ /* Tell the cache to start the receiving CCC for the answer */
+ a_Chain_fcb(OpSend, Info, &S->SockFD, "SockFD");
+}
+
+/*
+ * This function gets called after the DNS succeeds solving a hostname.
+ * Task: Finish socket setup and start connecting the socket.
+ * Return value: 0 on success; -1 on error.
+ */
+static int Http_connect_socket(ChainLink *Info)
+{
+ int status;
+#ifdef ENABLE_IPV6
+ struct sockaddr_in6 name;
+#else
+ struct sockaddr_in name;
+#endif
+ SocketData_t *S;
+ DilloHost *dh;
+ socklen_t socket_len = 0;
+
+ S = a_Klist_get_data(ValidSocks, VOIDP2INT(Info->LocalKey));
+
+ /* TODO: iterate this address list until success, or end-of-list */
+ dh = dList_nth_data(S->addr_list, 0);
+
+ if ((S->SockFD = socket(dh->af, SOCK_STREAM, IPPROTO_TCP)) < 0) {
+ S->Err = errno;
+ DEBUG_MSG(5, "Http_connect_socket ERROR: %s\n", dStrerror(errno));
+ return -1;
+ }
+ /* set NONBLOCKING and close on exec. */
+ fcntl(S->SockFD, F_SETFL, O_NONBLOCK | fcntl(S->SockFD, F_GETFL));
+ fcntl(S->SockFD, F_SETFD, FD_CLOEXEC | fcntl(S->SockFD, F_GETFD));
+
+ /* Some OSes require this... */
+ memset(&name, 0, sizeof(name));
+ /* Set remaining parms. */
+ switch (dh->af) {
+ case AF_INET:
+ {
+ struct sockaddr_in *sin = (struct sockaddr_in *)&name;
+ socket_len = sizeof(struct sockaddr_in);
+ sin->sin_family = dh->af;
+ sin->sin_port = S->port ? htons(S->port) : htons(DILLO_URL_HTTP_PORT);
+ memcpy(&sin->sin_addr, dh->data, (size_t)dh->alen);
+ if (a_Web_valid(S->web) && (S->web->flags & WEB_RootUrl))
+ DEBUG_MSG(5, "Connecting to %s\n", inet_ntoa(sin->sin_addr));
+ break;
+ }
+#ifdef ENABLE_IPV6
+ case AF_INET6:
+ {
+ char buf[128];
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&name;
+ socket_len = sizeof(struct sockaddr_in6);
+ sin6->sin6_family = dh->af;
+ sin6->sin6_port = S->port ? htons(S->port) : htons(DILLO_URL_HTTP_PORT);
+ memcpy(&sin6->sin6_addr, dh->data, dh->alen);
+ inet_ntop(dh->af, dh->data, buf, sizeof(buf));
+ if (a_Web_valid(S->web) && (S->web->flags & WEB_RootUrl))
+ DEBUG_MSG(5, "Connecting to %s\n", buf);
+ break;
+ }
+#endif
+ }
+
+ MSG_BW(S->web, 1, "Contacting host...");
+ status = connect(S->SockFD, (struct sockaddr *)&name, socket_len);
+ if (status == -1 && errno != EINPROGRESS) {
+ S->Err = errno;
+ Http_socket_close(S);
+ DEBUG_MSG(5, "Http_connect_socket ERROR: %s\n", dStrerror(S->Err));
+ return -1;
+ } else {
+ Http_send_query(S->Info, S);
+ }
+
+ return 0; /* Success */
+}
+
+/*
+ * Test proxy settings and check the no_proxy domains list
+ * Return value: whether to use proxy or not.
+ */
+static int Http_must_use_proxy(const DilloUrl *url)
+{
+ char *np, *p, *tok;
+ int ret = 0;
+
+ if (HTTP_Proxy) {
+ ret = 1;
+ if (prefs.no_proxy) {
+ np = dStrdup(prefs.no_proxy);
+ for (p = np; (tok = dStrsep(&p, " ")); ) {
+ if (dStristr(URL_AUTHORITY(url), tok)) {
+ ret = 0;
+ break;
+ }
+ }
+ dFree(np);
+ }
+ }
+ return ret;
+}
+
+/*
+ * Callback function for the DNS resolver.
+ * Continue connecting the socket, or abort upon error condition.
+ */
+void a_Http_dns_cb(int Status, Dlist *addr_list, void *data)
+{
+ int SKey = VOIDP2INT(data);
+ SocketData_t *S;
+
+ S = a_Klist_get_data(ValidSocks, SKey);
+ if (S) {
+ if (Status == 0 && addr_list) {
+ /* Successful DNS answer; save the IP */
+ S->addr_list = addr_list;
+ /* start connecting the socket */
+ if (Http_connect_socket(S->Info) < 0) {
+ MSG_BW(S->web, 1, "ERROR: %s", dStrerror(S->Err));
+ a_Chain_fcb(OpAbort, S->Info, NULL, NULL);
+ dFree(S->Info);
+ Http_socket_free(SKey);
+ }
+
+ } else {
+ /* DNS wasn't able to resolve the hostname */
+ MSG_BW(S->web, 0, "ERROR: Dns can't resolve %s",
+ (S->use_proxy) ? URL_HOST_(HTTP_Proxy) : URL_HOST_(S->Url));
+ a_Chain_fcb(OpAbort, S->Info, NULL, NULL);
+ dFree(S->Info);
+ Http_socket_free(SKey);
+ }
+ }
+}
+
+/*
+ * Asynchronously create a new http connection for 'Url'
+ * We'll set some socket parameters; the rest will be set later
+ * when the IP is known.
+ * ( Data1 = Web structure )
+ * Return value: 0 on success, -1 otherwise
+ */
+static int Http_get(ChainLink *Info, void *Data1)
+{
+ SocketData_t *S;
+ char *hostname;
+
+ S = a_Klist_get_data(ValidSocks, VOIDP2INT(Info->LocalKey));
+ /* Reference Web data */
+ S->web = Data1;
+ /* Reference URL data */
+ S->Url = S->web->url;
+ /* Reference Info data */
+ S->Info = Info;
+
+ /* Proxy support */
+ if (Http_must_use_proxy(S->Url)) {
+ hostname = dStrdup(URL_HOST(HTTP_Proxy));
+ S->port = URL_PORT(HTTP_Proxy);
+ S->use_proxy = TRUE;
+ } else {
+ hostname = dStrdup(URL_HOST(S->Url));
+ S->port = URL_PORT(S->Url);
+ S->use_proxy = FALSE;
+ }
+
+ /* Let the user know what we'll do */
+ MSG_BW(S->web, 1, "DNS resolving %s", URL_HOST_(S->Url));
+
+ /* Let the DNS engine resolve the hostname, and when done,
+ * we'll try to connect the socket from the callback function */
+ a_Dns_resolve(hostname, a_Http_dns_cb, Info->LocalKey);
+
+ dFree(hostname);
+ return 0;
+}
+
+/*
+ * CCC function for the HTTP module
+ */
+void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info,
+ void *Data1, void *Data2)
+{
+ int SKey = VOIDP2INT(Info->LocalKey);
+
+ a_Chain_debug_msg("a_Http_ccc", Op, Branch, Dir);
+
+ if (Branch == 1) {
+ if (Dir == BCK) {
+ /* HTTP query branch */
+ switch (Op) {
+ case OpStart:
+ /* ( Data1 = Web ) */
+ SKey = Http_sock_new();
+ Info->LocalKey = INT2VOIDP(SKey);
+ Http_get(Info, Data1);
+ break;
+ case OpEnd:
+ /* finished the HTTP query branch */
+ a_Chain_bcb(OpEnd, Info, NULL, NULL);
+ Http_socket_free(SKey);
+ dFree(Info);
+ break;
+ case OpAbort:
+ /* something bad happened... */
+ a_Chain_bcb(OpAbort, Info, NULL, NULL);
+ Http_socket_free(SKey);
+ dFree(Info);
+ break;
+ }
+ } else { /* FWD */
+ /* HTTP send-query status branch */
+ switch (Op) {
+ default:
+ MSG_WARN("Unused CCC\n");
+ break;
+ }
+ }
+ }
+}
+
+
+
+/*
+ * Deallocate memory used by http module
+ * (Call this one at exit time)
+ */
+void a_Http_freeall(void)
+{
+ a_Klist_free(&ValidSocks);
+ a_Url_free(HTTP_Proxy);
+ dFree(HTTP_Proxy_Auth_base64);
+}
diff --git a/src/IO/iowatch.cc b/src/IO/iowatch.cc
new file mode 100644
index 00000000..2c1465ba
--- /dev/null
+++ b/src/IO/iowatch.cc
@@ -0,0 +1,35 @@
+/*
+ * File: iowatch.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.
+ */
+
+// Simple ADT for watching file descriptor activity
+
+#include <fltk/run.h>
+#include "iowatch.hh"
+
+using namespace fltk;
+
+
+//
+// Hook a Callback for a certain activities in a FD
+//
+void a_IOwatch_add_fd(int fd, int when, FileHandler Callback, void *usr_data=0)
+{
+ add_fd(fd, when, Callback, usr_data);
+}
+
+//
+// Remove a Callback for a given FD (or just remove some events)
+//
+void a_IOwatch_remove_fd(int fd, int when)
+{
+ remove_fd(fd, when);
+}
+
diff --git a/src/IO/iowatch.hh b/src/IO/iowatch.hh
new file mode 100644
index 00000000..681d0080
--- /dev/null
+++ b/src/IO/iowatch.hh
@@ -0,0 +1,25 @@
+#ifndef __IO_WATCH_H__
+#define __IO_WATCH_H__
+
+/*
+ * BUG: enum {READ = 1, WRITE = 4, EXCEPT = 8} borrowed from fltk/run.h
+ */
+#define DIO_READ 1
+#define DIO_WRITE 4
+#define DIO_EXCEPT 8
+
+typedef void (*CbFunction_t)(int fd, void *data);
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+void a_IOwatch_add_fd(int fd,int when,CbFunction_t Callback,void *usr_data);
+void a_IOwatch_remove_fd(int fd,int when);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __IO_WATCH_H__ */
+
diff --git a/src/IO/mime.c b/src/IO/mime.c
new file mode 100644
index 00000000..3a8040ae
--- /dev/null
+++ b/src/IO/mime.c
@@ -0,0 +1,152 @@
+/*
+ * File: mime.c
+ *
+ * Copyright (C) 2000 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 "mime.h"
+#include "../msg.h"
+#include "../list.h"
+
+
+typedef struct {
+ const char *Name; /* MIME type name */
+ Viewer_t Data; /* Pointer to a function */
+} MimeItem_t;
+
+
+/*
+ * Local data
+ */
+static int MimeMinItemsSize = 0, MimeMinItemsMax = 8;
+static MimeItem_t *MimeMinItems = NULL;
+
+static int MimeMajItemsSize = 0, MimeMajItemsMax = 8;
+static MimeItem_t *MimeMajItems = NULL;
+
+
+/*
+ * Add a specific MIME type (as "image/png") to our viewer list
+ * 'Key' is the content-type string that identifies the MIME type
+ * 'Method' is the function that handles it
+ */
+static int Mime_add_minor_type(const char *Key, Viewer_t Method)
+{
+ a_List_add(MimeMinItems, MimeMinItemsSize, MimeMinItemsMax);
+ MimeMinItems[MimeMinItemsSize].Name = Key;
+ MimeMinItems[MimeMinItemsSize].Data = Method;
+ MimeMinItemsSize++;
+ return 0;
+}
+
+/*
+ * Add a major MIME type (as "text") to our viewer list
+ * 'Key' is the content-type string that identifies the MIME type
+ * 'Method' is the function that handles it
+ */
+static int Mime_add_major_type(const char *Key, Viewer_t Method)
+{
+ a_List_add(MimeMajItems, MimeMajItemsSize, MimeMajItemsMax);
+ MimeMajItems[MimeMajItemsSize].Name = Key;
+ MimeMajItems[MimeMajItemsSize].Data = Method;
+ MimeMajItemsSize++;
+ return 0;
+}
+
+/*
+ * Search the list of specific MIME viewers, for a Method that matches 'Key'
+ * 'Key' is the content-type string that identifies the MIME type
+ */
+static Viewer_t Mime_minor_type_fetch(const char *Key, uint_t Size)
+{
+ int i;
+
+ if (Size) {
+ for ( i = 0; i < MimeMinItemsSize; ++i )
+ if (dStrncasecmp(Key, MimeMinItems[i].Name, Size) == 0)
+ return MimeMinItems[i].Data;
+ }
+ return NULL;
+}
+
+/*
+ * Search the list of major MIME viewers, for a Method that matches 'Key'
+ * 'Key' is the content-type string that identifies the MIME type
+ */
+static Viewer_t Mime_major_type_fetch(const char *Key, uint_t Size)
+{
+ int i;
+
+ if (Size) {
+ for ( i = 0; i < MimeMajItemsSize; ++i )
+ if (dStrncasecmp(Key, MimeMajItems[i].Name, Size) == 0)
+ return MimeMajItems[i].Data;
+ }
+ return NULL;
+}
+
+
+/*
+ * Initializes Mime module and, sets the supported Mime types.
+ */
+void a_Mime_init()
+{
+#ifdef ENABLE_GIF
+ Mime_add_minor_type("image/gif", a_Gif_image);
+#endif
+#ifdef ENABLE_JPEG
+ Mime_add_minor_type("image/jpeg", a_Jpeg_image);
+ Mime_add_minor_type("image/pjpeg", a_Jpeg_image);
+ Mime_add_minor_type("image/jpg", a_Jpeg_image);
+#endif
+#ifdef ENABLE_PNG
+ Mime_add_minor_type("image/png", a_Png_image);
+ Mime_add_minor_type("image/x-png", a_Png_image); /* deprecated */
+#endif
+ Mime_add_minor_type("text/html", a_Html_text);
+
+ /* Add a major type to handle all the text stuff */
+ Mime_add_major_type("text", a_Plain_text);
+}
+
+
+/*
+ * Call the handler for the MIME type to set Call and Data as appropriate
+ *
+ * Return Value:
+ * On success: a new Dw (and Call and Data properly set).
+ * On failure: NULL (and Call and Data untouched).
+ */
+void *a_Mime_set_viewer(const char *content_type, void *Ptr,
+ CA_Callback_t *Call, void **Data)
+{
+
+ Viewer_t viewer;
+ uint_t MinSize, MajSize, i;
+ const char *str = content_type;
+
+ MajSize = 0;
+ for (i = 0; str[i] && str[i] != ' ' && str[i] != ';'; ++i) {
+ if (str[i] == '/' && !MajSize)
+ MajSize = i;
+ }
+ MinSize = i;
+
+ /* Try minor type */
+ viewer = Mime_minor_type_fetch(content_type, MinSize);
+ if (viewer)
+ return viewer(content_type, Ptr, Call, Data);
+
+ /* Try major type */
+ viewer = Mime_major_type_fetch(content_type, MajSize);
+ if (viewer)
+ return viewer(content_type, Ptr, Call, Data);
+
+ /* Type not handled */
+ return NULL;
+}
diff --git a/src/IO/mime.h b/src/IO/mime.h
new file mode 100644
index 00000000..0f51a1e2
--- /dev/null
+++ b/src/IO/mime.h
@@ -0,0 +1,58 @@
+/*
+ * File: mime.h
+ *
+ * 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 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef __MIME_H__
+#define __MIME_H__
+
+#include <config.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#include "../cache.h"
+
+typedef void* (*Viewer_t) (const char*, void*, CA_Callback_t*, void**);
+
+/*
+ * Function prototypes defined elsewhere
+ */
+void *a_Html_text (const char *Type,void *web, CA_Callback_t *Call,
+ void **Data);
+void *a_Plain_text(const char *Type,void *web, CA_Callback_t *Call,
+ void **Data);
+#ifdef ENABLE_JPEG
+void *a_Jpeg_image(const char *Type,void *web, CA_Callback_t *Call,
+ void **Data);
+#endif
+#ifdef ENABLE_PNG
+void *a_Png_image (const char *Type,void *web, CA_Callback_t *Call,
+ void **Data);
+#endif
+#ifdef ENABLE_GIF
+void *a_Gif_image (const char *Type,void *web, CA_Callback_t *Call,
+ void **Data);
+#endif
+
+/*
+ * Functions defined inside Mime module
+ */
+void a_Mime_init(void);
+void *a_Mime_set_viewer(const char *content_type, void *Ptr,
+ CA_Callback_t *Call, void **Data);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __MIME_H__ */
diff --git a/src/IO/proto.c b/src/IO/proto.c
new file mode 100644
index 00000000..3a9e2177
--- /dev/null
+++ b/src/IO/proto.c
@@ -0,0 +1,13 @@
+/*
+ * File: proto.c
+ *
+ * Copyright (C) 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.
+ */
+
+/* This module may be programmed to manage dpi-programs. */
+
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..a2afa02e
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,87 @@
+AM_CPPFLAGS=-DDILLORC_SYS='"$(sysconfdir)/dillorc"' @LIBJPEG_CPPFLAGS@
+AM_CFLAGS = -I../../dw-testbed @LIBPNG_CFLAGS@
+AM_CXXFLAGS = -I../../dw-testbed @LIBPNG_CFLAGS@ @LIBFLTK_CXXFLAGS@
+
+SUBDIRS = IO
+
+bin_PROGRAMS = dillo-fltk
+
+dillo_fltk_LDADD = \
+ ../dlib/libDlib.a \
+ ../dpip/libDpip.a \
+ IO/libDiof.a \
+ ../../dw-testbed/dw/libDw-widgets.a \
+ ../../dw-testbed/dw/libDw-fltk.a \
+ ../../dw-testbed/dw/libDw-core.a \
+ ../../dw-testbed/lout/liblout.a \
+ @LIBJPEG_LIBS@ @LIBPNG_LIBS@ @LIBFLTK_LIBS@ @LIBZ_LIBS@
+
+dillo_fltk_SOURCES = \
+ dillo.cc \
+ dir.c \
+ dir.h \
+ ui.cc \
+ ui.hh \
+ uicmd.cc \
+ uicmd.hh \
+ bw.h \
+ bw.c \
+ cookies.c \
+ cookies.h \
+ colors.c \
+ colors.h \
+ binaryconst.h \
+ misc.c \
+ misc.h \
+ history.h \
+ history.c \
+ prefs.c \
+ prefs.h \
+ debug.h \
+ msg.h \
+ list.h \
+ url.c \
+ url.h \
+ bitvec.c \
+ bitvec.h \
+ klist.c \
+ klist.h \
+ chain.c \
+ chain.h \
+ timeout.cc \
+ timeout.hh \
+ dialog.cc \
+ dialog.hh \
+ \
+ \
+ web.cc \
+ web.hh \
+ nav.c \
+ nav.h \
+ cache.c \
+ cache.h \
+ dicache.c \
+ dicache.h \
+ capi.c \
+ capi.h \
+ plain.cc \
+ form.cc \
+ form.hh \
+ html.cc \
+ html.hh \
+ bookmark.c \
+ bookmark.h \
+ dns.c \
+ dns.h \
+ gif.c \
+ jpeg.c \
+ png.c \
+ image.cc \
+ image.hh \
+ menu.hh \
+ menu.cc \
+ dpiapi.c \
+ dpiapi.h \
+ pixmaps.h
+
+EXTRA_DIST = chg srch
diff --git a/src/binaryconst.h b/src/binaryconst.h
new file mode 100644
index 00000000..b5b9735b
--- /dev/null
+++ b/src/binaryconst.h
@@ -0,0 +1,38 @@
+#ifndef __BINARYCONST_H__
+#define __BINARYCONST_H__
+
+/* Macros for allowing binary constants in C
+ * By Tom Torfs - donated to the public domain */
+
+#define HEX__(n) 0x##n##LU
+
+/* 8-bit conversion function */
+#define B8__(x) ((x&0x0000000FLU)?1:0) \
+ +((x&0x000000F0LU)?2:0) \
+ +((x&0x00000F00LU)?4:0) \
+ +((x&0x0000F000LU)?8:0) \
+ +((x&0x000F0000LU)?16:0) \
+ +((x&0x00F00000LU)?32:0) \
+ +((x&0x0F000000LU)?64:0) \
+ +((x&0xF0000000LU)?128:0)
+
+
+/*
+ * *** USER MACROS ***
+ */
+
+/* for upto 8-bit binary constants */
+#define B8(d) ((unsigned char)B8__(HEX__(d)))
+
+/* for upto 16-bit binary constants, MSB first */
+#define B16(dmsb,dlsb) (((unsigned short)B8(dmsb)<<8) + B8(dlsb))
+
+/*
+ * Sample usage:
+ * B8(01010101) = 85
+ * B16(10101010,01010101) = 43605
+ */
+
+
+#endif /* __BINARYCONST_H__ */
+
diff --git a/src/bitvec.c b/src/bitvec.c
new file mode 100644
index 00000000..fc308fc6
--- /dev/null
+++ b/src/bitvec.c
@@ -0,0 +1,59 @@
+/*
+ * File: bitvec.c
+ *
+ * Copyright 2001 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 simple ADT for bit arrays
+ */
+
+#include "../dlib/dlib.h"
+#include "bitvec.h"
+
+
+/*
+ * Create a new bitvec with 'num_bits' size
+ */
+bitvec_t *a_Bitvec_new(int num_bits)
+{
+ bitvec_t *bvec = dNew(bitvec_t, 1);
+
+ bvec->vec = dNew0(uchar_t, num_bits/BVEC_SIZE + 1);
+ bvec->len = num_bits;
+ return bvec;
+}
+
+/*
+ * Free a bitvec
+ */
+void a_Bitvec_free(bitvec_t *bvec)
+{
+ if (bvec) {
+ dFree(bvec->vec);
+ dFree(bvec);
+ }
+}
+
+/*
+ * Get a bit
+ */
+int a_Bitvec_get_bit(bitvec_t *bvec, int pos)
+{
+ dReturn_val_if_fail (pos < bvec->len, 0);
+ return (bvec->vec[pos/BVEC_SIZE] & 1 << pos % BVEC_SIZE);
+}
+
+/*
+ * Set a bit
+ */
+void a_Bitvec_set_bit(bitvec_t *bvec, int pos)
+{
+ dReturn_if_fail (pos < bvec->len);
+ bvec->vec[pos/BVEC_SIZE] |= 1 << (pos % BVEC_SIZE);
+}
diff --git a/src/bitvec.h b/src/bitvec.h
new file mode 100644
index 00000000..ab6797ce
--- /dev/null
+++ b/src/bitvec.h
@@ -0,0 +1,36 @@
+#ifndef __BITVEC_H__
+#define __BITVEC_H__
+
+#include "d_size.h"
+
+#define BVEC_TYPE uchar_t
+#define BVEC_SIZE sizeof(BVEC_TYPE)
+
+typedef struct _bitvec bitvec_t;
+
+struct _bitvec {
+ BVEC_TYPE *vec;
+ int len; /* number of bits [1 based] */
+};
+
+
+/*
+ * Function prototypes
+ */
+bitvec_t *a_Bitvec_new(int bits);
+void a_Bitvec_free(bitvec_t *bvec);
+int a_Bitvec_get_bit(bitvec_t *bvec, int pos);
+void a_Bitvec_set_bit(bitvec_t *bvec, int pos);
+
+/*
+#define a_Bitvec_get_bit(bvec,pos) \
+ ((bvec)->vec[(pos)/BVEC_SIZE] & 1 << (pos) % BVEC_SIZE)
+
+#define a_Bitvec_set_bit(bvec,pos) \
+ ((bvec)->vec[(pos)/BVEC_SIZE] |= 1 << (pos) % BVEC_SIZE)
+*/
+#define a_Bitvec_clear_bit(bvec,pos) \
+ ((bvec)->vec[(pos)/BVEC_SIZE] &= ~(1 << (pos) % BVEC_SIZE))
+
+
+#endif /* __BITVEC_H__ */
diff --git a/src/bookmark.c b/src/bookmark.c
new file mode 100644
index 00000000..af37bfed
--- /dev/null
+++ b/src/bookmark.c
@@ -0,0 +1,89 @@
+/*
+ * File: bookmark.c
+ *
+ * Copyright 2002 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "msg.h"
+#include "history.h"
+#include "capi.h"
+#include "bookmark.h" /* for prototypes */
+#include "../dpip/dpip.h"
+
+
+
+/*
+ * Have a short chat with the bookmarks server,
+ * and finally ask it to add a new bookmark.
+ * (this is an example of dpi chat)
+ */
+void a_Bookmarks_chat_add(BrowserWindow *Bw, char *Cmd, char *answer)
+{
+ static char *cmd1 = NULL, *cmd2 = NULL, *cmd3 = NULL, *cmd4 = NULL;
+ static BrowserWindow *bw = NULL;
+
+ if (!cmd1) {
+ cmd1 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat", "Hi server");
+ cmd2 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat",
+ "I want to set a bookmark");
+ cmd3 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat", "Sure it is!");
+ }
+
+ _MSG("a_Bookmarks_chat_add\n answer=%s\n", answer ? answer : "(null)");
+
+ if (Bw)
+ bw = Bw;
+ if (!cmd4 && Cmd)
+ cmd4 = dStrdup(Cmd);
+
+ if (!answer) {
+ a_Capi_dpi_send_cmd(NULL, bw, cmd1, "bookmarks", 1);
+
+ } else {
+ /* we have an answer */
+ if (answer) {
+ if (*answer == 'H') {
+ /* "Hi browser" */
+ a_Capi_dpi_send_cmd(NULL, bw, cmd2, "bookmarks", 0);
+ } else if (*answer == 'I') {
+ /* "Is it worth?" */
+ a_Capi_dpi_send_cmd(NULL, bw, cmd3, "bookmarks", 0);
+ } else if (*answer == 'O') {
+ /* "OK, send it!" */
+ a_Capi_dpi_send_cmd(NULL, bw, cmd4, "bookmarks", 0);
+ dFree(cmd4);
+ cmd4 = NULL;
+ }
+ }
+ }
+}
+
+/*
+ * Add the new bookmark through the bookmarks server
+ */
+void a_Bookmarks_add(BrowserWindow *bw, DilloUrl *url)
+{
+ const char *title;
+ char *cmd;
+
+ dReturn_if_fail(url != NULL);
+
+ /* if the page has no title, we'll use the url string */
+ title = a_History_get_title_by_url(url, 1);
+
+ cmd = a_Dpip_build_cmd("cmd=%s url=%s title=%s",
+ "add_bookmark", URL_STR(url), title);
+ a_Bookmarks_chat_add(bw, cmd, NULL);
+ dFree(cmd);
+}
+
diff --git a/src/bookmark.h b/src/bookmark.h
new file mode 100644
index 00000000..e1053204
--- /dev/null
+++ b/src/bookmark.h
@@ -0,0 +1,19 @@
+#ifndef __BOOKMARK_H__
+#define __BOOKMARK_H__
+
+#include "bw.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+void a_Bookmarks_add(BrowserWindow *bw, DilloUrl *url);
+
+/* todo: this is for testing purposes */
+void a_Bookmarks_chat_add(BrowserWindow *Bw, char *Cmd, char *answer);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __BOOKMARK_H__ */
diff --git a/src/bw.c b/src/bw.c
new file mode 100644
index 00000000..f207557b
--- /dev/null
+++ b/src/bw.c
@@ -0,0 +1,248 @@
+/*
+ * File: bw.c
+ *
+ * Copyright (C) 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.
+ */
+
+/* Data structures for each browser window */
+
+
+#include "bw.h"
+#include "list.h"
+#include "capi.h"
+#include "uicmd.hh"
+
+
+/*
+ * Local Data
+ */
+/* A list of working browser windows */
+static BrowserWindow **bws;
+static int num_bws, num_bws_max;
+
+
+/*
+ * Initialize global data
+ */
+void a_Bw_init(void)
+{
+ num_bws = 0;
+ num_bws_max = 16;
+ bws = NULL;
+}
+
+/*
+ * Create a new browser window and return it.
+ * (the new window is stored in browser_window[])
+ */
+BrowserWindow *a_Bw_new(int width, int height, uint32_t xid)
+{
+ BrowserWindow *bw;
+
+ /* We use dNew0() to zero the memory */
+ bw = dNew0(BrowserWindow, 1);
+ a_List_add(bws, num_bws, num_bws_max);
+ bws[num_bws++] = bw;
+
+ /* Initialize nav_stack */
+ bw->nav_stack_size = 0;
+ bw->nav_stack_size_max = 16;
+ bw->nav_stack = NULL;
+ bw->nav_stack_ptr = -1;
+ bw->nav_expecting = FALSE;
+ bw->nav_expect_url = NULL;
+
+// if (!xid)
+// bw->main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+// else
+// bw->main_window = gtk_plug_new(xid);
+
+
+ bw->redirect_level = 0;
+ bw->sens_idle_up = 0;
+
+ bw->RootClients = dList_new(8);
+ bw->ImageClients = dList_new(8);
+ bw->NumImages = 0;
+ bw->NumImagesGot = 0;
+ bw->PageUrls = dList_new(8);
+
+ bw->question_dialog_data = NULL;
+
+ bw->num_page_bugs = 0;
+ bw->page_bugs = dStr_new("");
+
+ /* now that the bw is made, let's customize it.. */
+ //Interface_browser_window_customize(bw);
+
+ return bw;
+}
+
+/*
+ * Free resources associated to a bw.
+ */
+void a_Bw_free(BrowserWindow *bw)
+{
+ int i, j;
+
+ for (i = 0; i < num_bws; i++) {
+ if (bws[i] == bw) {
+ a_List_remove(bws, i, num_bws);
+
+ dList_free(bw->RootClients);
+ dList_free(bw->ImageClients);
+
+ for (j = 0; j < dList_length(bw->PageUrls); ++j)
+ a_Url_free(dList_nth_data(bw->PageUrls, j));
+ dList_free(bw->PageUrls);
+
+ dFree(bw->nav_stack);
+ dStr_free(bw->page_bugs, 1);
+ dFree(bw);
+ break;
+ }
+ }
+}
+
+/*- Clients ----------------------------------------------------------------*/
+/*
+ * Add a reference to a cache-client. It is kept int this bw's list.
+ * This helps us keep track of which are active in the window so that it's
+ * possible to abort/stop them.
+ * (Root: Flag, whether a Root URL or not)
+ *
+ * TODO: Make NumImages count different images.
+ */
+void a_Bw_add_client(BrowserWindow *bw, int Key, int Root)
+{
+ dReturn_if_fail ( bw != NULL );
+
+ if (Root) {
+ dList_append(bw->RootClients, INT2VOIDP(Key));
+ } else {
+ dList_append(bw->ImageClients, INT2VOIDP(Key));
+ bw->NumImages++;
+ /* --Images progress-bar stuff-- */
+ a_UIcmd_set_img_prog(bw, bw->NumImagesGot, bw->NumImages, 1);
+ }
+}
+
+/*
+ * Remove the cache-client from the bw's list
+ * (client can be a image or a html page)
+ * Return: 0 if found, 1 otherwise.
+ */
+int a_Bw_remove_client(BrowserWindow *bw, int ClientKey)
+{
+ void *data;
+
+ if ((data = dList_find(bw->RootClients, INT2VOIDP(ClientKey)))) {
+ dList_remove_fast(bw->RootClients, data);
+ } else if ((data = dList_find(bw->ImageClients, INT2VOIDP(ClientKey)))) {
+ dList_remove_fast(bw->ImageClients, data);
+ ++bw->NumImagesGot;
+ }
+ return data ? 0 : 1;
+}
+
+/*
+ * Close a cache-client upon successful retrieval.
+ * Remove the cache-client from the bw list and update the meters.
+ * (client can be a image or a html page)
+ */
+void a_Bw_close_client(BrowserWindow *bw, int ClientKey)
+{
+ if (a_Bw_remove_client(bw, ClientKey) == 0) {
+ a_UIcmd_set_img_prog(bw, bw->NumImagesGot, bw->NumImages, 1);
+ if (bw->NumImagesGot == bw->NumImages)
+ a_UIcmd_set_img_prog(bw, 0, 0, 0);
+ if (dList_length(bw->RootClients) == 0)
+ a_UIcmd_set_buttons_sens(bw);
+ }
+}
+
+/*
+ * Stop the active clients of this bw's top page.
+ * Note: rendering stops, but the cache continues to be fed.
+ */
+void a_Bw_stop_clients(BrowserWindow *bw, int flags)
+{
+ void *data;
+
+ if (flags & BW_Root) {
+ /* Remove root clients */
+ while ((data = dList_nth_data(bw->RootClients, 0))) {
+ a_Capi_stop_client(VOIDP2INT(data), (flags & Bw_Force));
+ dList_remove_fast(bw->RootClients, data);
+ }
+ }
+
+ if (flags & BW_Img) {
+ /* Remove image clients */
+ while ((data = dList_nth_data(bw->ImageClients, 0))) {
+ a_Capi_stop_client(VOIDP2INT(data), (flags & Bw_Force));
+ dList_remove_fast(bw->ImageClients, data);
+ }
+ }
+}
+
+/*- PageUrls ---------------------------------------------------------------*/
+/*
+ * Add an URL to the browser window's list.
+ * This helps us keep track of page-requested URLs so that it's
+ * possible to stop, abort and reload them.
+ */
+void a_Bw_add_url(BrowserWindow *bw, const DilloUrl *Url)
+{
+ dReturn_if_fail ( bw != NULL && Url != NULL );
+
+ if (!dList_find_custom(bw->PageUrls, Url, (dCompareFunc)a_Url_cmp)) {
+ dList_append(bw->PageUrls, a_Url_dup(Url));
+ }
+}
+
+/*- Cleanup ----------------------------------------------------------------*/
+/*
+ * Empty RootClients, ImageClients and PageUrls lists and
+ * reset progress bar data.
+ */
+void a_Bw_cleanup(BrowserWindow *bw)
+{
+ void *data;
+
+ /* Remove root clients */
+ while ((data = dList_nth_data(bw->RootClients, 0))) {
+ dList_remove_fast(bw->RootClients, data);
+ }
+ /* Remove image clients */
+ while ((data = dList_nth_data(bw->ImageClients, 0))) {
+ dList_remove_fast(bw->ImageClients, data);
+ }
+ /* Remove PageUrls */
+ while ((data = dList_nth_data(bw->PageUrls, 0))) {
+ a_Url_free(data);
+ dList_remove_fast(bw->PageUrls, data);
+ }
+
+ /* Zero image-progress data */
+ bw->NumImages = 0;
+ bw->NumImagesGot = 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+/*
+ * TODO: remove this Hack.
+ */
+BrowserWindow *a_Bw_get()
+{
+ if (num_bws > 0)
+ return bws[0];
+ return NULL;
+}
+
diff --git a/src/bw.h b/src/bw.h
new file mode 100644
index 00000000..42cb97d6
--- /dev/null
+++ b/src/bw.h
@@ -0,0 +1,96 @@
+#ifndef __BW_H__
+#define __BW_H__
+
+#include <sys/types.h>
+
+#include "url.h" /* for DilloUrl */
+
+/*
+ * Flag Defines for a_Bw_stop_clients()
+ */
+#define BW_Root (1) /* Root URLs */
+#define BW_Img (2) /* Image URLs */
+#define Bw_Force (4) /* Stop connection too */
+
+
+typedef struct _BrowserWindow BrowserWindow;
+
+
+/* browser_window contains the specific data for a single window */
+struct _BrowserWindow
+{
+ /* Pointer to the UI object this bw belongs to */
+ void *ui;
+
+ /* All the rendering is done by this.
+ * It is defined as a void pointer to avoid C++ in this structure.
+ * C++ sources have to include browser.h and cast it into an object. */
+ void *render_layout;
+
+ /* A list of active cache clients in the window (The primary Key) */
+ Dlist *RootClients;
+ /* Image Keys for all active connections in the window */
+ Dlist *ImageClients;
+ /* Number of images in the page */
+ int NumImages;
+ /* Number of images already loaded */
+ int NumImagesGot;
+ /* List of all Urls requested by this page (and its types) */
+ Dlist *PageUrls;
+
+ /* The navigation stack (holds indexes to history list) */
+ int *nav_stack;
+ int nav_stack_size; /* [1 based] */
+ int nav_stack_size_max;
+ /* 'nav_stack_ptr' refers to what's being displayed */
+ int nav_stack_ptr; /* [0 based] */
+ /* When the user clicks a link, the URL isn't pushed directly to history;
+ * nav_expect_url holds it until the first answer-bytes are got. Only then
+ * it is sent to history and referenced in 'nav_stack[++nav_stack_ptr]' */
+ DilloUrl *nav_expect_url;
+ /* 'nav_expecting' is true if the last URL is being loaded for
+ * the first time and has not gotten the dw yet. */
+ bool_t nav_expecting;
+
+ /* Counter for the number of hops on a redirection. Used to stop
+ * redirection loops (accounts for WEB_RootUrl only) */
+ int redirect_level;
+
+ /* flag for button sens idle function */
+ int sens_idle_up;
+
+ /* TODO: remove me */
+ void *question_dialog_data;
+ void *question_dialog_answer;
+
+ /* TODO: maybe this fits better in the linkblock.
+ * Although having it here avoids having a signal for handling it. */
+ int num_page_bugs;
+ Dstr *page_bugs;
+};
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+void a_Bw_init(void);
+BrowserWindow *a_Bw_new(int width, int height, uint32_t xid);
+void a_Bw_free(BrowserWindow *bw);
+BrowserWindow *a_Bw_get();
+
+void a_Bw_add_client(BrowserWindow *bw, int Key, int Root);
+int a_Bw_remove_client(BrowserWindow *bw, int ClientKey);
+void a_Bw_close_client(BrowserWindow *bw, int ClientKey);
+void a_Bw_stop_clients(BrowserWindow *bw, int flags);
+void a_Bw_add_url(BrowserWindow *bw, const DilloUrl *Url);
+void a_Bw_cleanup(BrowserWindow *bw);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __BROWSER_H__ */
+
diff --git a/src/cache.c b/src/cache.c
new file mode 100644
index 00000000..efe7d0c1
--- /dev/null
+++ b/src/cache.c
@@ -0,0 +1,932 @@
+/*
+ * File: cache.c
+ *
+ * Copyright 2000, 2001, 2002, 2003, 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.
+ */
+
+/*
+ * Dillo's cache module
+ */
+
+#include <ctype.h> /* for tolower */
+#include <sys/types.h>
+
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "msg.h"
+#include "list.h"
+#include "IO/Url.h"
+#include "IO/IO.h"
+#include "web.hh"
+#include "dicache.h"
+#include "nav.h"
+#include "cookies.h"
+#include "misc.h"
+#include "capi.h"
+
+#include "timeout.hh"
+#include "uicmd.hh"
+
+#define NULLKey 0
+
+#define DEBUG_LEVEL 5
+#include "debug.h"
+
+/* Maximun initial size for the automatically-growing data buffer */
+#define MAX_INIT_BUF 1024*1024
+/* Maximum filesize for a URL, before offering a download */
+#define HUGE_FILESIZE 5*1024*1024
+
+/*
+ * Local data types
+ */
+
+typedef struct {
+ const DilloUrl *Url; /* Cached Url. Url is used as a primary Key */
+ char *TypeDet; /* MIME type string (detected from data) */
+ char *TypeHdr; /* MIME type string as from the HTTP Header */
+ Dstr *Header; /* HTTP header */
+ const DilloUrl *Location; /* New URI for redirects */
+ Dstr *Data; /* Pointer to raw data */
+ int TotalSize; /* Goal size of the whole data (0 if unknown) */
+ uint_t Flags; /* Look Flag Defines in cache.h */
+} CacheEntry_t;
+
+
+/*
+ * Local data
+ */
+/* A sorted list for cached data. Holds pointers to CacheEntry_t structs */
+static Dlist *CachedURLs;
+
+/* A list for cache clients.
+ * Although implemented as a list, we'll call it ClientQueue --Jcid */
+static Dlist *ClientQueue;
+
+/* A list for delayed clients (it holds weak pointers to cache entries,
+ * which are used to make deferred calls to Cache_process_queue) */
+static Dlist *DelayedQueue;
+static uint_t DelayedQueueIdleId = 0;
+
+
+/*
+ * Forward declarations
+ */
+static void Cache_process_queue(CacheEntry_t *entry);
+static void Cache_delayed_process_queue(CacheEntry_t *entry);
+
+
+/*
+ * Determine if two cache entries are equal (used by CachedURLs)
+ */
+static int Cache_entry_cmp(const void *v1, const void *v2)
+{
+ const CacheEntry_t *d1 = v1, *d2 = v2;
+
+ return a_Url_cmp(d1->Url, d2->Url);
+}
+
+/*
+ * Determine if two cache entries are equal, using a URL as key.
+ */
+static int Cache_entry_by_url_cmp(const void *v1, const void *v2)
+{
+ const DilloUrl *u1 = ((CacheEntry_t*)v1)->Url;
+ const DilloUrl *u2 = v2;
+
+ return a_Url_cmp(u1, u2);
+}
+
+/*
+ * Initialize dicache data
+ */
+void a_Cache_init(void)
+{
+ ClientQueue = dList_new(32);
+ DelayedQueue = dList_new(32);
+ CachedURLs = dList_new(256);
+
+ /* inject the splash screen in the cache */
+ {
+ DilloUrl *url = a_Url_new("about:splash", NULL, 0, 0, 0);
+ Dstr *ds = dStr_new(AboutSplash);
+ a_Cache_entry_inject(url, ds);
+ dStr_free(ds, 1);
+ a_Url_free(url);
+ }
+}
+
+/* Client operations ------------------------------------------------------ */
+
+/*
+ * Make a unique primary-key for cache clients
+ */
+static int Cache_client_make_key(void)
+{
+ static int ClientKey = 0; /* Provide a primary key for each client */
+
+ if (++ClientKey < 0)
+ ClientKey = 1;
+ return ClientKey;
+}
+
+/*
+ * Add a client to ClientQueue.
+ * - Every client-camp is just a reference (except 'Web').
+ * - Return a unique number for identifying the client.
+ */
+static int Cache_client_enqueue(const DilloUrl *Url, DilloWeb *Web,
+ CA_Callback_t Callback, void *CbData)
+{
+ int ClientKey;
+ CacheClient_t *NewClient;
+
+ NewClient = dNew(CacheClient_t, 1);
+ ClientKey = Cache_client_make_key();
+ NewClient->Key = ClientKey;
+ NewClient->Url = Url;
+ NewClient->Buf = NULL;
+ NewClient->Callback = Callback;
+ NewClient->CbData = CbData;
+ NewClient->Web = Web;
+
+ dList_append(ClientQueue, NewClient);
+
+ return ClientKey;
+}
+
+/*
+ * Compare function for searching a Client by its key
+ */
+static int Cache_client_by_key_cmp(const void *client, const void *key)
+{
+ return ((CacheClient_t *)client)->Key - VOIDP2INT(key);
+}
+
+/*
+ * Remove a client from the queue
+ */
+static void Cache_client_dequeue(CacheClient_t *Client, int Key)
+{
+ if (!Client) {
+ Client = dList_find_custom(ClientQueue, INT2VOIDP(Key),
+ Cache_client_by_key_cmp);
+ }
+ if (Client) {
+ dList_remove(ClientQueue, Client);
+ a_Web_free(Client->Web);
+ dFree(Client);
+ }
+}
+
+
+/* Entry operations ------------------------------------------------------- */
+
+/*
+ * Set safe values for a new cache entry
+ */
+static void Cache_entry_init(CacheEntry_t *NewEntry, const DilloUrl *Url)
+{
+ NewEntry->Url = a_Url_dup(Url);
+ NewEntry->TypeDet = NULL;
+ NewEntry->TypeHdr = NULL;
+ NewEntry->Header = dStr_new("");
+ NewEntry->Location = NULL;
+ NewEntry->Data = dStr_sized_new(8*1024);
+ NewEntry->TotalSize = 0;
+ NewEntry->Flags = 0;
+}
+
+/*
+ * Get the data structure for a cached URL (using 'Url' as the search key)
+ * If 'Url' isn't cached, return NULL
+ */
+static CacheEntry_t *Cache_entry_search(const DilloUrl *Url)
+{
+ return dList_find_sorted(CachedURLs, Url, Cache_entry_by_url_cmp);
+}
+
+/*
+ * Allocate and set a new entry in the cache list
+ */
+static CacheEntry_t *Cache_entry_add(const DilloUrl *Url)
+{
+ CacheEntry_t *old_entry, *new_entry;
+
+ if ((old_entry = Cache_entry_search(Url))) {
+ MSG_WARN("Cache_entry_add, leaking an entry.\n");
+ dList_remove(CachedURLs, old_entry);
+ }
+
+ new_entry = dNew(CacheEntry_t, 1);
+ Cache_entry_init(new_entry, Url); /* Set safe values */
+ dList_insert_sorted(CachedURLs, new_entry, Cache_entry_cmp);
+ return new_entry;
+}
+
+/*
+ * Inject full page content directly into the cache.
+ * Used for "about:splash". May be used for "about:cache" too.
+ */
+void a_Cache_entry_inject(const DilloUrl *Url, Dstr *data_ds)
+{
+ CacheEntry_t *entry;
+
+ if (!(entry = Cache_entry_search(Url)))
+ entry = Cache_entry_add(Url);
+ entry->Flags |= CA_GotData + CA_GotHeader + CA_GotLength + CA_InternalUrl;
+ dStr_truncate(entry->Data, 0);
+ dStr_append_l(entry->Data, data_ds->str, data_ds->len);
+ dStr_fit(entry->Data);
+ entry->TotalSize = entry->Data->len;
+}
+
+/*
+ * Free the components of a CacheEntry_t struct.
+ */
+static void Cache_entry_free(CacheEntry_t *entry)
+{
+ a_Url_free((DilloUrl *)entry->Url);
+ dFree(entry->TypeDet);
+ dFree(entry->TypeHdr);
+ dStr_free(entry->Header, TRUE);
+ a_Url_free((DilloUrl *)entry->Location);
+ dStr_free(entry->Data, 1);
+ dFree(entry);
+}
+
+/*
+ * Remove an entry, from the cache.
+ * All the entry clients are removed too! (it may stop rendering of this
+ * same resource on other windows, but nothing more).
+ */
+void Cache_entry_remove(CacheEntry_t *entry, DilloUrl *url)
+{
+ int i;
+ CacheClient_t *Client;
+
+ if (!entry && !(entry = Cache_entry_search(url)))
+ return;
+ if (entry->Flags & CA_InternalUrl)
+ return;
+
+ /* remove all clients for this entry */
+ for (i = 0; (Client = dList_nth_data(ClientQueue, i)); ++i) {
+ if (Client->Url == entry->Url) {
+ a_Cache_stop_client(Client->Key);
+ --i;
+ }
+ }
+
+ /* remove from DelayedQueue */
+ dList_remove(DelayedQueue, entry);
+
+ /* remove from dicache */
+ a_Dicache_invalidate_entry(entry->Url);
+
+ /* remove from cache */
+ dList_remove(CachedURLs, entry);
+ Cache_entry_free(entry);
+}
+
+/*
+ * Wrapper for capi.
+ */
+void a_Cache_entry_remove_by_url(DilloUrl *url)
+{
+ Cache_entry_remove(NULL, url);
+}
+
+/* Misc. operations ------------------------------------------------------- */
+
+/*
+ * Try finding the url in the cache. If it hits, send the cache contents
+ * from there. If it misses, set up a new connection.
+ *
+ * - 'Web' is an auxiliar data structure with misc. parameters.
+ * - 'Call' is the callback that receives the data
+ * - 'CbData' is custom data passed to 'Call'
+ * Note: 'Call' and/or 'CbData' can be NULL, in that case they get set
+ * later by a_Web_dispatch_by_type, based on content/type and 'Web' data.
+ *
+ * Return value: A primary key for identifying the client,
+ * 0 if the client is aborted in the process.
+ */
+int a_Cache_open_url(void *web, CA_Callback_t Call, void *CbData)
+{
+ int ClientKey;
+ CacheEntry_t *entry;
+ DilloWeb *Web = web;
+ DilloUrl *Url = Web->url;
+
+ if (URL_FLAGS(Url) & URL_E2EReload) {
+ /* remove current entry */
+ Cache_entry_remove(NULL, Url);
+ }
+
+ if ((entry = Cache_entry_search(Url))) {
+ /* URL is cached: feed our client with cached data */
+ ClientKey = Cache_client_enqueue(entry->Url, Web, Call, CbData);
+ Cache_delayed_process_queue(entry);
+
+ } else {
+ /* URL not cached: create an entry, send our client to the queue,
+ * and open a new connection */
+ entry = Cache_entry_add(Url);
+ ClientKey = Cache_client_enqueue(entry->Url, Web, Call, CbData);
+ }
+
+ return ClientKey;
+}
+
+/*
+ * Get the pointer to the URL document, and its size, from the cache entry.
+ * Return: 1 cached, 0 not cached.
+ */
+int a_Cache_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize)
+{
+ int i;
+ CacheEntry_t *entry;
+
+ for (i = 0; (entry = Cache_entry_search(Url)); ++i) {
+
+ /* Test for a redirection loop */
+ if (entry->Flags & CA_RedirectLoop || i == 3) {
+ MSG_WARN("Redirect loop for URL: >%s<\n", URL_STR_(Url));
+ break;
+ }
+ /* Test for a working redirection */
+ if (entry && entry->Flags & CA_Redirect && entry->Location) {
+ Url = entry->Location;
+ } else
+ break;
+ }
+
+ *BufSize = (entry) ? entry->Data->len : 0;
+ *PBuf = (entry) ? entry->Data->str : NULL;
+ return (entry ? 1 : 0);
+}
+
+/*
+ * Extract a single field from the header, allocating and storing the value
+ * in 'field'. ('fieldname' must not include the trailing ':')
+ * Return a new string with the field-content if found (NULL on error)
+ * (This function expects a '\r' stripped header)
+ */
+static char *Cache_parse_field(const char *header, const char *fieldname)
+{
+ char *field;
+ uint_t i, j;
+
+ for (i = 0; header[i]; i++) {
+ /* Search fieldname */
+ for (j = 0; fieldname[j]; j++)
+ if (tolower(fieldname[j]) != tolower(header[i + j]))
+ break;
+ if (fieldname[j]) {
+ /* skip to next line */
+ for ( i += j; header[i] != '\n'; i++);
+ continue;
+ }
+
+ i += j;
+ while (header[i] == ' ') i++;
+ if (header[i] == ':') {
+ /* Field found! */
+ while (header[++i] == ' ');
+ for (j = 0; header[i + j] != '\n'; j++);
+ field = dStrndup(header + i, j);
+ return field;
+ }
+ }
+ return NULL;
+}
+
+#ifndef DISABLE_COOKIES
+/*
+ * Extract multiple fields from the header.
+ */
+static Dlist *Cache_parse_multiple_fields(const char *header,
+ const char *fieldname)
+{
+ uint_t i, j;
+ Dlist *fields = dList_new(8);
+ char *field;
+
+ for (i = 0; header[i]; i++) {
+ /* Search fieldname */
+ for (j = 0; fieldname[j]; j++)
+ if (tolower(fieldname[j]) != tolower(header[i + j]))
+ break;
+ if (fieldname[j]) {
+ /* skip to next line */
+ for (i += j; header[i] != '\n'; i++);
+ continue;
+ }
+
+ i += j;
+ for ( ; header[i] == ' '; i++);
+ if (header[i] == ':') {
+ /* Field found! */
+ while (header[++i] == ' ');
+ for (j = 0; header[i + j] != '\n'; j++);
+ field = dStrndup(header + i, j);
+ dList_append(fields, field);
+ }
+ }
+ return fields;
+}
+#endif /* !DISABLE_COOKIES */
+
+/*
+ * Scan, allocate, and set things according to header info.
+ * (This function needs the whole header to work)
+ */
+static void Cache_parse_header(CacheEntry_t *entry,
+ const char *buf, size_t buf_size, int HdrLen)
+{
+ char *header = entry->Header->str;
+ char *Length, *Type, *location_str;
+#ifndef DISABLE_COOKIES
+ Dlist *Cookies;
+ void *data;
+ int i;
+#endif
+
+ if (HdrLen < 12) {
+ /* Not enough info. */
+
+ } if (header[9] == '3' && header[10] == '0') {
+ /* 30x: URL redirection */
+ entry->Flags |= CA_Redirect;
+ if (header[11] == '1')
+ entry->Flags |= CA_ForceRedirect; /* 301 Moved Permanently */
+ else if (header[11] == '2')
+ entry->Flags |= CA_TempRedirect; /* 302 Temporal Redirect */
+
+ location_str = Cache_parse_field(header, "Location");
+ entry->Location = a_Url_new(location_str, URL_STR_(entry->Url), 0, 0, 0);
+ dFree(location_str);
+
+ } else if (strncmp(header + 9, "404", 3) == 0) {
+ entry->Flags |= CA_NotFound;
+ }
+
+ if ((Length = Cache_parse_field(header, "Content-Length")) != NULL) {
+ entry->Flags |= CA_GotLength;
+ entry->TotalSize = strtol(Length, NULL, 10);
+ if (entry->TotalSize < 0)
+ entry->TotalSize = 0;
+ dFree(Length);
+ }
+
+#ifndef DISABLE_COOKIES
+ /* BUG: If a server feels like mixing Set-Cookie2 and Set-Cookie
+ * responses which aren't identical, then we have a problem. I don't
+ * know if that is a real issue though. */
+ if ((Cookies = Cache_parse_multiple_fields(header, "Set-Cookie2")) ||
+ (Cookies = Cache_parse_multiple_fields(header, "Set-Cookie"))) {
+ a_Cookies_set(Cookies, entry->Url);
+ for (i = 0; (data = dList_nth_data(Cookies, i)); ++i)
+ dFree(data);
+ dList_free(Cookies);
+ }
+#endif /* !DISABLE_COOKIES */
+
+ if (entry->TotalSize > 0) {
+ if (entry->TotalSize > HUGE_FILESIZE) {
+ entry->Flags |= CA_HugeFile;
+ }
+ /* Avoid some reallocs. With MAX_INIT_BUF we avoid a SEGFAULT
+ * with huge files (e.g. iso files).
+ * Note: the buffer grows automatically. */
+ dStr_free(entry->Data, 1);
+ entry->Data = dStr_sized_new(MIN(entry->TotalSize+1, MAX_INIT_BUF));
+ }
+ dStr_append_l(entry->Data, buf + HdrLen, (int)buf_size - HdrLen);
+
+ /* Get Content-Type */
+ if ((Type = Cache_parse_field(header, "Content-Type")) == NULL) {
+ MSG_HTTP("Server didn't send Content-Type in header.\n");
+ } else {
+ entry->TypeHdr = Type;
+ /* This Content-Type is not trusted. It's checked against real data
+ * in Cache_process_queue(); only then CA_GotContentType becomes true.
+ */
+ }
+}
+
+/*
+ * Consume bytes until the whole header is got (up to a "\r\n\r\n" sequence)
+ * (Also strip '\r' chars from header)
+ */
+static int Cache_get_header(CacheEntry_t *entry,
+ const char *buf, size_t buf_size)
+{
+ size_t N, i;
+ Dstr *hdr = entry->Header;
+
+ /* Header finishes when N = 2 */
+ N = (hdr->len && hdr->str[hdr->len - 1] == '\n');
+ for (i = 0; i < buf_size && N < 2; ++i) {
+ if (buf[i] == '\r' || !buf[i])
+ continue;
+ N = (buf[i] == '\n') ? N + 1 : 0;
+ dStr_append_c(hdr, buf[i]);
+ }
+
+ if (N == 2) {
+ /* Got whole header */
+ _MSG("Header [buf_size=%d]\n%s", i, hdr->str);
+ entry->Flags |= CA_GotHeader;
+ dStr_fit(hdr);
+ /* Return number of header bytes in 'buf' [1 based] */
+ return i;
+ }
+ return 0;
+}
+
+/*
+ * Receive new data, update the reception buffer (for next read), update the
+ * cache, and service the client queue.
+ *
+ * This function gets called whenever the IO has new data.
+ * 'Op' is the operation to perform
+ * 'VPtr' is a (void) pointer to the IO control structure
+ */
+void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size,
+ const DilloUrl *Url)
+{
+ int len;
+ CacheEntry_t *entry = Cache_entry_search(Url);
+
+ /* Assert a valid entry (not aborted) */
+ if (!entry)
+ return;
+
+ if (Op == IOClose) {
+ if (entry->Flags & CA_GotLength && entry->TotalSize != entry->Data->len){
+ MSG_HTTP("Content-Length does NOT match message body,\n"
+ " at: %s\n", URL_STR_(entry->Url));
+ }
+ entry->Flags |= CA_GotData;
+ entry->Flags &= ~CA_Stopped; /* it may catch up! */
+ entry->TotalSize = entry->Data->len;
+ dStr_fit(entry->Data); /* fit buffer size! */
+ Cache_process_queue(entry);
+ return;
+ } else if (Op == IOAbort) {
+ /* unused */
+ MSG("a_Cache_process_dbuf Op = IOAbort; not implemented!\n");
+ return;
+ }
+
+ if (!(entry->Flags & CA_GotHeader)) {
+ /* Haven't got the whole header yet */
+ len = Cache_get_header(entry, buf, buf_size);
+ if (entry->Flags & CA_GotHeader) {
+ /* Let's scan, allocate, and set things according to header info */
+ Cache_parse_header(entry, buf, buf_size, len);
+ /* Now that we have it parsed, let's update our clients */
+ Cache_process_queue(entry);
+ }
+ return;
+ }
+
+ dStr_append_l(entry->Data, buf, (int)buf_size);
+ Cache_process_queue(entry);
+}
+
+/*
+ * Process redirections (HTTP 30x answers)
+ * (This is a work in progress --not finished yet)
+ */
+static int Cache_redirect(CacheEntry_t *entry, int Flags, BrowserWindow *bw)
+{
+ DilloUrl *NewUrl;
+
+ _MSG(" Cache_redirect: redirect_level = %d\n", bw->redirect_level);
+
+ /* if there's a redirect loop, stop now */
+ if (bw->redirect_level >= 5)
+ entry->Flags |= CA_RedirectLoop;
+
+ if (entry->Flags & CA_RedirectLoop) {
+ a_UIcmd_set_msg(bw, "ERROR: redirect loop for: %s", URL_STR_(entry->Url));
+ bw->redirect_level = 0;
+ return 0;
+ }
+
+ if ((entry->Flags & CA_Redirect && entry->Location) &&
+ (entry->Flags & CA_ForceRedirect || entry->Flags & CA_TempRedirect ||
+ !entry->Data->len || entry->Data->len < 1024)) {
+
+ _MSG(">>>Redirect from: %s\n to %s\n",
+ URL_STR_(entry->Url), URL_STR_(entry->Location));
+ _MSG("%s", entry->Header->str);
+
+ if (Flags & WEB_RootUrl) {
+ /* Redirection of the main page */
+ NewUrl = a_Url_new(URL_STR_(entry->Location), URL_STR_(entry->Url),
+ 0, 0, 0);
+ if (entry->Flags & CA_TempRedirect)
+ a_Url_set_flags(NewUrl, URL_FLAGS(NewUrl) | URL_E2EReload);
+ a_Nav_push(bw, NewUrl);
+ a_Url_free(NewUrl);
+ } else {
+ /* Sub entity redirection (most probably an image) */
+ if (!entry->Data->len) {
+ DEBUG_MSG(3,">>>Image redirection without entity-content<<<\n");
+ } else {
+ DEBUG_MSG(3, ">>>Image redirection with entity-content<<<\n");
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+ * Check whether a URL scheme is downloadable.
+ * Return: 1 enabled, 0 disabled.
+ */
+int Cache_download_enabled(const DilloUrl *url)
+{
+ if (!dStrcasecmp(URL_SCHEME(url), "http") ||
+ !dStrcasecmp(URL_SCHEME(url), "https") ||
+ !dStrcasecmp(URL_SCHEME(url), "ftp"))
+ return 1;
+ return 0;
+}
+
+/*
+ * Don't process data any further, but let the cache fill the entry.
+ * (Currently used to handle WEB_RootUrl redirects,
+ * and to ignore image redirects --Jcid)
+ */
+static void Cache_null_client(int Op, CacheClient_t *Client)
+{
+ DilloWeb *Web = Client->Web;
+
+ /* make the stop button insensitive when done */
+ if (Op == CA_Close) {
+ if (Web->flags & WEB_RootUrl) {
+ /* Remove this client from our active list */
+ a_Bw_close_client(Web->bw, Client->Key);
+ }
+ }
+
+ /* else ignore */
+
+ return;
+}
+
+/*
+ * Update cache clients for a single cache-entry
+ * Tasks:
+ * - Set the client function (if not already set)
+ * - Look if new data is available and pass it to client functions
+ * - Remove clients when done
+ * - Call redirect handler
+ *
+ * todo: Implement CA_Abort Op in client callback
+ */
+static void Cache_process_queue(CacheEntry_t *entry)
+{
+ uint_t i;
+ int st;
+ const char *Type;
+ CacheClient_t *Client;
+ DilloWeb *ClientWeb;
+ BrowserWindow *Client_bw = NULL;
+ static bool_t Busy = FALSE;
+ bool_t AbortEntry = FALSE;
+ bool_t OfferDownload = FALSE;
+ bool_t TypeMismatch = FALSE;
+
+ if (Busy)
+ MSG_ERR("FATAL!: >>>> Cache_process_queue Caught busy!!! <<<<\n");
+ if (!(entry->Flags & CA_GotHeader))
+ return;
+ if (!(entry->Flags & CA_GotContentType)) {
+ st = a_Misc_get_content_type_from_data(
+ entry->Data->str, entry->Data->len, &Type);
+ if (st == 0 || entry->Flags & CA_GotData) {
+ if (a_Misc_content_type_check(entry->TypeHdr, Type) < 0) {
+ MSG_HTTP("Content-Type '%s' doesn't match the real data.\n",
+ entry->TypeHdr);
+ TypeMismatch = TRUE;
+ }
+ entry->TypeDet = dStrdup(Type);
+ entry->Flags |= CA_GotContentType;
+ } else
+ return; /* i.e., wait for more data */
+ }
+
+ Busy = TRUE;
+ for (i = 0; (Client = dList_nth_data(ClientQueue, i)); ++i) {
+ if (Client->Url == entry->Url) {
+ ClientWeb = Client->Web; /* It was a (void*) */
+ Client_bw = ClientWeb->bw; /* 'bw' in a local var */
+
+ if (ClientWeb->flags & WEB_RootUrl) {
+ if (!(entry->Flags & CA_MsgErased)) {
+ /* clear the "expecting for reply..." message */
+ a_UIcmd_set_msg(Client_bw, "");
+ entry->Flags |= CA_MsgErased;
+ }
+ if (TypeMismatch) {
+ a_UIcmd_set_msg(Client_bw,"HTTP warning: Content-Type '%s' "
+ "doesn't match the real data.", entry->TypeHdr);
+ }
+ if (entry->Flags & CA_Redirect) {
+ if (!Client->Callback) {
+ Client->Callback = Cache_null_client;
+ Client_bw->redirect_level++;
+ }
+ } else {
+ Client_bw->redirect_level = 0;
+ }
+ if (entry->Flags & CA_HugeFile) {
+ a_UIcmd_set_msg(Client_bw,"Huge file! (%dMB)",
+ entry->TotalSize / (1024*1024));
+ AbortEntry = OfferDownload = TRUE;
+ }
+ } else {
+ /* For non root URLs, ignore redirections and 404 answers */
+ if (entry->Flags & CA_Redirect || entry->Flags & CA_NotFound)
+ Client->Callback = Cache_null_client;
+ }
+
+ /* Set the client function */
+ if (!Client->Callback) {
+ Client->Callback = Cache_null_client;
+ if (TypeMismatch) {
+ AbortEntry = TRUE;
+ } else {
+ st = a_Web_dispatch_by_type(
+ entry->TypeHdr ? entry->TypeHdr : entry->TypeDet,
+ ClientWeb, &Client->Callback, &Client->CbData);
+ if (st == -1) {
+ /* MIME type is not viewable */
+ if (ClientWeb->flags & WEB_RootUrl) {
+ /* prepare a download offer... */
+ AbortEntry = OfferDownload = TRUE;
+ } else {
+ /* TODO: Resource Type not handled.
+ * Not aborted to avoid multiple connections on the same
+ * resource. A better idea is to abort the connection and
+ * to keep a failed-resource flag in the cache entry. */
+ }
+ }
+ }
+ if (AbortEntry) {
+ a_Bw_remove_client(Client_bw, Client->Key);
+ Cache_client_dequeue(Client, NULLKey);
+ --i; /* Keep the index value in the next iteration */
+ continue;
+ }
+ }
+
+ /* Send data to our client */
+ if ((Client->BufSize = entry->Data->len) > 0) {
+ Client->Buf = entry->Data->str;
+ (Client->Callback)(CA_Send, Client);
+ }
+
+ /* Remove client when done */
+ if (entry->Flags & CA_GotData) {
+ /* Copy flags to a local var */
+ int flags = ClientWeb->flags;
+ /* We finished sending data, let the client know */
+ (Client->Callback)(CA_Close, Client);
+ Cache_client_dequeue(Client, NULLKey);
+ --i; /* Keep the index value in the next iteration */
+
+ /* within CA_GotData, we assert just one redirect call */
+ if (entry->Flags & CA_Redirect)
+ Cache_redirect(entry, flags, Client_bw);
+ }
+ }
+ } /* for */
+
+ if (AbortEntry) {
+ /* Abort the entry, remove it from cache, and maybe offer download. */
+ DilloUrl *url = a_Url_dup(entry->Url);
+ a_Capi_conn_abort_by_url(url);
+ Cache_entry_remove(entry, NULL);
+ if (OfferDownload && Cache_download_enabled(url)) {
+ a_UIcmd_save_link(Client_bw, url);
+ }
+ a_Url_free(url);
+ }
+
+ /* Trigger cleanup when there're no cache clients */
+ if (dList_length(ClientQueue) == 0) {
+ MSG(" a_Dicache_cleanup()\n");
+ a_Dicache_cleanup();
+ }
+
+ Busy = FALSE;
+ _MSG("QueueSize ====> %d\n", dList_length(ClientQueue));
+}
+
+/*
+ * Callback function for Cache_delayed_process_queue.
+ */
+static void Cache_delayed_process_queue_callback(void *data)
+{
+ void *entry;
+
+ while ((entry = dList_nth_data(DelayedQueue, 0))) {
+ Cache_process_queue((CacheEntry_t *)entry);
+ /* note that if Cache_process_queue removes the entry,
+ * the following dList_remove has no effect. */
+ dList_remove(DelayedQueue, entry);
+ }
+ DelayedQueueIdleId = 0;
+ a_Timeout_remove();
+}
+
+/*
+ * Set a call to Cache_process_queue from the main cycle.
+ */
+static void Cache_delayed_process_queue(CacheEntry_t *entry)
+{
+ /* there's no need to repeat entries in the queue */
+ if (!dList_find(DelayedQueue, entry))
+ dList_append(DelayedQueue, entry);
+
+ if (DelayedQueueIdleId == 0) {
+ _MSG(" Setting timeout callback\n");
+ a_Timeout_add(0.0, Cache_delayed_process_queue_callback, NULL);
+ DelayedQueueIdleId = 1;
+ }
+}
+
+/*
+ * Last Client for this entry?
+ * Return: Client if true, NULL otherwise
+ * (cache.c has only one call to a capi function. This avoids a second one)
+ */
+CacheClient_t *a_Cache_client_get_if_unique(int Key)
+{
+ int i, n = 0;
+ CacheClient_t *Client, *iClient;
+
+ if ((Client = dList_find_custom(ClientQueue, INT2VOIDP(Key),
+ Cache_client_by_key_cmp))) {
+ for (i = 0; (iClient = dList_nth_data(ClientQueue, i)); ++i) {
+ if (iClient->Url == Client->Url) {
+ ++n;
+ }
+ }
+ }
+ return (n == 1) ? Client : NULL;
+}
+
+/*
+ * Remove a client from the client queue
+ * todo: notify the dicache and upper layers
+ */
+void a_Cache_stop_client(int Key)
+{
+ CacheClient_t *Client;
+
+ if ((Client = dList_find_custom(ClientQueue, INT2VOIDP(Key),
+ Cache_client_by_key_cmp))) {
+ Cache_client_dequeue(Client, NULLKey);
+ } else {
+ _MSG("WARNING: Cache_stop_client, inexistent client\n");
+ }
+}
+
+
+/*
+ * Memory deallocator (only called at exit time)
+ */
+void a_Cache_freeall(void)
+{
+ CacheClient_t *Client;
+ void *data;
+
+ /* free the client queue */
+ while ((Client = dList_nth_data(ClientQueue, 0)))
+ Cache_client_dequeue(Client, NULLKey);
+
+ /* Remove every cache entry */
+ while ((data = dList_nth_data(CachedURLs, 0))) {
+ dList_remove(CachedURLs, data);
+ Cache_entry_free(data);
+ }
+ /* Remove the cache list */
+ dList_free(CachedURLs);
+}
diff --git a/src/cache.h b/src/cache.h
new file mode 100644
index 00000000..e3891740
--- /dev/null
+++ b/src/cache.h
@@ -0,0 +1,75 @@
+#ifndef __CACHE_H__
+#define __CACHE_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#include "chain.h"
+#include "url.h"
+
+/*
+ * Cache Op codes
+ */
+#define CA_Send (0) /* Normal update */
+#define CA_Close (1) /* Successful operation close */
+#define CA_Abort (2) /* Operation abort */
+
+/*
+ * Flag Defines
+ */
+#define CA_GotHeader (1) /* True after header is completely got */
+#define CA_GotContentType (2) /* True after Content-Type is known */
+#define CA_GotLength (4) /* True if Content-Length is known */
+#define CA_GotData (8) /* True if we have all Data in cache */
+#define CA_FreeData (16) /* Free the cache Data on close */
+#define CA_Redirect (32) /* Data actually points to a redirect */
+#define CA_ForceRedirect (64) /* Unconditional redirect */
+#define CA_TempRedirect (128) /* Temporal redirect */
+#define CA_NotFound (256) /* True if remote server didn't find the URL */
+#define CA_Stopped (512) /* True if the entry has been stopped */
+#define CA_MsgErased (1024) /* Used to erase the bw's status bar */
+#define CA_RedirectLoop (2048) /* Redirect loop */
+#define CA_InternalUrl (4096) /* URL content is generated by dillo */
+#define CA_HugeFile (8192) /* URL content is too big */
+
+/*
+ * Callback type for cache clients
+ */
+typedef struct _CacheClient CacheClient_t;
+typedef void (*CA_Callback_t)(int Op, CacheClient_t *Client);
+
+/*
+ * Data structure for cache clients.
+ */
+struct _CacheClient {
+ int Key; /* Primary Key for this client */
+ const DilloUrl *Url; /* Pointer to a cache entry Url */
+ void *Buf; /* Pointer to cache-data */
+ uint_t BufSize; /* Valid size of cache-data */
+ CA_Callback_t Callback; /* Client function */
+ void *CbData; /* Client function data */
+ void *Web; /* Pointer to the Web structure of our client */
+};
+
+/*
+ * Function prototypes
+ */
+void a_Cache_init(void);
+int a_Cache_open_url(void *Web, CA_Callback_t Call, void *CbData);
+int a_Cache_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize);
+void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size,
+ const DilloUrl *Url);
+void a_Cache_entry_inject(const DilloUrl *Url, Dstr *data_ds);
+void a_Cache_entry_remove_by_url(DilloUrl *url);
+void a_Cache_freeall(void);
+CacheClient_t *a_Cache_client_get_if_unique(int Key);
+void a_Cache_stop_client(int Key);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* __CACHE_H__ */
+
diff --git a/src/capi.c b/src/capi.c
new file mode 100644
index 00000000..2b77614d
--- /dev/null
+++ b/src/capi.c
@@ -0,0 +1,587 @@
+/*
+ * File: capi.c
+ *
+ * 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.
+ */
+
+/*
+ * Cache API
+ * This is the module that manages the cache and starts the CCC chains
+ * to get the requests served. Kind of a broker.
+ */
+
+#include <string.h>
+
+#include "msg.h"
+#include "capi.h"
+#include "IO/IO.h" /* for IORead &friends */
+#include "IO/Url.h"
+#include "chain.h"
+#include "list.h"
+#include "history.h"
+#include "nav.h"
+#include "misc.h"
+#include "dpiapi.h"
+#include "uicmd.hh"
+#include "../dpip/dpip.h"
+
+/* for testing dpi chat */
+#include "bookmark.h"
+
+#define DEBUG_LEVEL 5
+#include "debug.h"
+
+typedef struct {
+ DilloUrl *url; /* local copy of web->url */
+ void *bw;
+ char *server;
+ char *datastr;
+ int SockFD;
+ int Flags;
+ ChainLink *InfoSend;
+ ChainLink *InfoRecv;
+
+ int Ref;
+} capi_conn_t;
+
+/* Flags for conn */
+enum {
+ PENDING = 1,
+ TIMEOUT = 2, /* unused */
+ ABORTED = 4
+};
+
+/*
+ * Local data
+ */
+/* Data list for active dpi connections */
+static capi_conn_t **DpiConn = NULL;
+static int DpiConnSize;
+static int DpiConnMax = 4;
+
+
+/*
+ * Forward declarations
+ */
+void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info,
+ void *Data1, void *Data2);
+
+
+/* ------------------------------------------------------------------------- */
+
+/*
+ * Initialize capi&cache data
+ */
+void a_Capi_init(void)
+{
+ /* nothing to do for capi yet, just for cache */
+ a_Cache_init();
+}
+
+/* ------------------------------------------------------------------------- */
+
+/*
+ * Create a new connection data structure
+ */
+static capi_conn_t *
+ Capi_conn_new(DilloUrl *url, void *bw, char *server, char *datastr)
+{
+ capi_conn_t *conn;
+
+ conn = dNew(capi_conn_t, 1);
+ conn->url = url ? a_Url_dup(url) : NULL;
+ conn->bw = bw;
+ conn->server = dStrdup(server);
+ conn->datastr = dStrdup(datastr);
+ conn->SockFD = -1;
+ conn->Flags = (strcmp(server, "http") != 0) ? PENDING : 0;
+ conn->InfoSend = NULL;
+ conn->InfoRecv = NULL;
+ conn->Ref = 0; /* Reference count */
+ return conn;
+}
+
+/*
+ * Increment the reference count and add to the list if not present
+ */
+static void Capi_conn_ref(capi_conn_t *conn)
+{
+ if (++conn->Ref == 1) {
+ /* add the connection data to list */
+ a_List_add(DpiConn, DpiConnSize, DpiConnMax);
+ DpiConn[DpiConnSize] = conn;
+ DpiConnSize++;
+ }
+ _MSG(" Capi_conn_ref #%d %p\n", conn->Ref, conn);
+}
+
+/*
+ * Decrement the reference count (and remove from list when zero)
+ */
+static void Capi_conn_unref(capi_conn_t *conn)
+{
+ int i, j;
+
+ _MSG(" Capi_conn_unref #%d %p\n", conn->Ref - 1, conn);
+
+ if (--conn->Ref == 0) {
+ for (i = 0; i < DpiConnSize; ++i) {
+ if (DpiConn[i] == conn) {
+ /* remove conn preserving the list order */
+ for (j = i; j + 1 < DpiConnSize; ++j)
+ DpiConn[j] = DpiConn[j + 1];
+ --DpiConnSize;
+ /* free dynamic memory */
+ a_Url_free(conn->url);
+ dFree(conn->server);
+ dFree(conn->datastr);
+ dFree(conn);
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * Find connection data by server
+ */
+static capi_conn_t *Capi_conn_find(char *server)
+{
+ int i;
+
+ for (i = 0; i < DpiConnSize; ++i)
+ if (strcmp(server, DpiConn[i]->server) == 0)
+ return DpiConn[i];
+
+ return NULL;
+}
+
+/*
+ * Resume connections that were waiting for dpid to start.
+ */
+static void Capi_conn_resume(void)
+{
+ int i;
+ DataBuf *dbuf;
+
+ for (i = 0; i < DpiConnSize; ++i) {
+ if (DpiConn[i]->Flags & PENDING) {
+ capi_conn_t *conn = DpiConn[i];
+ dbuf = a_Chain_dbuf_new(conn->datastr,(int)strlen(conn->datastr), 0);
+ a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL);
+ dFree(dbuf);
+ conn->Flags &= ~PENDING;
+ }
+ }
+}
+
+/*
+ * Abort the connection for a given url, using its CCC.
+ */
+void a_Capi_conn_abort_by_url(const DilloUrl *url)
+{
+ int i;
+
+ for (i = 0; i < DpiConnSize; ++i) {
+ if (a_Url_cmp(DpiConn[i]->url, url) == 0) {
+ capi_conn_t *conn = DpiConn[i];
+ if (conn->InfoSend) {
+ a_Capi_ccc(OpAbort, 1, BCK, conn->InfoSend, NULL, NULL);
+ }
+ if (conn->InfoRecv) {
+ a_Capi_ccc(OpAbort, 2, BCK, conn->InfoRecv, NULL, NULL);
+ }
+ break;
+ }
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+
+/*
+ * Safety test: only allow dpi-urls from dpi-generated pages.
+ */
+static int Capi_dpi_verify_request(DilloWeb *web)
+{
+ DilloUrl *referer;
+ int allow = FALSE;
+
+ /* test POST and GET */
+ if (dStrcasecmp(URL_SCHEME(web->url), "dpi") == 0 &&
+ (strchr(URL_STR(web->url), '?') || URL_DATA_(web->url))) {
+ /* only allow dpi requests from dpi-generated urls */
+ if (a_Nav_stack_size(web->bw)) {
+ referer = a_History_get_url(NAV_TOP(web->bw));
+ if (dStrcasecmp(URL_SCHEME(referer), "dpi") == 0) {
+ allow = TRUE;
+ }
+ }
+ } else {
+ allow = TRUE;
+ }
+
+ if (!allow) {
+ MSG("Capi_dpi_verify_request: Permission Denied!\n");
+ MSG(" URL_STR : %s\n", URL_STR(web->url));
+ if (URL_DATA_(web->url))
+ MSG(" URL_DATA: %s\n", URL_DATA(web->url));
+ }
+ return allow;
+}
+
+/*
+ * If the url belongs to a dpi server, return its name.
+ */
+int Capi_url_uses_dpi(DilloUrl *url, char **server_ptr)
+{
+ char *p, *server = NULL, *url_str = URL_STR(url);
+
+ if (dStrncasecmp(url_str, "dpi:/", 5) == 0) {
+ /* dpi prefix, get this server's name */
+ if ((p = strchr(url_str + 5, '/')) != NULL) {
+ server = dStrndup(url_str + 5, (uint_t)(p - url_str - 5));
+ } else {
+ server = dStrdup("?");
+ }
+ if (strcmp(server, "bm") == 0) {
+ dFree(server);
+ server = dStrdup("bookmarks");
+ }
+
+ } else if (dStrncasecmp(url_str, "ftp:/", 5) == 0) {
+ server = dStrdup("ftp");
+
+ } else if (dStrncasecmp(url_str, "https:/", 7) == 0) {
+ server = dStrdup("https");
+ } else if (dStrncasecmp(url_str, "file:", 5) == 0) {
+ server = dStrdup("file");
+ } else if (dStrncasecmp(url_str, "data:", 5) == 0) {
+ server = dStrdup("datauri");
+ }
+
+ return ((*server_ptr = server) ? 1 : 0);
+}
+
+/*
+ * Build the dpip command tag, according to URL and server.
+ * todo: make it PROXY-aware (AFAIS, it should be easy)
+ */
+static char *Capi_dpi_build_cmd(DilloWeb *web, char *server)
+{
+ char *cmd, *http_query;
+
+ if (strcmp(server, "https") == 0) {
+ /* Let's be kind and make the HTTP query string for the dpi */
+ http_query = a_Http_make_query_str(web->url, FALSE);
+ cmd = a_Dpip_build_cmd("cmd=%s url=%s query=%s",
+ "open_url", URL_STR(web->url), http_query);
+ dFree(http_query);
+
+ } else if (strcmp(server, "downloads") == 0) {
+ /* let the downloads server get it */
+ cmd = a_Dpip_build_cmd("cmd=%s url=%s destination=%s",
+ "download", URL_STR(web->url), web->filename);
+
+ } else {
+ /* For everyone else, the url string is enough... */
+ cmd = a_Dpip_build_cmd("cmd=%s url=%s", "open_url", URL_STR(web->url));
+ }
+ return cmd;
+}
+
+/*
+ * Most used function for requesting a URL.
+ * todo: clean up the ad-hoc bindings with an API that allows dynamic
+ * addition of new plugins.
+ *
+ * Return value: A primary key for identifying the client,
+ * 0 if the client is aborted in the process.
+ */
+int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData)
+{
+ capi_conn_t *conn;
+ int buf_size, reload;
+ char *cmd, *server, *buf;
+ const char *scheme = URL_SCHEME(web->url);
+ int safe = 0, ret = 0, use_cache = 0;
+
+ /* reload test */
+ reload = (!a_Capi_get_buf(web->url, &buf, &buf_size) ||
+ (URL_FLAGS(web->url) & URL_E2EReload));
+
+ if (web->flags & WEB_Download) {
+ /* donwload request: if cached save from cache, else
+ * for http, ftp or https, use the downloads dpi */
+ if (a_Capi_get_buf(web->url, &buf, &buf_size)) {
+ if (web->filename && (web->stream = fopen(web->filename, "w"))) {
+ use_cache = 1;
+ }
+ } else {
+ if (!dStrcasecmp(scheme, "https") ||
+ !dStrcasecmp(scheme, "http") ||
+ !dStrcasecmp(scheme, "ftp")) {
+ server = "downloads";
+ cmd = Capi_dpi_build_cmd(web, server);
+ a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1);
+ dFree(cmd);
+ }
+ }
+
+ } else if (Capi_url_uses_dpi(web->url, &server)) {
+ /* dpi request */
+ if ((safe = Capi_dpi_verify_request(web))) {
+ if (dStrcasecmp(scheme, "dpi") == 0) {
+ /* make "dpi:/" prefixed urls always reload. */
+ a_Url_set_flags(web->url, URL_FLAGS(web->url) | URL_E2EReload);
+ reload = 1;
+ }
+ if (reload) {
+ a_Capi_conn_abort_by_url(web->url);
+ /* Send dpip command */
+ cmd = Capi_dpi_build_cmd(web, server);
+ a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1);
+ dFree(cmd);
+ }
+ use_cache = 1;
+ }
+ dFree(server);
+
+ } else if (!dStrcasecmp(scheme, "http")) {
+ /* http request */
+ if (reload) {
+ a_Capi_conn_abort_by_url(web->url);
+ /* create a new connection and start the CCC operations */
+ conn = Capi_conn_new(web->url, web->bw, "http", "none");
+ a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, web);
+ }
+ use_cache = 1;
+
+ } else if (!dStrcasecmp(scheme, "about")) {
+ /* internal request */
+ use_cache = 1;
+ }
+
+ if (use_cache) {
+ ret = a_Cache_open_url(web, Call, CbData);
+ } else {
+ a_Web_free(web);
+ }
+ return ret;
+}
+
+/*
+ * Get the cache's buffer for the URL, and its size.
+ * Return: 1 cached, 0 not cached.
+ */
+int a_Capi_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize)
+{
+ return a_Cache_get_buf(Url, PBuf, BufSize);
+}
+
+/*
+ * Send a dpi cmd.
+ * (For instance: add_bookmark, open_url, send_preferences, ...)
+ */
+int a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server,
+ int flags)
+{
+ capi_conn_t *conn;
+ DataBuf *dbuf;
+
+ if (flags & 1) {
+ /* open a new connection to server */
+
+ /* Create a new connection data struct and add it to the list */
+ conn = Capi_conn_new(url, bw, server, cmd);
+ /* start the CCC operations */
+ a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, server);
+
+ } else {
+ /* Re-use an open connection */
+ conn = Capi_conn_find(server);
+ if (conn) {
+ /* found */
+ dbuf = a_Chain_dbuf_new(cmd, (int)strlen(cmd), 0);
+ a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL);
+ dFree(dbuf);
+ } else {
+ MSG(" ERROR: [a_Capi_dpi_send_cmd] No open connection found\n");
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Remove a client from the cache client queue.
+ * force = also abort the CCC if this is the last client.
+ */
+void a_Capi_stop_client(int Key, int force)
+{
+ CacheClient_t *Client;
+
+ if (force && (Client = a_Cache_client_get_if_unique(Key))) {
+ a_Capi_conn_abort_by_url(Client->Url);
+ }
+ a_Cache_stop_client(Key);
+}
+
+/*
+ * CCC function for the CAPI module
+ */
+void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info,
+ void *Data1, void *Data2)
+{
+ capi_conn_t *conn;
+
+ a_Chain_debug_msg("a_Capi_ccc", Op, Branch, Dir);
+
+ if (Branch == 1) {
+ if (Dir == BCK) {
+ /* Command sending branch */
+ switch (Op) {
+ case OpStart:
+ /* Data1 = conn; Data2 = {Web | server} */
+ conn = Data1;
+ Capi_conn_ref(conn);
+ Info->LocalKey = conn;
+ conn->InfoSend = Info;
+ if (strcmp(conn->server, "http") == 0) {
+ a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Http_ccc, 1, 1);
+ a_Chain_bcb(OpStart, Info, Data2, NULL);
+ } else {
+ a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 1, 1);
+ a_Chain_bcb(OpStart, Info, Data2, NULL);
+ }
+ break;
+ case OpSend:
+ /* Data1 = dbuf */
+ a_Chain_bcb(OpSend, Info, Data1, NULL);
+ break;
+ case OpEnd:
+ conn = Info->LocalKey;
+ conn->InfoSend = NULL;
+ a_Chain_bcb(OpEnd, Info, NULL, NULL);
+ Capi_conn_unref(conn);
+ dFree(Info);
+ break;
+ case OpAbort:
+ conn = Info->LocalKey;
+ conn->InfoSend = NULL;
+ a_Chain_bcb(OpAbort, Info, NULL, NULL);
+ Capi_conn_unref(conn);
+ dFree(Info);
+ break;
+ default:
+ MSG_WARN("Unused CCC\n");
+ break;
+ }
+ } else { /* FWD */
+ /* Command sending branch (status) */
+ switch (Op) {
+ case OpSend:
+ if (!Data2) {
+ MSG_WARN("Capi.c: Opsend [1F] Data2 = NULL\n");
+ } else if (strcmp(Data2, "SockFD") == 0) {
+ /* start the receiving branch */
+ capi_conn_t *conn = Info->LocalKey;
+ conn->SockFD = *(int*)Data1;
+ a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(), Info->LocalKey,
+ conn->server);
+ } else if (strcmp(Data2, "DpidOK") == 0) {
+ /* resume pending dpi requests */
+ Capi_conn_resume();
+ }
+ break;
+ case OpAbort:
+ conn = Info->LocalKey;
+ conn->InfoSend = NULL;
+ /* remove the cache entry for this URL */
+ a_Cache_entry_remove_by_url(conn->url);
+ if (Data2 && !strcmp(Data2, "DpidERROR"))
+ a_UIcmd_set_msg(conn->bw, "ERROR: can't start dpid daemon!");
+ /* finish conn */
+ Capi_conn_unref(conn);
+ dFree(Info);
+ break;
+ default:
+ MSG_WARN("Unused CCC\n");
+ break;
+ }
+ }
+
+ } else if (Branch == 2) {
+ if (Dir == BCK) {
+ /* Server listening branch (status)
+ * (Data1 = conn; Data2 = {"HttpFD" | "DpiFD"}) */
+ switch (Op) {
+ case OpStart:
+ conn = Data1;
+ Capi_conn_ref(conn);
+ Info->LocalKey = conn;
+ conn->InfoRecv = Info;
+ a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 2, 2);
+ a_Chain_bcb(OpStart, Info, &conn->SockFD, Data2);
+ break;
+ case OpAbort:
+ conn = Info->LocalKey;
+ conn->InfoRecv = NULL;
+ a_Chain_bcb(OpAbort, Info, NULL, NULL);
+ Capi_conn_unref(conn);
+ dFree(Info);
+ break;
+ default:
+ MSG_WARN("Unused CCC\n");
+ break;
+ }
+ } else { /* FWD */
+ /* Server listening branch */
+ switch (Op) {
+ case OpSend:
+ conn = Info->LocalKey;
+ if (strcmp(Data2, "send_page_2eof") == 0) {
+ /* Data1 = dbuf */
+ DataBuf *dbuf = Data1;
+ a_Cache_process_dbuf(IORead, dbuf->Buf, dbuf->Size, conn->url);
+ } else if (strcmp(Data2, "send_status_message") == 0) {
+ a_UIcmd_set_msg(conn->bw, "%s", Data1);
+ } else if (strcmp(Data2, "chat") == 0) {
+ a_UIcmd_set_msg(conn->bw, "%s", Data1);
+ a_Bookmarks_chat_add(NULL, NULL, Data1);
+ } else if (strcmp(Data2, "dialog") == 0) {
+ a_Dpiapi_dialog(conn->bw, conn->server, Data1);
+ } else if (strcmp(Data2, "reload_request") == 0) {
+ a_Nav_reload(conn->bw);
+ } else if (strcmp(Data2, "start_send_page") == 0) {
+ /* prepare the cache to receive the data stream for this URL
+ *
+ * a_Capi_open_url() already added a new cache entry,
+ * and a client for it.
+ */
+ }
+ break;
+ case OpEnd:
+ conn = Info->LocalKey;
+ conn->InfoRecv = NULL;
+
+ a_Cache_process_dbuf(IOClose, NULL, 0, conn->url);
+
+ if (conn->InfoSend) {
+ /* Propagate OpEnd to the sending branch too */
+ a_Capi_ccc(OpEnd, 1, BCK, conn->InfoSend, NULL, NULL);
+ }
+ Capi_conn_unref(conn);
+ dFree(Info);
+ break;
+ default:
+ MSG_WARN("Unused CCC\n");
+ break;
+ }
+ }
+ }
+}
diff --git a/src/capi.h b/src/capi.h
new file mode 100644
index 00000000..e61d815b
--- /dev/null
+++ b/src/capi.h
@@ -0,0 +1,29 @@
+#ifndef __CAPI_H__
+#define __CAPI_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#include "cache.h"
+#include "web.hh"
+
+/*
+ * Function prototypes
+ */
+void a_Capi_init(void);
+int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData);
+int a_Capi_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize);
+int a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server,
+ int flags);
+void a_Capi_stop_client(int Key, int force);
+void a_Capi_conn_abort_by_url(const DilloUrl *url);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __CAPI_H__ */
+
diff --git a/src/chain.c b/src/chain.c
new file mode 100644
index 00000000..0324687d
--- /dev/null
+++ b/src/chain.c
@@ -0,0 +1,128 @@
+/*
+ * File: chain.c
+ * Concomitant control chain (CCC)
+ * Theory and code by Jorge Arellano Cid
+ *
+ * Copyright 2001, 2002 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 "msg.h"
+#include "chain.h"
+#include "../dlib/dlib.h"
+
+#define VERBOSE 0
+
+/*
+ * Create and initialize a new chain-link
+ */
+ChainLink *a_Chain_new(void)
+{
+ return dNew0(ChainLink, 1);
+}
+
+/*
+ * Create a new link from module A to module B.
+ * 'Direction' tells whether to make a forward or backward link.
+ * => The link from 'A' to 'B' has 'Direction' direction.
+ * => The main flow of information names the FWD direction.
+ * => AtoB_branch: branch on which 'B' receives communications from 'A'
+ * => BtoA_branch: branch on which 'A' receives communications from 'B'
+ */
+ChainLink *a_Chain_link_new(ChainLink *AInfo, ChainFunction_t AFunc,
+ int Direction, ChainFunction_t BFunc,
+ int AtoB_branch, int BtoA_branch)
+{
+ ChainLink *NewLink = a_Chain_new();
+ ChainLink *OldLink = AInfo;
+
+ if (Direction == BCK) {
+ NewLink->Fcb = AFunc;
+ NewLink->FcbInfo = AInfo;
+ NewLink->FcbBranch = BtoA_branch;
+ OldLink->Bcb = BFunc;
+ OldLink->BcbInfo = NewLink;
+ OldLink->BcbBranch = AtoB_branch;
+
+ } else { /* FWD */
+ NewLink->Bcb = AFunc;
+ NewLink->BcbInfo = AInfo;
+ NewLink->BcbBranch = BtoA_branch;
+ OldLink->Fcb = BFunc;
+ OldLink->FcbInfo = NewLink;
+ OldLink->FcbBranch = AtoB_branch;
+ }
+
+ return NewLink;
+}
+
+/*
+ * Unlink a previously used link.
+ * 'Direction' tells whether to unlink the forward or backward link.
+ */
+void a_Chain_unlink(ChainLink *Info, int Direction)
+{
+ if (Direction == FWD) {
+ Info->Fcb = NULL;
+ Info->FcbInfo = NULL;
+ Info->FcbBranch = 0;
+ } else { /* BCK */
+ Info->Bcb = NULL;
+ Info->BcbInfo = NULL;
+ Info->BcbBranch = 0;
+ }
+}
+
+/*
+ * Issue the forward callback of the 'Info' link
+ */
+int a_Chain_fcb(int Op, ChainLink *Info, void *Data1, void *Data2)
+{
+ if (Info->Fcb) {
+ Info->Fcb(Op, Info->FcbBranch, FWD, Info->FcbInfo, Data1, Data2);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Issue the backward callback of the 'Info' link
+ */
+int a_Chain_bcb(int Op, ChainLink *Info, void *Data1, void *Data2)
+{
+ if (Info->Bcb) {
+ Info->Bcb(Op, Info->BcbBranch, BCK, Info->BcbInfo, Data1, Data2);
+ return 1;
+ }
+ return 0;
+}
+
+
+/*
+ * Allocate and initialize a new DataBuf structure
+ */
+DataBuf *a_Chain_dbuf_new(void *buf, int size, int code)
+{
+ DataBuf *dbuf = dNew(DataBuf, 1);
+ dbuf->Buf = buf;
+ dbuf->Size = size;
+ dbuf->Code = code;
+ return dbuf;
+}
+
+/*
+ * Show some debugging info
+ */
+void a_Chain_debug_msg(char *FuncStr, int Op, int Branch, int Dir)
+{
+#if VERBOSE
+ const char *StrOps[] = {"", "OpStart", "OpSend",
+ "OpStop", "OpEnd", "OpAbort"};
+ MSG("%-*s: %-*s [%d%s]\n",
+ 12, FuncStr, 7, StrOps[Op], Branch, (Dir == 1) ? "F" : "B");
+#endif
+}
diff --git a/src/chain.h b/src/chain.h
new file mode 100644
index 00000000..2d5f0aae
--- /dev/null
+++ b/src/chain.h
@@ -0,0 +1,69 @@
+#ifndef __CHAIN_H__
+#define __CHAIN_H__
+
+/*
+ * Concomitant control chain (CCC)
+ * Theory and code by Jorge Arellano Cid <jcid@dillo.org>
+ */
+
+
+/*
+ * Supported CCC operations
+ */
+#define OpStart 1
+#define OpSend 2
+#define OpStop 3
+#define OpEnd 4
+#define OpAbort 5
+
+
+/*
+ * Linking direction
+ */
+#define FWD 1
+#define BCK 2
+
+
+typedef struct _ChainLink ChainLink;
+typedef struct _DataBuf DataBuf;
+typedef void (*ChainFunction_t)(int Op, int Branch, int Dir, ChainLink *Info,
+ void *Data1, void *Data2);
+
+/* This is the main data structure for CCC nodes */
+struct _ChainLink {
+ void *LocalKey;
+
+ ChainLink *FcbInfo;
+ ChainFunction_t Fcb;
+ int FcbBranch;
+
+ ChainLink *BcbInfo;
+ ChainFunction_t Bcb;
+ int BcbBranch;
+};
+
+/* A convenience data structure for passing data chunks between nodes */
+struct _DataBuf {
+ char *Buf;
+ int Size;
+ int Code;
+};
+
+
+
+/*
+ * Function prototypes
+ */
+ChainLink *a_Chain_new(void);
+ChainLink *a_Chain_link_new(ChainLink *AInfo, ChainFunction_t AFunc,
+ int Direction, ChainFunction_t BFunc,
+ int AtoB_branch, int BtoA_branch);
+void a_Chain_unlink(ChainLink *Info, int Direction);
+int a_Chain_fcb(int Op, ChainLink *Info, void *Data1, void *Data2);
+int a_Chain_bcb(int Op, ChainLink *Info, void *Data1, void *Data2);
+
+DataBuf *a_Chain_dbuf_new(void *buf, int size, int code);
+void a_Chain_debug_msg(char *FuncStr, int Op, int Branch, int Dir);
+
+
+#endif /* __CHAIN_H__ */
diff --git a/src/chg b/src/chg
new file mode 100755
index 00000000..32d525f7
--- /dev/null
+++ b/src/chg
@@ -0,0 +1,28 @@
+#!/bin/sh
+#
+# Shell script for name changing source code
+#
+
+if [ ! $# = 3 ]; then
+ echo "Usage: chg <source> <old_word> <new_word>"
+ echo " (this script changes <source> directly)"
+ exit 1
+fi
+
+if [ ! -r $1 ]; then
+ echo "source file ->$1<- doesn't exist..."
+ exit 1
+fi
+
+if [ ! -r $1.BAK ]; then
+ echo "creating backup file: $1.BAK"
+ cp $1 $1.BAK
+fi
+
+sed "s/$2/$3/g" $1 > out
+#sed s/$2/$3/ $1 > out
+rm $1
+mv out $1
+echo "done!"
+
+
diff --git a/src/colors.c b/src/colors.c
new file mode 100644
index 00000000..86db3b4f
--- /dev/null
+++ b/src/colors.c
@@ -0,0 +1,366 @@
+/*
+ * File: colors.c
+ *
+ * Copyright (C) 2000-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.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include "colors.h"
+
+#define DEBUG_LEVEL 5
+#include "debug.h"
+#include "msg.h"
+
+/*
+ * If EXTENDED_COLOR is defined, the extended set of named colors is supported.
+ * These colors're not standard but they're supported in most browsers.
+ * NOTE: The colors MUST be in alphabetical order and lower case because the
+ * code uses a binary search.
+ */
+
+#define EXTENDED_COLOR
+
+static const struct key {
+ char *key;
+ int32_t val;
+} color_keyword [] = {
+#ifdef EXTENDED_COLOR
+ { "aliceblue", 0xf0f8ff},
+ { "antiquewhite", 0xfaebd7},
+#endif
+ { "aqua", 0x00ffff},
+#ifdef EXTENDED_COLOR
+ { "aquamarine", 0x7fffd4},
+ { "azure", 0xf0ffff},
+ { "beige", 0xf5f5dc},
+ { "bisque", 0xffe4c4},
+#endif
+ { "black", 0x000000},
+#ifdef EXTENDED_COLOR
+ { "blanchedalmond", 0xffebcd},
+#endif
+ {"blue", 0x0000ff},
+#ifdef EXTENDED_COLOR
+ { "blueviolet", 0x8a2be2},
+ { "brown", 0xa52a2a},
+ { "burlywood", 0xdeb887},
+ { "cadetblue", 0x5f9ea0},
+ { "chartreuse", 0x7fff00},
+ { "chocolate", 0xd2691e},
+ { "coral", 0xff7f50},
+ { "cornflowerblue", 0x6495ed},
+ { "cornsilk", 0xfff8dc},
+ { "crimson", 0xdc1436},
+ { "cyan", 0x00ffff},
+ { "darkblue", 0x00008b},
+ { "darkcyan", 0x008b8b},
+ { "darkgoldenrod", 0xb8860b},
+ { "darkgray", 0xa9a9a9},
+ { "darkgreen", 0x006400},
+ { "darkkhaki", 0xbdb76b},
+ { "darkmagenta", 0x8b008b},
+ { "darkolivegreen", 0x556b2f},
+ { "darkorange", 0xff8c00},
+ { "darkorchid", 0x9932cc},
+ { "darkred", 0x8b0000},
+ { "darksalmon", 0xe9967a},
+ { "darkseagreen", 0x8fbc8f},
+ { "darkslateblue", 0x483d8b},
+ { "darkslategray", 0x2f4f4f},
+ { "darkturquoise", 0x00ced1},
+ { "darkviolet", 0x9400d3},
+ { "deeppink", 0xff1493},
+ { "deepskyblue", 0x00bfff},
+ { "dimgray", 0x696969},
+ { "dodgerblue", 0x1e90ff},
+ { "firebrick", 0xb22222},
+ { "floralwhite", 0xfffaf0},
+ { "forestgreen", 0x228b22},
+#endif
+ { "fuchsia", 0xff00ff},
+#ifdef EXTENDED_COLOR
+ { "gainsboro", 0xdcdcdc},
+ { "ghostwhite", 0xf8f8ff},
+ { "gold", 0xffd700},
+ { "goldenrod", 0xdaa520},
+#endif
+ { "gray", 0x808080},
+ { "green", 0x008000},
+#ifdef EXTENDED_COLOR
+ { "greenyellow", 0xadff2f},
+ { "honeydew", 0xf0fff0},
+ { "hotpink", 0xff69b4},
+ { "indianred", 0xcd5c5c},
+ { "indigo", 0x4b0082},
+ { "ivory", 0xfffff0},
+ { "khaki", 0xf0e68c},
+ { "lavender", 0xe6e6fa},
+ { "lavenderblush", 0xfff0f5},
+ { "lawngreen", 0x7cfc00},
+ { "lemonchiffon", 0xfffacd},
+ { "lightblue", 0xadd8e6},
+ { "lightcoral", 0xf08080},
+ { "lightcyan", 0xe0ffff},
+ { "lightgoldenrodyellow", 0xfafad2},
+ { "lightgreen", 0x90ee90},
+ { "lightgrey", 0xd3d3d3},
+ { "lightpink", 0xffb6c1},
+ { "lightsalmon", 0xffa07a},
+ { "lightseagreen", 0x20b2aa},
+ { "lightskyblue", 0x87cefa},
+ { "lightslategray", 0x778899},
+ { "lightsteelblue", 0xb0c4de},
+ { "lightyellow", 0xffffe0},
+#endif
+ { "lime", 0x00ff00},
+#ifdef EXTENDED_COLOR
+ { "limegreen", 0x32cd32},
+ { "linen", 0xfaf0e6},
+ { "magenta", 0xff00ff},
+#endif
+ { "maroon", 0x800000},
+#ifdef EXTENDED_COLOR
+ { "mediumaquamarine", 0x66cdaa},
+ { "mediumblue", 0x0000cd},
+ { "mediumorchid", 0xba55d3},
+ { "mediumpurple", 0x9370db},
+ { "mediumseagreen", 0x3cb371},
+ { "mediumslateblue", 0x7b68ee},
+ { "mediumspringgreen", 0x00fa9a},
+ { "mediumturquoise", 0x48d1cc},
+ { "mediumvioletred", 0xc71585},
+ { "midnightblue", 0x191970},
+ { "mintcream", 0xf5fffa},
+ { "mistyrose", 0xffe4e1},
+ { "moccasin", 0xffe4b5},
+ { "navajowhite", 0xffdead},
+#endif
+ { "navy", 0x000080},
+#ifdef EXTENDED_COLOR
+ { "oldlace", 0xfdf5e6},
+#endif
+ { "olive", 0x808000},
+#ifdef EXTENDED_COLOR
+ { "olivedrab", 0x6b8e23},
+ { "orange", 0xffa500},
+ { "orangered", 0xff4500},
+ { "orchid", 0xda70d6},
+ { "palegoldenrod", 0xeee8aa},
+ { "palegreen", 0x98fb98},
+ { "paleturquoise", 0xafeeee},
+ { "palevioletred", 0xdb7093},
+ { "papayawhip", 0xffefd5},
+ { "peachpuff", 0xffdab9},
+ { "peru", 0xcd853f},
+ { "pink", 0xffc0cb},
+ { "plum", 0xdda0dd},
+ { "powderblue", 0xb0e0e6},
+#endif
+ { "purple", 0x800080},
+ { "red", 0xff0000},
+#ifdef EXTENDED_COLOR
+ { "rosybrown", 0xbc8f8f},
+ { "royalblue", 0x4169e1},
+ { "saddlebrown", 0x8b4513},
+ { "salmon", 0xfa8072},
+ { "sandybrown", 0xf4a460},
+ { "seagreen", 0x2e8b57},
+ { "seashell", 0xfff5ee},
+ { "sienna", 0xa0522d},
+#endif
+ { "silver", 0xc0c0c0},
+#ifdef EXTENDED_COLOR
+ { "skyblue", 0x87ceeb},
+ { "slateblue", 0x6a5acd},
+ { "slategray", 0x708090},
+ { "snow", 0xfffafa},
+ { "springgreen", 0x00ff7f},
+ { "steelblue", 0x4682b4},
+ { "tan", 0xd2b48c},
+#endif
+ { "teal", 0x008080},
+#ifdef EXTENDED_COLOR
+ { "thistle", 0xd8bfd8},
+ { "tomato", 0xff6347},
+ { "turquoise", 0x40e0d0},
+ { "violet", 0xee82ee},
+ { "wheat", 0xf5deb3},
+#endif
+ { "white", 0xffffff},
+#ifdef EXTENDED_COLOR
+ { "whitesmoke", 0xf5f5f5},
+#endif
+ { "yellow", 0xffff00},
+#ifdef EXTENDED_COLOR
+ { "yellowgreen", 0x9acd32},
+#endif
+};
+
+#define NCOLORS (sizeof(color_keyword) / sizeof(struct key))
+
+/*
+ * Parse a color in hex (RRGGBB)
+ *
+ * Return Value:
+ * parsed color if successful (err = 0),
+ * default_color on error (err = 1).
+ */
+static int32_t Color_parse_hex (const char *s, int32_t default_color, int *err)
+{
+ int32_t ret_color;
+ char *tail;
+
+ *err = 1;
+ ret_color = strtol(s, &tail, 16);
+ if (tail - s == 6)
+ *err = 0;
+ else
+ ret_color = default_color;
+
+ return ret_color;
+}
+
+/*
+ * Parse the color info from a subtag.
+ * If subtag string begins with # or with 0x simply return the color number
+ * otherwise search one color from the set of named colors.
+ *
+ * Return Value:
+ * Parsed color if successful,
+ * default_color (+ error code) on error.
+ */
+int32_t a_Color_parse (const char *subtag, int32_t default_color, int *err)
+{
+ const char *cp;
+ int32_t ret_color;
+ int ret, low, mid, high, st = 1;
+
+ /* skip leading spaces */
+ for (cp = subtag; isspace(*cp); cp++);
+
+ ret_color = default_color;
+ if (*cp == '#') {
+ ret_color = Color_parse_hex(cp + 1, default_color, &st);
+
+ } else if (*cp == '0' && (cp[1] == 'x' || cp[1] == 'X') ) {
+ ret_color = Color_parse_hex(cp + 2, default_color, &st);
+ st = 2;
+
+ } else {
+ /* Binary search */
+ low = 0;
+ high = NCOLORS - 1;
+ while (low <= high) {
+ mid = (low + high) / 2;
+ if ((ret = dStrcasecmp(cp, color_keyword[mid].key)) < 0)
+ high = mid - 1;
+ else if (ret > 0)
+ low = mid + 1;
+ else {
+ ret_color = color_keyword[mid].val;
+ st = 0;
+ break;
+ }
+ }
+
+ if (low > high) {
+ /* try for RRGGBB lacking the leading '#' */
+ ret_color = Color_parse_hex(cp, default_color, &st);
+ st = 1;
+ }
+ }
+
+ DEBUG_MSG(3, "subtag: %s\n", subtag);
+ DEBUG_MSG(3, "color : %X\n", ret_color);
+
+ *err = st;
+ return ret_color;
+}
+
+#if 0
+/*
+ * Return a "distance" measure (between [0, 10])
+ */
+static int Color_distance(long c1, long c2)
+{
+ return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) +
+ labs(((c1 & 0x00ff00) - (c2 & 0x00ff00)) >> 8) +
+ labs(((c1 & 0xff0000) - (c2 & 0xff0000)) >> 16)) / 75;
+}
+#endif
+
+/*
+ * Return: [0-3]
+ */
+static int Color_distance2(long c1, long c2)
+{
+ return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) >= 0x000060) +
+ (labs((c1 & 0x00ff00) - (c2 & 0x00ff00)) >= 0x006000) +
+ (labs((c1 & 0xff0000) - (c2 & 0xff0000)) >= 0x600000);
+}
+
+/*
+ * Return: [0-3] (requires less contrast than distance2)
+ */
+static int Color_distance3(long c1, long c2)
+{
+ return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) >= 0x000040) +
+ (labs((c1 & 0x00ff00) - (c2 & 0x00ff00)) >= 0x004000) +
+ (labs((c1 & 0xff0000) - (c2 & 0xff0000)) >= 0x400000);
+}
+
+/*
+ * Return a suitable "visited link" color
+ * Return value:
+ * if candidate has good contrast with C_txt, C_lnk and C_bg -> candidate
+ * else another color (from the internal list)
+ */
+int32_t a_Color_vc(int32_t candidate, int32_t C_txt, int32_t C_lnk, int32_t C_bg)
+{
+ /* candidate purple darkcyan darkmagenta olive */
+ static int32_t v[] = {0x000000, 0x800080, 0x008b8b, 0x8b008b, 0x808000,
+ /* darkred coral black */
+ 0x8b0000, 0xff7f50, 0x000000};
+ int v_size = sizeof(v) / sizeof(v[0]);
+ int i, max_i, score, max_score, d_bg, d_txt, d_lnk;
+
+
+ /* set candidate in the list */
+ v[0] = candidate;
+
+ /* Try to get good overall and individual contrast */
+ max_i = max_score = 0;
+ for (i = 0; i < v_size; ++i) {
+ _MSG("a_Color_vc: [%d]%.6x: %d %d %d\n", i, v[i],
+ Color_distance2(C_txt, v[i]),
+ Color_distance2(C_lnk, v[i]),
+ Color_distance2(C_bg, v[i]));
+
+ /* Tuned with: slashdot.org, paulgraham.com, newsforge.com,
+ * linuxjournal.com
+ */
+ d_txt = Color_distance2(C_txt, v[i]);
+ d_lnk = Color_distance2(C_lnk, v[i]);
+ d_bg = Color_distance2(C_bg, v[i]);
+ score = (d_bg >= 2 ? 4 : 2 * d_bg) +
+ (d_txt + d_lnk >= 2 ? 2 : d_txt + d_lnk) +
+ (Color_distance3(C_lnk, v[i]) >= 1 ? 1 : 0);
+ if (score >= 7) {
+ /* enough distance, use this color */
+ max_i = i;
+ break;
+ } else if (score > max_score) {
+ /* keep track of the best candidate so far */
+ max_score = score;
+ max_i = i;
+ }
+ }
+ return v[max_i];
+}
diff --git a/src/colors.h b/src/colors.h
new file mode 100644
index 00000000..99d1bc0f
--- /dev/null
+++ b/src/colors.h
@@ -0,0 +1,15 @@
+#ifndef __COLORS_H__
+#define __COLORS_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+int32_t a_Color_parse (const char *subtag, int32_t default_color, int *err);
+int32_t a_Color_vc(int32_t candidate, int32_t c1, int32_t c2, int32_t c3);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __COLORS_H__ */
diff --git a/src/cookies.c b/src/cookies.c
new file mode 100644
index 00000000..f6002842
--- /dev/null
+++ b/src/cookies.c
@@ -0,0 +1,332 @@
+/*
+ * File: cookies.c
+ *
+ * Copyright 2001 Lars Clausen <lrclause@cs.uiuc.edu>
+ * Jörgen Viksell <jorgen.viksell@telia.com>
+ *
+ * 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
+ */
+
+#define DEBUG_LEVEL 10
+#include "debug.h"
+
+
+#ifdef DISABLE_COOKIES
+
+/*
+ * Initialize the cookies module
+ */
+void a_Cookies_init(void)
+{
+ DEBUG_MSG(10, "Cookies: absolutely disabled at compilation time.\n");
+}
+
+#else
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h> /* for time() and time_t */
+#include <ctype.h>
+
+#include "msg.h"
+#include "IO/Url.h"
+#include "list.h"
+#include "cookies.h"
+#include "capi.h"
+#include "dpiapi.h"
+#include "../dpip/dpip.h"
+
+
+/* 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 {
+ CookieControlAction action;
+ char *domain;
+} CookieControl;
+
+/* 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 *Cookies_fopen(const char *file, char *init_str);
+static CookieControlAction Cookies_control_check(const DilloUrl *url);
+static CookieControlAction Cookies_control_check_domain(const char *domain);
+static int Cookie_control_init(void);
+
+/*
+ * 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);
+
+ DEBUG_MSG(10, "Cookies: Created file: %s\n", filename);
+ F_in = Cookies_fopen(filename, NULL);
+ } else {
+ DEBUG_MSG(10, "Cookies: 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;
+}
+
+/*
+ * Initialize the cookies module
+ * (The 'disabled' variable is writable only within a_Cookies_init)
+ */
+void a_Cookies_init(void)
+{
+ /* Default setting */
+ disabled = TRUE;
+
+ /* Read and parse the cookie control file (cookiesrc) */
+ if (Cookie_control_init() != 0) {
+ DEBUG_MSG(10, "Disabling cookies.\n");
+ return;
+ }
+
+ DEBUG_MSG(10, "Enabling cookies as from cookiesrc...\n");
+ disabled = FALSE;
+}
+
+/*
+ * Flush cookies to disk and free all the memory allocated.
+ */
+void a_Cookies_freeall()
+{
+}
+
+/*
+ * Set the value corresponding to the cookie string
+ */
+void a_Cookies_set(Dlist *cookie_strings, const DilloUrl *set_url)
+{
+ CookieControlAction action;
+ char *cmd, *cookie_string, numstr[16];
+ const char *path;
+ int i;
+
+ if (disabled)
+ return;
+
+ action = Cookies_control_check(set_url);
+ if (action == COOKIE_DENY) {
+ DEBUG_MSG(5, "Cookies: denied SET for %s\n", URL_HOST_(set_url));
+ return;
+ }
+
+ for (i = 0; (cookie_string = dList_nth_data(cookie_strings, i)); ++i) {
+ path = URL_PATH_(set_url);
+ snprintf(numstr, 16, "%d", URL_PORT(set_url));
+ cmd = a_Dpip_build_cmd("cmd=%s cookie=%s host=%s path=%s port=%s",
+ "set_cookie", cookie_string, URL_HOST_(set_url),
+ path ? path : "/", numstr);
+
+ DEBUG_MSG(5, "Cookies.c: a_Cookies_set \n\t \"%s\" \n",cmd );
+ a_Capi_dpi_send_cmd(NULL, NULL, cmd, "cookies", 1);
+ dFree(cmd);
+ }
+}
+
+/*
+ * Return a string that contains all relevant cookies as headers.
+ */
+char *a_Cookies_get(const DilloUrl *request_url)
+{
+ char *cmd, *dpip_tag, *cookie, numstr[16];
+ const char *path;
+ CookieControlAction action;
+
+ cookie = dStrdup("");
+
+ if (disabled)
+ return cookie;
+
+ action = Cookies_control_check(request_url);
+ if (action == COOKIE_DENY) {
+ DEBUG_MSG(5, "Cookies: denied GET for %s\n", URL_HOST_(request_url));
+ return cookie;
+ }
+ path = URL_PATH_(request_url);
+
+ snprintf(numstr, 16, "%d", URL_PORT(request_url));
+ cmd = a_Dpip_build_cmd("cmd=%s scheme=%s host=%s path=%s port=%s",
+ "get_cookie", URL_SCHEME(request_url),
+ URL_HOST(request_url), path ? path : "/", numstr);
+
+ /* Get the answer from cookies.dpi */
+ dpip_tag = a_Dpi_send_blocking_cmd("cookies", cmd);
+ dFree(cmd);
+
+ if (dpip_tag != NULL) {
+ cookie = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cookie");
+ dFree(dpip_tag);
+ }
+ return cookie;
+}
+
+/* -------------------------------------------------------------
+ * 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;
+}
+
+/*
+ * Same as the above except it takes an URL
+ */
+static CookieControlAction Cookies_control_check(const DilloUrl *url)
+{
+ return Cookies_control_check_domain(URL_HOST(url));
+}
+
+#endif /* !DISABLE_COOKIES */
diff --git a/src/cookies.h b/src/cookies.h
new file mode 100644
index 00000000..c792d633
--- /dev/null
+++ b/src/cookies.h
@@ -0,0 +1,24 @@
+#ifndef __COOKIES_H__
+#define __COOKIES_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#ifdef DISABLE_COOKIES
+# define a_Cookies_get(url) dStrdup("")
+# define a_Cookies_init() ;
+# define a_Cookies_freeall() ;
+#else
+ char *a_Cookies_get(const DilloUrl *request_url);
+ void a_Cookies_set(Dlist *cookie_string, const DilloUrl *set_url);
+ void a_Cookies_init( void );
+ void a_Cookies_freeall( void );
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* !__COOKIES_H__ */
diff --git a/src/debug.h b/src/debug.h
new file mode 100644
index 00000000..ff8baf36
--- /dev/null
+++ b/src/debug.h
@@ -0,0 +1,149 @@
+#ifndef __DEBUG_H__
+#define __DEBUG_H__
+
+/*
+ * Simple debug messages. Add:
+ *
+ * #define DEBUG_LEVEL <n>
+ * #include "debug.h"
+ *
+ * to the file you are working on, or let DEBUG_LEVEL undefined to
+ * disable all messages. A higher level denotes a greater importance
+ * of the message.
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+
+
+# ifdef DEBUG_LEVEL
+# define DEBUG_MSG(level, ...) \
+ D_STMT_START { \
+ if (DEBUG_LEVEL && (level) >= DEBUG_LEVEL) \
+ printf(__VA_ARGS__); \
+ } D_STMT_END
+# else
+# define DEBUG_MSG(level, ...)
+# endif /* DEBUG_LEVEL */
+
+
+
+/*
+ * Following is experimental, and will be explained soon.
+ */
+
+#ifdef DBG_RTFL
+
+
+#define DBG_MSG(obj, aspect, prio, msg) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:msg:%p:%s:%d:%s\n", \
+ __FILE__, __LINE__, getpid(), obj, aspect, prio, msg); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_MSGF(obj, aspect, prio, fmt, ...) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:msg:%p:%s:%d:" fmt "\n", \
+ __FILE__, __LINE__, getpid(), obj, aspect, prio, __VA_ARGS__); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_MSG_START(obj) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:msg-start:%p\n", \
+ __FILE__, __LINE__, getpid(), obj); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_MSG_END(obj) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:msg-end:%p\n", \
+ __FILE__, __LINE__, getpid(), obj); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_CREATE(obj, klass) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-create:%p:%s\n", \
+ __FILE__, __LINE__, getpid(), obj, klass); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_ASSOC(child, parent) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-assoc:%p:%p\n", \
+ __FILE__, __LINE__, getpid(), child, parent); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_SET_NUM(obj, var, val) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-set:%p:%s:%d\n", \
+ __FILE__, __LINE__, getpid(), obj, var, val); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_SET_STR(obj, var, val) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-set:%p:%s:%s\n", \
+ __FILE__, __LINE__, getpid(), obj, var, val); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_SET_PTR(obj, var, val) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-set:%p:%s:%p\n", \
+ __FILE__, __LINE__, getpid(), obj, var, val); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_ARRSET_NUM(obj, var, ind, val) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-set:%p:" var ":%d\n", \
+ __FILE__, __LINE__, getpid(), obj, ind, val); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_ARRSET_STR(obj, var, ind, val) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-set:%p:" var ":%s\n", \
+ __FILE__, __LINE__, getpid(), obj, ind, val); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_ARRSET_PTR(obj, var, ind, val) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-set:%p:" var ":%p\n", \
+ __FILE__, __LINE__, getpid(), obj, ind, val); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#define DBG_OBJ_COLOR(klass, color) \
+ D_STMT_START { \
+ printf ("[rtfl]%s:%d:%d:obj-color:%s:%s\n", \
+ __FILE__, __LINE__, getpid(), klass, color); \
+ fflush (stdout); \
+ } D_STMT_END
+
+#else /* DBG_RTFL */
+
+#define DBG_MSG(obj, aspect, prio, msg)
+#define DBG_MSGF(obj, aspect, prio, fmt, ...)
+#define DBG_MSG_START(obj)
+#define DBG_MSG_END(obj)
+#define DBG_OBJ_CREATE(obj, klass)
+#define DBG_OBJ_ASSOC(child, parent)
+#define DBG_OBJ_SET_NUM(obj, var, val)
+#define DBG_OBJ_SET_STR(obj, var, val)
+#define DBG_OBJ_SET_PTR(obj, var, val)
+#define DBG_OBJ_ARRSET_NUM(obj, var, ind, val)
+#define DBG_OBJ_ARRSET_STR(obj, var, ind, val)
+#define DBG_OBJ_ARRSET_PTR(obj, var, ind, val)
+#define DBG_OBJ_COLOR(klass, color)
+
+#endif /* DBG_RTFL */
+
+#endif /* __DEBUG_H__ */
+
+
diff --git a/src/dialog.cc b/src/dialog.cc
new file mode 100644
index 00000000..3b1badb2
--- /dev/null
+++ b/src/dialog.cc
@@ -0,0 +1,116 @@
+/*
+ * File: dialog.cc
+ *
+ * Copyright (C) 2005-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.
+ */
+
+// UI dialogs
+
+#include <fltk/Window.h>
+#include <fltk/ask.h>
+#include <fltk/file_chooser.h>
+#include <fltk/TextBuffer.h>
+#include <fltk/ReturnButton.h>
+#include <fltk/TextDisplay.h>
+
+#include "dialog.hh"
+#include "misc.h"
+
+using namespace fltk;
+
+/*
+ * Callback for the text window dialog.
+ */
+void text_window_cb(Widget *, void *vwin)
+{
+ Window *window = (Window*)vwin;
+
+ window->destroy();
+}
+
+/*
+ * Display a message in a popup window.
+ */
+void a_Dialog_msg(const char *msg)
+{
+ message("%s", msg);
+}
+
+/*
+ * Offer a three choice dialog.
+ * The option string that begins with "*" is the default.
+ *
+ * Return: 0, 1 or 2 (esc = 2, window close = 2)
+ */
+int a_Dialog_choice3(const char *msg,
+ const char *b0, const char *b1, const char *b2)
+{
+ return choice(msg, b0, b1, b2);
+}
+
+/*
+ * Dialog for one line of Input with a message.
+ */
+const char *a_Dialog_input(const char *msg)
+{
+ return input("%s", "", msg);
+}
+
+/*
+ * Show the save file dialog.
+ *
+ * Return: pointer to chosen filename, or NULL on Cancel.
+ */
+const char *a_Dialog_save_file(const char *msg,
+ const char *pattern, const char *fname)
+{
+ return file_chooser(msg, pattern, fname);
+}
+
+//#include <fltk/FileIcon.h>
+/*
+ * Show the open file dialog.
+ *
+ * Return: pointer to chosen filename, or NULL on Cancel.
+ */
+char *a_Dialog_open_file(const char *msg,
+ const char *pattern, const char *fname)
+{
+ const char *fc_name;
+/*
+ static int icons_loaded = 0;
+ if (!icons_loaded)
+ FileIcon::load_system_icons();
+*/
+ fc_name = file_chooser(msg, pattern, fname);
+ return (fc_name) ? a_Misc_escape_chars(fc_name, "% ") : NULL;
+}
+
+/*
+ * Show a new window with the provided text
+ */
+void a_Dialog_text_window(const char *txt, const char *title)
+{
+ int wh = 500, ww = 480, bh = 30;
+ TextBuffer *text_buf = new TextBuffer();
+ text_buf->text(txt);
+
+ Window *window = new Window(ww, wh, title ? title : "Untitled");
+ window->begin();
+
+ TextDisplay *td = new TextDisplay(0,0,ww, wh-bh);
+ td->buffer(text_buf);
+
+ ReturnButton *b = new ReturnButton (0, wh-bh, ww, bh, "Close");
+ b->callback(text_window_cb, window);
+
+ window->resizable(window);
+ window->end();
+ window->show();
+}
+
diff --git a/src/dialog.hh b/src/dialog.hh
new file mode 100644
index 00000000..9b927832
--- /dev/null
+++ b/src/dialog.hh
@@ -0,0 +1,22 @@
+#ifndef __DIALOG_HH__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+void a_Dialog_msg(const char *msg);
+int a_Dialog_choice3(const char *msg,
+ const char *b0, const char *b1, const char *b2);
+const char *a_Dialog_input(const char *msg);
+const char *a_Dialog_save_file(const char *msg,
+ const char *pattern, const char *fname);
+char *a_Dialog_open_file(const char *msg,
+ const char *pattern, const char *fname);
+void a_Dialog_text_window(const char *txt, const char *title);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif // __DIALOG_HH__
diff --git a/src/dicache.c b/src/dicache.c
new file mode 100644
index 00000000..eac01ec2
--- /dev/null
+++ b/src/dicache.c
@@ -0,0 +1,451 @@
+/*
+ * File: dicache.c
+ *
+ * Copyright 2000-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.
+ */
+
+#include <sys/time.h> /* for libc5 compatibility */
+#include <string.h> /* for memset */
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "image.hh"
+#include "web.hh"
+#include "dicache.h"
+#include "cache.h"
+#include "prefs.h"
+
+typedef struct _DICacheNode DICacheNode;
+
+struct _DICacheNode {
+ int valid; /* flag */
+ DilloUrl *url; /* primary "Key" for this dicache entry */
+ DICacheEntry *first; /* pointer to the first dicache entry in this list */
+};
+
+/*
+ * List of DICacheNode. One node per URL. Each node may have several
+ * versions of the same image in a linked list.
+ */
+static Dlist *CachedIMGs = NULL;
+
+static int dicache_size_total; /* invariant: dicache_size_total is
+ * the sum of the image sizes (3*w*h)
+ * of all the images in the dicache. */
+
+/*
+ * Compare two dicache nodes
+ */
+static int Dicache_node_cmp(const void *v1, const void *v2)
+{
+ const DICacheNode *n1 = v1, *n2 = v2;
+
+ return a_Url_cmp(n1->url, n2->url);
+}
+
+/*
+ * Compare function for searching a node by Url
+ */
+static int Dicache_node_by_url_cmp(const void *v1, const void *v2)
+{
+ const DICacheNode *node = v1;
+ const DilloUrl *url = v2;
+
+ return a_Url_cmp(node->url, url);
+}
+
+/*
+ * Initialize dicache data
+ */
+void a_Dicache_init(void)
+{
+ CachedIMGs = dList_new(256);
+ dicache_size_total = 0;
+}
+
+/*
+ * Create, and initialize a new, empty, dicache entry
+ */
+static DICacheEntry *Dicache_entry_new(void)
+{
+ DICacheEntry *entry = dNew(DICacheEntry, 1);
+
+ entry->width = 0;
+ entry->height = 0;
+ entry->type = DILLO_IMG_TYPE_NOTSET;
+ entry->cmap = NULL;
+ entry->linebuf = NULL;
+ entry->v_imgbuf = NULL;
+ entry->RefCount = 1;
+ entry->TotalSize = 0;
+ entry->Y = 0;
+ entry->BitVec = NULL;
+ entry->State = DIC_Empty;
+ entry->version = 0;
+ entry->next = NULL;
+
+ return entry;
+}
+
+/*
+ * Add a new entry in the dicache
+ * (a single node (URL) may have several entries)
+ */
+DICacheEntry *a_Dicache_add_entry(const DilloUrl *Url)
+{
+ DICacheEntry *entry;
+ DICacheNode *node;
+
+ entry = Dicache_entry_new();
+
+ if ((node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp))) {
+ /* this URL is already in CachedIMGs, add entry at the END of the list */
+ DICacheEntry *ptr = node->first;
+
+ node->valid = 1;
+ for ( ; ptr->next; ptr = ptr->next);
+ ptr->next = entry;
+ entry->version = ptr->version+1;
+ entry->url = node->url;
+
+ } else { /* no node yet, so create one */
+ DICacheNode *node = dNew(DICacheNode, 1);
+
+ node->url = a_Url_dup(Url);
+ entry->url = node->url;
+ node->first = entry;
+ node->valid = 1;
+ dList_insert_sorted(CachedIMGs, node, Dicache_node_cmp);
+ }
+
+ return entry;
+}
+
+/*
+ * Search an entry in the dicache (given the Url).
+ * Return value: a pointer to the entry of the _newest_ (i.e. highest)
+ * version if found; NULL otherwise.
+ */
+DICacheEntry *a_Dicache_get_entry(const DilloUrl *Url)
+{
+ DICacheNode *node;
+ DICacheEntry *entry;
+
+ node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp);
+
+ if (!node || !node->valid)
+ return NULL;
+
+ for (entry = node->first; (entry && entry->next); entry = entry->next);
+
+ return entry;
+}
+
+/*
+ * Search a particular version of a URL in the Dicache.
+ * Return value: a pointer to the entry if found; NULL otherwise.
+ */
+static DICacheEntry *Dicache_get_entry_version(const DilloUrl *Url,
+ int version)
+{
+ DICacheNode *node;
+ DICacheEntry *entry;
+
+ node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp);
+ entry = (node) ? node->first : NULL;
+
+ while (entry && entry->version != version)
+ entry = entry->next;
+
+ return entry;
+}
+
+/*
+ * Actually free a dicache entry, given the URL and the version number.
+ */
+static void Dicache_remove(const DilloUrl *Url, int version)
+{
+ DICacheNode *node;
+ DICacheEntry *entry, *prev;
+
+ node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp);
+ prev = entry = (node) ? node->first : NULL;
+
+ while (entry && (entry->version != version) ) {
+ prev = entry;
+ entry = entry->next;
+ }
+
+ if (entry) {
+ /* Eliminate this dicache entry */
+ dFree(entry->cmap);
+ dFree(entry->linebuf);
+ a_Bitvec_free(entry->BitVec);
+ a_Image_imgbuf_unref(entry->v_imgbuf);
+ dicache_size_total -= entry->TotalSize;
+
+ if (node->first == entry) {
+ if (!entry->next) {
+ /* last entry with this URL. Remove the node as well */
+ dList_remove(CachedIMGs, node);
+ a_Url_free(node->url);
+ dFree(node);
+ } else
+ node->first = entry->next;
+ } else {
+ prev->next = entry->next;
+ }
+ dFree(entry);
+ }
+}
+
+/*
+ * Unrefs the counter of a dicache entry, and _if_ no DwImage is acessing
+ * this buffer, then we call Dicache_free to do the dirty job.
+ */
+void a_Dicache_unref(const DilloUrl *Url, int version)
+{
+ DICacheEntry *entry;
+
+ if ((entry = Dicache_get_entry_version(Url, version))) {
+ /*if (--entry->RefCount == 0 && (entry->next || !prefs.use_dicache)) {*/
+ if (--entry->RefCount == 0) {
+ Dicache_remove(Url, version);
+ }
+ }
+}
+
+/*
+ * Refs the counter of a dicache entry.
+ */
+
+DICacheEntry* a_Dicache_ref(const DilloUrl *Url, int version)
+{
+ DICacheEntry *entry;
+
+ if ((entry = Dicache_get_entry_version(Url, version))) {
+ ++entry->RefCount;
+ }
+ return entry;
+}
+
+/*
+ * Invalidate this entry. This is used for the reloading mechanism.
+ * Can't erase current versions, but a_Dicache_get_entry must return NULL.
+ */
+void a_Dicache_invalidate_entry(const DilloUrl *Url)
+{
+ DICacheNode *node;
+
+ node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp);
+ if (node)
+ node->valid = 0;
+}
+
+
+/* ------------------------------------------------------------------------- */
+
+/*
+ * This function is a cache client; (but feeds its clients from dicache)
+ */
+void a_Dicache_callback(int Op, CacheClient_t *Client)
+{
+ /* todo: Handle Op = CA_Abort (to show what was got) --Jcid */
+ uint_t i;
+ DilloWeb *Web = Client->Web;
+ DilloImage *Image = Web->Image;
+ DICacheEntry *DicEntry = a_Dicache_get_entry(Web->url);
+
+ dReturn_if_fail ( DicEntry != NULL );
+
+ /* when the data stream is not an image 'v_imgbuf' keeps NULL */
+ if (Op == CA_Send && DicEntry->v_imgbuf) {
+ if (Image->height == 0 && DicEntry->State >= DIC_SetParms) {
+ /* Set parms */
+ a_Image_set_parms(
+ Image, DicEntry->v_imgbuf, DicEntry->url,
+ DicEntry->version, DicEntry->width, DicEntry->height,
+ DicEntry->type);
+ }
+ if (DicEntry->State == DIC_Write) {
+ for (i = 0; i < DicEntry->height; ++i)
+ if (a_Bitvec_get_bit(DicEntry->BitVec, (int)i) &&
+ !a_Bitvec_get_bit(Image->BitVec, (int)i) )
+ a_Image_write(Image, DicEntry->v_imgbuf,
+ DicEntry->linebuf, i, FALSE);
+ }
+ } else if (Op == CA_Close || Op == CA_Abort) {
+ a_Image_close(Web->Image);
+ a_Bw_close_client(Web->bw, Client->Key);
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+
+/*
+ * Set image's width, height & type
+ * (By now, we'll use the image information despite the html tags --Jcid)
+ */
+void a_Dicache_set_parms(DilloUrl *url, int version, DilloImage *Image,
+ uint_t width, uint_t height, DilloImgType type)
+{
+ DICacheEntry *DicEntry;
+ size_t Size = width * height * 3;
+
+ dReturn_if_fail ( Image != NULL && width && height );
+ /* Find the DicEntry for this Image */
+ DicEntry = Dicache_get_entry_version(url, version);
+ dReturn_if_fail ( DicEntry != NULL );
+
+ /* Initialize the DicEntry */
+ DicEntry->linebuf = dNew(uchar_t, width * 3);
+ dReturn_if_fail ( DicEntry->linebuf != NULL );
+
+ /* BUG: there's just one image-type now */
+ #define I_RGB 0
+ DicEntry->v_imgbuf = a_Image_imgbuf_new(Image->dw, I_RGB, width, height);
+
+ /* This extra reference activates the dicache ALWAYS.
+ * Extra code is necessary in Imgbuf to be able to free it */
+ //a_Image_imgbuf_ref(DicEntry->v_imgbuf);
+
+ DicEntry->TotalSize = Size;
+ DicEntry->width = width;
+ DicEntry->height = height;
+ DicEntry->type = type;
+ DicEntry->BitVec = a_Bitvec_new((int)height);
+ DicEntry->State = DIC_SetParms;
+
+ dicache_size_total += Size;
+
+ /* Allocate and initialize this image */
+ a_Image_set_parms(Image, DicEntry->v_imgbuf, url, version,
+ width, height, type);
+}
+
+/*
+ * Implement the set_cmap method for the Image
+ */
+void a_Dicache_set_cmap(DilloUrl *url, int version, DilloImage *Image,
+ const uchar_t *cmap, uint_t num_colors,
+ int num_colors_max, int bg_index)
+{
+ DICacheEntry *DicEntry = Dicache_get_entry_version(url, version);
+
+ dReturn_if_fail ( DicEntry != NULL );
+
+ dFree(DicEntry->cmap);
+ DicEntry->cmap = dNew0(uchar_t, 3 * num_colors_max);
+ memcpy(DicEntry->cmap, cmap, 3 * num_colors);
+ if (bg_index >= 0 && (uint_t)bg_index < num_colors) {
+ DicEntry->cmap[bg_index * 3] = (Image->bg_color >> 16) & 0xff;
+ DicEntry->cmap[bg_index * 3 + 1] = (Image->bg_color >> 8) & 0xff;
+ DicEntry->cmap[bg_index * 3 + 2] = (Image->bg_color) & 0xff;
+ }
+
+ a_Image_set_cmap(Image, DicEntry->cmap);
+ DicEntry->State = DIC_SetCmap;
+}
+
+/*
+ * Implement the write method
+ * (Write a scan line into the Dicache entry)
+ * buf: row buffer
+ * Y : row number
+ * x : horizontal offset? (always zero)
+ */
+void a_Dicache_write(DilloImage *Image, DilloUrl *url, int version,
+ const uchar_t *buf, int x, uint_t Y)
+{
+ DICacheEntry *DicEntry;
+
+ dReturn_if_fail ( Image != NULL );
+ DicEntry = Dicache_get_entry_version(url, version);
+ dReturn_if_fail ( DicEntry != NULL );
+ dReturn_if_fail ( DicEntry->width > 0 && DicEntry->height > 0 );
+
+ a_Image_write(Image, DicEntry->v_imgbuf, buf, Y, TRUE);
+ DicEntry->Y = Y;
+ a_Bitvec_set_bit(DicEntry->BitVec, (int)Y);
+ DicEntry->State = DIC_Write;
+}
+
+/*
+ * Implement the close method of the decoding process
+ */
+void a_Dicache_close(DilloUrl *url, int version, CacheClient_t *Client)
+{
+ DilloWeb *Web = Client->Web;
+ DICacheEntry *DicEntry = Dicache_get_entry_version(url, version);
+
+ dReturn_if_fail ( DicEntry != NULL );
+
+ DicEntry->State = DIC_Close;
+ dFree(DicEntry->cmap);
+ DicEntry->cmap = NULL;
+ dFree(DicEntry->linebuf);
+ DicEntry->linebuf = NULL;
+ a_Image_close(Web->Image);
+ a_Bw_close_client(Web->bw, Client->Key);
+}
+
+/*
+ * Free the imgbuf (RGB data) of unused entries.
+ */
+void a_Dicache_cleanup(void)
+{
+ int i;
+ DICacheNode *node;
+ DICacheEntry *entry;
+
+ for (i = 0; i < dList_length(CachedIMGs); ++i) {
+ node = dList_nth_data(CachedIMGs, i);
+ /* iterate each entry of this node */
+ for (entry = node->first; entry; entry = entry->next) {
+ if (entry->v_imgbuf &&
+ a_Image_imgbuf_last_reference(entry->v_imgbuf)) {
+ /* free this unused entry */
+ if (entry->next) {
+ Dicache_remove(node->url, entry->version);
+ } else {
+ Dicache_remove(node->url, entry->version);
+ --i;
+ break;
+ }
+ }
+ }
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+
+/*
+ * Deallocate memory used by dicache module
+ * (Call this one at exit time)
+ */
+void a_Dicache_freeall(void)
+{
+ DICacheNode *node;
+ DICacheEntry *entry;
+
+ /* Remove every dicache node and its entries */
+ while ((node = dList_nth_data(CachedIMGs, 0))) {
+ while ((entry = node->first)) {
+ node->first = entry->next;
+ dFree(entry->cmap);
+ dFree(entry->linebuf);
+ a_Bitvec_free(entry->BitVec);
+ a_Image_imgbuf_unref(entry->v_imgbuf);
+ dicache_size_total -= entry->TotalSize;
+ }
+ dList_remove(CachedIMGs, node);
+ a_Url_free(node->url);
+ dFree(node);
+ }
+ dList_free(CachedIMGs);
+}
diff --git a/src/dicache.h b/src/dicache.h
new file mode 100644
index 00000000..9afa5045
--- /dev/null
+++ b/src/dicache.h
@@ -0,0 +1,70 @@
+#ifndef __DICACHE_H__
+#define __DICACHE_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#include "bitvec.h"
+#include "image.hh"
+#include "cache.h"
+
+/* These will reflect the entry's "state" */
+typedef enum {
+ DIC_Empty, /* Just created the entry */
+ DIC_SetParms, /* Parameters set */
+ DIC_SetCmap, /* Color map set */
+ DIC_Write, /* Feeding the entry */
+ DIC_Close, /* Whole image got! */
+ DIC_Abort /* Image transfer aborted */
+} DicEntryState;
+
+typedef struct _DICacheEntry DICacheEntry;
+
+struct _DICacheEntry {
+ DilloUrl *url; /* Image URL for this entry */
+ uint_t width, height; /* As taken from image data */
+ DilloImgType type; /* Image type */
+ uchar_t *cmap; /* Color map */
+ uchar_t *linebuf; /* Decompressed RGB buffer for one line */
+ void *v_imgbuf; /* Void pointer to an Imgbuf object */
+ size_t TotalSize; /* Amount of memory the image takes up */
+ int Y; /* Current decoding row */
+ bitvec_t *BitVec; /* Bit vector for decoded rows */
+ DicEntryState State; /* Current status for this entry */
+ int RefCount; /* Reference Counter */
+ int version; /* Version number, used for different
+ versions of the same URL image */
+
+ DICacheEntry *next; /* Link to the next "newer" version */
+};
+
+
+void a_Dicache_init (void);
+
+DICacheEntry *a_Dicache_get_entry(const DilloUrl *Url);
+DICacheEntry *a_Dicache_add_entry(const DilloUrl *Url);
+
+void a_Dicache_callback(int Op, CacheClient_t *Client);
+
+void a_Dicache_set_parms(DilloUrl *url, int version, DilloImage *Image,
+ uint_t width, uint_t height, DilloImgType type);
+void a_Dicache_set_cmap(DilloUrl *url, int version, DilloImage *Image,
+ const uchar_t *cmap, uint_t num_colors,
+ int num_colors_max, int bg_index);
+void a_Dicache_write(DilloImage *Image, DilloUrl *url, int version,
+ const uchar_t *buf, int x, uint_t Y);
+void a_Dicache_close(DilloUrl *url, int version, CacheClient_t *Client);
+
+void a_Dicache_invalidate_entry(const DilloUrl *Url);
+DICacheEntry* a_Dicache_ref(const DilloUrl *Url, int version);
+void a_Dicache_unref(const DilloUrl *Url, int version);
+void a_Dicache_cleanup(void);
+void a_Dicache_freeall(void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* __DICACHE_H__ */
diff --git a/src/dillo.cc b/src/dillo.cc
new file mode 100644
index 00000000..bd8ceeb8
--- /dev/null
+++ b/src/dillo.cc
@@ -0,0 +1,108 @@
+/*
+ * Dillo web browser
+ *
+ * Copyright 1999-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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <fltk/Window.h>
+#include <fltk/run.h>
+
+#include "dir.h"
+#include "uicmd.hh"
+
+#include "msg.h"
+#include "bw.h"
+#include "bookmark.h"
+#include "misc.h"
+#include "nav.h"
+
+#include "dns.h"
+#include "web.hh"
+#include "IO/Url.h"
+#include "IO/mime.h"
+#include "capi.h"
+#include "dicache.h"
+#include "cookies.h"
+
+
+/*
+ * Given a command line argument, build a DilloUrl for it.
+ */
+static DilloUrl *Dillo_make_start_url(char *str)
+{
+ char *url_str, *p;
+ DilloUrl *start_url;
+ int is_file = FALSE;
+
+ /* Relative path to a local file? */
+ p = (*str == '/') ? dStrdup(str) : dStrconcat(a_Dir_get_owd(),"/",str,NULL);
+
+ if (access(p, F_OK) == 0) {
+ /* absolute path may have non-URL characters */
+ url_str = a_Misc_escape_chars(p, "% ");
+ is_file = TRUE;
+ } else {
+ /* Not a file, filter URL string */
+ url_str = a_Url_string_strip_delimiters(str);
+ }
+ dFree(p);
+
+ if (is_file) {
+ start_url = a_Url_new(url_str + 1, "file:/", 0, 0, 0);
+ } else {
+ start_url = a_Url_new(url_str, NULL, 0, 0, 0);
+ }
+ dFree(url_str);
+
+ return start_url;
+}
+
+/*
+ * MAIN
+ */
+int main(int argc, char **argv)
+{
+ // Initialize internal modules
+ a_Dir_init();
+ a_Prefs_init();
+ a_Dpi_init();
+ a_Dns_init();
+ a_Web_init();
+ a_Http_init();
+ a_Mime_init();
+ a_Capi_init();
+ a_Dicache_init();
+ a_Bw_init();
+ a_Cookies_init();
+
+ // Create a new UI/bw pair
+ BrowserWindow *bw = a_UIcmd_browser_window_new(0, 0);
+
+ if (argc == 2) {
+ DilloUrl *url = Dillo_make_start_url(argv[1]);
+ a_UIcmd_open_urlstr(bw, URL_STR(url));
+ a_Url_free(url);
+ } else {
+ /* Send startup screen */
+ //a_Nav_push(bw, prefs.start_page);
+ }
+
+ return fltk::run();
+}
diff --git a/src/dir.c b/src/dir.c
new file mode 100644
index 00000000..53497895
--- /dev/null
+++ b/src/dir.c
@@ -0,0 +1,48 @@
+/*
+ * File: dir.c
+ *
+ * Copyright 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.
+ */
+
+#include <unistd.h>
+
+#include "../dlib/dlib.h"
+
+
+/*
+ * Local data
+ */
+/* Dillo works from an unmounted directory (/tmp). */
+static char *OldWorkingDirectory = NULL;
+
+/*
+ * Change current working directory to "/tmp".
+ */
+void a_Dir_init(void)
+{
+ dFree(OldWorkingDirectory);
+ OldWorkingDirectory = dGetcwd();
+ chdir("/tmp");
+}
+
+/*
+ * Return the initial current working directory in a string.
+ */
+char *a_Dir_get_owd(void)
+{
+ return OldWorkingDirectory;
+}
+
+/*
+ * Free memory
+ */
+void a_Dir_free(void)
+{
+ dFree(OldWorkingDirectory);
+}
+
diff --git a/src/dir.h b/src/dir.h
new file mode 100644
index 00000000..580122f0
--- /dev/null
+++ b/src/dir.h
@@ -0,0 +1,19 @@
+#ifndef __DIR_H__
+#define __DIR_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+void a_Dir_init(void);
+char *a_Dir_get_owd(void);
+void a_Dir_free(void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __DIR_H__ */
+
diff --git a/src/dns.c b/src/dns.c
new file mode 100644
index 00000000..2813f54f
--- /dev/null
+++ b/src/dns.c
@@ -0,0 +1,535 @@
+/*
+ * File: dns.c
+ *
+ * Copyright (C) 1999-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.
+ */
+
+/*
+ * Non blocking pthread-handled Dns scheme
+ */
+
+#include <pthread.h>
+
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <signal.h>
+#include <string.h>
+
+#include "msg.h"
+#include "dns.h"
+#include "list.h"
+#include "timeout.hh"
+
+#define DEBUG_LEVEL 5
+#include "debug.h"
+
+
+/*
+ * Uncomment the following line for debugging or gprof profiling.
+ */
+/* #undef D_DNS_THREADED */
+
+/*
+ * Uncomment the following line for libc5 optimization
+ */
+/* #define LIBC5 */
+
+
+/* Maximum dns resolving threads */
+#ifdef D_DNS_THREADED
+# define D_DNS_MAX_SERVERS 4
+#else
+# define D_DNS_MAX_SERVERS 1
+#endif
+
+
+typedef struct {
+ int channel; /* Index of this channel [0 based] */
+ bool_t in_use; /* boolean to tell if server is doing a lookup */
+ bool_t ip_ready; /* boolean: is IP lookup done? */
+ Dlist *addr_list; /* IP address */
+ char *hostname; /* Adress to resolve */
+ int status; /* errno code for resolving function */
+#ifdef D_DNS_THREADED
+ pthread_t th1; /* Thread id */
+#endif
+} DnsServer;
+
+typedef struct {
+ char *hostname; /* host name for cache */
+ Dlist *addr_list; /* addresses of host */
+} GDnsCache;
+
+typedef struct {
+ int channel; /* -2 if waiting, otherwise index to dns_server[] */
+ char *hostname; /* The one we're resolving */
+ DnsCallback_t cb_func; /* callback function */
+ void *cb_data; /* extra data for the callback function */
+} GDnsQueue;
+
+
+/*
+ * Forward declarations
+ */
+static void Dns_timeout_client(void *data);
+
+/*
+ * Local Data
+ */
+static DnsServer dns_server[D_DNS_MAX_SERVERS];
+static int num_servers;
+static GDnsCache *dns_cache;
+static int dns_cache_size, dns_cache_size_max;
+static GDnsQueue *dns_queue;
+static int dns_queue_size, dns_queue_size_max;
+static bool_t ipv6_enabled;
+
+
+/* ----------------------------------------------------------------------
+ * Dns queue functions
+ */
+static void Dns_queue_add(int channel, const char *hostname,
+ DnsCallback_t cb_func, void *cb_data)
+{
+ a_List_add(dns_queue, dns_queue_size, dns_queue_size_max);
+ dns_queue[dns_queue_size].channel = channel;
+ dns_queue[dns_queue_size].hostname = dStrdup(hostname);
+ dns_queue[dns_queue_size].cb_func = cb_func;
+ dns_queue[dns_queue_size].cb_data = cb_data;
+ dns_queue_size++;
+}
+
+/*
+ * Find hostname index in dns_queue
+ * (if found, returns queue index; -1 if not)
+ */
+static int Dns_queue_find(const char *hostname)
+{
+ int i;
+
+ for (i = 0; i < dns_queue_size; i++)
+ if (!strcmp(hostname, dns_queue[i].hostname))
+ return i;
+
+ return -1;
+}
+
+/*
+ * Given an index, remove an entry from the dns_queue
+ */
+static void Dns_queue_remove(int index)
+{
+ int i;
+
+ DEBUG_MSG(2, "Dns_queue_remove: deleting client [%d] [queue_size=%d]\n",
+ index, dns_queue_size);
+
+ if (index < dns_queue_size) {
+ dFree(dns_queue[index].hostname);
+ --dns_queue_size; /* you'll find out why ;-) */
+ for (i = index; i < dns_queue_size; i++)
+ dns_queue[i] = dns_queue[i + 1];
+ }
+}
+
+/*
+ * Debug function
+ *
+void Dns_queue_print()
+{
+ int i;
+
+ MSG("Queue: [");
+ for (i = 0; i < dns_queue_size; i++)
+ MSG("%d:%s ", dns_queue[i].channel, dns_queue[i].hostname);
+ MSG("]\n");
+}
+ */
+
+/*
+ * Add an IP/hostname pair to Dns-cache
+ */
+static void Dns_cache_add(char *hostname, Dlist *addr_list)
+{
+ a_List_add(dns_cache, dns_cache_size, dns_cache_size_max);
+ dns_cache[dns_cache_size].hostname = dStrdup(hostname);
+ dns_cache[dns_cache_size].addr_list = addr_list;
+ ++dns_cache_size;
+ DEBUG_MSG(1, "Cache objects: %d\n", dns_cache_size);
+}
+
+
+/*
+ * Initializer function
+ */
+void a_Dns_init(void)
+{
+ int i;
+
+#ifdef D_DNS_THREADED
+ DEBUG_MSG(5, "dillo_dns_init: Here we go! (threaded)\n");
+#else
+ DEBUG_MSG(5, "dillo_dns_init: Here we go! (not threaded)\n");
+#endif
+
+ dns_queue_size = 0;
+ dns_queue_size_max = 16;
+ dns_queue = dNew(GDnsQueue, dns_queue_size_max);
+
+ dns_cache_size = 0;
+ dns_cache_size_max = 16;
+ dns_cache = dNew(GDnsCache, dns_cache_size_max);
+
+ num_servers = D_DNS_MAX_SERVERS;
+
+ /* Initialize servers data */
+ for (i = 0; i < num_servers; ++i) {
+ dns_server[i].channel = i;
+ dns_server[i].in_use = FALSE;
+ dns_server[i].ip_ready = FALSE;
+ dns_server[i].addr_list = NULL;
+ dns_server[i].hostname = NULL;
+ dns_server[i].status = 0;
+#ifdef D_DNS_THREADED
+ dns_server[i].th1 = (pthread_t) -1;
+#endif
+ }
+
+ /* IPv6 test */
+ ipv6_enabled = FALSE;
+#ifdef ENABLE_IPV6
+ {
+ /* If the IPv6 address family is not available there is no point
+ wasting time trying to connect to v6 addresses. */
+ int fd = socket(AF_INET6, SOCK_STREAM, 0);
+ if (fd >= 0) {
+ close(fd);
+ ipv6_enabled = TRUE;
+ }
+ }
+#endif
+}
+
+/*
+ * Allocate a host structure and add it to the list
+ */
+static void Dns_note_hosts(Dlist *list, int af, struct hostent *host)
+{
+ int i;
+
+ if (host->h_length > DILLO_ADDR_MAX)
+ return;
+ for (i = 0; host->h_addr_list[i]; i++) {
+ DilloHost *dh = dNew0(DilloHost, 1);
+ dh->af = af;
+ dh->alen = host->h_length;
+ memcpy(&dh->data[0], host->h_addr_list[i], (size_t)host->h_length);
+ dList_append(list, dh);
+ }
+}
+
+#ifdef D_DNS_THREADED
+/*
+ * Server function (runs on its own thread)
+ */
+static void *Dns_server(void *data)
+{
+ struct hostent *host;
+ int channel = VOIDP2INT(data);
+#ifdef LIBC5
+ int h_err;
+ char buff[1024];
+ struct hostent sh;
+#endif
+ Dlist *hosts = dList_new(2);
+
+ DEBUG_MSG(3, "Dns_server: starting...\n ch: %d host: %s\n",
+ channel, dns_server[channel].hostname);
+
+#ifdef ENABLE_IPV6
+ if (ipv6_enabled) {
+ host = gethostbyname2(dns_server[channel].hostname, AF_INET6);
+ if (host) {
+ Dns_note_hosts(hosts, AF_INET6, host);
+ }
+ }
+#endif
+
+#ifdef LIBC5
+ host = gethostbyname_r(dns_server[channel].hostname, &sh, buff,
+ sizeof(buff), &h_err);
+#else
+ host = gethostbyname(dns_server[channel].hostname);
+#endif
+
+ if (!host) {
+#ifdef LIBC5
+ dns_server[channel].status = h_err;
+#else
+ dns_server[channel].status = h_errno;
+ if (h_errno == HOST_NOT_FOUND)
+ MSG("DNS error: HOST_NOT_FOUND\n");
+ else if (h_errno == TRY_AGAIN)
+ MSG("DNS error: TRY_AGAIN\n");
+ else if (h_errno == NO_RECOVERY)
+ MSG("DNS error: NO_RECOVERY\n");
+ else if (h_errno == NO_ADDRESS)
+ MSG("DNS error: NO_ADDRESS\n");
+#endif
+ } else {
+ dns_server[channel].status = 0;
+ Dns_note_hosts(hosts, AF_INET, host);
+ }
+ if (dList_length(hosts) > 0) {
+ dns_server[channel].status = 0;
+ } else {
+ dList_free(hosts);
+ hosts = NULL;
+ }
+
+ /* tell our findings */
+ DEBUG_MSG(5, "Dns_server [%d]: %s is %p\n", channel,
+ dns_server[channel].hostname, hosts);
+ dns_server[channel].addr_list = hosts;
+ dns_server[channel].ip_ready = TRUE;
+
+ return NULL; /* (avoids a compiler warning) */
+}
+#endif
+
+#ifndef D_DNS_THREADED
+/*
+ * Blocking server-function (it doesn't use threads)
+ */
+static void Dns_blocking_server(void)
+{
+ int channel = 0;
+ struct hostent *host = NULL;
+ dList *hosts = dList_new(2);
+#ifdef LIBC5
+ int h_err;
+#endif
+
+ DEBUG_MSG(3, "Dns_blocking_server: starting...\n");
+ DEBUG_MSG(3, "Dns_blocking_server: dns_server[%d].hostname = %s\n",
+ channel, dns_server[channel].hostname);
+
+#ifdef ENABLE_IPV6
+ if (ipv6_enabled) {
+ host = gethostbyname2(dns_server[channel].hostname, AF_INET6);
+ if (host) {
+ Dns_note_hosts(hosts, AF_INET6, host);
+ }
+ }
+#endif
+
+#ifdef LIBC5
+ host = gethostbyname_r(dns_server[channel].hostname, &sh, buff,
+ sizeof(buff), &h_err);
+#else
+ host = gethostbyname(dns_server[channel].hostname);
+#endif
+
+ if (!host) {
+#ifdef LIBC5
+ dns_server[channel].status = h_err;
+#else
+ dns_server[channel].status = h_errno;
+#endif
+ } else {
+ Dns_note_hosts(hosts, AF_INET, host);
+ }
+ if (dList_length(hosts) > 0) {
+ /* at least one entry on the list is ok */
+ dns_server[channel].status = 0;
+ } else {
+ dList_free(hosts);
+ hosts = NULL;
+ }
+
+ /* write IP to server data channel */
+ DEBUG_MSG(3, "Dns_blocking_server: IP of %s is %p\n",
+ dns_server[channel].hostname, hosts);
+ dns_server[channel].addr_list = hosts;
+ dns_server[channel].ip_ready = TRUE;
+
+ DEBUG_MSG(3, "Dns_blocking_server: leaving...\n");
+}
+#endif
+
+/*
+ * Request function (spawn a server and let it handle the request)
+ */
+static void Dns_server_req(int channel, const char *hostname)
+{
+#ifdef D_DNS_THREADED
+ static pthread_attr_t thrATTR;
+ static int thrATTRInitialized = 0;
+#endif
+
+ dns_server[channel].in_use = TRUE;
+ dns_server[channel].ip_ready = FALSE;
+
+ dFree(dns_server[channel].hostname);
+ dns_server[channel].hostname = dStrdup(hostname);
+
+ /* Let's set a timeout client to poll the server channel (5 times/sec) */
+ a_Timeout_add(0.2,Dns_timeout_client,(void*)(dns_server[channel].channel));
+
+#ifdef D_DNS_THREADED
+ /* set the thread attribute to the detached state */
+ if (!thrATTRInitialized) {
+ pthread_attr_init(&thrATTR);
+ pthread_attr_setdetachstate(&thrATTR, PTHREAD_CREATE_DETACHED);
+ thrATTRInitialized = 1;
+ }
+ /* Spawn thread */
+ pthread_create(&dns_server[channel].th1, &thrATTR, Dns_server,
+ INT2VOIDP(dns_server[channel].channel));
+#else
+ Dns_blocking_server();
+#endif
+}
+
+/*
+ * Return the IP for the given hostname using a callback.
+ * Side effect: a thread is spawned when hostname is not cached.
+ */
+void a_Dns_resolve(const char *hostname, DnsCallback_t cb_func, void *cb_data)
+{
+ int i, channel;
+
+ if (!hostname)
+ return;
+
+ /* check for cache hit. */
+ for (i = 0; i < dns_cache_size; i++)
+ if (!strcmp(hostname, dns_cache[i].hostname))
+ break;
+
+ if (i < dns_cache_size) {
+ /* already resolved, call the Callback inmediately. */
+ cb_func(0, dns_cache[i].addr_list, cb_data);
+
+ } else if ((i = Dns_queue_find(hostname)) != -1) {
+ /* hit in queue, but answer hasn't come back yet. */
+ Dns_queue_add(dns_queue[i].channel, hostname, cb_func, cb_data);
+
+ } else {
+ /* Never requested before -- we must resolve it! */
+
+ /* Find a channel we can send the request to */
+ for (channel = 0; channel < num_servers; channel++)
+ if (!dns_server[channel].in_use)
+ break;
+ if (channel < num_servers) {
+ /* Found a free channel! */
+ Dns_queue_add(channel, hostname, cb_func, cb_data);
+ Dns_server_req(channel, hostname);
+ } else {
+ /* We'll have to wait for a thread to finish... */
+ Dns_queue_add(-2, hostname, cb_func, cb_data);
+ }
+ }
+}
+
+/*
+ * Give answer to all queued callbacks on this channel
+ */
+static void Dns_serve_channel(int channel)
+{
+ int i;
+ DnsServer *srv = &dns_server[channel];
+
+ for (i = 0; i < dns_queue_size; i++) {
+ if (dns_queue[i].channel == channel) {
+ dns_queue[i].cb_func(srv->status, srv->addr_list,
+ dns_queue[i].cb_data);
+ Dns_queue_remove(i);
+ --i;
+ }
+ }
+ /* set current channel free */
+ srv->in_use = FALSE;
+}
+
+/*
+ * Assign free channels to waiting clients (-2)
+ */
+static void Dns_assign_channels(void)
+{
+ int ch, i, j;
+
+ for (ch = 0; ch < num_servers; ++ch) {
+ if (dns_server[ch].in_use == FALSE) {
+ /* Find the next query in the queue (we're a FIFO) */
+ for (i = 0; i < dns_queue_size; i++)
+ if (dns_queue[i].channel == -2)
+ break;
+
+ if (i < dns_queue_size) {
+ /* assign this channel to every queued request
+ * with the same hostname*/
+ for (j = i; j < dns_queue_size; j++)
+ if (dns_queue[j].channel == -2 &&
+ !strcmp(dns_queue[j].hostname, dns_queue[i].hostname))
+ dns_queue[j].channel = ch;
+ Dns_server_req(ch, dns_queue[i].hostname);
+ } else
+ return;
+ }
+ }
+}
+
+/*
+ * This is a timeout function that
+ * reads the DNS results and resumes the stopped jobs.
+ */
+static void Dns_timeout_client(void *data)
+{
+ int channel = (int)data;
+ DnsServer *srv = &dns_server[channel];
+
+ if (srv->ip_ready) {
+ if (srv->addr_list != NULL) {
+ /* DNS succeeded, let's cache it */
+ Dns_cache_add(srv->hostname, srv->addr_list);
+ }
+ Dns_serve_channel(channel);
+ Dns_assign_channels();
+ a_Timeout_remove(); /* Done! */
+
+ } else {
+ /* IP not already resolved, keep on trying... */
+ a_Timeout_repeat(0.2, Dns_timeout_client, data);
+ }
+}
+
+
+/*
+ * Dns memory-deallocation
+ * (Call this one at exit time)
+ * The Dns_queue is deallocated at execution time (no need to do that here)
+ * 'dns_cache' is the only one that grows dinamically
+ */
+void a_Dns_freeall(void)
+{
+ int i;
+
+ for ( i = 0; i < dns_cache_size; ++i ){
+ dFree(dns_cache[i].hostname);
+ }
+ dFree(dns_cache);
+}
+
diff --git a/src/dns.h b/src/dns.h
new file mode 100644
index 00000000..13392eba
--- /dev/null
+++ b/src/dns.h
@@ -0,0 +1,31 @@
+#ifndef __DNS_H__
+#define __DNS_H__
+
+#include "chain.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+typedef void (*DnsCallback_t)(int Status, Dlist *addr_list, void *data);
+
+void a_Dns_init (void);
+void a_Dns_freeall(void);
+void a_Dns_resolve(const char *hostname, DnsCallback_t cb_func, void *cb_data);
+
+#define DILLO_ADDR_MAX 16
+
+typedef struct _DilloHost
+{
+ int af;
+ int alen;
+ char data[DILLO_ADDR_MAX];
+} DilloHost;
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __DNS_H__ */
diff --git a/src/dpiapi.c b/src/dpiapi.c
new file mode 100644
index 00000000..382cf122
--- /dev/null
+++ b/src/dpiapi.c
@@ -0,0 +1,82 @@
+/*
+ * File: dpiapi.c
+ *
+ * Copyright (C) 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.
+ */
+
+/* Support for dpi/dpip from Dillo's side */
+
+#include "msg.h"
+#include "bw.h"
+#include "capi.h"
+#include "dpiapi.h" /* for prototypes */
+#include "../dpip/dpip.h"
+
+
+//----------------------------------------------------------------------------
+// Dialog interface
+//
+
+/* This variable can be eliminated as a parameter with a cleaner API. */
+static char *dialog_server = NULL;
+
+
+/*
+ * Generic callback function for dpip dialogs.
+ */
+//static void Dpiapi_dialog_answer_cb(BrowserWindow *bw)
+//{
+// DialogAnswer *answer = bw->question_dialog_answer;
+// char *cmd, numstr[16];
+//
+// /* make dpip tag with the answer */
+// snprintf(numstr, 16, "%d", answer->alt_num);
+// cmd = a_Dpip_build_cmd("cmd=%s to_cmd=%s msg=%s",
+// "answer", "dialog", numstr);
+//
+// /* Send answer */
+// a_Capi_dpi_send_cmd(NULL, bw, cmd, dialog_server, 0);
+//
+// /* cleanup */
+// bw->question_dialog_data = NULL;
+// dFree(answer->tthis);
+// bw->question_dialog_answer = NULL;
+//}
+
+/*
+ * Process a dpip "dialog" command from any dpi.
+ */
+void a_Dpiapi_dialog(BrowserWindow *bw, char *server, char *dpip_tag)
+{
+ char *question, *alt1, *alt2, *alt3, *alt4, *alt5;
+ size_t dpip_tag_len;
+
+ MSG("a_Dpiapi_dialog:\n");
+ MSG(" dpip_tag: %s\n", dpip_tag);
+
+ /* set the module scoped variable */
+ dialog_server = server;
+
+ /* other options can be parsed the same way */
+ dpip_tag_len = strlen(dpip_tag);
+ question = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "msg");
+ alt1 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt1");
+ alt2 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt2");
+ alt3 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt3");
+ alt4 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt4");
+ alt5 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt5");
+
+ //a_Dialog_question5(
+ // bw, question, TRUE,
+ // alt1, alt2, alt3, alt4, alt5,
+ // Dpiapi_dialog_answer_cb);
+
+ dFree(alt1); dFree(alt2); dFree(alt3); dFree(alt4); dFree(alt5);
+ dFree(question);
+}
+
diff --git a/src/dpiapi.h b/src/dpiapi.h
new file mode 100644
index 00000000..26823026
--- /dev/null
+++ b/src/dpiapi.h
@@ -0,0 +1,3 @@
+
+void a_Dpiapi_dialog(BrowserWindow *bw, char *server, char *dpip_tag);
+
diff --git a/src/form.cc b/src/form.cc
new file mode 100644
index 00000000..f170cbef
--- /dev/null
+++ b/src/form.cc
@@ -0,0 +1,98 @@
+#include "form.hh"
+#include "html.hh"
+
+namespace form {
+
+using namespace dw::core::ui;
+
+Form::ResourceDecorator::ResourceDecorator (const char *name)
+{
+ this->name = strdup (name);
+}
+
+Form::ResourceDecorator::~ResourceDecorator ()
+{
+ delete name;
+}
+
+Form::TextResourceDecorator::TextResourceDecorator (const char *name,
+ TextResource *resource):
+ Form::ResourceDecorator (name)
+{
+ this->resource = resource;
+}
+
+const char *Form::TextResourceDecorator::getValue ()
+{
+ return resource->getText ();
+}
+
+Form::RadioButtonResourceDecorator::RadioButtonResourceDecorator
+ (const char *name, RadioButtonResource *resource, const char **values):
+ Form::ResourceDecorator (name)
+{
+ this->resource = resource;
+
+ int n = 0;
+ while (values[n])
+ n++;
+ this->values = new const char*[n];
+ for(int i = 0; i < n; i++)
+ this->values[i] = strdup (values[i]);
+ values[n] = 0;
+}
+
+Form::RadioButtonResourceDecorator::~RadioButtonResourceDecorator ()
+{
+ for(int i = 0; values[i]; i++)
+ delete values[i];
+ delete values;
+}
+
+const char *Form::RadioButtonResourceDecorator::getValue ()
+{
+ RadioButtonResource::GroupIterator *it;
+ int i;
+ for (it = resource->groupIterator (), i = 0; it->hasNext (); i++) {
+ RadioButtonResource *resource = it->getNext ();
+ if(resource->isActivated ()) {
+ it->unref ();
+ return values[i];
+ }
+ }
+
+ it->unref ();
+ return NULL;
+}
+
+
+Form::Form (void *p)
+{
+ ext_data = p;
+ resources = new container::typed::List <ResourceDecorator> (true);
+}
+
+Form::~Form ()
+{
+ delete resources;
+}
+
+void Form::clicked (ButtonResource *resource, int buttonNo)
+{
+/*
+ for (container::typed::Iterator <ResourceDecorator> it =
+ resources->iterator ();
+ it.hasNext (); ) {
+ ResourceDecorator *resource = it.getNext ();
+ const char *value = resource->getValue ();
+ if (value)
+ printf ("%s = %s\n", resource->getName (), value);
+ }
+*/
+ printf ("Form::clicked:: Button was clicked\n");
+
+ // Let html.cc handle the event
+ a_Html_form_event_handler(ext_data, this, (Resource*)resource);
+}
+
+} // namespace form
diff --git a/src/form.hh b/src/form.hh
new file mode 100644
index 00000000..9ea47bb0
--- /dev/null
+++ b/src/form.hh
@@ -0,0 +1,87 @@
+#ifndef __FORM_HH__
+#define __FORM_HH__
+
+#include "dw/core.hh"
+#include "dw/ui.hh"
+
+namespace form {
+
+/**
+ * \brief Handles HTML form data.
+ *
+ * Add resources by calling the respective add...Resource method. Furtermore,
+ * this class impelements dw::core::ui::ButtonResource::ClickedReceiver, the
+ * form data is printed to stdout, when the "clicked" signal is received.
+ */
+class Form: public dw::core::ui::ButtonResource::ClickedReceiver
+{
+private:
+ /**
+ * \brief Decorates instances of dw::core::ui::Resource.
+ *
+ * This is the abstract base class, sub classes have to be defined to
+ * decorate specific sub interfaces of dw::core::ui::Resource.
+ */
+ class ResourceDecorator: public object::Object
+ {
+ private:
+ const char *name;
+
+ protected:
+ ResourceDecorator (const char *name);
+ ~ResourceDecorator ();
+
+ public:
+ inline const char *getName () { return name; }
+ virtual const char *getValue () = 0;
+ };
+
+ /**
+ * \brief Decorates instances of dw::core::ui::TextResource.
+ */
+ class TextResourceDecorator: public ResourceDecorator
+ {
+ private:
+ dw::core::ui::TextResource *resource;
+
+ public:
+ TextResourceDecorator (const char *name,
+ dw::core::ui::TextResource *resource);
+ const char *getValue ();
+ };
+
+ /**
+ * \brief Decorates instances of dw::core::ui::RadioButtonResource.
+ *
+ * This class has to be instanciated only once for a group of radio
+ * buttons.
+ */
+ class RadioButtonResourceDecorator: public ResourceDecorator
+ {
+ private:
+ dw::core::ui::RadioButtonResource *resource;
+ const char **values;
+
+ public:
+ RadioButtonResourceDecorator (const char *name,
+ dw::core::ui::RadioButtonResource
+ *resource,
+ const char **values);
+ ~RadioButtonResourceDecorator ();
+ const char *getValue ();
+ };
+
+ container::typed::List <ResourceDecorator> *resources;
+
+ void *ext_data; // external data pointer
+
+public:
+ Form (void *p);
+ ~Form ();
+ void clicked (dw::core::ui::ButtonResource *resource, int buttonNo);
+
+};
+
+} // namespace form
+
+#endif // __FORM_HH__
diff --git a/src/gif.c b/src/gif.c
new file mode 100644
index 00000000..9806cbb1
--- /dev/null
+++ b/src/gif.c
@@ -0,0 +1,1054 @@
+/*
+ * File: gif.c
+ *
+ * Copyright (C) 1997 Raph Levien <raph@acm.org>
+ * Copyright (C) 2000-2002 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.
+ */
+
+/*
+ * The GIF decoder for dillo. It is responsible for decoding GIF data
+ * and transferring it to the dicache.
+ */
+
+
+/* Notes 13 Oct 1997 --RLL
+ *
+ * Today, just for the hell of it, I implemented a new decoder from
+ * scratch. It's oriented around pushing bytes, while the old decoder
+ * was based around reads which may suspend. There were basically
+ * three motivations.
+ *
+ * 1. To increase the speed.
+ *
+ * 2. To fix some bugs I had seen, most likely due to suspension.
+ *
+ * 3. To make sure that the code had no buffer overruns or the like.
+ *
+ * 4. So that the code could be released under a freer license.
+ *
+ * Let's see how we did on speed. I used a large image for testing
+ * (fvwm95-2.gif).
+ *
+ * The old decoder spent a total of about 1.04 seconds decoding the
+ * image. Another .58 seconds went into Image_line, almost
+ * entirely conversion from colormap to RGB.
+ *
+ * The new decoder spent a total of 0.46 seconds decoding the image.
+ * However, the time for Image_line went up to 1.01 seconds.
+ * Thus, even though the decoder seems to be about twice as fast,
+ * the net gain is pretty minimal. Could this be because of cache
+ * effects?
+ *
+ * Lessons learned: The first, which I keep learning over and over, is
+ * not to try to optimize too much. It doesn't work. Just keep things
+ * simple.
+ *
+ * Second, it seems that the colormap to RGB conversion is really a
+ * significant part of the overall time. It's possible that going
+ * directly to 16 bits would help, but that's optimization again :)
+ */
+
+
+/* todo:
+ * + Make sure to handle error cases gracefully (including aborting the
+ * connection, if necessary).
+ */
+
+#include <config.h>
+#ifdef ENABLE_GIF
+
+#include <stdio.h> /* for sprintf */
+#include <string.h> /* for memcpy and memmove */
+
+#include "msg.h"
+#include "image.hh"
+#include "web.hh"
+#include "cache.h"
+#include "dicache.h"
+#include "prefs.h"
+
+#define DEBUG_LEVEL 6
+#include "debug.h"
+
+#define INTERLACE 0x40
+#define LOCALCOLORMAP 0x80
+
+#define LM_to_uint(a,b) ((((uchar_t)b)<<8)|((uchar_t)a))
+
+#define MAXCOLORMAPSIZE 256
+#define MAX_LWZ_BITS 12
+
+
+typedef struct _DilloGif {
+ DilloImage *Image;
+ DilloUrl *url;
+ int version;
+
+ int state;
+ size_t Start_Ofs;
+ uint_t Flags;
+
+ uchar_t input_code_size;
+ uchar_t *linebuf;
+ int pass;
+
+ uint_t y;
+
+ /* state for lwz_read_byte */
+ int code_size;
+
+ /* The original GifScreen from giftopnm */
+ uint_t Width;
+ uint_t Height;
+ size_t ColorMap_ofs;
+ uint_t ColorResolution;
+ uint_t NumColors;
+ int Background;
+ uint_t spill_line_index;
+#if 0
+ uint_t AspectRatio; /* AspectRatio (not used) */
+#endif
+
+ /* Gif89 extensions */
+ int transparent;
+#if 0
+ /* None are used: */
+ int delayTime;
+ int inputFlag;
+ int disposal;
+#endif
+
+ /* state for the new push-oriented decoder */
+ int packet_size; /* The amount of the data block left to process */
+ uint_t window;
+ int bits_in_window;
+ uint_t last_code; /* Last "compressed" code in the look up table */
+ uint_t line_index;
+ uchar_t **spill_lines;
+ int num_spill_lines_max;
+ int length[(1 << MAX_LWZ_BITS) + 1];
+ int code_and_byte[(1 << MAX_LWZ_BITS) + 1];
+} DilloGif;
+
+/* Some invariants:
+ *
+ * last_code <= code_mask
+ *
+ * code_and_byte is stored packed: (code << 8) | byte
+ */
+
+
+/*
+ * Forward declarations
+ */
+static void Gif_write(DilloGif *gif, void *Buf, uint_t BufSize);
+static void Gif_close(DilloGif *gif, CacheClient_t *Client);
+static size_t Gif_process_bytes(DilloGif *gif, const uchar_t *buf,
+ int bufsize, void *Buf);
+static DilloGif *Gif_new(DilloImage *Image, DilloUrl *url, int version);
+static void Gif_callback(int Op, CacheClient_t *Client);
+
+/* exported function */
+void *a_Gif_image(const char *Type, void *Ptr, CA_Callback_t *Call,
+ void **Data);
+
+
+/*
+ * MIME handler for "image/gif" type
+ * (Sets Gif_callback as cache-client)
+ */
+void *a_Gif_image(const char *Type, void *Ptr, CA_Callback_t *Call,
+ void **Data)
+{
+ DilloWeb *web = Ptr;
+ DICacheEntry *DicEntry;
+
+ if (!web->Image)
+ web->Image = a_Image_new(0, 0, NULL, prefs.bg_color);
+ /* todo: get the backgound color from the parent widget -- Livio. */
+
+ /* Add an extra reference to the Image (for dicache usage) */
+ a_Image_ref(web->Image);
+
+ DicEntry = a_Dicache_get_entry(web->url);
+ if (!DicEntry) {
+ /* Let's create an entry for this image... */
+ DicEntry = a_Dicache_add_entry(web->url);
+
+ /* ... and let the decoder feed it! */
+ *Data = Gif_new(web->Image, DicEntry->url, DicEntry->version);
+ *Call = (CA_Callback_t) Gif_callback;
+ } else {
+ /* Let's feed our client from the dicache */
+ a_Dicache_ref(DicEntry->url, DicEntry->version);
+ *Data = web->Image;
+ *Call = (CA_Callback_t) a_Dicache_callback;
+ }
+ return (web->Image->dw);
+}
+
+/*
+ * Create a new gif structure for decoding a gif into a RGB buffer
+ */
+static DilloGif *Gif_new(DilloImage *Image, DilloUrl *url, int version)
+{
+ DilloGif *gif = dMalloc(sizeof(DilloGif));
+
+ gif->Image = Image;
+ gif->url = url;
+ gif->version = version;
+
+ gif->Flags = 0;
+ gif->state = 0;
+ gif->Start_Ofs = 0;
+ gif->linebuf = NULL;
+ gif->Background = -1;
+ gif->transparent = -1;
+ gif->num_spill_lines_max = 0;
+ gif->spill_lines = NULL;
+ gif->window = 0;
+ gif->packet_size = 0;
+ gif->ColorMap_ofs = 0;
+
+ return gif;
+}
+
+/*
+ * This function is a cache client, it receives data from the cache
+ * and dispatches it to the appropriate gif-processing functions
+ */
+static void Gif_callback(int Op, CacheClient_t *Client)
+{
+ if (Op)
+ Gif_close(Client->CbData, Client);
+ else
+ Gif_write(Client->CbData, Client->Buf, Client->BufSize);
+}
+
+/*
+ * Receive and process new chunks of GIF image data
+ */
+static void Gif_write(DilloGif *gif, void *Buf, uint_t BufSize)
+{
+ uchar_t *buf;
+ int bufsize, bytes_consumed;
+
+ /* Sanity checks */
+ if (!Buf || !gif->Image || BufSize == 0)
+ return;
+
+ buf = ((uchar_t *) Buf) + gif->Start_Ofs;
+ bufsize = BufSize - gif->Start_Ofs;
+
+ DEBUG_MSG(5, "Gif_write: %u bytes\n", BufSize);
+
+ /* Process the bytes in the input buffer. */
+ bytes_consumed = Gif_process_bytes(gif, buf, bufsize, Buf);
+
+ if (bytes_consumed < 1)
+ return;
+ gif->Start_Ofs += bytes_consumed;
+
+ DEBUG_MSG(5, "exit Gif_write, bufsize=%ld\n", (long)bufsize);
+}
+
+/*
+ * Finish the decoding process (and free the memory)
+ */
+static void Gif_close(DilloGif *gif, CacheClient_t *Client)
+{
+ int i;
+
+ DEBUG_MSG(5, "destroy gif %p\n", gif);
+
+ a_Dicache_close(gif->url, gif->version, Client);
+
+ dFree(gif->linebuf);
+
+ if (gif->spill_lines != NULL) {
+ for (i = 0; i < gif->num_spill_lines_max; i++)
+ dFree(gif->spill_lines[i]);
+ dFree(gif->spill_lines);
+ }
+ dFree(gif);
+}
+
+
+/* --- GIF Extensions ----------------------------------------------------- */
+
+/*
+ * This reads a sequence of GIF data blocks.. and ignores them!
+ * Buf points to the first data block.
+ *
+ * Return Value
+ * 0 = There wasn't enough bytes read yet to read the whole datablock
+ * otherwise the size of the data blocks
+ */
+static inline size_t Gif_data_blocks(const uchar_t *Buf, size_t BSize)
+{
+ size_t Size = 0;
+
+ if (BSize < 1)
+ return 0;
+ while (Buf[0]) {
+ if (BSize <= (size_t)(Buf[0] + 1))
+ return 0;
+ Size += Buf[0] + 1;
+ BSize -= Buf[0] + 1;
+ Buf += Buf[0] + 1;
+ }
+ return Size + 1;
+}
+
+/*
+ * This is a GIF extension. We ignore it with this routine.
+ * Buffer points to just after the extension label.
+ *
+ * Return Value
+ * 0 -- block not processed
+ * otherwise the size of the extension label.
+ */
+static inline size_t Gif_do_generic_ext(const uchar_t *Buf, size_t BSize)
+{
+ size_t Size = Buf[0] + 1, DSize;
+
+ /* The Block size (the first byte) is supposed to be a specific size
+ * for each extension... we don't check.
+ */
+
+ if (Buf[0] > BSize)
+ return 0;
+ DSize = Gif_data_blocks(Buf + Size, BSize - Size);
+ if (!DSize)
+ return 0;
+ Size += DSize;
+ return Size <= BSize ? Size : 0;
+}
+
+/*
+ * ?
+ */
+static inline size_t
+ Gif_do_gc_ext(DilloGif *gif, const uchar_t *Buf, size_t BSize)
+{
+ /* Graphic Control Extension */
+ size_t Size = Buf[0] + 2;
+ uint_t Flags;
+
+ if (Size > BSize)
+ return 0;
+ Buf++;
+ Flags = Buf[0];
+
+ /* The packed fields */
+#if 0
+ gif->disposal = (Buf[0] >> 2) & 0x7;
+ gif->inputFlag = (Buf[0] >> 1) & 0x1;
+
+ /* Delay time */
+ gif->delayTime = LM_to_uint(Buf[1], Buf[2]);
+#endif
+
+ /* Transparent color index, may not be valid (unless flag is set) */
+ if ((Flags & 0x1)) {
+ gif->transparent = Buf[3];
+ }
+ return Size;
+}
+
+#define App_Ext (0xff)
+#define Cmt_Ext (0xfe)
+#define GC_Ext (0xf9)
+#define Txt_Ext (0x01)
+
+/*
+ * ?
+ * Return value:
+ * TRUE when the extension is over
+ */
+static size_t Gif_do_extension(DilloGif *gif, uint_t Label,
+ const uchar_t *buf,
+ size_t BSize)
+{
+ switch (Label) {
+ case GC_Ext: /* Graphics extension */
+ return Gif_do_gc_ext(gif, buf, BSize);
+
+ case Cmt_Ext: /* Comment extension */
+ return Gif_data_blocks(buf, BSize);
+
+ case Txt_Ext: /* Plain text Extension */
+ /* This extension allows (rcm thinks) the image to be rendered as text.
+ */
+ case App_Ext: /* Application Extension */
+ default:
+ return Gif_do_generic_ext(buf, BSize); /*Ignore Extension */
+ }
+}
+
+/* --- General Image Decoder ----------------------------------------------- */
+/* Here begins the new push-oriented decoder. */
+
+/*
+ * ?
+ */
+static void Gif_lwz_init(DilloGif *gif)
+{
+ gif->num_spill_lines_max = 1;
+ gif->spill_lines = dMalloc(sizeof(uchar_t *) * gif->num_spill_lines_max);
+
+ gif->spill_lines[0] = dMalloc(gif->Width);
+ gif->bits_in_window = 0;
+
+ /* First code in table = clear_code +1
+ * Last code in table = first code in table
+ * clear_code = (1<< input code size)
+ */
+ gif->last_code = (1 << gif->input_code_size) + 1;
+ memset(gif->code_and_byte, 0,
+ (1 + gif->last_code) * sizeof(gif->code_and_byte[0]));
+ gif->code_size = gif->input_code_size + 1;
+ gif->line_index = 0;
+}
+
+/*
+ * Send the image line to the dicache, also handling the interlacing.
+ */
+static void Gif_emit_line(DilloGif *gif, const uchar_t *linebuf)
+{
+ a_Dicache_write(gif->Image, gif->url, gif->version, linebuf, 0, gif->y);
+ if (gif->Flags & INTERLACE) {
+ switch (gif->pass) {
+ case 0:
+ case 1:
+ gif->y += 8;
+ break;
+ case 2:
+ gif->y += 4;
+ break;
+ case 3:
+ gif->y += 2;
+ break;
+ }
+ if (gif->y >= gif->Height) {
+ gif->pass++;
+ switch (gif->pass) {
+ case 1:
+ gif->y = 4;
+ break;
+ case 2:
+ gif->y = 2;
+ break;
+ case 3:
+ gif->y = 1;
+ break;
+ default:
+ /* arriving here is an error in the input image. */
+ gif->y = 0;
+ break;
+ }
+ }
+ } else {
+ if (gif->y < gif->Height)
+ gif->y++;
+ }
+}
+
+/*
+ * I apologize for the large size of this routine and the goto error
+ * construct - I almost _never_ do that. I offer the excuse of
+ * optimizing for speed.
+ *
+ * RCM -- busted these down into smaller subroutines... still very hard to
+ * read.
+ */
+
+
+/*
+ * Decode the packetized lwz bytes
+ */
+static void Gif_literal(DilloGif *gif, uint_t code)
+{
+ gif->linebuf[gif->line_index++] = code;
+ if (gif->line_index >= gif->Width) {
+ Gif_emit_line(gif, gif->linebuf);
+ gif->line_index = 0;
+ }
+ gif->length[gif->last_code + 1] = 2;
+ gif->code_and_byte[gif->last_code + 1] = (code << 8);
+ gif->code_and_byte[gif->last_code] |= code;
+}
+
+/*
+ * ?
+ */
+/* Profiling reveals over half the GIF time is spent here: */
+static void Gif_sequence(DilloGif *gif, uint_t code)
+{
+ uint_t o_index, o_size, orig_code;
+ uint_t sequence_length = gif->length[code];
+ uint_t line_index = gif->line_index;
+ int num_spill_lines;
+ int spill_line_index = gif->spill_line_index;
+ uchar_t *last_byte_ptr, *obuf;
+
+ gif->length[gif->last_code + 1] = sequence_length + 1;
+ gif->code_and_byte[gif->last_code + 1] = (code << 8);
+
+ /* We're going to traverse the sequence backwards. Thus,
+ * we need to allocate spill lines if the sequence won't
+ * fit entirely within the present scan line. */
+ if (line_index + sequence_length <= gif->Width) {
+ num_spill_lines = 0;
+ obuf = gif->linebuf;
+ o_index = line_index + sequence_length;
+ o_size = sequence_length - 1;
+ } else {
+ num_spill_lines = (line_index + sequence_length - 1) /
+ gif->Width;
+ o_index = (line_index + sequence_length - 1) % gif->Width + 1;
+ if (num_spill_lines > gif->num_spill_lines_max) {
+ /* Allocate more spill lines. */
+ spill_line_index = gif->num_spill_lines_max;
+ gif->num_spill_lines_max = num_spill_lines << 1;
+ gif->spill_lines = dRealloc(gif->spill_lines,
+ gif->num_spill_lines_max *
+ sizeof(uchar_t *));
+
+ for (; spill_line_index < gif->num_spill_lines_max;
+ spill_line_index++)
+ gif->spill_lines[spill_line_index] =
+ dMalloc(gif->Width);
+ }
+ spill_line_index = num_spill_lines - 1;
+ obuf = gif->spill_lines[spill_line_index];
+ o_size = o_index;
+ }
+ gif->line_index = o_index; /* for afterwards */
+
+ /* for fixing up later if last_code == code */
+ orig_code = code;
+ last_byte_ptr = obuf + o_index - 1;
+
+ /* spill lines are allocated, and we are clear to
+ * write. This loop does not write the first byte of
+ * the sequence, however (last byte traversed). */
+ while (sequence_length > 1) {
+ sequence_length -= o_size;
+ /* Write o_size bytes to
+ * obuf[o_index - o_size..o_index). */
+ for (; o_size > 0 && o_index > 0; o_size--) {
+ uint_t code_and_byte = gif->code_and_byte[code];
+
+ DEBUG_MSG(5, "%d ", gif->code_and_byte[code] & 255);
+
+ obuf[--o_index] = code_and_byte & 255;
+ code = code_and_byte >> 8;
+ }
+ /* Prepare for writing to next line. */
+ if (o_index == 0) {
+ if (spill_line_index > 0) {
+ spill_line_index--;
+ obuf = gif->spill_lines[spill_line_index];
+ o_size = gif->Width;
+ } else {
+ obuf = gif->linebuf;
+ o_size = sequence_length - 1;
+ }
+ o_index = gif->Width;
+ }
+ }
+ /* Ok, now we write the first byte of the sequence. */
+ /* We are sure that the code is literal. */
+ DEBUG_MSG(5, "%d", code);
+ obuf[--o_index] = code;
+ gif->code_and_byte[gif->last_code] |= code;
+
+ /* Fix up the output if the original code was last_code. */
+ if (orig_code == gif->last_code) {
+ *last_byte_ptr = code;
+ DEBUG_MSG(5, " fixed (%d)!", code);
+ }
+ DEBUG_MSG(5, "\n");
+
+ /* Output any full lines. */
+ if (gif->line_index >= gif->Width) {
+ Gif_emit_line(gif, gif->linebuf);
+ gif->line_index = 0;
+ }
+ if (num_spill_lines) {
+ if (gif->line_index)
+ Gif_emit_line(gif, gif->linebuf);
+ for (spill_line_index = 0;
+ spill_line_index < num_spill_lines - (gif->line_index ? 1 : 0);
+ spill_line_index++)
+ Gif_emit_line(gif, gif->spill_lines[spill_line_index]);
+ }
+ if (num_spill_lines) {
+ /* Swap the last spill line with the gif line, using
+ * linebuf as the swap temporary. */
+ uchar_t *linebuf = gif->spill_lines[num_spill_lines - 1];
+
+ gif->spill_lines[num_spill_lines - 1] = gif->linebuf;
+ gif->linebuf = linebuf;
+ }
+ gif->spill_line_index = spill_line_index;
+}
+
+/*
+ * ?
+ *
+ * Return Value:
+ * 2 -- quit
+ * 1 -- new last code needs to be done
+ * 0 -- okay, but reset the code table
+ * < 0 on error
+ * -1 if the decompression code was not in the lookup table
+ */
+static int Gif_process_code(DilloGif *gif, uint_t code, uint_t clear_code)
+{
+
+ /* A short table describing what to do with the code:
+ * code < clear_code : This is uncompressed, raw data
+ * code== clear_code : Reset the decompression table
+ * code== clear_code+1: End of data stream
+ * code > clear_code+1: Compressed code; look up in table
+ */
+ if (code < clear_code) {
+ /* a literal code. */
+ DEBUG_MSG(5, "literal\n");
+ Gif_literal(gif, code);
+ return 1;
+ } else if (code >= clear_code + 2) {
+ /* a sequence code. */
+ if (code > gif->last_code)
+ return -1;
+ Gif_sequence(gif, code);
+ return 1;
+ } else if (code == clear_code) {
+ /* clear code. Resets the whole table */
+ DEBUG_MSG(5, "clear\n");
+ return 0;
+ } else {
+ /* end code. */
+ DEBUG_MSG(5, "end\n");
+ return 2;
+ }
+}
+
+/*
+ * ?
+ */
+static int Gif_decode(DilloGif *gif, const uchar_t *buf, size_t bsize)
+{
+ /*
+ * Data block processing. The image stuff is a series of data blocks.
+ * Each data block is 1 to 256 bytes long. The first byte is the length
+ * of the data block. 0 == the last data block.
+ */
+ size_t bufsize, packet_size;
+ uint_t clear_code;
+ uint_t window;
+ int bits_in_window;
+ uint_t code;
+ int code_size;
+ uint_t code_mask;
+
+ bufsize = bsize;
+
+ /* Want to get all inner loop state into local variables. */
+ packet_size = gif->packet_size;
+ window = gif->window;
+ bits_in_window = gif->bits_in_window;
+ code_size = gif->code_size;
+ code_mask = (1 << code_size) - 1;
+ clear_code = 1 << gif->input_code_size;
+
+ /* If packet size == 0, we are at the start of a data block.
+ * The first byte of the data block indicates how big it is (0 == last
+ * datablock)
+ * packet size is set to this size; it indicates how much of the data block
+ * we have left to process.
+ */
+ while (bufsize > 0) {
+ /* lwz_bytes is the number of remaining lwz bytes in the packet. */
+ int lwz_bytes = MIN(packet_size, bufsize);
+
+ bufsize -= lwz_bytes;
+ packet_size -= lwz_bytes;
+ for (; lwz_bytes > 0; lwz_bytes--) {
+ /* printf ("%d ", *buf) would print the depacketized lwz stream. */
+
+ /* Push the byte onto the "end" of the window (MSB). The low order
+ * bits always come first in the LZW stream. */
+ window = (window >> 8) | (*buf++ << 24);
+ bits_in_window += 8;
+
+ while (bits_in_window >= code_size) {
+ /* Extract the code. The code is code_size (3 to 12) bits long,
+ * at the start of the window */
+ code = (window >> (32 - bits_in_window)) & code_mask;
+
+ DEBUG_MSG(5, "code = %d, ", code);
+
+ bits_in_window -= code_size;
+ switch (Gif_process_code(gif, code, clear_code)) {
+ case 1: /* Increment last code */
+ gif->last_code++;
+ /*gif->code_and_byte[gif->last_code+1]=0; */
+
+ if ((gif->last_code & code_mask) == 0) {
+ if (gif->last_code == (1 << MAX_LWZ_BITS))
+ gif->last_code--;
+ else {
+ code_size++;
+ code_mask = (1 << code_size) - 1;
+ }
+ }
+ break;
+
+ case 0: /* Reset codes size and mask */
+ gif->last_code = clear_code + 1;
+ code_size = gif->input_code_size + 1;
+ code_mask = (1 << code_size) - 1;
+ break;
+
+ case 2: /* End code... consume remaining data chunks..? */
+ goto error; /* Could clean up better? */
+ default:
+ printf("dillo_gif_decode: error!\n");
+ goto error;
+ }
+ }
+ }
+
+ /* We reach here if
+ * a) We have reached the end of the data block;
+ * b) we ran out of data before reaching the end of the data block
+ */
+ if (bufsize <= 0)
+ break; /* We are out of data; */
+
+ /* Start of new data block */
+ bufsize--;
+ if (!(packet_size = *buf++)) {
+ /* This is the "block terminator" -- the last data block */
+ gif->state = 999; /* BUG: should Go back to getting GIF blocks. */
+ break;
+ }
+ }
+
+ gif->packet_size = packet_size;
+ gif->window = window;
+ gif->bits_in_window = bits_in_window;
+ gif->code_size = code_size;
+ return bsize - bufsize;
+
+ error:
+ gif->state = 999;
+ return bsize - bufsize;
+}
+
+/*
+ * ?
+ */
+static int Gif_check_sig(DilloGif *gif, const uchar_t *ibuf, int ibsize)
+{
+ /* at beginning of file - read magic number */
+ if (ibsize < 6)
+ return 0;
+ if (memcmp(ibuf, "GIF", 3) != 0) {
+ gif->state = 999;
+ return 6;
+ }
+ if (memcmp(ibuf + 3, "87a", 3) != 0 &&
+ memcmp(ibuf + 3, "89a", 3) != 0) {
+ gif->state = 999;
+ return 6;
+ }
+ gif->state = 1;
+ return 6;
+}
+
+/* Read the color map
+ *
+ * Implements, from the spec:
+ * Global Color Table
+ * Local Color Table
+ */
+static inline size_t
+ Gif_do_color_table(DilloGif *gif, void *Buf,
+ const uchar_t *buf, size_t bsize, size_t CT_Size)
+{
+ size_t Size = 3 * (1 << (1 + CT_Size));
+
+ if (Size > bsize)
+ return 0;
+
+ gif->ColorMap_ofs = (ulong_t) buf - (ulong_t) Buf;
+ gif->NumColors = (1 << (1 + CT_Size));
+ return Size;
+}
+
+/*
+ * This implements, from the spec:
+ * <Logical Screen> ::= Logical Screen Descriptor [Global Color Table]
+ */
+static size_t Gif_get_descriptor(DilloGif *gif, void *Buf,
+ const uchar_t *buf, int bsize)
+{
+
+ /* screen descriptor */
+ size_t Size = 7, /* Size of descriptor */
+ mysize; /* Size of color table */
+ uchar_t Flags;
+
+ if (bsize < 7)
+ return 0;
+ Flags = buf[4];
+
+ if (Flags & LOCALCOLORMAP) {
+ mysize = Gif_do_color_table(
+ gif, Buf, buf + 7, (size_t)bsize - 7, Flags & (size_t)0x7);
+ if (!mysize)
+ return 0;
+ Size += mysize; /* Size of the color table that follows */
+ gif->Background = buf[5];
+ }
+ /* gif->Width = LM_to_uint(buf[0], buf[1]);
+ gif->Height = LM_to_uint(buf[2], buf[3]); */
+ gif->ColorResolution = (((buf[4] & 0x70) >> 3) + 1);
+ /* gif->AspectRatio = buf[6]; */
+
+ return Size;
+}
+
+/*
+ * This implements, from the spec:
+ * <Table-Based Image> ::= Image Descriptor [Local Color Table] Image Data
+ *
+ * ('Buf' points to just after the Image separator)
+ * we should probably just check that the local stuff is consistent
+ * with the stuff at the header. For now, we punt...
+ */
+static size_t Gif_do_img_desc(DilloGif *gif, void *Buf,
+ const uchar_t *buf, size_t bsize)
+{
+ uchar_t Flags;
+ size_t Size = 9 + 1; /* image descriptor size + first byte of image data */
+
+ if (bsize < 10)
+ return 0;
+
+ gif->Width = LM_to_uint(buf[4], buf[5]);
+ gif->Height = LM_to_uint(buf[6], buf[7]);
+ gif->linebuf = dMalloc(gif->Width);
+
+ a_Dicache_set_parms(gif->url, gif->version, gif->Image,
+ gif->Width, gif->Height, DILLO_IMG_TYPE_INDEXED);
+
+ Flags = buf[8];
+
+ gif->Flags |= Flags & INTERLACE;
+ gif->pass = 0;
+ bsize -= 9;
+ buf += 9;
+ if (Flags & LOCALCOLORMAP) {
+ size_t LSize = Gif_do_color_table(
+ gif, Buf, buf, bsize, Flags & (size_t)0x7);
+
+ if (!LSize)
+ return 0;
+ Size += LSize;
+ buf += LSize;
+ bsize -= LSize;
+ }
+ /* Finally, get the first byte of the LZW image data */
+ if (bsize < 1)
+ return 0;
+ gif->input_code_size = *buf++;
+ if (gif->input_code_size > 8) {
+ gif->state = 999;
+ return Size;
+ }
+ gif->y = 0;
+ Gif_lwz_init(gif);
+ gif->spill_line_index = 0;
+ gif->state = 3; /*Process the lzw data next */
+ if (gif->Image && gif->ColorMap_ofs) {
+ a_Dicache_set_cmap(gif->url, gif->version, gif->Image,
+ (uchar_t *) Buf + gif->ColorMap_ofs,
+ gif->NumColors, 256, gif->transparent);
+ }
+ return Size;
+}
+
+/* --- Top level data block processors ------------------------------------ */
+#define Img_Desc (0x2c)
+#define Trailer (0x3B)
+#define Ext_Id (0x21)
+
+/*
+ * This identifies which kind of GIF blocks are next, and processes them.
+ * It returns if there isn't enough data to process the next blocks, or if
+ * the next block is the lzw data (which is streamed differently)
+ *
+ * This implements, from the spec, <Data>* Trailer
+ * <Data> ::= <Graphic Block> | <Special-Purpose Block>
+ * <Special-Purpose Block> ::= Application Extension | Comment Extension
+ * <Graphic Block> ::= [Graphic Control Extension] <Graphic-Rendering Block>
+ * <Graphic-Rendering Block> ::= <Table-Based Image> | Plain Text Extension
+ *
+ * <Data>* --> GIF_Block
+ * <Data> --> while (...)
+ * <Special-Purpose Block> --> Gif_do_extension
+ * Graphic Control Extension --> Gif_do_extension
+ * Plain Text Extension --> Gif_do_extension
+ * <Table-Based Image> --> Gif_do_img_desc
+ *
+ * Return Value
+ * 0 if not enough data is present, otherwise the number of bytes
+ * "consumed"
+ */
+static size_t GIF_Block(DilloGif * gif, void *Buf,
+ const uchar_t *buf, size_t bsize)
+{
+ size_t Size = 0, mysize;
+ uchar_t C;
+
+ if (bsize < 1)
+ return 0;
+ while (gif->state == 2) {
+ if (bsize < 1)
+ return Size;
+ bsize--;
+ switch (*buf++) {
+ case Ext_Id:
+ /* get the extension type */
+ if (bsize < 2)
+ return Size;
+
+ /* Have the extension block intepreted. */
+ C = *buf++;
+ bsize--;
+ mysize = Gif_do_extension(gif, C, buf, bsize);
+
+ if (!mysize)
+ /* Not all of the extension is there.. quit until more data
+ * arrives */
+ return Size;
+
+ bsize -= mysize;
+ buf += mysize;
+
+ /* Increment the amount consumed by the extension introducer
+ * and id, and extension block size */
+ Size += mysize + 2;
+ /* Do more GIF Blocks */
+ continue;
+
+ case Img_Desc: /* Image descriptor */
+ mysize = Gif_do_img_desc(gif, Buf, buf, bsize);
+ if (!mysize)
+ return Size;
+
+ /* Increment the amount consumed by the Image Separator and the
+ * Resultant blocks */
+ Size += 1 + mysize;
+ return Size;
+
+ case Trailer:
+ gif->state = 999; /* BUG: should close the rest of the file */
+ return Size + 1;
+ break; /* GIF terminator */
+
+ default: /* Unknown */
+ /* gripe and complain */
+ MSG ("gif.c::GIF_Block: Error, 0x%x found\n", *(buf-1));
+ gif->state = 999;
+ return Size + 1;
+ }
+ }
+ return Size;
+}
+
+
+/*
+ * Process some bytes from the input gif stream. It's a state machine.
+ *
+ * From the GIF spec:
+ * <GIF Data Stream> ::= Header <Logical Screen> <Data>* Trailer
+ *
+ * <GIF Data Stream> --> Gif_process_bytes
+ * Header --> State 0
+ * <Logical Screen> --> State 1
+ * <Data>* --> State 2
+ * Trailer --> State > 3
+ *
+ * State == 3 is special... this is inside of <Data> but all of the stuff in
+ * there has been gotten and set up. So we stream it outside.
+ */
+static size_t Gif_process_bytes(DilloGif *gif, const uchar_t *ibuf,
+ int bufsize, void *Buf)
+{
+ int tmp_bufsize = bufsize;
+ size_t mysize;
+
+ switch (gif->state) {
+ case 0:
+ mysize = Gif_check_sig(gif, ibuf, tmp_bufsize);
+ if (!mysize)
+ break;
+ tmp_bufsize -= mysize;
+ ibuf += mysize;
+ if (gif->state != 1)
+ break;
+
+ case 1:
+ mysize = Gif_get_descriptor(gif, Buf, ibuf, tmp_bufsize);
+ if (!mysize)
+ break;
+ tmp_bufsize -= mysize;
+ ibuf += mysize;
+ gif->state = 2;
+
+ case 2:
+ /* Ok, this loop construction looks weird. It implements the <Data>* of
+ * the GIF grammar. All sorts of stuff is allocated to set up for the
+ * decode part (state ==2) and then there is the actual decode part (3)
+ */
+ mysize = GIF_Block(gif, Buf, ibuf, (size_t)tmp_bufsize);
+ if (!mysize)
+ break;
+ tmp_bufsize -= mysize;
+ ibuf += mysize;
+ if (gif->state != 3)
+ break;
+
+ case 3:
+ /* get an image byte */
+ /* The users sees all of this stuff */
+ mysize = Gif_decode(gif, ibuf, (size_t)tmp_bufsize);
+ if (mysize == 0)
+ break;
+ ibuf += mysize;
+ tmp_bufsize -= mysize;
+
+ default:
+ /* error - just consume all input */
+ tmp_bufsize = 0;
+ break;
+ }
+
+ DEBUG_MSG(5, "Gif_process_bytes: final state %d, %ld bytes consumed\n",
+ gif->state, (long)(bufsize - tmp_bufsize));
+
+ return bufsize - tmp_bufsize;
+}
+
+#endif /* ENABLE_GIF */
diff --git a/src/history.c b/src/history.c
new file mode 100644
index 00000000..7819e58e
--- /dev/null
+++ b/src/history.c
@@ -0,0 +1,125 @@
+/*
+ * File: history.c
+ *
+ * Copyright (C) 2001, 2002 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.
+ */
+
+/*
+ * Linear history (it also provides indexes for the navigation stack)
+ */
+
+#include "list.h"
+#include "history.h"
+
+
+typedef struct {
+ DilloUrl *url;
+ char *title;
+} H_Item;
+
+
+/* Global history list */
+static H_Item *history = NULL;
+static int history_size = 0; /* [1 based] */
+static int history_size_max = 16;
+
+
+/*
+ * Add a new H_Item at the end of the history list
+ * (taking care of not making a duplicate entry)
+ */
+int a_History_add_url(DilloUrl *url)
+{
+ int i, idx;
+
+ for (i = 0; i < history_size; ++i)
+ if (a_Url_cmp(history[i].url, url) == 0)
+ return i;
+
+ idx = history_size;
+ a_List_add(history, history_size, history_size_max);
+ history[idx].url = a_Url_dup(url);
+ history[idx].title = NULL;
+ ++history_size;
+ return idx;
+}
+
+/*
+ * Set the page-title for a given URL (by idx)
+ * (this is known when the first chunks of HTML data arrive)
+ */
+int a_History_set_title(int idx, const char *title)
+{
+ dReturn_val_if_fail(idx >= 0 && idx < history_size, 0);
+
+ dFree(history[idx].title);
+ history[idx].title = dStrdup(title);
+ return 1;
+}
+
+/*
+ * Return the DilloUrl camp (by index)
+ */
+DilloUrl *a_History_get_url(int idx)
+{
+ dReturn_val_if_fail(idx >= 0 && idx < history_size, NULL);
+
+ return history[idx].url;
+}
+
+/*
+ * Return the title camp (by index)
+ * ('force' returns URL_STR when there's no title)
+ */
+const char *a_History_get_title(int idx, int force)
+{
+ dReturn_val_if_fail(idx >= 0 && idx < history_size, NULL);
+
+ if (history[idx].title)
+ return history[idx].title;
+ else if (force)
+ return URL_STR(history[idx].url);
+ else
+ return NULL;
+}
+
+/*
+ * Return the title camp (by url)
+ * ('force' returns URL_STR when there's no title)
+ */
+const char *a_History_get_title_by_url(DilloUrl *url, int force)
+{
+ int i;
+
+ dReturn_val_if_fail(url != NULL, NULL);
+
+ for (i = 0; i < history_size; ++i)
+ if (a_Url_cmp(url, history[i].url) == 0)
+ break;
+
+ if (i < history_size && history[i].title)
+ return history[i].title;
+ else if (force)
+ return URL_STR_(url);
+ return NULL;
+}
+
+
+/*
+ * Free all the memory used by this module
+ */
+void a_History_free()
+{
+ int i;
+
+ for (i = 0; i < history_size; ++i) {
+ a_Url_free(history[i].url);
+ dFree(history[i].title);
+ }
+ dFree(history);
+}
diff --git a/src/history.h b/src/history.h
new file mode 100644
index 00000000..a6f9f13f
--- /dev/null
+++ b/src/history.h
@@ -0,0 +1,24 @@
+
+#ifndef __DILLO_HISTORY_H__
+#define __DILLO_HISTORY_H__
+
+#include "url.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+int a_History_add_url(DilloUrl *url);
+int a_History_set_title(int idx, const char *title);
+DilloUrl *a_History_get_url(int idx);
+const char *a_History_get_title(int idx, int force);
+const char *a_History_get_title_by_url(DilloUrl *url, int force);
+void a_History_free(void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __DILLO_HISTORY_H__ */
diff --git a/src/html.cc b/src/html.cc
new file mode 100644
index 00000000..4661525a
--- /dev/null
+++ b/src/html.cc
@@ -0,0 +1,5123 @@
+/*
+ * File: html.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.
+ */
+
+/*
+ * Dillo HTML parsing routines
+ */
+
+/* Undefine if you want to unroll tables. For instance for PDAs */
+#define USE_TABLES
+
+/* Define to 1 to ignore white space immediately after an open tag,
+ * and immediately before a close tag. */
+#define SGML_SPCDEL 0
+
+
+#include <ctype.h> /* for isspace and tolower */
+#include <string.h> /* for memcpy and memmove */
+#include <stdlib.h>
+#include <stdio.h> /* for sprintf */
+#include <math.h> /* for rint */
+#include <errno.h>
+
+#include <fltk/utf.h> /* for utf8encode */
+
+#define DEBUG_LEVEL 10
+#include "debug.h"
+
+#include "msg.h"
+#include "binaryconst.h"
+#include "colors.h"
+
+#include "uicmd.hh"
+
+#define dillo_dbg_rendering 0
+
+#include "history.h"
+#include "nav.h"
+#include "menu.hh"
+#include "prefs.h"
+#include "misc.h"
+#include "capi.h"
+
+#include "html.hh"
+#include "dw/textblock.hh"
+#include "dw/bullet.hh"
+#include "dw/table.hh"
+#include "dw/tablecell.hh"
+#include "dw/listitem.hh"
+#include "dw/image.hh"
+#include "dw/ruler.hh"
+
+
+using namespace dw;
+using namespace dw::core;
+using namespace dw::core::ui;
+using namespace dw::core::style;
+
+typedef void (*TagOpenFunct) (DilloHtml *Html, char *Tag, int Tagsize);
+typedef void (*TagCloseFunct) (DilloHtml *Html, int TagIdx);
+
+#define TAB_SIZE 8
+
+// Dw to Textblock
+#define DW2TB(dw) ((Textblock*)dw)
+// "html struct" to "Layout"
+#define HT2LT(html) ((Layout*)html->bw->render_layout)
+// "Image" to "Dw Widget"
+#define IM2DW(Image) ((Widget*)Image->dw)
+// Top of the parsing stack
+#define S_TOP(html) (html->stack->getRef(html->stack->size()-1))
+
+/*
+ * Exported function with C linkage.
+ */
+extern "C" {
+void *a_Html_text(const char *type, void *P, CA_Callback_t *Call,void **Data);
+}
+
+/*
+ * Forward declarations
+ */
+static const char *Html_get_attr(DilloHtml *html,
+ const char *tag,
+ int tagsize,
+ const char *attrname);
+static const char *Html_get_attr2(DilloHtml *html,
+ const char *tag,
+ int tagsize,
+ const char *attrname,
+ int tag_parsing_flags);
+static char *Html_get_attr_wdef(DilloHtml *html,
+ const char *tag,
+ int tagsize,
+ const char *attrname,
+ const char *def);
+static void Html_add_widget(DilloHtml *html, Widget *widget,
+ char *width_str, char *height_str,
+ StyleAttrs *style_attrs);
+static int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof);
+static void Html_write(DilloHtml *html, char *Buf, int BufSize, int Eof);
+static void Html_close(DilloHtml *html, int ClientKey);
+static void Html_callback(int Op, CacheClient_t *Client);
+static DilloHtml *Html_new(BrowserWindow *bw, const DilloUrl *url);
+static void Html_tag_open_input(DilloHtml *html, char *tag, int tagsize);
+static void Html_add_input(DilloHtmlForm *form,
+ DilloHtmlInputType type,
+ Widget *widget,
+ Embed *embed,
+ const char *name,
+ const char *init_str,
+ DilloHtmlSelect *select,
+ bool_t init_val);
+//static void Html_reset_form(GtkWidget *reset, DilloHtmlLB *html_lb);
+static int Html_tag_index(const char *tag);
+
+
+/*
+ * Local Data
+ */
+
+/* The following array of font sizes has to be _strictly_ crescent */
+static const int FontSizes[] = {8, 10, 12, 14, 18, 24};
+static const int FontSizesNum = 6;
+static const int FontSizesBase = 2;
+
+/* Parsing table structure */
+typedef struct {
+ char *name; /* element name */
+ unsigned char Flags; /* flags (explained near the table data) */
+ char EndTag; /* Is it Required, Optional or Forbidden */
+ uchar_t TagLevel; /* Used to heuristically parse bad HTML */
+ TagOpenFunct open; /* Open function */
+ TagCloseFunct close; /* Close function */
+} TagInfo;
+extern const TagInfo Tags[];
+
+/*
+ * Return the line number of the tag being processed by the parser.
+ */
+static int Html_get_line_number(DilloHtml *html)
+{
+ int i, ofs, line;
+ const char *p = html->Start_Buf;
+
+ dReturn_val_if_fail(p != NULL, -1);
+
+ ofs = html->CurrTagOfs;
+ line = html->OldTagLine;
+ for (i = html->OldTagOfs; i < ofs; ++i)
+ if (p[i] == '\n')
+ ++line;
+ html->OldTagOfs = html->CurrTagOfs;
+ html->OldTagLine = line;
+ return line;
+}
+
+/*
+ * Collect HTML error strings inside the linkblock.
+ */
+static void Html_msg(DilloHtml *html, const char *format, ... )
+{
+ va_list argp;
+
+ dStr_sprintfa(html->bw->page_bugs,
+ "HTML warning: line %d, ",
+ Html_get_line_number(html));
+ va_start(argp, format);
+ dStr_vsprintfa(html->bw->page_bugs, format, argp);
+ va_end(argp);
+ a_UIcmd_set_bug_prog(html->bw, ++html->bw->num_page_bugs);
+}
+
+/*
+ * Wrapper for a_Url_new that adds an error detection message.
+ * (if use_base_url is TRUE, html->linkblock->base_url is used)
+ */
+static DilloUrl *Html_url_new(DilloHtml *html,
+ const char *url_str, const char *base_url,
+ int flags, int32_t posx, int32_t posy,
+ int use_base_url)
+{
+ DilloUrl *url;
+ int n_ic, n_ic_spc;
+
+ url = a_Url_new(
+ url_str,
+ (use_base_url) ? base_url : URL_STR_(html->linkblock->base_url),
+ flags, posx, posy);
+ if ((n_ic = URL_ILLEGAL_CHARS(url)) != 0) {
+ const char *suffix = (n_ic) > 1 ? "s" : "";
+ n_ic_spc = URL_ILLEGAL_CHARS_SPC(url);
+ if (n_ic == n_ic_spc) {
+ MSG_HTML("URL has %d illegal character%s [%d space%s]\n",
+ n_ic, suffix, n_ic_spc, suffix);
+ } else if (n_ic_spc == 0) {
+ MSG_HTML("URL has %d illegal character%s [%d in (00-1F or 7F)]\n",
+ n_ic, suffix, n_ic);
+ } else {
+ MSG_HTML("URL has %d illegal character%s "
+ "[%d space%s and %d in (00-1F or 7F)]\n",
+ n_ic, suffix, n_ic_spc, n_ic_spc ? "s" : "", n_ic-n_ic_spc);
+ }
+ }
+ return url;
+}
+
+/*
+ * Set callback function and callback data for "html/text" MIME type.
+ */
+void *a_Html_text(const char *Type, void *P, CA_Callback_t *Call, void **Data)
+{
+ DilloWeb *web = (DilloWeb*)P;
+ DilloHtml *html = Html_new(web->bw, web->url);
+
+ *Data = (void *) html;
+ *Call = (CA_Callback_t) Html_callback;
+
+ return (void*) html->dw;
+}
+
+bool DilloHtmlLB::HtmlLinkReceiver::enter (Widget *widget, int link,
+ int x, int y)
+{
+ BrowserWindow *bw = this->lb->bw;
+
+ MSG(" ** ");
+ if (link == -1 && x == -1 && y == -1) {
+ _MSG(" Link LEAVE notify...\n");
+ a_UIcmd_set_msg(bw, "");
+ } else {
+ _MSG(" Link ENTER notify...\n");
+ a_UIcmd_set_msg(bw, "%s", URL_STR(this->lb->links->get(link)));
+ }
+ return true;
+}
+
+bool DilloHtmlLB::HtmlLinkReceiver::press (Widget *widget, int link,
+ int x, int y, EventButton *event)
+{
+ int ret = false;
+
+ _MSG("pressed button %d\n", event->button);
+ if (event->button == 3) {
+ a_UIcmd_page_popup(lb->bw, lb->links->get(link),
+ lb->bw->num_page_bugs ? lb->bw->page_bugs->str:NULL);
+ //a_UIcmd_link_popup(lb->bw, lb->links->get(link));
+ //a_UIcmd_bugmeter_popup(lb->bw);
+ ret = true;
+ }
+ return ret;
+}
+
+bool DilloHtmlLB::HtmlLinkReceiver::click (Widget *widget, int link,
+ int x, int y, EventButton *event)
+{
+ DilloUrl *url = lb->links->get(link);
+ _MSG("clicked on URL %d: %s\n", link, a_Url_str (url));
+
+
+ if (x != -1) {
+ char data[64];
+ snprintf(data, 64, "?%d,%d", x, y);
+ a_Url_set_ismap_coords(url, data);
+ }
+
+ if (event->button == 1) {
+ a_Nav_push(lb->bw, url);
+ } else if (event->button == 2) {
+ a_Nav_push_nw(lb->bw, url);
+ } else {
+ return false;
+ }
+
+ /* Change the link color to "visited" as visual feedback */
+ for (Widget *w = widget; w; w = w->getParent()) {
+ _MSG(" ->%s\n", w->getClassName());
+ if (w->instanceOf(dw::Textblock::CLASS_ID)) {
+ ((Textblock*)w)->changeLinkColor (link, lb->visited_color);
+ break;
+ }
+ }
+
+ return true;
+}
+
+
+/*
+ * We'll make the linkblock first to get it out of the way.
+ */
+static DilloHtmlLB *Html_lb_new(BrowserWindow *bw, const DilloUrl *url)
+{
+ DilloHtmlLB *html_lb = dNew(DilloHtmlLB, 1);
+
+ html_lb->bw = bw;
+ html_lb->base_url = a_Url_dup(url);
+ html_lb->linkReceiver = new DilloHtmlLB::HtmlLinkReceiver (html_lb);
+
+ html_lb->forms = new misc::SimpleVector <DilloHtmlForm> (1);
+
+ html_lb->links = new misc::SimpleVector <DilloUrl*> (64);
+
+ //a_Dw_image_map_list_init(&html_lb->maps);
+
+ html_lb->link_color = prefs.link_color;
+ html_lb->visited_color = prefs.visited_color;
+
+ return html_lb;
+}
+
+/*
+ * Free the memory used by the linkblock
+ */
+static void Html_lb_free(void *lb)
+{
+ int i, j, k;
+ DilloHtmlForm *form;
+ DilloHtmlInput *input_j;
+ DilloHtmlLB *html_lb = (DilloHtmlLB*)lb;
+
+ DEBUG_MSG(3, "Html_lb_free\n");
+
+ delete html_lb->linkReceiver;
+ a_Url_free(html_lb->base_url);
+
+ for (i = 0; i < html_lb->forms->size(); i++) {
+ form = html_lb->forms->getRef(i);
+ a_Url_free(form->action);
+ for (j = 0; j < form->inputs->size(); j++) {
+ input_j = form->inputs->getRef(j);
+ dFree(input_j->name);
+ dFree(input_j->init_str);
+
+ if (input_j->type == DILLO_HTML_INPUT_SELECT ||
+ input_j->type == DILLO_HTML_INPUT_SEL_LIST) {
+ for (k = 0; k < input_j->select->num_options; k++) {
+ dFree(input_j->select->options[k].value);
+ }
+ dFree(input_j->select->options);
+ dFree(input_j->select);
+ }
+ }
+ delete(form->inputs);
+ delete(form->form_receiver);
+ }
+ delete(html_lb->forms);
+
+ for (i = 0; i < html_lb->links->size(); i++)
+ if (html_lb->links->get(i))
+ a_Url_free(html_lb->links->get(i));
+ delete (html_lb->links);
+
+ //a_Dw_image_map_list_free(&html_lb->maps);
+
+ dFree(html_lb);
+}
+
+
+/*
+ * Set the URL data for image maps.
+ */
+//static void Html_set_link_coordinates(DilloHtmlLB *lb,
+// int link, int x, int y)
+//{
+// char data[64];
+//
+// if (x != -1) {
+// snprintf(data, 64, "?%d,%d", x, y);
+// a_Url_set_ismap_coords(lb->links->get(link), data);
+// }
+//}
+
+///*
+// * Handle the status function generated by the dw scroller,
+// * and show the url in the browser status-bar.
+// */
+//static void Html_handle_status(Widget *widget, int link, int x, int y,
+// DilloHtmlLB *lb)
+//{
+// DilloUrl *url;
+//
+// url = (link == -1) ? NULL : lb->links->get(link);
+// if (url) {
+// Html_set_link_coordinates(lb, link, x, y);
+// a_UIcmd_set_msg(lb->bw, "%s",
+// URL_ALT_(url) ? URL_ALT_(url) : URL_STR_(url));
+// lb->bw->status_is_link = 1;
+//
+// } else {
+// if (lb->bw->status_is_link)
+// a_UIcmd_set_msg(lb->bw, "");
+// }
+//}
+
+///*
+// * Activate a link ("link_clicked" callback of the page)
+// */
+//static bool_t Html_link_clicked(Widget *widget, int link, int x, int y,
+// EventButton *event, DilloHtmlLB *lb)
+//{
+// Html_set_link_coordinates(lb, link, x, y);
+// if (event->button == 1)
+// a_Nav_push(lb->bw, lb->links->get(link));
+// else if (event->button == 2) {
+// a_Nav_push_nw(lb->bw, lb->links->get(link));
+// } else {
+// return FALSE;
+// }
+//
+// if (widget->instanceOf (Textblock::CLASS_ID))
+// ((Textblock*)widget)->changeLinkColor (link, lb->visited_color);
+//
+// return TRUE;
+//}
+
+/*
+ * Popup the image menu ("button_press_event" callback of image)
+ */
+static bool_t Html_image_menu(Widget *widget,
+ EventButton *event,
+ BrowserWindow *bw)
+{
+// Image *image = (Image*)widget;
+//:AL
+// if (event->button == 3 && image->url) {
+// a_Menu_popup_set_url(bw, image->url);
+// a_Menu_popup_clear_url2(bw->menu_popup.over_image);
+//
+// gtk_menu_popup(GTK_MENU(bw->menu_popup.over_image), NULL, NULL,
+// NULL, NULL, event->button, ((DwMouseEvent*)event)->time);
+// return TRUE;
+// }
+
+ return FALSE;
+}
+
+/*
+ * Connect all signals of a textblock or an image.
+ */
+static void Html_connect_signals(DilloHtml *html, Widget *widget)
+{
+ widget->connectLink (html->linkblock->linkReceiver);
+}
+
+
+/*
+ * Create a new link in the linkblock, set it as the url's parent
+ * and return the index.
+ */
+static int Html_set_new_link(DilloHtml *html, DilloUrl **url)
+{
+ int nl = html->linkblock->links->size();
+ html->linkblock->links->increase();
+ html->linkblock->links->set(nl, (*url) ? *url : NULL);
+ return nl;
+}
+
+
+/*
+ * Allocate and insert form information into the Html linkblock
+ */
+static int Html_form_new(DilloHtmlLB *html_lb,
+ DilloHtmlMethod method,
+ const DilloUrl *action,
+ DilloHtmlEnc enc)
+{
+ DilloHtmlForm *form;
+
+ html_lb->forms->increase();
+ form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+ form->method = method;
+ form->action = a_Url_dup(action);
+ form->enc = enc;
+ form->inputs = new misc::SimpleVector <DilloHtmlInput> (4);
+ form->num_entry_fields = 0;
+ form->num_submit_buttons = 0;
+ form->form_receiver = new form::Form(html_lb);
+
+ _MSG("Html_form_new: action=%s nform=%d\n", action, nf);
+ return html_lb->forms->size();
+}
+
+
+/*
+ * Change one toplevel attribute. var should be an identifier. val is
+ * only evaluated once, so you can safely use a function call for it.
+ */
+#define HTML_SET_TOP_ATTR(html, var, val) \
+ do { \
+ StyleAttrs style_attrs; \
+ Style *old_style; \
+ \
+ old_style = S_TOP(html)->style; \
+ style_attrs = *old_style; \
+ style_attrs.var = (val); \
+ S_TOP(html)->style = \
+ Style::create (HT2LT(html), &style_attrs); \
+ old_style->unref (); \
+ } while (FALSE)
+
+
+
+/*
+ * Set the font at the top of the stack. BImask specifies which
+ * attributes in BI should be changed.
+ */
+static void Html_set_top_font(DilloHtml *html, char *name, int size,
+ int BI, int BImask)
+{
+ FontAttrs font_attrs;
+
+ font_attrs = *S_TOP(html)->style->font;
+ if (name)
+ font_attrs.name = name;
+ if (size)
+ font_attrs.size = size;
+ if (BImask & 1)
+ font_attrs.weight = (BI & 1) ? 700 : 400;
+ if (BImask & 2)
+ font_attrs.style = (BI & 2) ?
+ (prefs.use_oblique ?
+ FONT_STYLE_OBLIQUE : FONT_STYLE_ITALIC) :
+ FONT_STYLE_NORMAL;
+
+ HTML_SET_TOP_ATTR (html, font,
+ Font::create (HT2LT(html), &font_attrs));
+}
+
+/*
+ * Evaluates the ALIGN attribute (left|center|right|justify) and
+ * sets the style at the top of the stack.
+ */
+static void Html_tag_set_align_attr(DilloHtml *html, char *tag, int tagsize)
+{
+ const char *align, *charattr;
+
+ if ((align = Html_get_attr(html, tag, tagsize, "align"))) {
+ if (dStrcasecmp (align, "left") == 0)
+ HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_LEFT);
+ else if (dStrcasecmp (align, "right") == 0)
+ HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_RIGHT);
+ else if (dStrcasecmp (align, "center") == 0)
+ HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_CENTER);
+ else if (dStrcasecmp (align, "justify") == 0)
+ HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_JUSTIFY);
+ else if (dStrcasecmp (align, "char") == 0) {
+ /* todo: Actually not supported for <p> etc. */
+ HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_STRING);
+ if ((charattr = Html_get_attr(html, tag, tagsize, "char"))) {
+ if (charattr[0] == 0)
+ /* todo: ALIGN=" ", and even ALIGN="&32;" will reult in
+ * an empty string (don't know whether the latter is
+ * correct, has to be clarified with the specs), so
+ * that for empty strings, " " is assumed. */
+ HTML_SET_TOP_ATTR (html, textAlignChar, ' ');
+ else
+ HTML_SET_TOP_ATTR (html, textAlignChar, charattr[0]);
+ } else
+ /* todo: Examine LANG attr of <html>. */
+ HTML_SET_TOP_ATTR (html, textAlignChar, '.');
+ }
+ }
+}
+
+/*
+ * Evaluates the VALIGN attribute (top|bottom|middle|baseline) and
+ * sets the style in style_attrs. Returns TRUE when set.
+ */
+static bool_t Html_tag_set_valign_attr(DilloHtml *html, char *tag,
+ int tagsize, StyleAttrs *style_attrs)
+{
+ const char *attr;
+
+ if ((attr = Html_get_attr(html, tag, tagsize, "valign"))) {
+ if (dStrcasecmp (attr, "top") == 0)
+ style_attrs->valign = VALIGN_TOP;
+ else if (dStrcasecmp (attr, "bottom") == 0)
+ style_attrs->valign = VALIGN_BOTTOM;
+ else if (dStrcasecmp (attr, "baseline") == 0)
+ style_attrs->valign = VALIGN_BASELINE;
+ else
+ style_attrs->valign = VALIGN_MIDDLE;
+ return TRUE;
+ } else
+ return FALSE;
+}
+
+
+/*
+ * Add a new DwPage into the current DwPage, for indentation.
+ * left and right are the horizontal indentation amounts, space is the
+ * vertical space around the block.
+ */
+static void Html_add_indented_widget(DilloHtml *html, Widget *textblock,
+ int left, int right, int space)
+{
+ StyleAttrs style_attrs;
+ Style *style;
+
+ style_attrs = *S_TOP(html)->style;
+
+ style_attrs.margin.setVal (0);
+ style_attrs.borderWidth.setVal (0);
+ style_attrs.padding.setVal(0);
+
+ /* Activate this for debugging */
+#if 0
+ style_attrs.borderWidth.setVal (1);
+ style_attrs.setBorderColor (
+ Color::createShaded (HT2LT(html), style_attrs.color->getColor());
+ style_attrs.setBorderStyle (BORDER_DASHED);
+#endif
+
+ style_attrs.margin.left = left;
+ style_attrs.margin.right = right;
+ style = Style::create (HT2LT(html), &style_attrs);
+
+ DW2TB(html->dw)->addParbreak (space, style);
+ DW2TB(html->dw)->addWidget (textblock, style);
+ DW2TB(html->dw)->addParbreak (space, style);
+ S_TOP(html)->textblock = html->dw = textblock;
+ S_TOP(html)->hand_over_break = TRUE;
+ style->unref ();
+
+ /* Handle it when the user clicks on a link */
+ Html_connect_signals(html, textblock);
+}
+
+/*
+ * Create and add a new indented DwPage to the current DwPage
+ */
+static void Html_add_indented(DilloHtml *html, int left, int right, int space)
+{
+ Textblock *textblock = new Textblock (false);
+ Html_add_indented_widget (html, textblock, left, right, space);
+}
+
+/*
+ * Given a font_size, this will return the correct 'level'.
+ * (or the closest, if the exact level isn't found).
+ */
+static int Html_fontsize_to_level(int fontsize)
+{
+ int i, level;
+ double normalized_size = fontsize / prefs.font_factor,
+ approximation = FontSizes[FontSizesNum-1] + 1;
+
+ for (i = level = 0; i < FontSizesNum; i++)
+ if (approximation >= fabs(normalized_size - FontSizes[i])) {
+ approximation = fabs(normalized_size - FontSizes[i]);
+ level = i;
+ } else {
+ break;
+ }
+
+ return level;
+}
+
+/*
+ * Given a level of a font, this will return the correct 'size'.
+ */
+static int Html_level_to_fontsize(int level)
+{
+ level = MAX(0, level);
+ level = MIN(FontSizesNum - 1, level);
+
+ return (int)rint(FontSizes[level]*prefs.font_factor);
+}
+
+/*
+ * Miscelaneous initializations for a DwPage
+ */
+static void Html_set_dwpage(DilloHtml *html)
+{
+ Widget *widget;
+ Textblock *textblock;
+ StyleAttrs style_attrs;
+ FontAttrs font_attrs;
+
+ dReturn_if_fail (html->dw == NULL);
+
+ widget = textblock = new Textblock (false);
+ html->dw = html->stack->getRef(0)->textblock = widget;
+
+ /* Create a dummy font, attribute, and tag for the bottom of the stack. */
+ font_attrs.name = prefs.vw_fontname; /* Helvetica */
+ font_attrs.size = Html_level_to_fontsize(FontSizesBase);
+ font_attrs.weight = 400;
+ font_attrs.style = FONT_STYLE_NORMAL;
+
+ style_attrs.initValues ();
+ style_attrs.font = Font::create (HT2LT(html), &font_attrs);
+ style_attrs.color = Color::createSimple (HT2LT(html), prefs.text_color);
+ html->stack->getRef(0)->style = Style::create (HT2LT(html), &style_attrs);
+
+ html->stack->getRef(0)->table_cell_style = NULL;
+
+ /* Handle it when the user clicks on a link */
+ Html_connect_signals(html, widget);
+
+ html->bw->num_page_bugs = 0;
+ dStr_truncate(html->bw->page_bugs, 0);
+
+// gtk_signal_connect_while_alive (
+// GTK_OBJECT(GTK_BIN(html->bw->render_main_scroll)->child),
+// "button_press_event", GTK_SIGNAL_FUNC(Html_page_menu),
+// html->bw, GTK_OBJECT (page));
+//
+// /* Destroy the linkblock when the DwPage is destroyed */
+// gtk_signal_connect_object(GTK_OBJECT(page), "destroy",
+// GTK_SIGNAL_FUNC(Html_lb_free),
+// html->linkblock);
+}
+
+/*
+ * Create and initialize a new DilloHtml structure
+ */
+static DilloHtml *Html_new(BrowserWindow *bw, const DilloUrl *url)
+{
+ DilloHtml *html;
+
+ html = dNew(DilloHtml, 1);
+
+ html->Start_Buf = NULL;
+ html->Start_Ofs = 0;
+ html->CurrTagOfs = 0;
+ html->OldTagOfs = 0;
+ html->OldTagLine = 1;
+
+ html->DocType = DT_NONE; /* assume Tag Soup 0.0! :-) */
+ html->DocTypeVersion = 0.0f;
+
+ html->dw = NULL;
+ html->bw = bw;
+ html->linkblock = Html_lb_new(bw, url);
+
+ html->stack = new misc::SimpleVector <DilloHtmlState> (16);
+ html->stack->increase();
+ html->stack->getRef(0)->tag_name = dStrdup("none");
+ html->stack->getRef(0)->style = NULL;
+ html->stack->getRef(0)->table_cell_style = NULL;
+ html->stack->getRef(0)->parse_mode = DILLO_HTML_PARSE_MODE_INIT;
+ html->stack->getRef(0)->table_mode = DILLO_HTML_TABLE_MODE_NONE;
+ html->stack->getRef(0)->cell_text_align_set = FALSE;
+ html->stack->getRef(0)->list_type = HTML_LIST_NONE;
+ html->stack->getRef(0)->list_number = 0;
+ html->stack->getRef(0)->tag_idx = -1; /* MUST not be used */
+ html->stack->getRef(0)->textblock = NULL;
+ html->stack->getRef(0)->table = NULL;
+ html->stack->getRef(0)->ref_list_item = NULL;
+ html->stack->getRef(0)->current_bg_color = prefs.bg_color;
+ html->stack->getRef(0)->hand_over_break = FALSE;
+
+ html->Stash = dStr_new("");
+ html->StashSpace = FALSE;
+
+ html->SPCBuf = NULL;
+
+ html->pre_column = 0;
+ html->PreFirstChar = FALSE;
+ html->PrevWasCR = FALSE;
+ html->PrevWasOpenTag = FALSE;
+ html->SPCPending = FALSE;
+ html->InVisitedLink = FALSE;
+ html->ReqTagClose = FALSE;
+ html->CloseOneTag = FALSE;
+ html->TagSoup = TRUE;
+ html->NameVal = NULL;
+
+ html->Num_HTML = html->Num_HEAD = html->Num_BODY = html->Num_TITLE = 0;
+
+ html->InFlags = IN_NONE;
+
+ html->attr_data = dStr_sized_new(1024);
+
+ Html_set_dwpage(html);
+
+ return html;
+}
+
+/*
+ * Initialize the stash buffer
+ */
+static void Html_stash_init(DilloHtml *html)
+{
+ S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_STASH;
+ html->StashSpace = FALSE;
+ dStr_truncate(html->Stash, 0);
+}
+
+/* Entities list from the HTML 4.01 DTD */
+typedef struct {
+ char *entity;
+ int isocode;
+} Ent_t;
+
+#define NumEnt 252
+static const Ent_t Entities[NumEnt] = {
+ {"AElig",0306}, {"Aacute",0301}, {"Acirc",0302}, {"Agrave",0300},
+ {"Alpha",01621},{"Aring",0305}, {"Atilde",0303}, {"Auml",0304},
+ {"Beta",01622}, {"Ccedil",0307}, {"Chi",01647}, {"Dagger",020041},
+ {"Delta",01624},{"ETH",0320}, {"Eacute",0311}, {"Ecirc",0312},
+ {"Egrave",0310},{"Epsilon",01625},{"Eta",01627}, {"Euml",0313},
+ {"Gamma",01623},{"Iacute",0315}, {"Icirc",0316}, {"Igrave",0314},
+ {"Iota",01631}, {"Iuml",0317}, {"Kappa",01632}, {"Lambda",01633},
+ {"Mu",01634}, {"Ntilde",0321}, {"Nu",01635}, {"OElig",0522},
+ {"Oacute",0323},{"Ocirc",0324}, {"Ograve",0322}, {"Omega",01651},
+ {"Omicron",01637},{"Oslash",0330},{"Otilde",0325},{"Ouml",0326},
+ {"Phi",01646}, {"Pi",01640}, {"Prime",020063},{"Psi",01650},
+ {"Rho",01641}, {"Scaron",0540}, {"Sigma",01643}, {"THORN",0336},
+ {"Tau",01644}, {"Theta",01630}, {"Uacute",0332}, {"Ucirc",0333},
+ {"Ugrave",0331},{"Upsilon",01645},{"Uuml",0334}, {"Xi",01636},
+ {"Yacute",0335},{"Yuml",0570}, {"Zeta",01626}, {"aacute",0341},
+ {"acirc",0342}, {"acute",0264}, {"aelig",0346}, {"agrave",0340},
+ {"alefsym",020465},{"alpha",01661},{"amp",38}, {"and",021047},
+ {"ang",021040}, {"aring",0345}, {"asymp",021110},{"atilde",0343},
+ {"auml",0344}, {"bdquo",020036},{"beta",01662}, {"brvbar",0246},
+ {"bull",020042},{"cap",021051}, {"ccedil",0347}, {"cedil",0270},
+ {"cent",0242}, {"chi",01707}, {"circ",01306}, {"clubs",023143},
+ {"cong",021105},{"copy",0251}, {"crarr",020665},{"cup",021052},
+ {"curren",0244},{"dArr",020723}, {"dagger",020040},{"darr",020623},
+ {"deg",0260}, {"delta",01664}, {"diams",023146},{"divide",0367},
+ {"eacute",0351},{"ecirc",0352}, {"egrave",0350}, {"empty",021005},
+ {"emsp",020003},{"ensp",020002}, {"epsilon",01665},{"equiv",021141},
+ {"eta",01667}, {"eth",0360}, {"euml",0353}, {"euro",020254},
+ {"exist",021003},{"fnof",0622}, {"forall",021000},{"frac12",0275},
+ {"frac14",0274},{"frac34",0276}, {"frasl",020104},{"gamma",01663},
+ {"ge",021145}, {"gt",62}, {"hArr",020724}, {"harr",020624},
+ {"hearts",023145},{"hellip",020046},{"iacute",0355},{"icirc",0356},
+ {"iexcl",0241}, {"igrave",0354}, {"image",020421},{"infin",021036},
+ {"int",021053}, {"iota",01671}, {"iquest",0277}, {"isin",021010},
+ {"iuml",0357}, {"kappa",01672}, {"lArr",020720}, {"lambda",01673},
+ {"lang",021451},{"laquo",0253}, {"larr",020620}, {"lceil",021410},
+ {"ldquo",020034},{"le",021144}, {"lfloor",021412},{"lowast",021027},
+ {"loz",022712}, {"lrm",020016}, {"lsaquo",020071},{"lsquo",020030},
+ {"lt",60}, {"macr",0257}, {"mdash",020024},{"micro",0265},
+ {"middot",0267},{"minus",021022},{"mu",01674}, {"nabla",021007},
+ {"nbsp",32}, {"ndash",020023},{"ne",021140}, {"ni",021013},
+ {"not",0254}, {"notin",021011},{"nsub",021204}, {"ntilde",0361},
+ {"nu",01675}, {"oacute",0363}, {"ocirc",0364}, {"oelig",0523},
+ {"ograve",0362},{"oline",020076},{"omega",01711}, {"omicron",01677},
+ {"oplus",021225},{"or",021050}, {"ordf",0252}, {"ordm",0272},
+ {"oslash",0370},{"otilde",0365}, {"otimes",021227},{"ouml",0366},
+ {"para",0266}, {"part",021002}, {"permil",020060},{"perp",021245},
+ {"phi",01706}, {"pi",01700}, {"piv",01726}, {"plusmn",0261},
+ {"pound",0243}, {"prime",020062},{"prod",021017}, {"prop",021035},
+ {"psi",01710}, {"quot",34}, {"rArr",020722}, {"radic",021032},
+ {"rang",021452},{"raquo",0273}, {"rarr",020622}, {"rceil",021411},
+ {"rdquo",020035},{"real",020434},{"reg",0256}, {"rfloor",021413},
+ {"rho",01701}, {"rlm",020017}, {"rsaquo",020072},{"rsquo",020031},
+ {"sbquo",020032},{"scaron",0541},{"sdot",021305}, {"sect",0247},
+ {"shy",0255}, {"sigma",01703}, {"sigmaf",01702},{"sim",021074},
+ {"spades",023140},{"sub",021202},{"sube",021206}, {"sum",021021},
+ {"sup",021203}, {"sup1",0271}, {"sup2",0262}, {"sup3",0263},
+ {"supe",021207},{"szlig",0337}, {"tau",01704}, {"there4",021064},
+ {"theta",01670},{"thetasym",01721},{"thinsp",020011},{"thorn",0376},
+ {"tilde",01334},{"times",0327}, {"trade",020442},{"uArr",020721},
+ {"uacute",0372},{"uarr",020621}, {"ucirc",0373}, {"ugrave",0371},
+ {"uml",0250}, {"upsih",01722}, {"upsilon",01705},{"uuml",0374},
+ {"weierp",020430},{"xi",01676}, {"yacute",0375}, {"yen",0245},
+ {"yuml",0377}, {"zeta",01666}, {"zwj",020015}, {"zwnj",020014}
+};
+
+
+/*
+ * Comparison function for binary search
+ */
+static int Html_entity_comp(const void *a, const void *b)
+{
+ return strcmp(((Ent_t *)a)->entity, ((Ent_t *)b)->entity);
+}
+
+/*
+ * Binary search of 'key' in entity list
+ */
+static int Html_entity_search(char *key)
+{
+ Ent_t *res, EntKey;
+
+ EntKey.entity = key;
+ res = (Ent_t*) bsearch(&EntKey, Entities, NumEnt,
+ sizeof(Ent_t), Html_entity_comp);
+ if (res)
+ return (res - Entities);
+ return -1;
+}
+
+/*
+ * This is M$ non-standard "smart quotes" (w1252). Now even deprecated by them!
+ *
+ * SGML for HTML4.01 defines c >= 128 and c <= 159 as UNUSED.
+ * TODO: Probably I should remove this hack, and add a HTML warning. --Jcid
+ */
+static int Html_ms_stupid_quotes_2ucs(int isocode)
+{
+ int ret;
+ switch (isocode) {
+ case 145:
+ case 146: ret = '\''; break;
+ case 147:
+ case 148: ret = '"'; break;
+ case 149: ret = 176; break;
+ case 150:
+ case 151: ret = '-'; break;
+ default: ret = isocode; break;
+ }
+ return ret;
+}
+
+/*
+ * Given an entity, return the UCS character code.
+ * Returns a negative value (error code) if not a valid entity.
+ *
+ * The first character *token is assumed to be == '&'
+ *
+ * For valid entities, *entsize is set to the length of the parsed entity.
+ */
+static int Html_parse_entity(DilloHtml *html, const char *token,
+ int toksize, int *entsize)
+{
+ int isocode, i;
+ char *tok, *s, c;
+
+ token++;
+ tok = s = toksize ? dStrndup(token, (uint_t)toksize) : dStrdup(token);
+
+ isocode = -1;
+
+ if (*s == '#') {
+ /* numeric character reference */
+ errno = 0;
+ if (*++s == 'x' || *s == 'X') {
+ if (isxdigit(*++s)) {
+ /* strtol with base 16 accepts leading "0x" - we don't */
+ if (*s == '0' && s[1] == 'x') {
+ s++;
+ isocode = 0;
+ } else {
+ isocode = strtol(s, &s, 16);
+ }
+ }
+ } else if (isdigit(*s)) {
+ isocode = strtol(s, &s, 10);
+ }
+
+ if (!isocode || errno || isocode > 0xffff) {
+ /* this catches null bytes, errors and codes >= 0xFFFF */
+ MSG_HTML("numeric character reference out of range\n");
+ isocode = -2;
+ }
+
+ if (isocode != -1) {
+ if (*s == ';')
+ s++;
+ else if (prefs.show_extra_warnings)
+ MSG_HTML("numeric character reference without trailing ';'\n");
+ }
+
+ } else if (isalpha(*s)) {
+ /* character entity reference */
+ while (isalnum(*++s) || strchr(":_.-", *s));
+ c = *s;
+ *s = 0;
+
+ if ((i = Html_entity_search(tok)) == -1) {
+ if ((html->DocType == DT_HTML && html->DocTypeVersion == 4.01f) ||
+ html->DocType == DT_XHTML)
+ MSG_HTML("undefined character entity '%s'\n", tok);
+ isocode = -3;
+ } else
+ isocode = Entities[i].isocode;
+
+ if (c == ';')
+ s++;
+ else if (prefs.show_extra_warnings)
+ MSG_HTML("character entity reference without trailing ';'\n");
+ }
+
+ *entsize = s-tok+1;
+ dFree(tok);
+
+ if (isocode >= 145 && isocode <= 151) {
+ /* TODO: remove this hack. */
+ isocode = Html_ms_stupid_quotes_2ucs(isocode);
+ } else if (isocode == -1 && prefs.show_extra_warnings)
+ MSG_HTML("literal '&'\n");
+
+ return isocode;
+}
+
+/*
+ * Convert all the entities in a token to utf8 encoding. Takes
+ * a token and its length, and returns a newly allocated string.
+ */
+static char *
+ Html_parse_entities(DilloHtml *html, char *token, int toksize)
+{
+ char *esc_set = "&\xE2\xC2";
+ char *new_str, buf[4];
+ int i, j, k, n, isocode, entsize;
+
+ new_str = dStrndup(token, toksize);
+ if (new_str[strcspn(new_str, esc_set)] == 0)
+ return new_str;
+
+ for (i = j = 0; i < toksize; i++) {
+ if (token[i] == '&' &&
+ (isocode = Html_parse_entity(html, token+i,
+ toksize-i, &entsize)) >= 0) {
+ if (isocode >= 128) {
+ /* multibyte encoding */
+ n = utf8encode(isocode, buf);
+ for (k = 0; k < n; ++k)
+ new_str[j++] = buf[k];
+ } else {
+ new_str[j++] = (char) isocode;
+ }
+ i += entsize-1;
+ } else {
+ new_str[j++] = token[i];
+ }
+ }
+ new_str[j] = '\0';
+ return new_str;
+}
+
+/*
+ * Parse spaces
+ */
+static void Html_process_space(DilloHtml *html, char *space, int spacesize)
+{
+ int i, offset;
+ DilloHtmlParseMode parse_mode = S_TOP(html)->parse_mode;
+
+ if (parse_mode == DILLO_HTML_PARSE_MODE_STASH) {
+ html->StashSpace = (html->Stash->len > 0);
+ html->SPCPending = FALSE;
+
+ } else if (parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) {
+ char *Pword = dStrndup(space, spacesize);
+ dStr_append(html->Stash, Pword);
+ dFree(Pword);
+ html->SPCPending = FALSE;
+
+ } else if (parse_mode == DILLO_HTML_PARSE_MODE_PRE) {
+ /* re-scan the string for characters that cause line breaks */
+ for (i = 0; i < spacesize; i++) {
+ /* Support for "\r", "\n" and "\r\n" line breaks (skips the first) */
+ if (!html->PreFirstChar &&
+ (space[i] == '\r' || (space[i] == '\n' && !html->PrevWasCR))) {
+ DW2TB(html->dw)->addLinebreak (S_TOP(html)->style);
+ html->pre_column = 0;
+ }
+ html->PreFirstChar = FALSE;
+
+ /* cr and lf should not be rendered -- they appear as a break */
+ switch (space[i]) {
+ case '\r':
+ case '\n':
+ break;
+ case '\t':
+ if (prefs.show_extra_warnings)
+ MSG_HTML("TAB character inside <PRE>\n");
+ offset = TAB_SIZE - html->pre_column % TAB_SIZE;
+ DW2TB(html->dw)->addText (dStrnfill(offset, ' '),
+ S_TOP(html)->style);
+ html->pre_column += offset;
+ break;
+ default:
+ DW2TB(html->dw)->addText (dStrndup(space + i, 1),
+ S_TOP(html)->style);
+ html->pre_column++;
+ break;
+ }
+
+ html->PrevWasCR = (space[i] == '\r');
+ }
+ html->SPCPending = FALSE;
+
+ } else {
+ if (SGML_SPCDEL && html->PrevWasOpenTag) {
+ /* SGML_SPCDEL ignores white space inmediately after an open tag */
+ html->SPCPending = FALSE;
+ } else {
+ dFree(html->SPCBuf);
+ html->SPCBuf = dStrndup(space, spacesize);
+ html->SPCPending = TRUE;
+ }
+
+ if (parse_mode == DILLO_HTML_PARSE_MODE_STASH_AND_BODY)
+ html->StashSpace = (html->Stash->len > 0);
+ }
+}
+
+/*
+ * Handles putting the word into its proper place
+ * > STASH and VERBATIM --> html->Stash
+ * > otherwise it goes through addText()
+ *
+ * Entities are parsed (or not) according to parse_mode.
+ */
+static void Html_process_word(DilloHtml *html, char *word, int size)
+{
+ int i, j, start;
+ char *Pword;
+ DilloHtmlParseMode parse_mode = S_TOP(html)->parse_mode;
+
+ if (parse_mode == DILLO_HTML_PARSE_MODE_STASH ||
+ parse_mode == DILLO_HTML_PARSE_MODE_STASH_AND_BODY) {
+ if (html->StashSpace) {
+ dStr_append_c(html->Stash, ' ');
+ html->StashSpace = FALSE;
+ }
+ Pword = Html_parse_entities(html, word, size);
+ dStr_append(html->Stash, Pword);
+ dFree(Pword);
+
+ } else if (parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) {
+ /* word goes in untouched, it is not processed here. */
+ Pword = dStrndup(word, size);
+ dStr_append(html->Stash, Pword);
+ dFree(Pword);
+ }
+
+ if (parse_mode == DILLO_HTML_PARSE_MODE_STASH ||
+ parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) {
+ /* skip until the closing instructions */
+
+ } else if (parse_mode == DILLO_HTML_PARSE_MODE_PRE) {
+ /* all this overhead is to catch white-space entities */
+ Pword = Html_parse_entities(html, word, size);
+ for (start = i = 0; Pword[i]; start = i)
+ if (isspace(Pword[i])) {
+ while (Pword[++i] && isspace(Pword[i]));
+ Html_process_space(html, Pword + start, i - start);
+ } else {
+ while (Pword[++i] && !isspace(Pword[i]));
+ DW2TB(html->dw)->addText(
+ dStrndup(Pword + start, i - start),
+ S_TOP(html)->style);
+ html->pre_column += i - start;
+ html->PreFirstChar = FALSE;
+ }
+ dFree(Pword);
+
+ } else {
+ /* add pending space if present */
+ if (html->SPCPending && (!SGML_SPCDEL || !html->PrevWasOpenTag))
+ /* SGML_SPCDEL ignores space after an open tag */
+ DW2TB(html->dw)->addSpace (S_TOP(html)->style);
+
+ /* Collapse white-space entities inside the word (except &nbsp;) */
+ Pword = Html_parse_entities(html, word, size);
+ for (i = 0; Pword[i]; ++i)
+ if (strchr("\t\f\n\r", Pword[i]))
+ for (j = i; (Pword[j] = Pword[j+1]); ++j);
+
+ DW2TB(html->dw)->addText(Pword, S_TOP(html)->style);
+ }
+
+ html->PrevWasOpenTag = FALSE;
+ html->SPCPending = FALSE;
+}
+
+/*
+ * Does the tag in tagstr (e.g. "p") match the tag in the tag, tagsize
+ * structure, with the initial < skipped over (e.g. "P align=center>")
+ */
+static bool_t Html_match_tag(const char *tagstr, char *tag, int tagsize)
+{
+ int i;
+
+ for (i = 0; i < tagsize && tagstr[i] != '\0'; i++) {
+ if (tolower(tagstr[i]) != tolower(tag[i]))
+ return FALSE;
+ }
+ /* The test for '/' is for xml compatibility: "empty/>" will be matched. */
+ if (i < tagsize && (isspace(tag[i]) || tag[i] == '>' || tag[i] == '/'))
+ return TRUE;
+ return FALSE;
+}
+
+/*
+ * This function is called after popping the stack, to
+ * handle nested DwPage widgets.
+ */
+static void Html_eventually_pop_dw(DilloHtml *html, bool_t hand_over_break)
+{
+ if (html->dw != S_TOP(html)->textblock) {
+ if (hand_over_break)
+ DW2TB(html->dw)->handOverBreak (S_TOP(html)->style);
+ DW2TB(html->dw)->flush ();
+ html->dw = S_TOP(html)->textblock;
+ }
+}
+
+/*
+ * Push the tag (copying attributes from the top of the stack)
+ */
+static void Html_push_tag(DilloHtml *html, int tag_idx)
+{
+ char *tagstr;
+ int n_items;
+
+ /* Save the element's name (no parameters) into tagstr. */
+ tagstr = dStrdup(Tags[tag_idx].name);
+
+ n_items = html->stack->size ();
+ html->stack->increase ();
+ /* We'll copy the former stack item and just change the tag and its index
+ * instead of copying all fields except for tag. --Jcid */
+ *html->stack->getRef(n_items) = *html->stack->getRef(n_items - 1);
+ html->stack->getRef(n_items)->tag_name = tagstr;
+ html->stack->getRef(n_items)->tag_idx = tag_idx;
+ /* proper memory management, may be unref'd later */
+ (S_TOP(html)->style)->ref ();
+ if (S_TOP(html)->table_cell_style)
+ (S_TOP(html)->table_cell_style)->ref ();
+ html->dw = S_TOP(html)->textblock;
+}
+
+/*
+ * Push the tag (used to force en element with optional open into the stack)
+ * Note: now it's the same as Html_push_tag(), but things may change...
+ */
+static void Html_force_push_tag(DilloHtml *html, int tag_idx)
+{
+ Html_push_tag(html, tag_idx);
+}
+
+/*
+ * Pop the top tag in the stack
+ */
+static void Html_real_pop_tag(DilloHtml *html)
+{
+ bool_t hand_over_break;
+
+ (S_TOP(html)->style)->unref ();
+ if (S_TOP(html)->table_cell_style)
+ (S_TOP(html)->table_cell_style)->unref ();
+ dFree(S_TOP(html)->tag_name);
+ hand_over_break = S_TOP(html)->hand_over_break;
+ html->stack->setSize (html->stack->size() - 1);
+ Html_eventually_pop_dw(html, hand_over_break);
+}
+
+/*
+ * Default close function for tags.
+ * (conditional cleanup of the stack)
+ * There're several ways of doing it. Considering the HTML 4.01 spec
+ * which defines optional close tags, and the will to deliver useful diagnose
+ * messages for bad-formed HTML, it'll go as follows:
+ * 1.- Search the stack for the first tag that requires a close tag.
+ * 2.- If it matches, clean all the optional-close tags in between.
+ * 3.- Cleanup the matching tag. (on error, give a warning message)
+ *
+ * If 'w3c_mode' is NOT enabled:
+ * 1.- Search the stack for a matching tag based on tag level.
+ * 2.- If it exists, clean all the tags in between.
+ * 3.- Cleanup the matching tag. (on error, give a warning message)
+ */
+static void Html_tag_cleanup_at_close(DilloHtml *html, int TagIdx)
+{
+ int w3c_mode = !prefs.w3c_plus_heuristics;
+ int stack_idx, cmp = 1;
+ int new_idx = TagIdx;
+
+ if (html->CloseOneTag) {
+ Html_real_pop_tag(html);
+ html->CloseOneTag = FALSE;
+ return;
+ }
+
+ /* Look for the candidate tag to close */
+ stack_idx = html->stack->size() - 1;
+ while (stack_idx &&
+ (cmp = (new_idx != html->stack->getRef(stack_idx)->tag_idx)) &&
+ ((w3c_mode &&
+ Tags[html->stack->getRef(stack_idx)->tag_idx].EndTag == 'O') ||
+ (!w3c_mode &&
+ Tags[html->stack->getRef(stack_idx)->tag_idx].TagLevel <
+ Tags[new_idx].TagLevel))) {
+ --stack_idx;
+ }
+
+ /* clean, up to the matching tag */
+ if (cmp == 0 && stack_idx > 0) {
+ /* There's a valid matching tag in the stack */
+ while (html->stack->size() > stack_idx) {
+ int toptag_idx = S_TOP(html)->tag_idx;
+ /* Warn when we decide to close an open tag (for !w3c_mode) */
+ if (html->stack->size() > stack_idx + 1 &&
+ Tags[toptag_idx].EndTag != 'O')
+ MSG_HTML(" - forcing close of open tag: <%s>\n",
+ Tags[toptag_idx].name);
+
+ /* Close this and only this tag */
+ html->CloseOneTag = TRUE;
+ Tags[toptag_idx].close (html, toptag_idx);
+ }
+
+ } else {
+ MSG_HTML("unexpected closing tag: </%s>. -- expected </%s>\n",
+ Tags[new_idx].name, html->stack->getRef(stack_idx)->tag_name);
+ }
+}
+
+/*
+ * Cleanup (conditional), and Pop the tag (if it matches)
+ */
+static void Html_pop_tag(DilloHtml *html, int TagIdx)
+{
+ Html_tag_cleanup_at_close(html, TagIdx);
+}
+
+/*
+ * Some parsing routines.
+ */
+
+/*
+ * Used by Html_parse_length
+ */
+static Length Html_parse_length_or_multi_length (const char *attr,
+ char **endptr)
+{
+ Length l;
+ double v;
+ char *end;
+
+ v = strtod (attr, &end);
+ switch (*end) {
+ case '%':
+ end++;
+ l = createPerLength (v / 100);
+ break;
+
+ case '*':
+ end++;
+ l = createRelLength (v);
+ break;
+/*
+ The "px" suffix seems not allowed by HTML4.01 SPEC.
+ case 'p':
+ if (end[1] == 'x')
+ end += 2;
+*/
+ default:
+ l = createAbsLength ((int)v);
+ break;
+ }
+
+ if (endptr)
+ *endptr = end;
+ return l;
+}
+
+
+/*
+ * Returns a length or a percentage, or UNDEF_LENGTH in case
+ * of an error, or if attr is NULL.
+ */
+static Length Html_parse_length (DilloHtml *html, const char *attr)
+{
+ Length l;
+ char *end;
+
+ l = Html_parse_length_or_multi_length (attr, &end);
+ if (isRelLength (l))
+ /* not allowed as &Length; */
+ return LENGTH_AUTO;
+ else {
+ /* allow only whitespaces */
+ if (*end && !isspace (*end)) {
+ MSG_HTML("Garbage after length: %s\n", attr);
+ return LENGTH_AUTO;
+ }
+ }
+
+ _MSG("Html_parse_length: \"%s\" %d\n", attr, absLengthVal(l));
+ return l;
+}
+
+/*
+ * Parse a color attribute.
+ * Return value: parsed color, or default_color (+ error msg) on error.
+ */
+static int32_t
+ Html_color_parse(DilloHtml *html, const char *subtag, int32_t default_color)
+{
+ int err = 1;
+ int32_t color = a_Color_parse(subtag, default_color, &err);
+
+ if (err) {
+ MSG_HTML("color is not in \"#RRGGBB\" format\n");
+ }
+ return color;
+}
+
+/*
+ * Check that 'val' is composed of characters inside [A-Za-z0-9:_.-]
+ * Note: ID can't have entities, but this check is enough (no '&').
+ * Return value: 1 if OK, 0 otherwise.
+ */
+static int
+ Html_check_name_val(DilloHtml *html, const char *val, const char *attrname)
+{
+ int i;
+
+ for (i = 0; val[i]; ++i)
+ if (!(isalnum(val[i]) || strchr(":_.-", val[i])))
+ break;
+
+ if (val[i] || !isalpha(val[0]))
+ MSG_HTML("'%s' value is not of the form "
+ "[A-Za-z][A-Za-z0-9:_.-]*\n", attrname);
+
+ return !(val[i]);
+}
+
+/*
+ * Handle DOCTYPE declaration
+ *
+ * Follows the convention that HTML 4.01
+ * doctypes which include a full w3c DTD url are treated as
+ * standards-compliant, but 4.01 without the url and HTML 4.0 and
+ * earlier are not. XHTML doctypes are always standards-compliant
+ * whether or not an url is present.
+ *
+ * Note: I'm not sure about this convention. The W3C validator
+ * recognizes the "HTML Level" with or without the URL. The convention
+ * comes from mozilla (see URLs below), but Dillo doesn't have the same
+ * rendering modes, so it may be better to chose another behaviour. --Jcid
+ *
+ * http://www.mozilla.org/docs/web-developer/quirks/doctypes.html
+ * http://lists.auriga.wearlab.de/pipermail/dillo-dev/2004-October/002300.html
+ *
+ * This is not a full DOCTYPE parser, just enough for what Dillo uses.
+ */
+static void Html_parse_doctype(DilloHtml *html, char *tag, int tagsize)
+{
+ char *HTML_sig = "<!DOCTYPE HTML PUBLIC ";
+ char *HTML20 = "-//IETF//DTD HTML//EN";
+ char *HTML32 = "-//W3C//DTD HTML 3.2";
+ char *HTML40 = "-//W3C//DTD HTML 4.0";
+ char *HTML401 = "-//W3C//DTD HTML 4.01";
+ char *HTML401_url = "http://www.w3.org/TR/html4/";
+ char *XHTML1 = "-//W3C//DTD XHTML 1.0";
+ char *XHTML1_url = "http://www.w3.org/TR/xhtml1/DTD/";
+ char *XHTML11 = "-//W3C//DTD XHTML 1.1";
+ char *XHTML11_url = "http://www.w3.org/TR/xhtml11/DTD/";
+
+ int i, quote;
+ char *p, *ntag = dStrndup(tag, tagsize);
+
+ /* Tag sanitization: Collapse whitespace between tokens
+ * and replace '\n' and '\r' with ' ' inside quoted strings. */
+ for (i = 0, p = ntag; *p; ++p) {
+ if (isspace(*p)) {
+ for (ntag[i++] = ' '; isspace(p[1]); ++p);
+ } else if ((quote = *p) == '"' || *p == '\'') {
+ for (ntag[i++] = *p++; (ntag[i++] = *p) && *p != quote; ++p) {
+ if (*p == '\n' || *p == '\r')
+ ntag[i - 1] = ' ';
+ p += (p[0] == '\r' && p[1] == '\n') ? 1 : 0;
+ }
+ } else {
+ ntag[i++] = *p;
+ }
+ if (!*p)
+ break;
+ }
+ ntag[i] = 0;
+
+ _MSG("New: {%s}\n", ntag);
+
+ /* The default DT_NONE type is TagSoup */
+ if (!dStrncasecmp(ntag, HTML_sig, strlen(HTML_sig))) {
+ p = ntag + strlen(HTML_sig) + 1;
+ if (!strncmp(p, HTML401, strlen(HTML401)) &&
+ dStristr(p + strlen(HTML401), HTML401_url)) {
+ html->DocType = DT_HTML;
+ html->DocTypeVersion = 4.01f;
+ } else if (!strncmp(p, XHTML1, strlen(XHTML1)) &&
+ dStristr(p + strlen(XHTML1), XHTML1_url)) {
+ html->DocType = DT_XHTML;
+ html->DocTypeVersion = 1.0f;
+ } else if (!strncmp(p, XHTML11, strlen(XHTML11)) &&
+ dStristr(p + strlen(XHTML11), XHTML11_url)) {
+ html->DocType = DT_XHTML;
+ html->DocTypeVersion = 1.1f;
+ } else if (!strncmp(p, HTML40, strlen(HTML40))) {
+ html->DocType = DT_HTML;
+ html->DocTypeVersion = 4.0f;
+ } else if (!strncmp(p, HTML32, strlen(HTML32))) {
+ html->DocType = DT_HTML;
+ html->DocTypeVersion = 3.2f;
+ } else if (!strncmp(p, HTML20, strlen(HTML20))) {
+ html->DocType = DT_HTML;
+ html->DocTypeVersion = 2.0f;
+ }
+ }
+
+ dFree(ntag);
+}
+
+/*
+ * Handle open HTML element
+ */
+static void Html_tag_open_html(DilloHtml *html, char *tag, int tagsize)
+{
+ if (!(html->InFlags & IN_HTML))
+ html->InFlags |= IN_HTML;
+ ++html->Num_HTML;
+
+ if (html->Num_HTML > 1) {
+ MSG_HTML("HTML element was already open\n");
+ }
+}
+
+/*
+ * Handle close HTML element
+ */
+static void Html_tag_close_html(DilloHtml *html, int TagIdx)
+{
+ /* todo: may add some checks here */
+ if (html->Num_HTML == 1) {
+ /* beware of pages with multiple HTML close tags... :-P */
+ html->InFlags &= ~IN_HTML;
+ }
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Handle open HEAD element
+ */
+static void Html_tag_open_head(DilloHtml *html, char *tag, int tagsize)
+{
+ if (html->InFlags & IN_BODY) {
+ MSG_HTML("HEAD element must go before the BODY section\n");
+ html->ReqTagClose = TRUE;
+ return;
+ }
+
+ if (!(html->InFlags & IN_HEAD))
+ html->InFlags |= IN_HEAD;
+ ++html->Num_HEAD;
+
+ if (html->Num_HEAD > 1) {
+ MSG_HTML("HEAD element was already open\n");
+ }
+}
+
+/*
+ * Handle close HEAD element
+ * Note: as a side effect of Html_test_section() this function is called
+ * twice when the head element is closed implicitly.
+ */
+static void Html_tag_close_head(DilloHtml *html, int TagIdx)
+{
+ if (html->InFlags & IN_HEAD) {
+ if (html->Num_TITLE == 0)
+ MSG_HTML("HEAD section lacks the TITLE element\n");
+
+ html->InFlags &= ~IN_HEAD;
+ }
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Handle open TITLE
+ * calls stash init, where the title string will be stored
+ */
+static void Html_tag_open_title(DilloHtml *html, char *tag, int tagsize)
+{
+ ++html->Num_TITLE;
+ Html_stash_init(html);
+}
+
+/*
+ * Handle close TITLE
+ * set page-title in the browser window and in the history.
+ */
+static void Html_tag_close_title(DilloHtml *html, int TagIdx)
+{
+ if (html->InFlags & IN_HEAD) {
+ /* title is only valid inside HEAD */
+ a_UIcmd_set_page_title(html->linkblock->bw, html->Stash->str);
+ a_History_set_title(NAV_TOP(html->linkblock->bw), html->Stash->str);
+ } else {
+ MSG_HTML("the TITLE element must be inside the HEAD section\n");
+ }
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Handle open SCRIPT
+ * initializes stash, where the embedded code will be stored.
+ * MODE_VERBATIM is used because MODE_STASH catches entities.
+ */
+static void Html_tag_open_script(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_stash_init(html);
+ S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM;
+}
+
+/*
+ * Handle close SCRIPT
+ */
+static void Html_tag_close_script(DilloHtml *html, int TagIdx)
+{
+ /* eventually the stash will be sent to an interpreter for parsing */
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Handle open STYLE
+ * store the contents to the stash where (in the future) the style
+ * sheet interpreter can get it.
+ */
+static void Html_tag_open_style(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_stash_init(html);
+ S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM;
+}
+
+/*
+ * Handle close STYLE
+ */
+static void Html_tag_close_style(DilloHtml *html, int TagIdx)
+{
+ /* eventually the stash will be sent to an interpreter for parsing */
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * <BODY>
+ */
+static void Html_tag_open_body(DilloHtml *html, char *tag, int tagsize)
+{
+ const char *attrbuf;
+ Textblock *textblock;
+ StyleAttrs style_attrs;
+ Style *style;
+ int32_t color;
+
+ if (!(html->InFlags & IN_BODY))
+ html->InFlags |= IN_BODY;
+ ++html->Num_BODY;
+
+ if (html->Num_BODY > 1) {
+ MSG_HTML("BODY element was already open\n");
+ return;
+ }
+ if (html->InFlags & IN_HEAD) {
+ /* if we're here, it's bad XHTML, no need to recover */
+ MSG_HTML("unclosed HEAD element\n");
+ }
+
+ textblock = DW2TB(html->dw);
+
+ if (!prefs.force_my_colors) {
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) {
+ color = Html_color_parse(html, attrbuf, prefs.bg_color);
+ if ((color == 0xffffff && !prefs.allow_white_bg) ||
+ prefs.force_my_colors)
+ color = prefs.bg_color;
+
+ style_attrs = *html->dw->getStyle ();
+ style_attrs.backgroundColor =
+ Color::createSimple (HT2LT(html), color);
+ style = Style::create (HT2LT(html), &style_attrs);
+ html->dw->setStyle (style);
+ style->unref ();
+ S_TOP(html)->current_bg_color = color;
+ }
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "text"))) {
+ color = Html_color_parse(html, attrbuf, prefs.text_color);
+ HTML_SET_TOP_ATTR (html, color,
+ Color::createSimple (HT2LT(html),color));
+ }
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "link")))
+ html->linkblock->link_color = Html_color_parse(html, attrbuf,
+ prefs.link_color);
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "vlink")))
+ html->linkblock->visited_color =
+ Html_color_parse(html, attrbuf, prefs.visited_color);
+
+ if (prefs.contrast_visited_color) {
+ /* get a color that has a "safe distance" from text, link and bg */
+ html->linkblock->visited_color =
+ a_Color_vc(html->linkblock->visited_color,
+ S_TOP(html)->style->color->getColor(),
+ html->linkblock->link_color,
+ S_TOP(html)->current_bg_color);
+ }
+ }
+
+ S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_BODY;
+}
+
+/*
+ * BODY
+ */
+static void Html_tag_close_body(DilloHtml *html, int TagIdx)
+{
+ if (html->Num_BODY == 1) {
+ /* some tag soup pages use multiple BODY tags... */
+ html->InFlags &= ~IN_BODY;
+ }
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * <P>
+ * todo: what's the point between adding the parbreak before and
+ * after the push?
+ */
+static void Html_tag_open_p(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_tag_set_align_attr (html, tag, tagsize);
+}
+
+/*
+ * <TABLE>
+ */
+static void Html_tag_open_table(DilloHtml *html, char *tag, int tagsize)
+{
+#ifdef USE_TABLES
+ Widget *table;
+ StyleAttrs style_attrs;
+ Style *tstyle, *old_style;
+ const char *attrbuf;
+ int32_t border = 0, cellspacing = 1, cellpadding = 2, bgcolor;
+#endif
+
+ DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style);
+
+#ifdef USE_TABLES
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "border")))
+ border = isdigit(attrbuf[0]) ? strtol (attrbuf, NULL, 10) : 1;
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "cellspacing")))
+ cellspacing = strtol (attrbuf, NULL, 10);
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "cellpadding")))
+ cellpadding = strtol (attrbuf, NULL, 10);
+
+ /* The style for the table */
+ style_attrs = *S_TOP(html)->style;
+
+ /* When dillo was started with the --debug-rendering option, there
+ * is always a border around the table. */
+ if (dillo_dbg_rendering)
+ style_attrs.borderWidth.setVal (MIN (border, 1));
+ else
+ style_attrs.borderWidth.setVal (border);
+
+ style_attrs.setBorderColor (
+ Color::createShaded (HT2LT(html),
+ S_TOP(html)->current_bg_color));
+ style_attrs.setBorderStyle (BORDER_OUTSET);
+ style_attrs.hBorderSpacing = cellspacing;
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "width")))
+ style_attrs.width = Html_parse_length (html, attrbuf);
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "align"))) {
+ if (dStrcasecmp (attrbuf, "left") == 0)
+ style_attrs.textAlign = TEXT_ALIGN_LEFT;
+ else if (dStrcasecmp (attrbuf, "right") == 0)
+ style_attrs.textAlign = TEXT_ALIGN_RIGHT;
+ else if (dStrcasecmp (attrbuf, "center") == 0)
+ style_attrs.textAlign = TEXT_ALIGN_CENTER;
+ }
+
+ if (!prefs.force_my_colors &&
+ (attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) {
+ bgcolor = Html_color_parse(html, attrbuf, -1);
+ if (bgcolor != -1) {
+ if (bgcolor == 0xffffff && !prefs.allow_white_bg)
+ bgcolor = prefs.bg_color;
+ S_TOP(html)->current_bg_color = bgcolor;
+ style_attrs.backgroundColor =
+ Color::createSimple (HT2LT(html), bgcolor);
+ }
+ }
+
+ tstyle = Style::create (HT2LT(html), &style_attrs);
+
+ /* The style for the cells */
+ style_attrs = *S_TOP(html)->style;
+ /* When dillo was started with the --debug-rendering option, there
+ * is always a border around the cells. */
+ if (dillo_dbg_rendering)
+ style_attrs.borderWidth.setVal (1);
+ else
+ style_attrs.borderWidth.setVal (border ? 1 : 0);
+
+ style_attrs.padding.setVal(cellpadding);
+ style_attrs.setBorderColor (tstyle->borderColor.top);
+ style_attrs.setBorderStyle (BORDER_INSET);
+
+ old_style = S_TOP(html)->table_cell_style;
+ S_TOP(html)->table_cell_style =
+ Style::create (HT2LT(html), &style_attrs);
+ if (old_style)
+ old_style->unref ();
+
+ table = new Table(false);
+ DW2TB(html->dw)->addWidget (table, tstyle);
+ tstyle->unref ();
+
+ S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TOP;
+ S_TOP(html)->cell_text_align_set = FALSE;
+ S_TOP(html)->table = table;
+#endif
+}
+
+
+/*
+ * used by <TD> and <TH>
+ */
+static void Html_tag_open_table_cell(DilloHtml *html, char *tag, int tagsize,
+ TextAlignType text_align)
+{
+#ifdef USE_TABLES
+ Widget *col_tb;
+ int colspan = 1, rowspan = 1;
+ const char *attrbuf;
+ StyleAttrs style_attrs;
+ Style *style, *old_style;
+ int32_t bgcolor;
+ bool_t new_style;
+
+ switch (S_TOP(html)->table_mode) {
+ case DILLO_HTML_TABLE_MODE_NONE:
+ MSG_HTML("<td> or <th> outside <table>\n");
+ return;
+
+ case DILLO_HTML_TABLE_MODE_TOP:
+ MSG_HTML("<td> or <th> outside <tr>\n");
+ /* a_Dw_table_add_cell takes care that dillo does not crash. */
+ /* continues */
+ case DILLO_HTML_TABLE_MODE_TR:
+ case DILLO_HTML_TABLE_MODE_TD:
+ /* todo: check errors? */
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "colspan")))
+ colspan = strtol (attrbuf, NULL, 10);
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "rowspan")))
+ rowspan = strtol (attrbuf, NULL, 10);
+
+ /* text style */
+ old_style = S_TOP(html)->style;
+ style_attrs = *old_style;
+ if (!S_TOP(html)->cell_text_align_set)
+ style_attrs.textAlign = text_align;
+ if (Html_get_attr(html, tag, tagsize, "nowrap"))
+ style_attrs.whiteSpace = WHITE_SPACE_NOWRAP;
+ else
+ style_attrs.whiteSpace = WHITE_SPACE_NORMAL;
+
+ S_TOP(html)->style =
+ Style::create (HT2LT(html), &style_attrs);
+ old_style->unref ();
+ Html_tag_set_align_attr (html, tag, tagsize);
+
+ /* cell style */
+ style_attrs = *S_TOP(html)->table_cell_style;
+ new_style = FALSE;
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "width"))) {
+ style_attrs.width = Html_parse_length (html, attrbuf);
+ new_style = TRUE;
+ }
+
+ if (Html_tag_set_valign_attr (html, tag, tagsize, &style_attrs))
+ new_style = TRUE;
+
+ if (!prefs.force_my_colors &&
+ (attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) {
+ bgcolor = Html_color_parse(html, attrbuf, -1);
+ if (bgcolor != -1) {
+ if (bgcolor == 0xffffff && !prefs.allow_white_bg)
+ bgcolor = prefs.bg_color;
+
+ new_style = TRUE;
+ style_attrs.backgroundColor =
+ Color::createSimple (HT2LT(html), bgcolor);
+ S_TOP(html)->current_bg_color = bgcolor;
+ }
+ }
+
+ if (S_TOP(html)->style->textAlign
+ == TEXT_ALIGN_STRING)
+ col_tb = new TableCell (
+ ((Table*)S_TOP(html)->table)->getCellRef (),
+ false);
+ else
+ col_tb = new Textblock (false);
+
+ if (new_style) {
+ style = Style::create (HT2LT(html), &style_attrs);
+ col_tb->setStyle (style);
+ style->unref ();
+ } else
+ col_tb->setStyle (S_TOP(html)->table_cell_style);
+
+ ((Table*)S_TOP(html)->table)->addCell (
+ col_tb, colspan, rowspan);
+ S_TOP(html)->textblock = html->dw = col_tb;
+
+ /* Handle it when the user clicks on a link */
+ Html_connect_signals(html, col_tb);
+ break;
+
+ default:
+ /* compiler happiness */
+ break;
+ }
+
+ S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TD;
+#endif
+}
+
+
+/*
+ * <TD>
+ */
+static void Html_tag_open_td(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_tag_open_table_cell (html, tag, tagsize, TEXT_ALIGN_LEFT);
+}
+
+
+/*
+ * <TH>
+ */
+static void Html_tag_open_th(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 1, 1);
+ Html_tag_open_table_cell (html, tag, tagsize, TEXT_ALIGN_CENTER);
+}
+
+
+/*
+ * <TR>
+ */
+static void Html_tag_open_tr(DilloHtml *html, char *tag, int tagsize)
+{
+ const char *attrbuf;
+ StyleAttrs style_attrs;
+ Style *style, *old_style;
+ int32_t bgcolor;
+
+#ifdef USE_TABLES
+ switch (S_TOP(html)->table_mode) {
+ case DILLO_HTML_TABLE_MODE_NONE:
+ _MSG("Invalid HTML syntax: <tr> outside <table>\n");
+ return;
+
+ case DILLO_HTML_TABLE_MODE_TOP:
+ case DILLO_HTML_TABLE_MODE_TR:
+ case DILLO_HTML_TABLE_MODE_TD:
+ style = NULL;
+
+ if (!prefs.force_my_colors &&
+ (attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) {
+ bgcolor = Html_color_parse(html, attrbuf, -1);
+ if (bgcolor != -1) {
+ if (bgcolor == 0xffffff && !prefs.allow_white_bg)
+ bgcolor = prefs.bg_color;
+
+ style_attrs = *S_TOP(html)->style;
+ style_attrs.backgroundColor =
+ Color::createSimple (HT2LT(html), bgcolor);
+ style = Style::create (HT2LT(html), &style_attrs);
+ S_TOP(html)->current_bg_color = bgcolor;
+ }
+ }
+
+ ((Table*)S_TOP(html)->table)->addRow (style);
+ if (style)
+ style->unref ();
+
+ if (Html_get_attr (html, tag, tagsize, "align")) {
+ S_TOP(html)->cell_text_align_set = TRUE;
+ Html_tag_set_align_attr (html, tag, tagsize);
+ }
+
+ style_attrs = *S_TOP(html)->table_cell_style;
+ if (Html_tag_set_valign_attr (html, tag, tagsize, &style_attrs)) {
+ old_style = S_TOP(html)->table_cell_style;
+ S_TOP(html)->table_cell_style =
+ Style::create (HT2LT(html), &style_attrs);
+ old_style->unref ();
+ } else
+
+ break;
+
+ default:
+ break;
+ }
+
+ S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TR;
+#else
+ DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style);
+#endif
+}
+
+/*
+ * <FRAME>, <IFRAME>
+ * todo: This is just a temporary fix while real frame support
+ * isn't finished. Imitates lynx/w3m's frames.
+ */
+static void Html_tag_open_frame (DilloHtml *html, char *tag, int tagsize)
+{
+ const char *attrbuf;
+ char *src, *buf;
+ DilloUrl *url;
+ Textblock *textblock;
+ StyleAttrs style_attrs;
+ Style *link_style;
+ Widget *bullet;
+ int buf_size;
+
+ textblock = DW2TB(html->dw);
+
+ if (!(attrbuf = Html_get_attr(html, tag, tagsize, "src")))
+ return;
+
+ if (!(url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0)))
+ return;
+
+ src = dStrdup(attrbuf);
+
+ style_attrs = *(S_TOP(html)->style);
+
+ if (a_Capi_get_buf(url, &buf, &buf_size)) /* visited frame */
+ style_attrs.color =
+ Color::createSimple (HT2LT(html),
+ html->linkblock->visited_color);
+ else /* unvisited frame */
+ style_attrs.color = Color::createSimple (HT2LT(html),
+ html->linkblock->link_color);
+
+ style_attrs.textDecoration |= TEXT_DECORATION_UNDERLINE;
+ style_attrs.x_link = Html_set_new_link(html, &url);
+ link_style = Style::create (HT2LT(html), &style_attrs);
+
+ textblock->addParbreak (5, S_TOP(html)->style);
+
+ /* The bullet will be assigned the current list style, which should
+ * be "disc" by default, but may in very weird pages be different.
+ * Anyway, there should be no harm. */
+ bullet = new Bullet();
+ textblock->addWidget(bullet, S_TOP(html)->style);
+ textblock->addSpace(S_TOP(html)->style);
+
+ if (tolower(tag[1]) == 'i') {
+ /* IFRAME usually comes with very long advertising/spying URLS,
+ * to not break rendering we will force name="IFRAME" */
+ textblock->addText (dStrdup("IFRAME"), link_style);
+
+ } else {
+ /* FRAME:
+ * If 'name' tag is present use it, if not use 'src' value */
+ if (!(attrbuf = Html_get_attr(html, tag, tagsize, "name"))) {
+ textblock->addText (dStrdup(src), link_style);
+ } else {
+ textblock->addText (dStrdup(attrbuf), link_style);
+ }
+ }
+
+ textblock->addParbreak (5, S_TOP(html)->style);
+
+ link_style->unref ();
+ dFree(src);
+}
+
+/*
+ * <FRAMESET>
+ * todo: This is just a temporary fix while real frame support
+ * isn't finished. Imitates lynx/w3m's frames.
+ */
+static void Html_tag_open_frameset (DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ DW2TB(html->dw)->addText(dStrdup("--FRAME--"),
+ S_TOP(html)->style);
+ Html_add_indented(html, 40, 0, 5);
+}
+
+/*
+ * <H1> | <H2> | <H3> | <H4> | <H5> | <H6>
+ */
+static void Html_tag_open_h(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+
+ /* todo: combining these two would be slightly faster */
+ Html_set_top_font(html, prefs.vw_fontname,
+ Html_level_to_fontsize(FontSizesNum - (tag[2] - '0')),
+ 1, 3);
+ Html_tag_set_align_attr (html, tag, tagsize);
+
+ /* First finalize unclosed H tags (we test if already named anyway) */
+ a_Menu_pagemarks_set_text(html->bw, html->Stash->str);
+ a_Menu_pagemarks_add(html->bw, DW2TB(html->dw),
+ S_TOP(html)->style, (tag[2] - '0'));
+ Html_stash_init(html);
+ S_TOP(html)->parse_mode =
+ DILLO_HTML_PARSE_MODE_STASH_AND_BODY;
+}
+
+/*
+ * Handle close: <H1> | <H2> | <H3> | <H4> | <H5> | <H6>
+ */
+static void Html_tag_close_h(DilloHtml *html, int TagIdx)
+{
+ a_Menu_pagemarks_set_text(html->bw, html->Stash->str);
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * <BIG> | <SMALL>
+ */
+static void Html_tag_open_big_small(DilloHtml *html, char *tag, int tagsize)
+{
+ int level;
+
+ level =
+ Html_fontsize_to_level(S_TOP(html)->style->font->size) +
+ ((dStrncasecmp(tag+1, "big", 3)) ? -1 : 1);
+ Html_set_top_font(html, NULL, Html_level_to_fontsize(level), 0, 0);
+}
+
+/*
+ * <BR>
+ */
+static void Html_tag_open_br(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addLinebreak (S_TOP(html)->style);
+}
+
+/*
+ * <BUTTON>
+ */
+static void Html_tag_open_button(DilloHtml *html, char *tag, int tagsize)
+{
+// // AL
+// /*
+// * Buttons are rendered on one line, this is (at several levels) a
+// * bit simpler. May be changed in the future.
+// */
+// StyleAttrs style_attrs;
+// Style *style;
+// Widget *button, *page;
+// DilloHtmlForm *form;
+// DilloHtmlLB *html_lb;
+// DilloHtmlInputType inp_type;
+// char *name, *value, *type;
+//
+// /* Render the button */
+// style_attrs = *S_TOP(html)->style;
+//
+// style_attrs.margin.setVal (0);
+// style_attrs.borderWidth.setVal (0);
+// style_attrs.padding.setVal(0);
+// style = Style::create (HT2LT(html), &style_attrs);
+// button = a_Dw_button_new (DW_USES_HINTS, TRUE);
+//
+// /* The new button is not set button-insensitive, since nested buttons
+// * (if they are anyway allowed, todo: search in spec) should all be
+// * activatable. */
+// a_Dw_widget_set_button_sensitive (button, TRUE);
+//
+// DW2TB(html->dw)->addParbreak (5, style);
+// DW2TB(html->dw)->addWidget (button, style);
+// DW2TB(html->dw)->addParbreak (5, style);
+// style->unref ();
+//
+// style_attrs.margin.setVal (5);
+// style = Style::create (HT2LT(html), &style_attrs);
+// page = new Textblock (false);
+// page->setStyle (style);
+// style->unref ();
+// a_Dw_container_add (DW_CONTAINER (button), page);
+// a_Dw_widget_set_button_sensitive (DW_WIDGET (page), FALSE);
+// style_attrs.margin.setVal (0);
+//
+// a_Dw_button_set_sensitive (DW_BUTTON (button), FALSE);
+//
+// S_TOP(html)->textblock = html->dw = page;
+//
+// /* Handle it when the user clicks on a link */
+// Html_connect_signals(html, GTK_OBJECT(page));
+//
+// /* Connect it to the form */
+// html_lb = html->linkblock;
+// form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+//
+// type = Html_get_attr_wdef(html, tag, tagsize, "type", "");
+//
+// if (strcmp(type, "submit") == 0) {
+// inp_type = DILLO_HTML_INPUT_BUTTON_SUBMIT;
+// gtk_signal_connect(GTK_OBJECT(button), "clicked",
+// GTK_SIGNAL_FUNC(Html_submit_form), html_lb);
+// } else if (strcmp(type, "reset") == 0) {
+// inp_type = DILLO_HTML_INPUT_BUTTON_RESET;
+// gtk_signal_connect(GTK_OBJECT(button), "clicked",
+// GTK_SIGNAL_FUNC(Html_reset_form), html_lb);
+// } else
+// return;
+//
+// value = Html_get_attr_wdef(html, tag, tagsize, "value", NULL);
+// name = Html_get_attr_wdef(html, tag, tagsize, "name", NULL);
+//
+// Html_add_input(form, inp_type, (GtkWidget*)button, name, value,
+// NULL, FALSE);
+//
+// dFree(type);
+// dFree(name);
+// dFree(value);
+}
+
+
+/*
+ * <FONT>
+ */
+static void Html_tag_open_font(DilloHtml *html, char *tag, int tagsize)
+{
+#if 1
+ StyleAttrs style_attrs;
+ Style *old_style;
+ /*Font font;*/
+ const char *attrbuf;
+ int32_t color;
+
+ if (!prefs.force_my_colors) {
+ old_style = S_TOP(html)->style;
+ style_attrs = *old_style;
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "color"))) {
+ if (prefs.contrast_visited_color && html->InVisitedLink) {
+ color = html->linkblock->visited_color;
+ } else {
+ /* use the tag-specified color */
+ color = Html_color_parse(
+ html, attrbuf, style_attrs.color->getColor());
+ style_attrs.color = Color::createSimple (HT2LT(html), color);
+ }
+ }
+
+#if 0
+ //if ((attrbuf = Html_get_attr(html, tag, tagsize, "face"))) {
+ // font = *( style_attrs.font );
+ // font.name = attrbuf;
+ // style_attrs.font = a_Dw_style_font_new_from_list (&font);
+ //}
+#endif
+
+ S_TOP(html)->style =
+ Style::create (HT2LT(html), &style_attrs);
+ old_style->unref ();
+ }
+
+#endif
+}
+
+/*
+ * <ABBR>
+ */
+static void Html_tag_open_abbr(DilloHtml *html, char *tag, int tagsize)
+{
+// DwTooltip *tooltip;
+// const char *attrbuf;
+//
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "title"))) {
+// tooltip = a_Dw_tooltip_new_no_ref(attrbuf);
+// HTML_SET_TOP_ATTR(html, x_tooltip, tooltip);
+// }
+}
+
+/*
+ * <B>
+ */
+static void Html_tag_open_b(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 1, 1);
+}
+
+/*
+ * <STRONG>
+ */
+static void Html_tag_open_strong(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 1, 1);
+}
+
+/*
+ * <I>
+ */
+static void Html_tag_open_i(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 2, 2);
+}
+
+/*
+ * <EM>
+ */
+static void Html_tag_open_em(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 2, 2);
+}
+
+/*
+ * <CITE>
+ */
+static void Html_tag_open_cite(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 2, 2);
+}
+
+/*
+ * <CENTER>
+ */
+static void Html_tag_open_center(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style);
+ HTML_SET_TOP_ATTR(html, textAlign, TEXT_ALIGN_CENTER);
+}
+
+/*
+ * <ADDRESS>
+ */
+static void Html_tag_open_address(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_set_top_font(html, NULL, 0, 2, 2);
+}
+
+/*
+ * <TT>
+ */
+static void Html_tag_open_tt(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0);
+}
+
+/*
+ * Read image-associated tag attributes,
+ * create new image and add it to the html page (if add is TRUE).
+ */
+static DilloImage *Html_add_new_image(DilloHtml *html, char *tag,
+ int tagsize, StyleAttrs *style_attrs,
+ bool_t add)
+{
+ const int MAX_W = 6000, MAX_H = 6000;
+
+ DilloImage *Image;
+ char *width_ptr, *height_ptr, *alt_ptr;
+ const char *attrbuf;
+ Length l_w, l_h;
+ int space, w = 0, h = 0;
+
+// if (prefs.show_tooltip &&
+// (attrbuf = Html_get_attr(html, tag, tagsize, "title")))
+// style_attrs->x_tooltip = a_Dw_tooltip_new_no_ref(attrbuf);
+
+ alt_ptr = Html_get_attr_wdef(html, tag, tagsize, "alt", NULL);
+ width_ptr = Html_get_attr_wdef(html, tag, tagsize, "width", NULL);
+ height_ptr = Html_get_attr_wdef(html, tag, tagsize, "height", NULL);
+ // Check for malicious values
+ // TODO: the same for percentage and relative lengths.
+ if (width_ptr) {
+ l_w = Html_parse_length (html, width_ptr);
+ w = isAbsLength(l_w) ? absLengthVal(l_w) : 0;
+ }
+ if (height_ptr) {
+ l_h = Html_parse_length (html, height_ptr);
+ h = isAbsLength(l_h) ? absLengthVal(l_h) : 0;
+ }
+ if (w < 0 || h < 0 || abs(w*h) > MAX_W * MAX_H) {
+ dFree(width_ptr);
+ dFree(height_ptr);
+ width_ptr = height_ptr = NULL;
+ MSG("Html_add_new_image: suspicious image size request %dx%d\n", w, h);
+ }
+
+ /* todo: we should scale the image respecting its ratio.
+ * As the image size is not known at this time, maybe a flag
+ * can be set to scale it later.
+ if ((width_ptr && !height_ptr) || (height_ptr && !width_ptr))
+ [...]
+ */
+
+ /* Spacing to the left and right */
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "hspace"))) {
+ space = strtol(attrbuf, NULL, 10);
+
+ if (space > 0)
+ style_attrs->margin.left = style_attrs->margin.right = space;
+ }
+
+ /* Spacing at the top and bottom */
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "vspace"))) {
+ space = strtol(attrbuf, NULL, 10);
+
+ if (space > 0)
+ style_attrs->margin.top = style_attrs->margin.bottom = space;
+ }
+
+ /* Add a new image widget to this page */
+ Image = a_Image_new(0, 0, alt_ptr, S_TOP(html)->current_bg_color);
+ if (add) {
+ Html_add_widget(html, (Widget*)Image->dw,
+ width_ptr, height_ptr, style_attrs);
+ }
+
+ dFree(width_ptr);
+ dFree(height_ptr);
+ dFree(alt_ptr);
+ return Image;
+}
+
+/*
+ * Tell cache to retrieve image
+ */
+static void Html_load_image(DilloHtml *html, DilloUrl *url, DilloImage *Image)
+{
+ DilloWeb *Web;
+ int ClientKey;
+ /* Fill a Web structure for the cache query */
+ Web = a_Web_new(url);
+ Web->bw = html->bw;
+ Web->Image = Image;
+ Web->flags |= WEB_Image;
+ /* Request image data from the cache */
+ if ((ClientKey = a_Capi_open_url(Web, NULL, NULL)) != 0) {
+ a_Bw_add_client(html->bw, ClientKey, 0);
+ a_Bw_add_url(html->bw, url);
+ }
+}
+
+/*
+ * Create a new Image struct and request the image-url to the cache
+ * (If it either hits or misses, is not relevant here; that's up to the
+ * cache functions)
+ */
+static void Html_tag_open_img(DilloHtml *html, char *tag, int tagsize)
+{
+ DilloImage *Image;
+ DilloUrl *url, *usemap_url;
+ Textblock *textblock;
+ StyleAttrs style_attrs;
+ const char *attrbuf;
+ int border;
+
+ /* This avoids loading images. Useful for viewing suspicious HTML email. */
+ if (URL_FLAGS(html->linkblock->base_url) & URL_SpamSafe)
+ return;
+
+ if (!(attrbuf = Html_get_attr(html, tag, tagsize, "src")) ||
+ !(url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0)))
+ return;
+
+ textblock = DW2TB(html->dw);
+
+
+ usemap_url = NULL;
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "usemap")))
+ /* todo: usemap URLs outside of the document are not used. */
+ usemap_url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0);
+
+ style_attrs = *S_TOP(html)->style;
+
+ if (S_TOP(html)->style->x_link != -1 ||
+ usemap_url != NULL) {
+ /* Images within links */
+ border = 1;
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "border")))
+ border = strtol (attrbuf, NULL, 10);
+
+ if (S_TOP(html)->style->x_link != -1) {
+ /* In this case we can use the text color */
+ style_attrs.setBorderColor (
+ Color::createShaded (HT2LT(html),
+ style_attrs.color->getColor()));
+ } else {
+ style_attrs.setBorderColor (
+ Color::createShaded (HT2LT(html),
+ html->linkblock->link_color));
+ }
+ style_attrs.setBorderStyle (BORDER_SOLID);
+ style_attrs.borderWidth.setVal (border);
+ }
+
+ Image = Html_add_new_image(html, tag, tagsize, &style_attrs, TRUE);
+ // Html_connect_signals(html, GTK_OBJECT(Image->dw));
+// gtk_signal_connect_after(GTK_OBJECT(Image->dw), "button_press_event",
+// GTK_SIGNAL_FUNC(Html_image_menu), html->bw);
+
+#if 0
+ /* Image maps */
+ if (Html_get_attr(html, tag, tagsize, "ismap")) {
+ /* BUG: if several ISMAP images follow each other without
+ * being separated with a word, only the first one is ISMAPed
+ */
+// a_Dw_image_set_ismap (Image->dw);
+ _MSG(" Html_tag_open_img: server-side map (ISMAP)\n");
+ } else if (S_TOP(html)->style->x_link != -1 &&
+ usemap_url == NULL)
+ /* For simple links, we have to suppress the "image_pressed" signal.
+ * This is overridden for USEMAP images. */
+// a_Dw_widget_set_button_sensitive (IM2DW(Image->dw), FALSE);
+
+ if (usemap_url) {
+// a_Dw_image_set_usemap (Image->dw, &html->linkblock->maps, usemap_url);
+ a_Url_free (usemap_url);
+ }
+#endif
+
+ Html_load_image(html, url, Image);
+ a_Url_free(url);
+}
+
+/*
+ * <map>
+ */
+static void Html_tag_open_map(DilloHtml *html, char *tag, int tagsize)
+{
+ char *hash_name;
+ const char *attrbuf;
+ DilloUrl *url;
+
+ if (html->InFlags & IN_MAP) {
+ MSG_HTML("nested <map>\n");
+ } else {
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "name"))) {
+ hash_name = dStrconcat("#", attrbuf, NULL);
+ url = Html_url_new(html, hash_name, NULL, 0, 0, 0, 0);
+ //a_Dw_image_map_list_add_map (&html->linkblock->maps, url);
+ a_Url_free (url);
+ dFree(hash_name);
+ }
+ html->InFlags |= IN_MAP;
+ }
+}
+
+/*
+ * Handle close <MAP>
+ */
+static void Html_tag_close_map(DilloHtml *html, int TagIdx)
+{
+ html->InFlags &= ~IN_MAP;
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * <AREA>
+ */
+static void Html_tag_open_area(DilloHtml *html, char *tag, int tagsize)
+{
+// // AL
+// /* todo: point must be a dynamic array */
+// GdkPoint point[1024];
+// DilloUrl* url;
+// const char *attrbuf;
+// int type = DW_IMAGE_MAP_SHAPE_RECT;
+// int nbpoints, link = -1;
+//
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "shape"))) {
+// if (dStrcasecmp(attrbuf, "rect") == 0)
+// type = DW_IMAGE_MAP_SHAPE_RECT;
+// else if (dStrcasecmp(attrbuf, "circle") == 0)
+// type = DW_IMAGE_MAP_SHAPE_CIRCLE;
+// else if (dStrncasecmp(attrbuf, "poly", 4) == 0)
+// type = DW_IMAGE_MAP_SHAPE_POLY;
+// else
+// type = DW_IMAGE_MAP_SHAPE_RECT;
+// }
+// /* todo: add support for coords in % */
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "coords"))) {
+// /* Is this a valid poly ?
+// * rect = x0,y0,x1,y1 => 2
+// * circle = x,y,r => 2
+// * poly = x0,y0,x1,y1,x2,y2 minimum => 3 */
+// nbpoints = Html_read_coords(html, attrbuf, point);
+// } else
+// return;
+//
+// if (Html_get_attr(html, tag, tagsize, "nohref")) {
+// link = -1;
+// _MSG("nohref");
+// }
+//
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "href"))) {
+// url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0);
+// dReturn_if_fail ( url != NULL );
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "alt")))
+// a_Url_set_alt(url, attrbuf);
+//
+// link = Html_set_new_link(html, &url);
+// }
+//
+// a_Dw_image_map_list_add_shape(&html->linkblock->maps, type, link,
+// point, nbpoints);
+}
+
+
+/*
+ * Test and extract the link from a javascript instruction.
+ */
+static const char* Html_get_javascript_link(DilloHtml *html)
+{
+ size_t i;
+ char ch, *p1, *p2;
+ Dstr *Buf = html->attr_data;
+
+ if (dStrncasecmp("javascript", Buf->str, 10) == 0) {
+ i = strcspn(Buf->str, "'\"");
+ ch = Buf->str[i];
+ if ((ch == '"' || ch == '\'') &&
+ (p2 = strchr(Buf->str + i + 1 , ch))) {
+ p1 = Buf->str + i;
+ MSG_HTML("link depends on javascript()\n");
+ dStr_truncate(Buf, p2 - Buf->str);
+ dStr_erase(Buf, 0, p1 - Buf->str + 1);
+ }
+ }
+ return Buf->str;
+}
+
+/*
+ * Register an anchor for this page.
+ */
+static void Html_add_anchor(DilloHtml *html, const char *name)
+{
+ _MSG("Registering ANCHOR: %s\n", name);
+ if (!DW2TB(html->dw)->addAnchor (name, S_TOP(html)->style))
+ MSG_HTML("Anchor names must be unique within the document\n");
+ /*
+ * According to Sec. 12.2.1 of the HTML 4.01 spec, "anchor names that
+ * differ only in case may not appear in the same document", but
+ * "comparisons between fragment identifiers and anchor names must be
+ * done by exact (case-sensitive) match." We ignore the case issue and
+ * always test for exact matches. Moreover, what does uppercase mean
+ * for Unicode characters outside the ASCII range?
+ */
+}
+
+/*
+ * <A>
+ */
+static void Html_tag_open_a(DilloHtml *html, char *tag, int tagsize)
+{
+ StyleAttrs style_attrs;
+ Style *old_style;
+ DilloUrl *url;
+ const char *attrbuf;
+ char *buf;
+ int buf_size;
+
+ /* todo: add support for MAP with A HREF */
+ Html_tag_open_area(html, tag, tagsize);
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "href"))) {
+ /* if it's a javascript link, extract the reference. */
+ if (tolower(attrbuf[0]) == 'j')
+ attrbuf = Html_get_javascript_link(html);
+
+ url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0);
+ dReturn_if_fail ( url != NULL );
+
+ old_style = S_TOP(html)->style;
+ style_attrs = *old_style;
+
+ if (a_Capi_get_buf(url, &buf, &buf_size)) {
+ html->InVisitedLink = TRUE;
+ style_attrs.color = Color::createSimple (
+ HT2LT(html),
+ html->linkblock->visited_color
+/*
+ a_Color_vc(html->linkblock->visited_color,
+ S_TOP(html)->style->color->getColor(),
+ html->linkblock->link_color,
+ S_TOP(html)->current_bg_color),
+*/
+ );
+ } else {
+ style_attrs.color = Color::createSimple(HT2LT(html),
+ html->linkblock->link_color);
+ }
+
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "title")))
+// style_attrs.x_tooltip = a_Dw_tooltip_new_no_ref(attrbuf);
+
+ style_attrs.textDecoration |= TEXT_DECORATION_UNDERLINE;
+ style_attrs.x_link = Html_set_new_link(html, &url);
+ style_attrs.cursor = CURSOR_POINTER;
+
+ S_TOP(html)->style =
+ Style::create (HT2LT(html), &style_attrs);
+ old_style->unref ();
+ }
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "name"))) {
+ if (prefs.show_extra_warnings)
+ Html_check_name_val(html, attrbuf, "name");
+ /* html->NameVal is freed in Html_process_tag */
+ html->NameVal = a_Url_decode_hex_str(attrbuf);
+ Html_add_anchor(html, html->NameVal);
+ }
+}
+
+/*
+ * <A> close function
+ */
+static void Html_tag_close_a(DilloHtml *html, int TagIdx)
+{
+ html->InVisitedLink = FALSE;
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Insert underlined text in the page.
+ */
+static void Html_tag_open_u(DilloHtml *html, char *tag, int tagsize)
+{
+ Style *style;
+ StyleAttrs style_attrs;
+
+ style = S_TOP(html)->style;
+ style_attrs = *style;
+ style_attrs.textDecoration |= TEXT_DECORATION_UNDERLINE;
+ S_TOP(html)->style =
+ Style::create (HT2LT(html), &style_attrs);
+ style->unref ();
+}
+
+/*
+ * Insert strike-through text. Used by <S>, <STRIKE> and <DEL>.
+ */
+static void Html_tag_open_strike(DilloHtml *html, char *tag, int tagsize)
+{
+ Style *style;
+ StyleAttrs style_attrs;
+
+ style = S_TOP(html)->style;
+ style_attrs = *style;
+ style_attrs.textDecoration |= TEXT_DECORATION_LINE_THROUGH;
+ S_TOP(html)->style =
+ Style::create (HT2LT(html), &style_attrs);
+ style->unref ();
+}
+
+/*
+ * <BLOCKQUOTE>
+ */
+static void Html_tag_open_blockquote(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_add_indented(html, 40, 40, 9);
+}
+
+/*
+ * Handle the <UL> tag.
+ */
+static void Html_tag_open_ul(DilloHtml *html, char *tag, int tagsize)
+{
+ const char *attrbuf;
+ ListStyleType list_style_type;
+
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_add_indented(html, 40, 0, 9);
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "type"))) {
+ /* list_style_type explicitly defined */
+ if (dStrncasecmp(attrbuf, "disc", 4) == 0)
+ list_style_type = LIST_STYLE_TYPE_DISC;
+ else if (dStrncasecmp(attrbuf, "circle", 6) == 0)
+ list_style_type = LIST_STYLE_TYPE_CIRCLE;
+ else if (dStrncasecmp(attrbuf, "square", 6) == 0)
+ list_style_type = LIST_STYLE_TYPE_SQUARE;
+ else
+ /* invalid value */
+ list_style_type = LIST_STYLE_TYPE_DISC;
+ } else {
+ if (S_TOP(html)->list_type == HTML_LIST_UNORDERED) {
+ /* Nested <UL>'s. */
+ /* --EG :: I changed the behavior here : types are cycling instead of
+ * being forced to square. It's easier for mixed lists level counting.
+ */
+ switch (S_TOP(html)->style->listStyleType) {
+ case LIST_STYLE_TYPE_DISC:
+ list_style_type = LIST_STYLE_TYPE_CIRCLE;
+ break;
+ case LIST_STYLE_TYPE_CIRCLE:
+ list_style_type = LIST_STYLE_TYPE_SQUARE;
+ break;
+ case LIST_STYLE_TYPE_SQUARE:
+ default: /* this is actually a bug */
+ list_style_type = LIST_STYLE_TYPE_DISC;
+ break;
+ }
+ } else {
+ /* Either first <UL>, or a <OL> before. */
+ list_style_type = LIST_STYLE_TYPE_DISC;
+ }
+ }
+
+ HTML_SET_TOP_ATTR(html, listStyleType, list_style_type);
+ S_TOP(html)->list_type = HTML_LIST_UNORDERED;
+
+ S_TOP(html)->list_number = 0;
+ S_TOP(html)->ref_list_item = NULL;
+}
+
+/*
+ * Handle the <MENU> tag.
+ * (Deprecated and almost the same as <UL>)
+ */
+static void Html_tag_open_menu(DilloHtml *html, char *tag, int tagsize)
+{
+ ListStyleType list_style_type = LIST_STYLE_TYPE_DISC;
+
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_add_indented(html, 40, 0, 9);
+ HTML_SET_TOP_ATTR(html, listStyleType, list_style_type);
+ S_TOP(html)->list_type = HTML_LIST_UNORDERED;
+ S_TOP(html)->list_number = 0;
+ S_TOP(html)->ref_list_item = NULL;
+
+ if (prefs.show_extra_warnings)
+ MSG_HTML("it is strongly recommended using <UL> instead of <MENU>\n");
+}
+
+/*
+ * Handle the <OL> tag.
+ */
+static void Html_tag_open_ol(DilloHtml *html, char *tag, int tagsize)
+{
+ const char *attrbuf;
+ ListStyleType list_style_type;
+ int n = 1;
+
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_add_indented(html, 40, 0, 9);
+
+ list_style_type = LIST_STYLE_TYPE_DECIMAL;
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "type"))) {
+ if (*attrbuf == '1')
+ list_style_type = LIST_STYLE_TYPE_DECIMAL;
+ else if (*attrbuf == 'a')
+ list_style_type = LIST_STYLE_TYPE_LOWER_ALPHA;
+ else if (*attrbuf == 'A')
+ list_style_type = LIST_STYLE_TYPE_UPPER_ALPHA;
+ else if (*attrbuf == 'i')
+ list_style_type = LIST_STYLE_TYPE_LOWER_ROMAN;
+ else if (*attrbuf == 'I')
+ list_style_type = LIST_STYLE_TYPE_UPPER_ROMAN;
+ }
+
+ HTML_SET_TOP_ATTR(html, listStyleType, list_style_type);
+ S_TOP(html)->list_type = HTML_LIST_ORDERED;
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "start")) &&
+ (n = (int) strtol(attrbuf, NULL, 10)) < 0) {
+ MSG_HTML( "illegal '-' character in START attribute; Starting from 0\n");
+ n = 0;
+ }
+ S_TOP(html)->list_number = n;
+ S_TOP(html)->ref_list_item = NULL;
+}
+
+/*
+ * Handle the <LI> tag.
+ */
+static void Html_tag_open_li(DilloHtml *html, char *tag, int tagsize)
+{
+ StyleAttrs style_attrs;
+ Style *item_style, *word_style;
+ Widget **ref_list_item;
+ ListItem *list_item;
+ int *list_number;
+ const char *attrbuf;
+ char buf[16];
+
+ /* Get our parent tag's variables (used as state storage) */
+ list_number = &html->stack->getRef(html->stack->size()-2)->list_number;
+ ref_list_item = &html->stack->getRef(html->stack->size()-2)->ref_list_item;
+
+ /* set the item style */
+ word_style = S_TOP(html)->style;
+ style_attrs = *word_style;
+ //style_attrs.backgroundColor = Color::createSimple (HT2LT(html), 0xffff40);
+ //style_attrs.setBorderColor (Color::createSimple (HT2LT(html), 0x000000));
+ //style_attrs.setBorderStyle (BORDER_SOLID);
+ //style_attrs.borderWidth.setVal (1);
+ item_style = Style::create (HT2LT(html), &style_attrs);
+
+ DW2TB(html->dw)->addParbreak (2, word_style);
+
+ list_item = new ListItem ((ListItem *)*ref_list_item, false);
+ DW2TB(html->dw)->addWidget (list_item, item_style);
+ DW2TB(html->dw)->addParbreak (2, word_style);
+ *ref_list_item = list_item;
+ S_TOP(html)->textblock = html->dw = list_item;
+ item_style->unref();
+
+ switch (S_TOP(html)->list_type) {
+ case HTML_LIST_ORDERED:
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "value")) &&
+ (*list_number = strtol(attrbuf, NULL, 10)) < 0) {
+ MSG_HTML("illegal negative LIST VALUE attribute; Starting from 0\n");
+ *list_number = 0;
+ }
+ numtostr((*list_number)++, buf, 16, S_TOP(html)->style->listStyleType);
+ list_item->initWithText (dStrdup(buf), word_style);
+ list_item->addSpace (word_style);
+ break;
+ case HTML_LIST_NONE:
+ MSG_HTML("<li> outside <ul> or <ol>\n");
+ default:
+ list_item->initWithWidget (new Bullet(), word_style);
+ list_item->addSpace (word_style);
+ break;
+ }
+
+ // list_item->flush (); Looks like there's no need to flush.
+ // If it MUST be done, it could be inside Html_tag_close_li().
+}
+
+/*
+ * Close <LI>
+ */
+static void Html_tag_close_li(DilloHtml *html, int TagIdx)
+{
+ ((ListItem *)html->dw)->flush ();
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * <HR>
+ */
+static void Html_tag_open_hr(DilloHtml *html, char *tag, int tagsize)
+{
+ Widget *hruler;
+ StyleAttrs style_attrs;
+ Style *style;
+ char *width_ptr;
+ const char *attrbuf;
+ int32_t size = 0;
+
+ width_ptr = Html_get_attr_wdef(html, tag, tagsize, "width", "100%");
+
+ style_attrs = *S_TOP(html)->style;
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "size")))
+ size = strtol(attrbuf, NULL, 10);
+
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "align"))) {
+ if (dStrcasecmp (attrbuf, "left") == 0)
+ style_attrs.textAlign = TEXT_ALIGN_LEFT;
+ else if (dStrcasecmp (attrbuf, "right") == 0)
+ style_attrs.textAlign = TEXT_ALIGN_RIGHT;
+ else if (dStrcasecmp (attrbuf, "center") == 0)
+ style_attrs.textAlign = TEXT_ALIGN_CENTER;
+ }
+
+ /* todo: evaluate attribute */
+ if (Html_get_attr(html, tag, tagsize, "noshade")) {
+ style_attrs.setBorderStyle (BORDER_SOLID);
+ style_attrs.setBorderColor (
+ Color::createShaded (HT2LT(html), style_attrs.color->getColor()));
+ if (size < 1)
+ size = 1;
+ } else {
+ style_attrs.setBorderStyle (BORDER_INSET);
+ style_attrs.setBorderColor (
+ Color::createShaded (HT2LT(html),
+ (S_TOP(html)->current_bg_color)));
+ if (size < 2)
+ size = 2;
+ }
+
+ style_attrs.borderWidth.top =
+ style_attrs.borderWidth.left = (size + 1) / 2;
+ style_attrs.borderWidth.bottom =
+ style_attrs.borderWidth.right = size / 2;
+ style = Style::create (HT2LT(html), &style_attrs);
+
+ DW2TB(html->dw)->addParbreak (5, S_TOP(html)->style);
+ hruler = new Ruler();
+ hruler->setStyle (style);
+ DW2TB(html->dw)->addWidget (hruler, style);
+ style->unref ();
+
+ //DW2TB(html->dw)->addWidget (hruler, S_TOP(html)->style);
+ DW2TB(html->dw)->addParbreak (5, S_TOP(html)->style);
+ dFree(width_ptr);
+}
+
+/*
+ * <DL>
+ */
+static void Html_tag_open_dl(DilloHtml *html, char *tag, int tagsize)
+{
+ /* may want to actually do some stuff here. */
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+}
+
+/*
+ * <DT>
+ */
+static void Html_tag_open_dt(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_set_top_font(html, NULL, 0, 1, 1);
+}
+
+/*
+ * <DD>
+ */
+static void Html_tag_open_dd(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_add_indented(html, 40, 40, 9);
+}
+
+/*
+ * <PRE>
+ */
+static void Html_tag_open_pre(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0);
+
+ /* Is the placement of this statement right? */
+ S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_PRE;
+ HTML_SET_TOP_ATTR (html, whiteSpace, WHITE_SPACE_PRE);
+ html->pre_column = 0;
+ html->PreFirstChar = TRUE;
+ html->InFlags |= IN_PRE;
+}
+
+/*
+ * Custom close for <PRE>
+ */
+static void Html_tag_close_pre(DilloHtml *html, int TagIdx)
+{
+ html->InFlags &= ~IN_PRE;
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Check whether a tag is in the "excluding" element set for PRE
+ * Excl. Set = {IMG, OBJECT, APPLET, BIG, SMALL, SUB, SUP, FONT, BASEFONT}
+ */
+static int Html_tag_pre_excludes(int tag_idx)
+{
+ char *es_set[] = {"img", "object", "applet", "big", "small", "sub", "sup",
+ "font", "basefont", NULL};
+ static int ei_set[10], i;
+
+ /* initialize array */
+ if (!ei_set[0])
+ for (i = 0; es_set[i]; ++i)
+ ei_set[i] = Html_tag_index(es_set[i]);
+
+ for (i = 0; ei_set[i]; ++i)
+ if (tag_idx == ei_set[i])
+ return 1;
+ return 0;
+}
+
+/*
+ * Handle <FORM> tag
+ */
+static void Html_tag_open_form(DilloHtml *html, char *tag, int tagsize)
+{
+ DilloUrl *action;
+ DilloHtmlMethod method;
+ DilloHtmlEnc enc;
+ const char *attrbuf;
+
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+
+ if (html->InFlags & IN_FORM) {
+ MSG_HTML("nested forms\n");
+ return;
+ }
+ html->InFlags |= IN_FORM;
+
+ method = DILLO_HTML_METHOD_GET;
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "method"))) {
+ if (!dStrcasecmp(attrbuf, "post"))
+ method = DILLO_HTML_METHOD_POST;
+ /* todo: maybe deal with unknown methods? */
+ }
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "action")))
+ action = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0);
+ else
+ action = a_Url_dup(html->linkblock->base_url);
+ enc = DILLO_HTML_ENC_URLENCODING;
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "encoding"))) {
+ /* todo: maybe deal with unknown encodings? */
+ }
+ Html_form_new(html->linkblock, method, action, enc);
+ a_Url_free(action);
+}
+
+static void Html_tag_close_form(DilloHtml *html, int TagIdx)
+{
+ static char *SubmitTag =
+ "<input type='submit' value='?Submit?' alt='dillo-generated-button'>";
+ DilloHtmlForm *form;
+// int i;
+
+ if (html->InFlags & IN_FORM) {
+ form = html->linkblock->forms->getRef (
+ html->linkblock->forms->size() - 1);
+ /* If we don't have a submit button and the user desires one,
+ let's add a custom one */
+ if (form->num_submit_buttons == 0) {
+ if (prefs.show_extra_warnings || form->num_entry_fields != 1)
+ MSG_HTML("FORM lacks a Submit button\n");
+ if (prefs.generate_submit) {
+ MSG_HTML(" (added a submit button internally)\n");
+ Html_tag_open_input(html, SubmitTag, strlen(SubmitTag));
+ form->num_submit_buttons = 0;
+ }
+ }
+
+// /* Make buttons sensitive again */
+// for (i = 0; i < form->inputs->size(); i++) {
+// input_i = form->inputs->getRef(i);
+// /* Check for tricky HTML (e.g. <input type=image>) */
+// if (!input_i->widget)
+// continue;
+// if (input_i->type == DILLO_HTML_INPUT_SUBMIT ||
+// input_i->type == DILLO_HTML_INPUT_RESET) {
+// gtk_widget_set_sensitive(input_i->widget, TRUE);
+// } else if (input_i->type == DILLO_HTML_INPUT_IMAGE ||
+// input_i->type == DILLO_HTML_INPUT_BUTTON_SUBMIT ||
+// input_i->type == DILLO_HTML_INPUT_BUTTON_RESET) {
+// a_Dw_button_set_sensitive(DW_BUTTON(input_i->widget), TRUE);
+// }
+// }
+ }
+ html->InFlags &= ~IN_FORM;
+ html->InFlags &= ~IN_SELECT;
+ html->InFlags &= ~IN_TEXTAREA;
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Handle <META>
+ * We do not support http-equiv=refresh because it's non standard,
+ * (the HTML 4.01 SPEC recommends explicitily to avoid it), and it
+ * can be easily abused!
+ *
+ * More info at:
+ * http://lists.w3.org/Archives/Public/www-html/2000Feb/thread.html#232
+ *
+ * todo: Note that we're sending custom HTML while still IN_HEAD. This
+ * is a hackish way to put the message. A much cleaner approach is to
+ * build a custom widget for it.
+ */
+static void Html_tag_open_meta(DilloHtml *html, char *tag, int tagsize)
+{
+ const char *meta_template =
+"<table width='100%%'><tr><td bgcolor='#ee0000'>Warning:</td>\n"
+" <td bgcolor='#8899aa' width='100%%'>\n"
+" This page uses the NON-STANDARD meta refresh tag.<br> The HTML 4.01 SPEC\n"
+" (sec 7.4.4) recommends explicitly to avoid it.</td></tr>\n"
+" <tr><td bgcolor='#a0a0a0' colspan='2'>The author wanted you to go\n"
+" <a href='%s'>here</a>%s</td></tr></table><br>\n";
+
+ const char *equiv, *content;
+ char delay_str[64];
+ Dstr *ds_msg;
+ int delay;
+
+ /* only valid inside HEAD */
+ if (!(html->InFlags & IN_HEAD)) {
+ MSG_HTML("META elements must be inside the HEAD section\n");
+ return;
+ }
+
+ if ((equiv = Html_get_attr(html, tag, tagsize, "http-equiv")) &&
+ !dStrcasecmp(equiv, "refresh") &&
+ (content = Html_get_attr(html, tag, tagsize, "content"))) {
+
+ /* Get delay, if present, and make a message with it */
+ if ((delay = strtol(content, NULL, 0)))
+ snprintf(delay_str, 64, " after %d second%s.",
+ delay, (delay > 1) ? "s" : "");
+ else
+ sprintf(delay_str, ".");
+
+ /* Skip to anything after "URL=" */
+ while (*content && *(content++) != '=');
+
+ /* Send a custom HTML message
+ * todo: this is a hairy hack, It'd be much better to build a widget. */
+ ds_msg = dStr_sized_new(256);
+ dStr_sprintf(ds_msg, meta_template, content, delay_str);
+ {
+ int SaveFlags = html->InFlags;
+ html->InFlags = IN_BODY;
+ html->TagSoup = FALSE;
+ Html_write_raw(html, ds_msg->str, ds_msg->len, 0);
+ html->TagSoup = TRUE;
+ html->InFlags = SaveFlags;
+ }
+ dStr_free(ds_msg, 1);
+ }
+}
+
+/*
+ * Set the history of the menu to be consistent with the active menuitem.
+ */
+//static void Html_select_set_history(DilloHtmlInput *input)
+//{
+// int i;
+//
+// for (i = 0; i < input->select->num_options; i++) {
+// if (GTK_CHECK_MENU_ITEM(input->select->options[i].menuitem)->active) {
+// gtk_option_menu_set_history(GTK_OPTION_MENU(input->widget), i);
+// break;
+// }
+// }
+//}
+
+/*
+ * Reset the input widget to the initial value.
+ */
+static void Html_reset_input(DilloHtmlInput *input)
+{
+ int i;
+
+ switch (input->type) {
+ case DILLO_HTML_INPUT_TEXT:
+ case DILLO_HTML_INPUT_PASSWORD:
+ EntryResource *entryres;
+ entryres = (EntryResource*)((Embed*)input->widget)->getResource();
+ entryres->setText(input->init_str ? input->init_str : "");
+ break;
+ case DILLO_HTML_INPUT_CHECKBOX:
+ case DILLO_HTML_INPUT_RADIO:
+ ToggleButtonResource *tb_r;
+ tb_r = (ToggleButtonResource*)((Embed*)input->widget)->getResource();
+ tb_r->setActivated(input->init_val);
+ break;
+ case DILLO_HTML_INPUT_SELECT:
+ if (input->select != NULL) {
+ /* this is in reverse order so that, in case more than one was
+ * selected, we get the last one, which is consistent with handling
+ * of multiple selected options in the layout code. */
+ for (i = input->select->num_options - 1; i >= 0; i--) {
+ if (input->select->options[i].init_val) {
+// gtk_menu_item_activate(GTK_MENU_ITEM
+// (input->select->options[i].menuitem));
+// Html_select_set_history(input);
+ break;
+ }
+ }
+ }
+ break;
+ case DILLO_HTML_INPUT_SEL_LIST:
+ if (!input->select)
+ break;
+ for (i = 0; i < input->select->num_options; i++) {
+// if (input->select->options[i].init_val) {
+// if (input->select->options[i].menuitem->state == GTK_STATE_NORMAL)
+// gtk_list_select_child(GTK_LIST(input->select->menu),
+// input->select->options[i].menuitem);
+// } else {
+// if (input->select->options[i].menuitem->state==GTK_STATE_SELECTED)
+// gtk_list_unselect_child(GTK_LIST(input->select->menu),
+// input->select->options[i].menuitem);
+// }
+ }
+ break;
+ case DILLO_HTML_INPUT_TEXTAREA:
+ if (input->init_str != NULL) {
+// int pos = 0;
+// gtk_editable_delete_text(GTK_EDITABLE(input->widget), 0, -1);
+// gtk_editable_insert_text(GTK_EDITABLE(input->widget), input->init_str,
+// strlen(input->init_str), &pos);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+
+/*
+ * Add a new input to the form data structure, setting the initial
+ * values.
+ */
+static void Html_add_input(DilloHtmlForm *form,
+ DilloHtmlInputType type,
+ Widget *widget,
+ Embed *embed,
+ const char *name,
+ const char *init_str,
+ DilloHtmlSelect *select,
+ bool_t init_val)
+{
+ DilloHtmlInput *input;
+
+ _MSG("name=[%s] init_str=[%s] init_val=[%d]\n",
+ name, init_str, init_val);
+ form->inputs->increase();
+ input = form->inputs->getRef (form->inputs->size() - 1);
+ input->type = type;
+ input->widget = widget;
+ input->embed = embed;
+ input->name = (name) ? dStrdup(name) : NULL;
+ input->init_str = (init_str) ? dStrdup(init_str) : NULL;
+ input->select = select;
+ input->init_val = init_val;
+ Html_reset_input(input);
+
+ /* some stats */
+ if (type == DILLO_HTML_INPUT_PASSWORD ||
+ type == DILLO_HTML_INPUT_TEXT ||
+ type == DILLO_HTML_INPUT_TEXTAREA) {
+ form->num_entry_fields++;
+ } else if (type == DILLO_HTML_INPUT_SUBMIT ||
+ type == DILLO_HTML_INPUT_BUTTON_SUBMIT ||
+ type == DILLO_HTML_INPUT_IMAGE) {
+ form->num_submit_buttons++;
+ }
+}
+
+
+/*
+ * Given a GtkWidget, find the form that contains it.
+ * Return value: form_index if successful, -1 otherwise.
+ */
+//static int Html_find_form(GtkWidget *reset, DilloHtmlLB *html_lb)
+//{
+// int form_index;
+// int input_idx;
+// DilloHtmlForm *form;
+//
+// for (form_index = 0; form_index < html_lb->forms->size(); form_index++) {
+// form = html_lb->forms->getRef (form_index);
+// for (input_idx = 0; input_idx < form->inputs->size(); input_idx++) {
+// if (form->inputs->get(input_idx).widget == reset) {
+// return form_index;
+// }
+// }
+// }
+// return -1;
+//}
+
+/*
+ * Reset all inputs in the form containing reset to their initial values.
+ * In general, reset is the reset button for the form.
+ */
+//static void Html_reset_form(GtkWidget *reset, DilloHtmlLB *html_lb)
+//{
+// int i, j;
+// DilloHtmlForm *form;
+//
+// if ((i = Html_find_form(reset, html_lb)) != -1){
+// form = html_lb->forms->getRef (i);
+// for ( j = 0; j < form->inputs->size(); j++)
+// Html_reset_input(&(form->inputs[j]));
+// }
+//}
+
+/*
+ * Urlencode 'val' and append it to 'str'
+ */
+static void Html_urlencode_append(Dstr *str, const char *val)
+{
+ char *enc_val = a_Url_encode_hex_str(val);
+ dStr_append(str, enc_val);
+ dFree(enc_val);
+}
+
+/*
+ * Append a name-value pair to an existing url.
+ * (name and value are urlencoded before appending them)
+ */
+static void
+ Html_append_input(Dstr *url, const char *name, const char *value)
+{
+ if (name != NULL) {
+ Html_urlencode_append(url, name);
+ dStr_append_c(url, '=');
+ Html_urlencode_append(url, value);
+ dStr_append_c(url, '&');
+ }
+}
+
+/*
+ * Append a image button click position to an existing url.
+ */
+//static void Html_append_clickpos(Dstr *url, const char *name, int x, int y)
+//{
+// if (name) {
+// Html_urlencode_append(url, name);
+// dStr_sprintfa(url, ".x=%d&", x);
+// Html_urlencode_append(url, name);
+// dStr_sprintfa(url, ".y=%d&", y);
+// } else
+// dStr_sprintfa(url, "x=%d&y=%d&", x, y);
+//}
+
+/*
+ * Submit the form containing the submit input by making a new query URL
+ * and sending it with a_Nav_push.
+ * (Called by GTK+)
+ * click_x and click_y are used only by input images and are set only when
+ * called by Html_image_clicked. GTK+ does NOT give these arguments.
+ */
+static void Html_submit_form2(DilloHtmlLB *html_lb, DilloHtmlForm *form,
+ int e_input_idx)
+{
+ int input_idx;
+ DilloHtmlInput *input;
+ DilloUrl *new_url;
+ char *url_str, *action_str, *p;
+
+ if ((form->method == DILLO_HTML_METHOD_GET) ||
+ (form->method == DILLO_HTML_METHOD_POST)) {
+ Dstr *DataStr = dStr_sized_new(4096);
+
+ _MSG("Html_submit_form2: form->action=%s\n",URL_STR_(form->action));
+
+ for (input_idx = 0; input_idx < form->inputs->size(); input_idx++) {
+ input = form->inputs->getRef (input_idx);
+ switch (input->type) {
+ case DILLO_HTML_INPUT_TEXT:
+ case DILLO_HTML_INPUT_PASSWORD:
+ EntryResource *entryres;
+ entryres = (EntryResource*)((Embed*)input->widget)->getResource();
+ Html_append_input(DataStr, input->name, entryres->getText());
+ break;
+ case DILLO_HTML_INPUT_CHECKBOX:
+ case DILLO_HTML_INPUT_RADIO:
+ ToggleButtonResource *cb_r;
+ cb_r=(ToggleButtonResource*)((Embed*)input->widget)->getResource();
+ if (input->name && input->init_str && cb_r->isActivated()) {
+ Html_append_input(DataStr, input->name, input->init_str);
+ }
+ break;
+ case DILLO_HTML_INPUT_HIDDEN:
+ Html_append_input(DataStr, input->name, input->init_str);
+ break;
+// case DILLO_HTML_INPUT_SELECT:
+// for (i = 0; i < input->select->num_options; i++) {
+// if (GTK_CHECK_MENU_ITEM(input->select->options[i].menuitem)->
+// active) {
+// Html_append_input(DataStr, input->name,
+// input->select->options[i].value);
+// break;
+// }
+// }
+// break;
+// case DILLO_HTML_INPUT_SEL_LIST:
+// for (i = 0; i < input->select->num_options; i++) {
+// if (input->select->options[i].menuitem->state ==
+// GTK_STATE_SELECTED) {
+// Html_append_input(DataStr, input->name,
+// input->select->options[i].value);
+// }
+// }
+// break;
+// case DILLO_HTML_INPUT_TEXTAREA:
+// text = gtk_editable_get_chars(GTK_EDITABLE (input->widget),0,-1);
+// Html_append_input(DataStr, input->name, text);
+// dFree(text);
+// break;
+// case DILLO_HTML_INPUT_INDEX:
+// Html_urlencode_append(DataStr,
+// gtk_entry_get_text(GTK_ENTRY(input->widget)));
+// break;
+// case DILLO_HTML_INPUT_IMAGE:
+// if (input->widget == submit) {
+// Html_append_input(DataStr, input->name, input->init_str);
+// Html_append_clickpos(DataStr, input->name, click_x, click_y);
+// }
+// break;
+ case DILLO_HTML_INPUT_SUBMIT:
+ case DILLO_HTML_INPUT_BUTTON_SUBMIT:
+ if (input_idx == e_input_idx && form->num_submit_buttons > 0)
+ Html_append_input(DataStr, input->name, input->init_str);
+ break;
+ default:
+ break;
+ } /* switch */
+ } /* for (inputs) */
+
+ if (DataStr->str[DataStr->len - 1] == '&')
+ dStr_truncate(DataStr, DataStr->len - 1);
+
+ /* form->action was previously resolved against base URL */
+ action_str = dStrdup(URL_STR(form->action));
+
+ if (form->method == DILLO_HTML_METHOD_POST) {
+ new_url = a_Url_new(action_str, NULL, 0, 0, 0);
+ a_Url_set_data(new_url, DataStr->str);
+ a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_Post);
+ } else {
+ /* remove <fragment> and <query> sections if present */
+ if ((p = strchr(action_str, '#')))
+ *p = 0;
+ if ((p = strchr(action_str, '?')))
+ *p = 0;
+
+ url_str = dStrconcat(action_str, "?", DataStr->str, NULL);
+ new_url = a_Url_new(url_str, NULL, 0, 0, 0);
+ a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_Get);
+ dFree(url_str);
+ }
+
+ a_Nav_push(html_lb->bw, new_url);
+ dFree(action_str);
+ dStr_free(DataStr, TRUE);
+ a_Url_free(new_url);
+ } else {
+ MSG("Html_submit_form2: Method unknown\n");
+ }
+
+// /* now, make the rendered area have its focus back */
+// gtk_widget_grab_focus(GTK_BIN(html_lb->bw->render_main_scroll)->child);
+}
+
+/*
+ * Handler for events related to forms.
+ *
+ * TODO: Currently there's "clicked" for buttons, we surely need "enter" for
+ * textentries, and maybe the "mouseover, ...." set for Javascript.
+ */
+void a_Html_form_event_handler(void *data,
+ form::Form *form_receiver,
+ void *v_resource)
+{
+ int form_index, input_idx = -1, idx;
+ DilloHtmlForm *form = NULL;
+ DilloHtmlLB *html_lb = (DilloHtmlLB*)data;
+
+ printf("Html_form_event_handler %p %p\n", html_lb, form_receiver);
+
+ /* Search the form that generated the submit event */
+ for (form_index = 0; form_index < html_lb->forms->size(); form_index++) {
+ form = html_lb->forms->getRef (form_index);
+ if (form->form_receiver == form_receiver) {
+ /* form found, let's get the input index for this event */
+ for (idx = 0; idx < form->inputs->size(); idx++) {
+ DilloHtmlInput *input = form->inputs->getRef(idx);
+ if (input->embed &&
+ v_resource == (void*)((Embed*)input->widget)->getResource()) {
+ input_idx = idx;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ if (form_index == html_lb->forms->size()) {
+ MSG("a_Html_form_event_handler: ERROR, form not found!\n");
+ } else {
+ Html_submit_form2(html_lb, form, input_idx);
+ }
+}
+
+/*
+ * Submit form if it has no submit button.
+ * (Called by GTK+ when the user presses enter in a text entry within a form)
+ */
+//static void Html_enter_submit_form(GtkWidget *submit, DilloHtmlLB *html_lb)
+//{
+// int i;
+//
+// /* Search the form that generated the submit event */
+// if ((i = Html_find_form(submit, html_lb)) == -1)
+// return;
+//
+// /* Submit on enterpress when there's a single text-entry only,
+// * or if the user set enter to always submit */
+// if ((html_lb->forms->getRef(i))->.num_entry_fields == 1) ||
+// prefs.enterpress_forces_submit)
+// Html_submit_form(submit, html_lb, 1, 1);
+//}
+
+/*
+ * Call submit form, when input image has been clicked
+ */
+//static void Html_image_clicked(Widget *widget, int x, int y,
+// DilloHtmlLB *lb)
+//{
+// _MSG("Hallo! (%d, %d, %p)\n", x, y, lb);
+// Html_submit_form((GtkWidget*) widget, lb, x, y);
+//}
+
+/*
+ * Create input image for the form
+ */
+static Widget *Html_input_image(DilloHtml *html, char *tag, int tagsize,
+ DilloHtmlLB *html_lb, DilloUrl *action)
+{
+// // AL
+// DilloImage *Image;
+// Widget *button;
+// DilloUrl *url = NULL;
+// Style style_attrs;
+// const char *attrbuf;
+//
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "src")) &&
+// (url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0))) {
+// button = a_Dw_button_new (0, FALSE);
+// DW2TB(html->dw)->addWidget (button,
+// S_TOP(html)->style);
+// gtk_signal_connect(GTK_OBJECT(button), "clicked_at",
+// GTK_SIGNAL_FUNC(Html_image_clicked), html_lb);
+// a_Dw_button_set_sensitive(DW_BUTTON(button), FALSE);
+//
+// /* create new image and add it to the button */
+// if ((Image = Html_add_new_image(html, tag, tagsize, &style_attrs,
+// FALSE))) {
+// /* By suppressing the "image_pressed" signal, the events are sent
+// * to the parent DwButton */
+// a_Dw_widget_set_button_sensitive (IM2DW(Image->dw), FALSE);
+// IM2DW(Image->dw)->setStyle (S_TOP(html)->style);
+// a_Dw_container_add(DW_CONTAINER(button), IM2DW(Image->dw));
+// IM2DW(Image->dw)->setCursor (CURSOR_HAND);
+// Html_load_image(html, url, Image);
+// a_Url_free(url);
+// return button;
+// }
+// }
+//
+// DEBUG_MSG(10, "Html_input_image: unable to create image submit.\n");
+// a_Url_free(url);
+ return NULL;
+}
+
+/*
+ * Add a new input to current form
+ */
+static void Html_tag_open_input(DilloHtml *html, char *tag, int tagsize)
+{
+ DilloHtmlForm *form;
+ DilloHtmlInputType inp_type;
+ DilloHtmlLB *html_lb;
+ Widget *widget = NULL;
+ Embed *embed = NULL;
+ char *value, *name, *type, *init_str;
+// const char *attrbuf, *label;
+ bool_t init_val = FALSE;
+ int input_idx;
+
+ if (!(html->InFlags & IN_FORM)) {
+ MSG_HTML("input camp outside <form>\n");
+ return;
+ }
+
+ html_lb = html->linkblock;
+ form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+
+ /* Get 'value', 'name' and 'type' */
+ value = Html_get_attr_wdef(html, tag, tagsize, "value", NULL);
+ name = Html_get_attr_wdef(html, tag, tagsize, "name", NULL);
+ type = Html_get_attr_wdef(html, tag, tagsize, "type", "");
+
+ init_str = NULL;
+ inp_type = DILLO_HTML_INPUT_UNKNOWN;
+ if (!dStrcasecmp(type, "password")) {
+ inp_type = DILLO_HTML_INPUT_PASSWORD;
+ EntryResource *entryResource =
+ HT2LT(html)->getResourceFactory()->createEntryResource (15, true);
+ embed = new Embed (entryResource);
+ widget = embed;
+ init_str = (value) ? value : NULL;
+ } else if (!dStrcasecmp(type, "checkbox")) {
+ inp_type = DILLO_HTML_INPUT_CHECKBOX;
+ CheckButtonResource *check_b_r = HT2LT(html)->getResourceFactory()
+ ->createCheckButtonResource(false);
+ embed = new Embed (check_b_r);
+ widget = embed;
+ init_val = (Html_get_attr(html, tag, tagsize, "checked") != NULL);
+ init_str = (value) ? value : dStrdup("on");
+ } else if (!dStrcasecmp(type, "radio")) {
+ inp_type = DILLO_HTML_INPUT_RADIO;
+ RadioButtonResource *rb_r = NULL;
+ for (input_idx = 0; input_idx < form->inputs->size(); input_idx++) {
+ DilloHtmlInput *input = form->inputs->getRef(input_idx);
+ if (input->type == DILLO_HTML_INPUT_RADIO &&
+ (input->name && !dStrcasecmp(input->name, name)) ) {
+ rb_r =(RadioButtonResource*)((Embed*)input->widget)->getResource();
+ break;
+ }
+ }
+ rb_r = HT2LT(html)->getResourceFactory()
+ ->createRadioButtonResource(rb_r, false);
+ embed = new Embed (rb_r);
+ widget = embed;
+
+ init_val = (Html_get_attr(html, tag, tagsize, "checked") != NULL);
+ init_str = (value) ? value : NULL;
+ } else if (!dStrcasecmp(type, "hidden")) {
+ inp_type = DILLO_HTML_INPUT_HIDDEN;
+ if (value)
+ init_str = dStrdup(Html_get_attr(html, tag, tagsize, "value"));
+ } else if (!dStrcasecmp(type, "submit")) {
+ inp_type = DILLO_HTML_INPUT_SUBMIT;
+ init_str = (value) ? value : dStrdup("submit");
+ LabelButtonResource *label_b_r = HT2LT(html)->getResourceFactory()
+ ->createLabelButtonResource(init_str);
+ widget = embed = new Embed (label_b_r);
+// gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */
+ label_b_r->connectClicked (form->form_receiver);
+ } else if (!dStrcasecmp(type, "reset")) {
+ inp_type = DILLO_HTML_INPUT_RESET;
+ init_str = (value) ? value : dStrdup("Reset");
+ LabelButtonResource *label_b_r = HT2LT(html)->getResourceFactory()
+ ->createLabelButtonResource(init_str);
+ widget = embed = new Embed (label_b_r);
+// gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */
+// gtk_signal_connect(GTK_OBJECT(widget), "clicked",
+// GTK_SIGNAL_FUNC(Html_reset_form), html_lb);
+// } else if (!dStrcasecmp(type, "image")) {
+// if (URL_FLAGS(html->linkblock->base_url) & URL_SpamSafe) {
+// /* Don't request the image, make a text submit button instead */
+// inp_type = DILLO_HTML_INPUT_SUBMIT;
+// attrbuf = Html_get_attr(html, tag, tagsize, "alt");
+// label = attrbuf ? attrbuf : value ? value : name ? name : "Submit";
+// init_str = dStrdup(label);
+// widget = gtk_button_new_with_label(init_str);
+// gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */
+// gtk_signal_connect(GTK_OBJECT(widget), "clicked",
+// GTK_SIGNAL_FUNC(Html_submit_form), html_lb);
+// } else {
+// inp_type = DILLO_HTML_INPUT_IMAGE;
+// /* use a dw_image widget */
+// widget = (GtkWidget*) Html_input_image(html, tag, tagsize,
+// html_lb, form->action);
+// init_str = value;
+// }
+// } else if (!dStrcasecmp(type, "file")) {
+// /* todo: implement it! */
+// inp_type = DILLO_HTML_INPUT_FILE;
+// init_str = (value) ? value : NULL;
+// MSG("An input of the type \"file\" wasn't rendered!\n");
+ } else if (!dStrcasecmp(type, "button")) {
+ inp_type = DILLO_HTML_INPUT_BUTTON;
+ if (value) {
+ init_str = value;
+ LabelButtonResource *label_b_r = HT2LT(html)->getResourceFactory()
+ ->createLabelButtonResource(init_str);
+ widget = embed = new Embed (label_b_r);
+ }
+ } else if (!dStrcasecmp(type, "text") || !*type) {
+ /* Text input, which also is the default */
+ inp_type = DILLO_HTML_INPUT_TEXT;
+ EntryResource *entryResource =
+ //HT2LT(html)->getResourceFactory()->createEntryResource (15, false);
+ HT2LT(html)->getResourceFactory()->createEntryResource (10, false);
+ widget = embed = new Embed (entryResource);
+ init_str = (value) ? value : NULL;
+ } else {
+ /* Unknown input type */
+ MSG_HTML("Unknown input type: \"%s\"\n", type);
+ }
+
+ if (inp_type != DILLO_HTML_INPUT_UNKNOWN) {
+ Html_add_input(form, inp_type, widget, embed, name,
+ (init_str) ? init_str : "", NULL, init_val);
+ }
+
+ if (widget != NULL && inp_type != DILLO_HTML_INPUT_IMAGE &&
+ inp_type != DILLO_HTML_INPUT_UNKNOWN) {
+ if (inp_type == DILLO_HTML_INPUT_TEXT ||
+ inp_type == DILLO_HTML_INPUT_PASSWORD) {
+ EntryResource *entryres = (EntryResource*)embed->getResource();
+ /* Readonly or not? */
+ if (Html_get_attr(html, tag, tagsize, "readonly"))
+ entryres->setEditable(false);
+
+// /* Set width of the entry */
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "size")))
+// gtk_widget_set_usize(widget, (strtol(attrbuf, NULL, 10) + 1) *
+// gdk_char_width(widget->style->font, '0'), 0);
+//
+// /* Maximum length of the text in the entry */
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "maxlength")))
+// gtk_entry_set_max_length(GTK_ENTRY(widget),
+// strtol(attrbuf, NULL, 10));
+ }
+
+ DW2TB(html->dw)->addWidget (embed, S_TOP(html)->style);
+ }
+
+ dFree(type);
+ dFree(name);
+ if (init_str != value)
+ dFree(init_str);
+ dFree(value);
+}
+
+/*
+ * The ISINDEX tag is just a deprecated form of <INPUT type=text> with
+ * implied FORM, afaics.
+ */
+static void Html_tag_open_isindex(DilloHtml *html, char *tag, int tagsize)
+{
+// // AL
+// DilloHtmlForm *form;
+// DilloHtmlLB *html_lb;
+// DilloUrl *action;
+// GtkWidget *widget;
+// Widget *embed_gtk;
+// const char *attrbuf;
+//
+// html_lb = html->linkblock;
+//
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "action")))
+// action = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0);
+// else
+// action = a_Url_dup(html->linkblock->base_url);
+//
+// Html_form_new(html->linkblock, DILLO_HTML_METHOD_GET, action,
+// DILLO_HTML_ENC_URLENCODING);
+//
+// form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+//
+// DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+//
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "prompt")))
+// DW2TB(html->dw)->addText(dStrdup(attrbuf),
+// S_TOP(html)->style);
+//
+// widget = gtk_entry_new();
+// Html_add_input(form, DILLO_HTML_INPUT_INDEX,
+// widget, NULL, NULL, NULL, FALSE);
+// gtk_signal_connect(GTK_OBJECT(widget), "activate",
+// GTK_SIGNAL_FUNC(Html_enter_submit_form),
+// html_lb);
+// gtk_widget_show(widget);
+// /* compare <input type=text> */
+// gtk_signal_connect_after(GTK_OBJECT(widget), "button_press_event",
+// GTK_SIGNAL_FUNC(gtk_true),
+// NULL);
+//
+// embed_gtk = a_Dw_embed_gtk_new();
+// a_Dw_embed_gtk_add_gtk(DW_EMBED_GTK(embed_gtk), widget);
+// DW2TB(html->dw)->addWidget (embed_gtk,
+// S_TOP(html)->style);
+//
+// a_Url_free(action);
+}
+
+/*
+ * Close textarea
+ * (TEXTAREA is parsed in VERBATIM mode, and entities are handled here)
+ */
+static void Html_tag_close_textarea(DilloHtml *html, int TagIdx)
+{
+ DilloHtmlLB *html_lb = html->linkblock;
+ char *str;
+ DilloHtmlForm *form;
+ int i;
+
+ if (html->InFlags & IN_FORM && html->InFlags & IN_TEXTAREA) {
+ /* Remove the line ending that follows the opening tag */
+ if (html->Stash->str[0] == '\r')
+ dStr_erase(html->Stash, 0, 1);
+ if (html->Stash->str[0] == '\n')
+ dStr_erase(html->Stash, 0, 1);
+
+ /* As the spec recommends to canonicalize line endings, it is safe
+ * to replace '\r' with '\n'. It will be canonicalized anyway! */
+ for (i = 0; i < html->Stash->len; ++i) {
+ if (html->Stash->str[i] == '\r') {
+ if (html->Stash->str[i + 1] == '\n')
+ dStr_erase(html->Stash, i, 1);
+ else
+ html->Stash->str[i] = '\n';
+ }
+ }
+
+ /* The HTML3.2 spec says it can have "text and character entities". */
+ str = Html_parse_entities(html, html->Stash->str, html->Stash->len);
+
+ form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+ form->inputs->get(form->inputs->size() - 1).init_str = str;
+// gtk_text_insert(GTK_TEXT(form->inputs[form->inputs->size() - 1].widget),
+// NULL, NULL, NULL, str, -1);
+
+ html->InFlags &= ~IN_TEXTAREA;
+ }
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * The textarea tag
+ * (todo: It doesn't support wrapping).
+ */
+static void Html_tag_open_textarea(DilloHtml *html, char *tag, int tagsize)
+{
+// // AL
+// DilloHtmlLB *html_lb;
+// DilloHtmlForm *form;
+// GtkWidget *widget;
+// GtkWidget *scroll;
+// Widget *embed_gtk;
+// char *name;
+// const char *attrbuf;
+// int cols, rows;
+//
+// /* We can't push a new <FORM> because the 'action' URL is unknown */
+// if (!(html->InFlags & IN_FORM)) {
+// MSG_HTML("<textarea> outside <form>\n");
+// html->ReqTagClose = TRUE;
+// return;
+// }
+// if (html->InFlags & IN_TEXTAREA) {
+// MSG_HTML("nested <textarea>\n");
+// html->ReqTagClose = TRUE;
+// return;
+// }
+//
+// html->InFlags |= IN_TEXTAREA;
+// html_lb = html->linkblock;
+// form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+// Html_stash_init(html);
+// S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM;
+//
+// cols = 20;
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "cols")))
+// cols = strtol(attrbuf, NULL, 10);
+// rows = 10;
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "rows")))
+// rows = strtol(attrbuf, NULL, 10);
+// name = NULL;
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "name")))
+// name = dStrdup(attrbuf);
+//
+// widget = gtk_text_new(NULL, NULL);
+// /* compare <input type=text> */
+// gtk_signal_connect_after(GTK_OBJECT(widget), "button_press_event",
+// GTK_SIGNAL_FUNC(gtk_true),
+// NULL);
+//
+// /* Calculate the width and height based on the cols and rows
+// * todo: Get it right... Get the metrics from the font that will be used.
+// */
+// gtk_widget_set_usize(widget, 6 * cols, 16 * rows);
+//
+// /* If the attribute readonly isn't specified we make the textarea
+// * editable. If readonly is set we don't have to do anything.
+// */
+// if (!Html_get_attr(html, tag, tagsize, "readonly"))
+// gtk_text_set_editable(GTK_TEXT(widget), TRUE);
+//
+// scroll = gtk_scrolled_window_new(NULL, NULL);
+// gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+// GTK_POLICY_AUTOMATIC,
+// GTK_POLICY_AUTOMATIC);
+// gtk_container_add(GTK_CONTAINER(scroll), widget);
+// gtk_widget_show(widget);
+// gtk_widget_show(scroll);
+//
+// Html_add_input(form, DILLO_HTML_INPUT_TEXTAREA,
+// widget, name, NULL, NULL, FALSE);
+// dFree(name);
+//
+// embed_gtk = a_Dw_embed_gtk_new ();
+// a_Dw_embed_gtk_add_gtk (DW_EMBED_GTK (embed_gtk), scroll);
+// DW2TB(html->dw)->addWidget (embed_gtk,
+// S_TOP(html)->style);
+}
+
+/*
+ * <SELECT>
+ */
+/* The select tag is quite tricky, because of gorpy html syntax. */
+static void Html_tag_open_select(DilloHtml *html, char *tag, int tagsize)
+{
+// DilloHtmlForm *form;
+// DilloHtmlSelect *Select;
+// DilloHtmlLB *html_lb;
+// GtkWidget *widget, *menu;
+// char *name;
+// const char *attrbuf;
+// int size, type, multi;
+//
+// if (!(html->InFlags & IN_FORM)) {
+// MSG_HTML("<select> outside <form>\n");
+// return;
+// }
+// if (html->InFlags & IN_SELECT) {
+// MSG_HTML("nested <select>\n");
+// return;
+// }
+// html->InFlags |= IN_SELECT;
+//
+// html_lb = html->linkblock;
+//
+// form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+//
+// name = Html_get_attr_wdef(html, tag, tagsize, "name", NULL);
+//
+// size = 0;
+// if ((attrbuf = Html_get_attr(html, tag, tagsize, "size")))
+// size = strtol(attrbuf, NULL, 10);
+//
+// multi = (Html_get_attr(html, tag, tagsize, "multiple")) ? 1 : 0;
+// if (size < 1)
+// size = multi ? 10 : 1;
+//
+// if (size == 1) {
+// menu = gtk_menu_new();
+// widget = gtk_option_menu_new();
+// type = DILLO_HTML_INPUT_SELECT;
+// } else {
+// menu = gtk_list_new();
+// widget = menu;
+// if (multi)
+// gtk_list_set_selection_mode(GTK_LIST(menu), GTK_SELECTION_MULTIPLE);
+// type = DILLO_HTML_INPUT_SEL_LIST;
+// }
+//
+// Select = dNew(DilloHtmlSelect, 1);
+// Select->menu = menu;
+// Select->size = size;
+// Select->num_options = 0;
+// Select->num_options_max = 8;
+// Select->options = dNew(DilloHtmlOption, Select->num_options_max);
+//
+// Html_add_input(form, type, widget, name, NULL, Select, FALSE);
+// Html_stash_init(html);
+// dFree(name);
+}
+
+/*
+ * ?
+ */
+static void Html_option_finish(DilloHtml *html)
+{
+// DilloHtmlForm *form;
+// DilloHtmlInput *input;
+// GtkWidget *menuitem;
+// GSList *group;
+// DilloHtmlSelect *select;
+//
+// if (!(html->InFlags & IN_FORM))
+// return;
+//
+// form = html->linkblock->forms->getRef (html->linkblock->forms->size() - 1);
+// input = form->inputs->getRef (form->inputs->size() - 1);
+// if (input->select->num_options <= 0)
+// return;
+//
+// select = input->select;
+// if (input->type == DILLO_HTML_INPUT_SELECT ) {
+// if (select->num_options == 1)
+// group = NULL;
+// else
+// group = gtk_radio_menu_item_group(GTK_RADIO_MENU_ITEM
+// (select->options[0].menuitem));
+// menuitem = gtk_radio_menu_item_new_with_label(group, html->Stash->str);
+// select->options[select->num_options - 1].menuitem = menuitem;
+// if (select->options[select->num_options - 1].value == NULL)
+// select->options[select->num_options - 1].value =
+// dStrdup(html->Stash->str);
+// gtk_menu_append(GTK_MENU(select->menu), menuitem);
+// if (select->options[select->num_options - 1].init_val)
+// gtk_menu_item_activate(GTK_MENU_ITEM(menuitem));
+// gtk_widget_show(menuitem);
+// } else if (input->type == DILLO_HTML_INPUT_SEL_LIST) {
+// menuitem = gtk_list_item_new_with_label(html->Stash->str);
+// select->options[select->num_options - 1].menuitem = menuitem;
+// if (select->options[select->num_options - 1].value == NULL)
+// select->options[select->num_options - 1].value =
+// dStrdup(html->Stash->str);
+// gtk_container_add(GTK_CONTAINER(select->menu), menuitem);
+// if (select->options[select->num_options - 1].init_val)
+// gtk_list_select_child(GTK_LIST(select->menu), menuitem);
+// gtk_widget_show(menuitem);
+// }
+}
+
+/*
+ * <OPTION>
+ */
+static void Html_tag_open_option(DilloHtml *html, char *tag, int tagsize)
+{
+// DilloHtmlForm *form;
+// DilloHtmlInput *input;
+// DilloHtmlLB *html_lb;
+// int no;
+//
+// if (!(html->InFlags & IN_SELECT))
+// return;
+//
+// html_lb = html->linkblock;
+//
+// form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+// input = form->inputs->getRef (form->inputs->size() - 1);
+// if (input->type == DILLO_HTML_INPUT_SELECT ||
+// input->type == DILLO_HTML_INPUT_SEL_LIST) {
+// Html_option_finish(html);
+// no = input->select->num_options;
+// a_List_add(input->select->options, no, input->select->num_options_max);
+// input->select->options[no].menuitem = NULL;
+// input->select->options[no].value = Html_get_attr_wdef(html, tag, tagsize,
+// "value", NULL);
+// input->select->options[no].init_val =
+// (Html_get_attr(html, tag, tagsize, "selected") != NULL);
+// input->select->num_options++;
+// }
+// Html_stash_init(html);
+}
+
+/*
+ * ?
+ */
+static void Html_tag_close_select(DilloHtml *html, int TagIdx)
+{
+// // AL
+// DilloHtmlForm *form;
+// DilloHtmlInput *input;
+// GtkWidget *scrolledwindow;
+// DilloHtmlLB *html_lb;
+// Widget *embed_gtk;
+// GtkRequisition req;
+// int height;
+//
+// if (html->InFlags & IN_SELECT) {
+// html->InFlags &= ~IN_SELECT;
+//
+// html_lb = html->linkblock;
+//
+// form = html_lb->forms->getRef (html_lb->forms->size() - 1);
+// input = form->inputs->getRef (form->inputs->size() - 1);
+// if (input->type == DILLO_HTML_INPUT_SELECT) {
+// Html_option_finish(html);
+//
+// gtk_option_menu_set_menu(GTK_OPTION_MENU(input->widget),
+// input->select->menu);
+// Html_select_set_history(input);
+//
+// // gtk_option_menu_set_history(GTK_OPTION_MENU(input->widget), 1);
+//
+// gtk_widget_show(input->widget);
+//
+// embed_gtk = a_Dw_embed_gtk_new ();
+// a_Dw_embed_gtk_add_gtk (DW_EMBED_GTK (embed_gtk), input->widget);
+// DW2TB(html->dw)->addWidget (embed_gtk,
+// S_TOP(html)->style);
+// } else if (input->type == DILLO_HTML_INPUT_SEL_LIST) {
+// Html_option_finish(html);
+//
+// if (input->select->size < input->select->num_options) {
+// scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
+// gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow),
+// GTK_POLICY_NEVER,
+// GTK_POLICY_AUTOMATIC);
+// gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW
+// (scrolledwindow),
+// input->widget);
+//
+// gtk_container_set_focus_vadjustment
+// (GTK_CONTAINER (input->widget),
+// gtk_scrolled_window_get_vadjustment
+// (GTK_SCROLLED_WINDOW(scrolledwindow)));
+//
+// /* Calculate the height of the scrolled window */
+// gtk_widget_size_request(input->select->options[0].menuitem, &req);
+// height = input->select->size * req.height +
+// 2 * scrolledwindow->style->klass->ythickness;
+// gtk_widget_set_usize(scrolledwindow, -1, height);
+//
+// gtk_widget_show(input->widget);
+// input->widget = scrolledwindow;
+// }
+// gtk_widget_show(input->widget);
+//
+// /* note: In this next call, scrolledwindows get a warning from
+// * gdkwindow.c:422. I'm not really going to sweat it now - the
+// * embedded widget stuff is going to get massively redone anyway. */
+// embed_gtk = a_Dw_embed_gtk_new ();
+// a_Dw_embed_gtk_add_gtk (DW_EMBED_GTK (embed_gtk), input->widget);
+// DW2TB(html->dw)->addWidget (embed_gtk,
+// S_TOP(html)->style);
+// }
+// }
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Set the Document Base URI
+ */
+static void Html_tag_open_base(DilloHtml *html, char *tag, int tagsize)
+{
+ const char *attrbuf;
+ DilloUrl *BaseUrl;
+
+ if (html->InFlags & IN_HEAD) {
+ if ((attrbuf = Html_get_attr(html, tag, tagsize, "href"))) {
+ BaseUrl = Html_url_new(html, attrbuf, "", 0, 0, 0, 1);
+ if (URL_SCHEME_(BaseUrl)) {
+ /* Pass the URL_SpamSafe flag to the new base url */
+ a_Url_set_flags(
+ BaseUrl, URL_FLAGS(html->linkblock->base_url) & URL_SpamSafe);
+ a_Url_free(html->linkblock->base_url);
+ html->linkblock->base_url = BaseUrl;
+ } else {
+ MSG_HTML("base URI is relative (it MUST be absolute)\n");
+ a_Url_free(BaseUrl);
+ }
+ }
+ } else {
+ MSG_HTML("the BASE element must appear in the HEAD section\n");
+ }
+}
+
+/*
+ * <CODE>
+ */
+static void Html_tag_open_code(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0);
+}
+
+/*
+ * <DFN>
+ */
+static void Html_tag_open_dfn(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 2, 3);
+}
+
+/*
+ * <KBD>
+ */
+static void Html_tag_open_kbd(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0);
+}
+
+/*
+ * <SAMP>
+ */
+static void Html_tag_open_samp(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0);
+}
+
+/*
+ * <VAR>
+ */
+static void Html_tag_open_var(DilloHtml *html, char *tag, int tagsize)
+{
+ Html_set_top_font(html, NULL, 0, 2, 2);
+}
+
+/*
+ * <SUB>
+ */
+static void Html_tag_open_sub(DilloHtml *html, char *tag, int tagsize)
+{
+ HTML_SET_TOP_ATTR (html, valign, VALIGN_SUB);
+}
+
+/*
+ * <SUP>
+ */
+static void Html_tag_open_sup(DilloHtml *html, char *tag, int tagsize)
+{
+ HTML_SET_TOP_ATTR (html, valign, VALIGN_SUPER);
+}
+
+/*
+ * <DIV> (todo: make a complete implementation)
+ */
+static void Html_tag_open_div(DilloHtml *html, char *tag, int tagsize)
+{
+ DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style);
+ Html_tag_set_align_attr (html, tag, tagsize);
+}
+
+/*
+ * </DIV>, also used for </TABLE> and </CENTER>
+ */
+static void Html_tag_close_div(DilloHtml *html, int TagIdx)
+{
+ DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style);
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Default close for most tags - just pop the stack.
+ */
+static void Html_tag_close_default(DilloHtml *html, int TagIdx)
+{
+ Html_pop_tag(html, TagIdx);
+}
+
+/*
+ * Default close for paragraph tags - pop the stack and break.
+ */
+static void Html_tag_close_par(DilloHtml *html, int TagIdx)
+{
+ DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style);
+ Html_pop_tag(html, TagIdx);
+}
+
+
+/*
+ * Function index for the open and close functions for each tag
+ * (Alphabetically sorted for a binary search)
+ *
+ * Explanation for the 'Flags' camp:
+ *
+ * {"address", B8(010110), ...}
+ * |||||`- inline element
+ * ||||`-- block element
+ * |||`--- inline container
+ * ||`---- block container
+ * |`----- body element
+ * `------ head element
+ *
+ * Notes:
+ * - The upper two bits are not used yet.
+ * - Empty elements have both inline and block container clear.
+ * (flow have both set)
+ */
+struct _TagInfo{
+ char *name;
+ unsigned char Flags;
+ char EndTag;
+ uchar_t TagLevel;
+ TagOpenFunct open;
+ TagCloseFunct close;
+};
+
+
+const TagInfo Tags[] = {
+ {"a", B8(010101),'R',2, Html_tag_open_a, Html_tag_close_a},
+ {"abbr", B8(010101),'R',2, Html_tag_open_abbr, Html_tag_close_default},
+ /* acronym 010101 */
+ {"address", B8(010110),'R',2, Html_tag_open_address, Html_tag_close_par},
+ {"area", B8(010001),'F',0, Html_tag_open_area, Html_tag_close_default},
+ {"b", B8(010101),'R',2, Html_tag_open_b, Html_tag_close_default},
+ {"base", B8(100001),'F',0, Html_tag_open_base, Html_tag_close_default},
+ /* basefont 010001 */
+ /* bdo 010101 */
+ {"big", B8(010101),'R',2, Html_tag_open_big_small, Html_tag_close_default},
+ {"blockquote", B8(011110),'R',2,Html_tag_open_blockquote,Html_tag_close_par},
+ {"body", B8(011110),'O',1, Html_tag_open_body, Html_tag_close_body},
+ {"br", B8(010001),'F',0, Html_tag_open_br, Html_tag_close_default},
+ {"button", B8(011101),'R',2, Html_tag_open_button, Html_tag_close_default},
+ /* caption */
+ {"center", B8(011110),'R',2, Html_tag_open_center, Html_tag_close_div},
+ {"cite", B8(010101),'R',2, Html_tag_open_cite, Html_tag_close_default},
+ {"code", B8(010101),'R',2, Html_tag_open_code, Html_tag_close_default},
+ /* col 010010 'F' */
+ /* colgroup */
+ {"dd", B8(011110),'O',1, Html_tag_open_dd, Html_tag_close_par},
+ {"del", B8(011101),'R',2, Html_tag_open_strike, Html_tag_close_default},
+ {"dfn", B8(010101),'R',2, Html_tag_open_dfn, Html_tag_close_default},
+ /* dir 011010 */
+ /* todo: complete <div> support! */
+ {"div", B8(011110),'R',2, Html_tag_open_div, Html_tag_close_div},
+ {"dl", B8(011010),'R',2, Html_tag_open_dl, Html_tag_close_par},
+ {"dt", B8(010110),'O',1, Html_tag_open_dt, Html_tag_close_par},
+ {"em", B8(010101),'R',2, Html_tag_open_em, Html_tag_close_default},
+ /* fieldset */
+ {"font", B8(010101),'R',2, Html_tag_open_font, Html_tag_close_default},
+ {"form", B8(011110),'R',2, Html_tag_open_form, Html_tag_close_form},
+ {"frame", B8(010010),'F',0, Html_tag_open_frame, Html_tag_close_default},
+ {"frameset", B8(011110),'R',2,Html_tag_open_frameset, Html_tag_close_default},
+ {"h1", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h},
+ {"h2", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h},
+ {"h3", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h},
+ {"h4", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h},
+ {"h5", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h},
+ {"h6", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h},
+ {"head", B8(101101),'O',1, Html_tag_open_head, Html_tag_close_head},
+ {"hr", B8(010010),'F',0, Html_tag_open_hr, Html_tag_close_default},
+ {"html", B8(001110),'O',1, Html_tag_open_html, Html_tag_close_html},
+ {"i", B8(010101),'R',2, Html_tag_open_i, Html_tag_close_default},
+ {"iframe", B8(011110),'R',2, Html_tag_open_frame, Html_tag_close_default},
+ {"img", B8(010001),'F',0, Html_tag_open_img, Html_tag_close_default},
+ {"input", B8(010001),'F',0, Html_tag_open_input, Html_tag_close_default},
+ /* ins */
+ {"isindex", B8(110001),'F',0, Html_tag_open_isindex, Html_tag_close_default},
+ {"kbd", B8(010101),'R',2, Html_tag_open_kbd, Html_tag_close_default},
+ /* label 010101 */
+ /* legend 01?? */
+ {"li", B8(011110),'O',1, Html_tag_open_li, Html_tag_close_li},
+ /* link 100000 'F' */
+ {"map", B8(011001),'R',2, Html_tag_open_map, Html_tag_close_map},
+ /* menu 1010 -- todo: not exactly 1010, it can contain LI and inline */
+ {"menu", B8(011010),'R',2, Html_tag_open_menu, Html_tag_close_par},
+ {"meta", B8(100001),'F',0, Html_tag_open_meta, Html_tag_close_default},
+ /* noframes 1011 */
+ /* noscript 1011 */
+ /* object 11xxxx */
+ {"ol", B8(011010),'R',2, Html_tag_open_ol, Html_tag_close_par},
+ /* optgroup */
+ {"option", B8(010001),'O',1, Html_tag_open_option, Html_tag_close_default},
+ {"p", B8(010110),'O',1, Html_tag_open_p, Html_tag_close_par},
+ /* param 010001 'F' */
+ {"pre", B8(010110),'R',2, Html_tag_open_pre, Html_tag_close_pre},
+ /* q 010101 */
+ {"s", B8(010101),'R',2, Html_tag_open_strike, Html_tag_close_default},
+ {"samp", B8(010101),'R',2, Html_tag_open_samp, Html_tag_close_default},
+ {"script", B8(111001),'R',2, Html_tag_open_script, Html_tag_close_script},
+ {"select", B8(011001),'R',2, Html_tag_open_select, Html_tag_close_select},
+ {"small", B8(010101),'R',2, Html_tag_open_big_small, Html_tag_close_default},
+ /* span 0101 */
+ {"strike", B8(010101),'R',2, Html_tag_open_strike, Html_tag_close_default},
+ {"strong", B8(010101),'R',2, Html_tag_open_strong, Html_tag_close_default},
+ {"style", B8(100101),'R',2, Html_tag_open_style, Html_tag_close_style},
+ {"sub", B8(010101),'R',2, Html_tag_open_sub, Html_tag_close_default},
+ {"sup", B8(010101),'R',2, Html_tag_open_sup, Html_tag_close_default},
+ {"table", B8(011010),'R',5, Html_tag_open_table, Html_tag_close_div},
+ /* tbody */
+ {"td", B8(011110),'O',3, Html_tag_open_td, Html_tag_close_default},
+ {"textarea", B8(010101),'R',2,Html_tag_open_textarea,Html_tag_close_textarea},
+ /* tfoot */
+ {"th", B8(011110),'O',1, Html_tag_open_th, Html_tag_close_default},
+ /* thead */
+ {"title", B8(100101),'R',2, Html_tag_open_title, Html_tag_close_title},
+ {"tr", B8(011010),'O',4, Html_tag_open_tr, Html_tag_close_default},
+ {"tt", B8(010101),'R',2, Html_tag_open_tt, Html_tag_close_default},
+ {"u", B8(010101),'R',2, Html_tag_open_u, Html_tag_close_default},
+ {"ul", B8(011010),'R',2, Html_tag_open_ul, Html_tag_close_par},
+ {"var", B8(010101),'R',2, Html_tag_open_var, Html_tag_close_default}
+
+};
+#define NTAGS (sizeof(Tags)/sizeof(Tags[0]))
+
+
+/*
+ * Compares tag from buffer ('/' or '>' or space-ended string) [p1]
+ * with tag from taglist (lowercase, zero ended string) [p2]
+ * Return value: as strcmp()
+ */
+static int Html_tag_compare(const char *p1, const char *p2)
+{
+ while ( *p2 ) {
+ if (tolower(*p1) != *p2)
+ return(tolower(*p1) - *p2);
+ ++p1;
+ ++p2;
+ }
+ return !strchr(" >/\n\r\t", *p1);
+}
+
+/*
+ * Get 'tag' index
+ * return -1 if tag is not handled yet
+ */
+static int Html_tag_index(const char *tag)
+{
+ int low, high, mid, cond;
+
+ /* Binary search */
+ low = 0;
+ high = NTAGS - 1; /* Last tag index */
+ while (low <= high) {
+ mid = (low + high) / 2;
+ if ((cond = Html_tag_compare(tag, Tags[mid].name)) < 0 )
+ high = mid - 1;
+ else if (cond > 0)
+ low = mid + 1;
+ else
+ return mid;
+ }
+ return -1;
+}
+
+/*
+ * For elements with optional close, check whether is time to close.
+ * Return value: (1: Close, 0: Don't close)
+ * --tuned for speed.
+ */
+static int Html_needs_optional_close(int old_idx, int cur_idx)
+{
+ static int i_P = -1, i_LI, i_TD, i_TR, i_TH, i_DD, i_DT, i_OPTION;
+ // i_THEAD, i_TFOOT, i_COLGROUP;
+
+ if (i_P == -1) {
+ /* initialize the indexes of elements with optional close */
+ i_P = Html_tag_index("p"),
+ i_LI = Html_tag_index("li"),
+ i_TD = Html_tag_index("td"),
+ i_TR = Html_tag_index("tr"),
+ i_TH = Html_tag_index("th"),
+ i_DD = Html_tag_index("dd"),
+ i_DT = Html_tag_index("dt"),
+ i_OPTION = Html_tag_index("option");
+ // i_THEAD = Html_tag_index("thead");
+ // i_TFOOT = Html_tag_index("tfoot");
+ // i_COLGROUP = Html_tag_index("colgroup");
+ }
+
+ if (old_idx == i_P || old_idx == i_DT) {
+ /* P and DT are closed by block elements */
+ return (Tags[cur_idx].Flags & 2);
+ } else if (old_idx == i_LI) {
+ /* LI closes LI */
+ return (cur_idx == i_LI);
+ } else if (old_idx == i_TD || old_idx == i_TH) {
+ /* TD and TH are closed by TD, TH and TR */
+ return (cur_idx == i_TD || cur_idx == i_TH || cur_idx == i_TR);
+ } else if (old_idx == i_TR) {
+ /* TR closes TR */
+ return (cur_idx == i_TR);
+ } else if (old_idx == i_DD) {
+ /* DD is closed by DD and DT */
+ return (cur_idx == i_DD || cur_idx == i_DT);
+ } else if (old_idx == i_OPTION) {
+ return 1; // OPTION always needs close
+ }
+
+ /* HTML, HEAD, BODY are handled by Html_test_section(), not here. */
+ /* todo: TBODY is pending */
+ return 0;
+}
+
+
+/*
+ * Conditional cleanup of the stack (at open time).
+ * - This helps catching block elements inside inline containers (a BUG).
+ * - It also closes elements with "optional" close tag.
+ *
+ * This function is called when opening a block element or <OPTION>.
+ *
+ * It searches the stack closing open inline containers, and closing
+ * elements with optional close tag when necessary.
+ *
+ * Note: OPTION is the only non-block element with an optional close.
+ */
+static void Html_stack_cleanup_at_open(DilloHtml *html, int new_idx)
+{
+ /* We know that the element we're about to push is a block element.
+ * (except for OPTION, which is an empty inline, so is closed anyway)
+ * Notes:
+ * Its 'tag' is not yet pushed into the stack,
+ * 'new_idx' is its index inside Tags[].
+ */
+
+ if (!html->TagSoup)
+ return;
+
+ while (html->stack->size() > 1) {
+ int oldtag_idx = S_TOP(html)->tag_idx;
+
+ if (Tags[oldtag_idx].EndTag == 'O') { // Element with optional close
+ if (!Html_needs_optional_close(oldtag_idx, new_idx))
+ break;
+ } else if (Tags[oldtag_idx].Flags & 8) { // Block container
+ break;
+ }
+
+ /* we have an inline (or empty) container... */
+ if (Tags[oldtag_idx].EndTag == 'R') {
+ MSG_HTML("<%s> is not allowed to contain <%s>. -- closing <%s>\n",
+ Tags[oldtag_idx].name, Tags[new_idx].name,
+ Tags[oldtag_idx].name);
+ }
+
+ /* Workaround for Apache and its bad HTML directory listings... */
+ if ((html->InFlags & IN_PRE) &&
+ strcmp(Tags[new_idx].name, "hr") == 0)
+ break;
+
+ /* This call closes the top tag only. */
+ Html_tag_cleanup_at_close(html, oldtag_idx);
+ }
+}
+
+/*
+ * HTML, HEAD and BODY elements have optional open and close tags.
+ * Handle this "magic" here.
+ */
+static void Html_test_section(DilloHtml *html, int new_idx, int IsCloseTag)
+{
+ char *tag;
+ int tag_idx;
+
+ if (!(html->InFlags & IN_HTML) && html->DocType == DT_NONE)
+ MSG_HTML("the required DOCTYPE declaration is missing (or invalid)\n");
+
+ if (!(html->InFlags & IN_HTML)) {
+ tag = "<html>";
+ tag_idx = Html_tag_index(tag + 1);
+ if (tag_idx != new_idx || IsCloseTag) {
+ /* implicit open */
+ Html_force_push_tag(html, tag_idx);
+ Tags[tag_idx].open (html, tag, strlen(tag));
+ }
+ }
+
+ if (Tags[new_idx].Flags & 32) {
+ /* head element */
+ if (!(html->InFlags & IN_HEAD)) {
+ tag = "<head>";
+ tag_idx = Html_tag_index(tag + 1);
+ if (tag_idx != new_idx || IsCloseTag) {
+ /* implicit open of the head element */
+ Html_force_push_tag(html, tag_idx);
+ Tags[tag_idx].open (html, tag, strlen(tag));
+ }
+ }
+
+ } else if (Tags[new_idx].Flags & 16) {
+ /* body element */
+ if (html->InFlags & IN_HEAD) {
+ tag = "</head>";
+ tag_idx = Html_tag_index(tag + 2);
+ Tags[tag_idx].close (html, tag_idx);
+ }
+ tag = "<body>";
+ tag_idx = Html_tag_index(tag + 1);
+ if (tag_idx != new_idx || IsCloseTag) {
+ /* implicit open */
+ Html_force_push_tag(html, tag_idx);
+ Tags[tag_idx].open (html, tag, strlen(tag));
+ }
+ }
+}
+
+/*
+ * Process a tag, given as 'tag' and 'tagsize'. -- tagsize is [1 based]
+ * ('tag' must include the enclosing angle brackets)
+ * This function calls the right open or close function for the tag.
+ */
+static void Html_process_tag(DilloHtml *html, char *tag, int tagsize)
+{
+ int ci, ni; /* current and new tag indexes */
+ const char *attrbuf;
+ char *start = tag + 1; /* discard the '<' */
+ int IsCloseTag = (*start == '/');
+
+ ni = Html_tag_index(start + IsCloseTag);
+
+ /* todo: doctype parsing is a bit fuzzy, but enough for the time being */
+ if (ni == -1 && !(html->InFlags & IN_HTML)) {
+ if (tagsize > 9 && !dStrncasecmp(tag, "<!doctype", 9))
+ Html_parse_doctype(html, tag, tagsize);
+ }
+
+ if (!(html->InFlags & IN_HTML)) {
+ _MSG("\nDoctype: %f\n\n", html->DocTypeVersion);
+ }
+
+ /* Handle HTML, HEAD and BODY. Elements with optional open and close */
+ if (ni != -1 && !(html->InFlags & IN_BODY) /* && parsing HTML */)
+ Html_test_section(html, ni, IsCloseTag);
+
+ /* White space handling */
+ if (html->SPCPending && (!SGML_SPCDEL || !IsCloseTag))
+ /* SGML_SPCDEL requires space pending and open tag */
+ DW2TB(html->dw)->addSpace(S_TOP(html)->style);
+ html->SPCPending = FALSE;
+
+ /* Tag processing */
+ ci = S_TOP(html)->tag_idx;
+ if (ni != -1) {
+
+ if (!IsCloseTag) {
+ /* Open function */
+
+ /* Cleanup when opening a block element, or
+ * when openning over an element with optional close */
+ if (Tags[ni].Flags & 2 || (ci != -1 && Tags[ci].EndTag == 'O'))
+ Html_stack_cleanup_at_open(html, ni);
+
+ /* todo: this is only raising a warning, take some defined action.
+ * Note: apache uses IMG inside PRE (we could use its "alt"). */
+ if ((html->InFlags & IN_PRE) && Html_tag_pre_excludes(ni))
+ MSG_HTML("<pre> is not allowed to contain <%s>\n", Tags[ni].name);
+
+ /* Push the tag into the stack */
+ Html_push_tag(html, ni);
+
+ /* Call the open function for this tag */
+ Tags[ni].open (html, tag, tagsize);
+
+ /* Now parse attributes that can appear on any tag */
+ if (tagsize >= 8 && /* length of "<t id=i>" */
+ (attrbuf = Html_get_attr2(html, tag, tagsize, "id",
+ HTML_LeftTrim | HTML_RightTrim))) {
+ /* According to the SGML declaration of HTML 4, all NAME values
+ * occuring outside entities must be converted to uppercase
+ * (this is what "NAMECASE GENERAL YES" says). But the HTML 4
+ * spec states in Sec. 7.5.2 that anchor ids are case-sensitive.
+ * So we don't do it and hope for better specs in the future ...
+ */
+ Html_check_name_val(html, attrbuf, "id");
+ /* We compare the "id" value with the url-decoded "name" value */
+ if (!html->NameVal || strcmp(html->NameVal, attrbuf)) {
+ if (html->NameVal)
+ MSG_HTML("'id' and 'name' attribute of <a> tag differ\n");
+ Html_add_anchor(html, attrbuf);
+ }
+ }
+
+ /* Reset NameVal */
+ if (html->NameVal) {
+ dFree(html->NameVal);
+ html->NameVal = NULL;
+ }
+
+ /* let the parser know this was an open tag */
+ html->PrevWasOpenTag = TRUE;
+
+ /* Request inmediate close for elements with forbidden close tag. */
+ /* todo: XHTML always requires close tags. A simple implementation
+ * of the commented clause below will make it work. */
+ if (/* parsing HTML && */ Tags[ni].EndTag == 'F')
+ html->ReqTagClose = TRUE;
+ }
+
+ /* Close function: test for </x>, ReqTagClose, <x /> and <x/> */
+ if (*start == '/' || /* </x> */
+ html->ReqTagClose || /* request */
+ (tag[tagsize - 2] == '/' && /* XML: */
+ (isspace(tag[tagsize - 3]) || /* <x /> */
+ (size_t)tagsize == strlen(Tags[ni].name) + 3))) { /* <x/> */
+
+ Tags[ni].close (html, ni);
+ /* This was a close tag */
+ html->PrevWasOpenTag = FALSE;
+ html->ReqTagClose = FALSE;
+ }
+
+ } else {
+ /* tag not working - just ignore it */
+ }
+}
+
+/*
+ * Get attribute value for 'attrname' and return it.
+ * Tags start with '<' and end with a '>' (Ex: "<P align=center>")
+ * tagsize = strlen(tag) from '<' to '>', inclusive.
+ *
+ * Returns one of the following:
+ * * The value of the attribute.
+ * * An empty string if the attribute exists but has no value.
+ * * NULL if the attribute doesn't exist.
+ */
+static const char *Html_get_attr2(DilloHtml *html,
+ const char *tag,
+ int tagsize,
+ const char *attrname,
+ int tag_parsing_flags)
+{
+ int i, isocode, entsize, Found = 0, delimiter = 0, attr_pos = 0;
+ Dstr *Buf = html->attr_data;
+ DilloHtmlTagParsingState state = SEEK_ATTR_START;
+
+ dReturn_val_if_fail(*attrname, NULL);
+
+ dStr_truncate(Buf, 0);
+
+ for (i = 1; i < tagsize; ++i) {
+ switch (state) {
+ case SEEK_ATTR_START:
+ if (isspace(tag[i]))
+ state = SEEK_TOKEN_START;
+ else if (tag[i] == '=')
+ state = SEEK_VALUE_START;
+ break;
+
+ case MATCH_ATTR_NAME:
+ if ((Found = (!(attrname[attr_pos]) &&
+ (tag[i] == '=' || isspace(tag[i]) || tag[i] == '>')))) {
+ state = SEEK_TOKEN_START;
+ --i;
+ } else if (tolower(tag[i]) != tolower(attrname[attr_pos++]))
+ state = SEEK_ATTR_START;
+ break;
+
+ case SEEK_TOKEN_START:
+ if (tag[i] == '=') {
+ state = SEEK_VALUE_START;
+ } else if (!isspace(tag[i])) {
+ attr_pos = 0;
+ state = (Found) ? FINISHED : MATCH_ATTR_NAME;
+ --i;
+ }
+ break;
+ case SEEK_VALUE_START:
+ if (!isspace(tag[i])) {
+ delimiter = (tag[i] == '"' || tag[i] == '\'') ? tag[i] : ' ';
+ i -= (delimiter == ' ');
+ state = (Found) ? GET_VALUE : SKIP_VALUE;
+ }
+ break;
+
+ case SKIP_VALUE:
+ if ((delimiter == ' ' && isspace(tag[i])) || tag[i] == delimiter)
+ state = SEEK_TOKEN_START;
+ break;
+ case GET_VALUE:
+ if ((delimiter == ' ' && (isspace(tag[i]) || tag[i] == '>')) ||
+ tag[i] == delimiter) {
+ state = FINISHED;
+ } else if (tag[i] == '&' &&
+ (tag_parsing_flags & HTML_ParseEntities)) {
+ if ((isocode = Html_parse_entity(html, tag+i,
+ tagsize-i, &entsize)) >= 0) {
+ if (isocode >= 128) {
+ char buf[4];
+ int k, n = utf8encode(isocode, buf);
+ for (k = 0; k < n; ++k)
+ dStr_append_c(Buf, buf[k]);
+ } else {
+ dStr_append_c(Buf, (char) isocode);
+ }
+ i += entsize-1;
+ } else {
+ dStr_append_c(Buf, tag[i]);
+ }
+ } else if (tag[i] == '\r' || tag[i] == '\t') {
+ dStr_append_c(Buf, ' ');
+ } else if (tag[i] == '\n') {
+ /* ignore */
+ } else {
+ dStr_append_c(Buf, tag[i]);
+ }
+ break;
+
+ case FINISHED:
+ i = tagsize;
+ break;
+ }
+ }
+
+ if (tag_parsing_flags & HTML_LeftTrim)
+ while (isspace(Buf->str[0]))
+ dStr_erase(Buf, 0, 1);
+ if (tag_parsing_flags & HTML_RightTrim)
+ while (Buf->len && isspace(Buf->str[Buf->len - 1]))
+ dStr_truncate(Buf, Buf->len - 1);
+
+ return (Found) ? Buf->str : NULL;
+}
+
+/*
+ * Call Html_get_attr2 telling it to parse entities and strip the result
+ */
+static const char *Html_get_attr(DilloHtml *html,
+ const char *tag,
+ int tagsize,
+ const char *attrname)
+{
+ return Html_get_attr2(html, tag, tagsize, attrname,
+ HTML_LeftTrim | HTML_RightTrim | HTML_ParseEntities);
+}
+
+/*
+ * "Html_get_attr with default"
+ * Call Html_get_attr() and strdup() the returned string.
+ * If the attribute isn't found a copy of 'def' is returned.
+ */
+static char *Html_get_attr_wdef(DilloHtml *html,
+ const char *tag,
+ int tagsize,
+ const char *attrname,
+ const char *def)
+{
+ const char *attrbuf = Html_get_attr(html, tag, tagsize, attrname);
+
+ return attrbuf ? dStrdup(attrbuf) : dStrdup(def);
+}
+
+/*
+ * Add a widget to the page.
+ */
+static void Html_add_widget(DilloHtml *html,
+ Widget *widget,
+ char *width_str,
+ char *height_str,
+ StyleAttrs *style_attrs)
+{
+ StyleAttrs new_style_attrs;
+ Style *style;
+
+ new_style_attrs = *style_attrs;
+ new_style_attrs.width = width_str ?
+ Html_parse_length (html, width_str) : LENGTH_AUTO;
+ new_style_attrs.height = height_str ?
+ Html_parse_length (html, height_str) : LENGTH_AUTO;
+ style = Style::create (HT2LT(html), &new_style_attrs);
+ DW2TB(html->dw)->addWidget (widget, style);
+ style->unref ();
+}
+
+
+/*
+ * Dispatch the apropriate function for 'Op'
+ * This function is a Cache client and gets called whenever new data arrives
+ * Op : operation to perform.
+ * CbData : a pointer to a DilloHtml structure
+ * Buf : a pointer to new data
+ * BufSize : new data size (in bytes)
+ */
+static void Html_callback(int Op, CacheClient_t *Client)
+{
+ if (Op) {
+ Html_write((DilloHtml*)Client->CbData, (char*)Client->Buf,
+ Client->BufSize, 1);
+ Html_close((DilloHtml*)Client->CbData, Client->Key);
+ } else {
+ Html_write((DilloHtml*)Client->CbData, (char*)Client->Buf,
+ Client->BufSize, 0);
+ }
+}
+
+/*
+ * Here's where we parse the html and put it into the Textblock structure.
+ * Return value: number of bytes parsed
+ */
+static int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof)
+{
+ char ch = 0, *p, *text;
+ Textblock *textblock;
+ int token_start, buf_index;
+
+ dReturn_val_if_fail ((textblock = DW2TB(html->dw)) != NULL, 0);
+
+ /* Now, 'buf' and 'bufsize' define a buffer aligned to start at a token
+ * boundary. Iterate through tokens until end of buffer is reached. */
+ buf_index = 0;
+ token_start = buf_index;
+ while (buf_index < bufsize) {
+ /* invariant: buf_index == bufsize || token_start == buf_index */
+
+ if (S_TOP(html)->parse_mode ==
+ DILLO_HTML_PARSE_MODE_VERBATIM) {
+ /* Non HTML code here, let's skip until closing tag */
+ do {
+ char *tag = S_TOP(html)->tag_name;
+ buf_index += strcspn(buf + buf_index, "<");
+ if (buf_index + (int)strlen(tag) + 3 > bufsize) {
+ buf_index = bufsize;
+ } else if (strncmp(buf + buf_index, "</", 2) == 0 &&
+ Html_match_tag(tag, buf+buf_index+2, strlen(tag)+1)) {
+ /* copy VERBATIM text into the stash buffer */
+ text = dStrndup(buf + token_start, buf_index - token_start);
+ dStr_append(html->Stash, text);
+ dFree(text);
+ token_start = buf_index;
+ break;
+ } else
+ ++buf_index;
+ } while (buf_index < bufsize);
+ }
+
+ if (isspace(buf[buf_index])) {
+ /* whitespace: group all available whitespace */
+ while (++buf_index < bufsize && isspace(buf[buf_index]));
+ Html_process_space(html, buf + token_start, buf_index - token_start);
+ token_start = buf_index;
+
+ } else if (buf[buf_index] == '<' && (ch = buf[buf_index + 1]) &&
+ (isalpha(ch) || strchr("/!?", ch)) ) {
+ /* Tag */
+ if (buf_index + 3 < bufsize && !strncmp(buf + buf_index, "<!--", 4)) {
+ /* Comment: search for close of comment, skipping over
+ * everything except a matching "-->" tag. */
+ while ( (p = (char*) memchr(buf + buf_index, '>',
+ bufsize - buf_index)) ){
+ buf_index = p - buf + 1;
+ if (p[-1] == '-' && p[-2] == '-') break;
+ }
+ if (p) {
+ /* Got the whole comment. Let's throw it away! :) */
+ token_start = buf_index;
+ } else
+ buf_index = bufsize;
+ } else {
+ /* Tag: search end of tag (skipping over quoted strings) */
+ html->CurrTagOfs = html->Start_Ofs + token_start;
+
+ while ( buf_index < bufsize ) {
+ buf_index++;
+ buf_index += strcspn(buf + buf_index, ">\"'<");
+ if ((ch = buf[buf_index]) == '>') {
+ break;
+ } else if (ch == '"' || ch == '\'') {
+ /* Skip over quoted string */
+ buf_index++;
+ buf_index += strcspn(buf + buf_index,
+ (ch == '"') ? "\">" : "'>");
+ if (buf[buf_index] == '>') {
+ /* Unterminated string value? Let's look ahead and test:
+ * (<: unterminated, closing-quote: terminated) */
+ int offset = buf_index + 1;
+ offset += strcspn(buf + offset,
+ (ch == '"') ? "\"<" : "'<");
+ if (buf[offset] == ch || !buf[offset]) {
+ buf_index = offset;
+ } else {
+ MSG_HTML("attribute lacks closing quote\n");
+ break;
+ }
+ }
+ } else if (ch == '<') {
+ /* unterminated tag detected */
+ p = dStrndup(buf+token_start+1,
+ strcspn(buf+token_start+1, " <"));
+ MSG_HTML("<%s> element lacks its closing '>'\n", p);
+ dFree(p);
+ --buf_index;
+ break;
+ }
+ }
+ if (buf_index < bufsize) {
+ buf_index++;
+ Html_process_tag(html, buf + token_start,
+ buf_index - token_start);
+ token_start = buf_index;
+ }
+ }
+ } else {
+ /* A Word: search for whitespace or tag open */
+ while (++buf_index < bufsize) {
+ buf_index += strcspn(buf + buf_index, " <\n\r\t\f\v");
+ if (buf[buf_index] == '<' && (ch = buf[buf_index + 1]) &&
+ !isalpha(ch) && !strchr("/!?", ch))
+ continue;
+ break;
+ }
+ if (buf_index < bufsize || Eof) {
+ /* successfully found end of token */
+ Html_process_word(html, buf + token_start,
+ buf_index - token_start);
+ token_start = buf_index;
+ }
+ }
+ }/*while*/
+
+ textblock->flush ();
+
+ return token_start;
+}
+
+/*
+ * Process the newly arrived html and put it into the page structure.
+ * (This function is called by Html_callback whenever there's new data)
+ */
+static void Html_write(DilloHtml *html, char *Buf, int BufSize, int Eof)
+{
+ int token_start;
+ char *buf = Buf + html->Start_Ofs;
+ int bufsize = BufSize - html->Start_Ofs;
+
+ dReturn_if_fail (DW2TB(html->dw) != NULL);
+
+ html->Start_Buf = Buf;
+ token_start = Html_write_raw(html, buf, bufsize, Eof);
+ html->Start_Ofs += token_start;
+
+ if (html->bw)
+ a_UIcmd_set_page_prog(html->bw, html->Start_Ofs, 1);
+}
+
+/*
+ * Finish parsing a HTML page
+ * (Free html struct, close the client and update the page progress bar).
+ */
+static void Html_close(DilloHtml *html, int ClientKey)
+{
+ int si;
+
+ //#if defined (DEBUG_LEVEL) && DEBUG_LEVEL >= 1
+ //a_Dw_widget_print_tree (GTK_DW_VIEWPORT(html->dw->viewport)->child);
+ //#endif
+
+ /* force the close of elements left open (todo: not for XHTML) */
+ while ((si = html->stack->size() - 1)) {
+ if (html->stack->getRef(si)->tag_idx != -1) {
+ Html_tag_cleanup_at_close(html, html->stack->getRef(si)->tag_idx);
+ }
+ }
+ dFree(html->stack->getRef(0)->tag_name); /* "none" */
+ (html->stack->getRef(0)->style)->unref (); /* template style */
+
+ delete (html->stack);
+
+ dStr_free(html->Stash, TRUE);
+ dFree(html->SPCBuf);
+ dStr_free(html->attr_data, TRUE);
+
+ /* Remove this client from our active list */
+ a_Bw_close_client(html->bw, ClientKey);
+
+ /* Set progress bar insensitive */
+ a_UIcmd_set_page_prog(html->bw, 0, 0);
+ dFree(html);
+}
+
+
diff --git a/src/html.hh b/src/html.hh
new file mode 100644
index 00000000..2d9202e6
--- /dev/null
+++ b/src/html.hh
@@ -0,0 +1,279 @@
+#ifndef __HTML_HH__
+#define __HTML_HH__
+
+#include "d_size.h" // for uchar_t
+#include "bw.h" // for BrowserWindow
+
+#include "dw/core.hh"
+#include "lout/misc.hh" // For SimpleVector
+
+//#include "dw_image.h" // for DwImageMapList
+
+#include "form.hh" // For receiving the "clicked" signal
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * First, the html linkblock. For now, this mostly has forms, although
+ * pointers to actual links will go here soon, if for no other reason
+ * than to implement history-sensitive link colors. Also, it seems
+ * likely that imagemaps will go here.
+ */
+
+typedef struct _DilloHtmlLB DilloHtmlLB;
+
+typedef struct _DilloHtml DilloHtml;
+typedef struct _DilloHtmlClass DilloHtmlClass;
+typedef struct _DilloHtmlState DilloHtmlState;
+typedef struct _DilloHtmlForm DilloHtmlForm;
+typedef struct _DilloHtmlOption DilloHtmlOption;
+typedef struct _DilloHtmlSelect DilloHtmlSelect;
+typedef struct _DilloHtmlInput DilloHtmlInput;
+
+
+struct _DilloHtmlLB {
+ class HtmlLinkReceiver: public dw::core::Widget::LinkReceiver
+ {
+ private:
+ DilloHtmlLB *lb;
+
+ public:
+ inline HtmlLinkReceiver (DilloHtmlLB *lb) { this->lb = lb; }
+
+ bool enter (dw::core::Widget *widget, int link, int x, int y);
+ bool press (dw::core::Widget *widget, int link, int x, int y,
+ dw::core::EventButton *event);
+ bool click (dw::core::Widget *widget, int link, int x, int y,
+ dw::core::EventButton *event);
+ };
+
+ // Since DilloHtmlLB is a struct, not a class, a simple
+ // "HtmlLinkReceiver linkReceiver" (see signal documentation) would not
+ // work, therefore the pointer.
+ HtmlLinkReceiver *linkReceiver;
+
+ BrowserWindow *bw;
+ DilloUrl *base_url;
+
+ misc::SimpleVector<DilloHtmlForm> *forms;
+
+ misc::SimpleVector<DilloUrl*> *links;
+
+ //DwImageMapList maps;
+
+ int32_t link_color;
+ int32_t visited_color;
+};
+
+
+typedef enum {
+ DT_NONE,
+ DT_HTML,
+ DT_XHTML
+} DilloHtmlDocumentType;
+
+typedef enum {
+ DILLO_HTML_PARSE_MODE_INIT = 0,
+ DILLO_HTML_PARSE_MODE_STASH,
+ DILLO_HTML_PARSE_MODE_STASH_AND_BODY,
+ DILLO_HTML_PARSE_MODE_VERBATIM,
+ DILLO_HTML_PARSE_MODE_BODY,
+ DILLO_HTML_PARSE_MODE_PRE
+} DilloHtmlParseMode;
+
+typedef enum {
+ SEEK_ATTR_START,
+ MATCH_ATTR_NAME,
+ SEEK_TOKEN_START,
+ SEEK_VALUE_START,
+ SKIP_VALUE,
+ GET_VALUE,
+ FINISHED
+} DilloHtmlTagParsingState;
+
+typedef enum {
+ HTML_LeftTrim = 1 << 0,
+ HTML_RightTrim = 1 << 1,
+ HTML_ParseEntities = 1 << 2
+} DilloHtmlTagParsingFlags;
+
+typedef enum {
+ DILLO_HTML_TABLE_MODE_NONE, /* no table at all */
+ DILLO_HTML_TABLE_MODE_TOP, /* outside of <tr> */
+ DILLO_HTML_TABLE_MODE_TR, /* inside of <tr>, outside of <td> */
+ DILLO_HTML_TABLE_MODE_TD /* inside of <td> */
+} DilloHtmlTableMode;
+
+typedef enum {
+ HTML_LIST_NONE,
+ HTML_LIST_UNORDERED,
+ HTML_LIST_ORDERED
+} DilloHtmlListMode;
+
+enum DilloHtmlProcessingState {
+ IN_NONE = 0,
+ IN_HTML = 1 << 0,
+ IN_HEAD = 1 << 1,
+ IN_BODY = 1 << 2,
+ IN_FORM = 1 << 3,
+ IN_SELECT = 1 << 4,
+ IN_TEXTAREA = 1 << 5,
+ IN_MAP = 1 << 6,
+ IN_PRE = 1 << 7,
+ IN_BUTTON = 1 << 8
+};
+
+
+struct _DilloHtmlState {
+ char *tag_name;
+ //DwStyle *style, *table_cell_style;
+ dw::core::style::Style *style, *table_cell_style;
+ DilloHtmlParseMode parse_mode;
+ DilloHtmlTableMode table_mode;
+ bool_t cell_text_align_set;
+ DilloHtmlListMode list_type;
+ int list_number;
+
+ /* TagInfo index for the tag that's being processed */
+ int tag_idx;
+
+ dw::core::Widget *textblock, *table;
+
+ /* This is used to align list items (especially in enumerated lists) */
+ dw::core::Widget *ref_list_item;
+
+ /* This makes image processing faster than a function
+ a_Dw_widget_get_background_color. */
+ int32_t current_bg_color;
+
+ /* This is used for list items etc; if it is set to TRUE, breaks
+ have to be "handed over" (see Html_add_indented and
+ Html_eventually_pop_dw). */
+ bool_t hand_over_break;
+};
+
+typedef enum {
+ DILLO_HTML_METHOD_UNKNOWN,
+ DILLO_HTML_METHOD_GET,
+ DILLO_HTML_METHOD_POST
+} DilloHtmlMethod;
+
+typedef enum {
+ DILLO_HTML_ENC_URLENCODING
+} DilloHtmlEnc;
+
+struct _DilloHtmlForm {
+ DilloHtmlMethod method;
+ DilloUrl *action;
+ DilloHtmlEnc enc;
+
+ misc::SimpleVector<DilloHtmlInput> *inputs;
+
+ int num_entry_fields;
+ int num_submit_buttons;
+
+ form::Form *form_receiver;
+};
+
+struct _DilloHtmlOption {
+ //GtkWidget *menuitem;
+ char *value;
+ bool_t init_val;
+};
+
+struct _DilloHtmlSelect {
+ //GtkWidget *menu;
+ int size;
+
+ DilloHtmlOption *options;
+ int num_options;
+ int num_options_max;
+};
+
+typedef enum {
+ DILLO_HTML_INPUT_UNKNOWN,
+ DILLO_HTML_INPUT_TEXT,
+ DILLO_HTML_INPUT_PASSWORD,
+ DILLO_HTML_INPUT_CHECKBOX,
+ DILLO_HTML_INPUT_RADIO,
+ DILLO_HTML_INPUT_IMAGE,
+ DILLO_HTML_INPUT_FILE,
+ DILLO_HTML_INPUT_BUTTON,
+ DILLO_HTML_INPUT_HIDDEN,
+ DILLO_HTML_INPUT_SUBMIT,
+ DILLO_HTML_INPUT_RESET,
+ DILLO_HTML_INPUT_BUTTON_SUBMIT,
+ DILLO_HTML_INPUT_BUTTON_RESET,
+ DILLO_HTML_INPUT_SELECT,
+ DILLO_HTML_INPUT_SEL_LIST,
+ DILLO_HTML_INPUT_TEXTAREA,
+ DILLO_HTML_INPUT_INDEX
+} DilloHtmlInputType;
+
+struct _DilloHtmlInput {
+ DilloHtmlInputType type;
+ void *widget; /* May be a FLTKWidget or a Dw Widget. */
+ void *embed; /* May be NULL */
+ char *name;
+ char *init_str; /* note: some overloading - for buttons, init_str
+ is simply the value of the button; for text
+ entries, it is the initial value */
+ DilloHtmlSelect *select;
+ bool_t init_val; /* only meaningful for buttons */
+};
+
+struct _DilloHtml {
+ dw::core::Widget *dw; /* this is duplicated in the stack (page) */
+
+ DilloHtmlLB *linkblock;
+ char *Start_Buf;
+ size_t Start_Ofs;
+ size_t CurrTagOfs;
+ size_t OldTagOfs, OldTagLine;
+
+ DilloHtmlDocumentType DocType; /* as given by DOCTYPE tag */
+ float DocTypeVersion; /* HTML or XHTML version number */
+
+ misc::SimpleVector<DilloHtmlState> *stack;
+
+ int InFlags; /* tracks which tags we are in */
+
+ Dstr *Stash;
+ bool_t StashSpace;
+
+ char *SPCBuf; /* Buffer for white space */
+
+ int pre_column; /* current column, used in PRE tags with tabs */
+ bool_t PreFirstChar; /* used to skip the first CR or CRLF in PRE tags */
+ bool_t PrevWasCR; /* Flag to help parsing of "\r\n" in PRE tags */
+ bool_t PrevWasOpenTag; /* Flag to help deferred parsing of white space */
+ bool_t SPCPending; /* Flag to help deferred parsing of white space */
+ bool_t InVisitedLink; /* used to 'contrast_visited_colors' */
+ bool_t ReqTagClose; /* Flag to help handling bad-formed HTML */
+ bool_t CloseOneTag; /* Flag to help Html_tag_cleanup_at_close() */
+ bool_t TagSoup; /* Flag to enable the parser's cleanup functions */
+ char *NameVal; /* used for validation of "NAME" and "ID" in <A> */
+
+ /* element counters: used for validation purposes */
+ uchar_t Num_HTML, Num_HEAD, Num_BODY, Num_TITLE;
+
+ Dstr *attr_data;
+
+ BrowserWindow *bw;
+};
+
+/*
+ * Exported functions
+ */
+void a_Html_form_event_handler(void *data,
+ form::Form *form_receiver,
+ void *v_resource);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* __HTML_HH__ */
diff --git a/src/image.cc b/src/image.cc
new file mode 100644
index 00000000..f815d575
--- /dev/null
+++ b/src/image.cc
@@ -0,0 +1,226 @@
+/*
+ * File: image.cc
+ *
+ * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org>,
+ * Sebastian Geerken <sgeerken@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.
+ */
+
+/*
+ * This file implements image data transfer methods. It handles the transfer
+ * of data from an Image to a DwImage widget.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "msg.h"
+
+#include "image.hh"
+#include "dw/core.hh"
+#include "dw/image.hh"
+
+using namespace dw::core;
+
+// Image to Object-Image macro
+#define OI(Image) ((dw::Image*)(Image->dw))
+
+
+/*
+ * Local data
+ */
+static size_t linebuf_size = 0;
+static uchar_t *linebuf = NULL;
+
+
+/*
+ * Create and initialize a new image structure.
+ */
+DilloImage *a_Image_new(int width,
+ int height,
+ const char *alt_text,
+ int32_t bg_color)
+{
+ DilloImage *Image;
+
+ Image = dNew(DilloImage, 1);
+ Image->dw = (void*) new dw::Image(alt_text);
+ Image->width = 0;
+ Image->height = 0;
+ Image->cmap = NULL;
+ Image->in_type = DILLO_IMG_TYPE_NOTSET;
+ Image->bg_color = bg_color;
+ Image->ProcessedBytes = 0;
+ Image->BitVec = NULL;
+ Image->State = IMG_Empty;
+
+ Image->RefCount = 1;
+
+ return Image;
+}
+
+/*
+ * Deallocate an Image structure
+ */
+static void Image_free(DilloImage *Image)
+{
+ a_Bitvec_free(Image->BitVec);
+ dFree(Image);
+}
+
+/*
+ * Unref and free if necessary
+ */
+void a_Image_unref(DilloImage *Image)
+{
+ _MSG(" %d ", Image->RefCount);
+ if (Image && --Image->RefCount == 0)
+ Image_free(Image);
+}
+
+/*
+ * Add a reference to an Image struct
+ */
+void a_Image_ref(DilloImage *Image)
+{
+ if (Image)
+ ++Image->RefCount;
+}
+
+/*
+ * Decode 'buf' (an image line) into RGB format.
+ */
+static uchar_t *
+ Image_line(DilloImage *Image, const uchar_t *buf, const uchar_t *cmap, int y)
+{
+ uint_t x;
+
+ switch (Image->in_type) {
+ case DILLO_IMG_TYPE_INDEXED:
+ if (cmap) {
+ for (x = 0; x < Image->width; x++)
+ memcpy(linebuf + x * 3, cmap + buf[x] * 3, 3);
+ } else {
+ MSG("Gif:: WARNING, image lacks a color map\n");
+ }
+ break;
+ case DILLO_IMG_TYPE_GRAY:
+ for (x = 0; x < Image->width; x++)
+ memset(linebuf + x * 3, buf[x], 3);
+ break;
+ case DILLO_IMG_TYPE_RGB:
+ /* avoid a memcpy here! --Jcid */
+ return (uchar_t *)buf;
+ case DILLO_IMG_TYPE_NOTSET:
+ MSG_ERR("Image_line: type not set...\n");
+ break;
+ }
+ return linebuf;
+}
+
+/*
+ * Set initial parameters of the image
+ */
+void a_Image_set_parms(DilloImage *Image, void *v_imgbuf, DilloUrl *url,
+ int version, uint_t width, uint_t height,
+ DilloImgType type)
+{
+ _MSG("a_Image_set_parms: width=%d height=%d\n", width, height);
+
+ OI(Image)->setBuffer((Imgbuf*)v_imgbuf);
+
+ if (!Image->BitVec)
+ Image->BitVec = a_Bitvec_new(height);
+ Image->in_type = type;
+ Image->width = width;
+ Image->height = height;
+ if (3 * width > linebuf_size) {
+ linebuf_size = 3 * width;
+ linebuf = (uchar_t*) dRealloc(linebuf, linebuf_size);
+ }
+ Image->State = IMG_SetParms;
+}
+
+/*
+ * Reference the dicache entry color map
+ */
+void a_Image_set_cmap(DilloImage *Image, const uchar_t *cmap)
+{
+ Image->cmap = cmap;
+ Image->State = IMG_SetCmap;
+}
+
+/*
+ * Implement the write method
+ */
+void a_Image_write(DilloImage *Image, void *v_imgbuf,
+ const uchar_t *buf, uint_t y, int decode)
+{
+ uchar_t *newbuf;
+
+ dReturn_if_fail ( y < Image->height );
+
+ if (decode) {
+ /* Decode 'buf' and copy it into the DicEntry buffer */
+ newbuf = Image_line(Image, buf, Image->cmap, y);
+ ((Imgbuf*)v_imgbuf)->copyRow(y, (byte *)newbuf);
+ }
+ a_Bitvec_set_bit(Image->BitVec, y);
+ Image->State = IMG_Write;
+
+ /* Update the row in DwImage */
+ OI(Image)->drawRow(y);
+}
+
+/*
+ * Implement the close method
+ */
+void a_Image_close(DilloImage *Image)
+{
+ a_Image_unref(Image);
+}
+
+
+// Wrappers for Imgbuf -------------------------------------------------------
+
+/*
+ * Increment reference count for an Imgbuf
+ */
+void a_Image_imgbuf_ref(void *v_imgbuf)
+{
+ ((Imgbuf*)v_imgbuf)->ref();
+}
+
+/*
+ * Decrement reference count for an Imgbuf
+ */
+void a_Image_imgbuf_unref(void *v_imgbuf)
+{
+ ((Imgbuf*)v_imgbuf)->unref();
+}
+
+/*
+ * Create a new Imgbuf
+ */
+void *a_Image_imgbuf_new(void *v_dw, int img_type, int width, int height)
+{
+ Layout *layout = ((Widget*)v_dw)->getLayout();
+ if (!layout) {
+ MSG_ERR("a_Image_imgbuf_new: layout is NULL.\n");
+ exit(1);
+ }
+ return (void*)layout->createImgbuf(Imgbuf::RGB, width, height);
+}
+
+/*
+ * Last reference for this Imgbuf?
+ */
+int a_Image_imgbuf_last_reference(void *v_imgbuf)
+{
+ return ((Imgbuf*)v_imgbuf)->lastReference () ? 1 : 0;
+}
+
diff --git a/src/image.hh b/src/image.hh
new file mode 100644
index 00000000..fde51838
--- /dev/null
+++ b/src/image.hh
@@ -0,0 +1,79 @@
+#ifndef __IMAGE_HH__
+#define __IMAGE_HH__
+
+// The DilloImage data-structure and methods
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#include "bitvec.h"
+#include "url.h"
+
+typedef struct _DilloImage DilloImage;
+
+typedef enum {
+ DILLO_IMG_TYPE_INDEXED,
+ DILLO_IMG_TYPE_RGB,
+ DILLO_IMG_TYPE_GRAY,
+ DILLO_IMG_TYPE_NOTSET /* Initial value */
+} DilloImgType;
+
+/* These will reflect the Image's "state" */
+typedef enum {
+ IMG_Empty, /* Just created the entry */
+ IMG_SetParms, /* Parameters set */
+ IMG_SetCmap, /* Color map set */
+ IMG_Write, /* Feeding the entry */
+ IMG_Close, /* Whole image got! */
+ IMG_Abort /* Image transfer aborted */
+} ImageState;
+
+struct _DilloImage {
+ void *dw;
+
+ /* Parameters as told by image data */
+ uint_t width;
+ uint_t height;
+
+ const uchar_t *cmap; /* Color map (only for indexed) */
+ DilloImgType in_type; /* Image Type */
+ int32_t bg_color; /* Background color */
+
+ int ProcessedBytes; /* Amount of bytes already decoded */
+ bitvec_t *BitVec; /* Bit vector for decoded rows */
+ ImageState State;
+
+ int RefCount; /* Reference counter */
+};
+
+
+/*
+ * Function prototypes
+ */
+DilloImage *a_Image_new(int width, int height,
+ const char *alt_text, int32_t bg_color);
+void a_Image_ref(DilloImage *Image);
+void a_Image_unref(DilloImage *Image);
+
+void a_Image_set_parms(DilloImage *Image, void *v_imgbuf, DilloUrl *url,
+ int version, uint_t width, uint_t height,
+ DilloImgType type);
+void a_Image_set_cmap(DilloImage *Image, const uchar_t *cmap);
+void a_Image_write(DilloImage *Image, void *v_imgbuf,
+ const uchar_t *buf, uint_t y, int decode);
+void a_Image_close(DilloImage *Image);
+
+void a_Image_imgbuf_ref(void *v_imgbuf);
+void a_Image_imgbuf_unref(void *v_imgbuf);
+void *a_Image_imgbuf_new(void *v_dw, int img_type, int width, int height) ;
+int a_Image_imgbuf_last_reference(void *v_imgbuf);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __IMAGE_HH__ */
+
diff --git a/src/jpeg.c b/src/jpeg.c
new file mode 100644
index 00000000..fbf3eeee
--- /dev/null
+++ b/src/jpeg.c
@@ -0,0 +1,334 @@
+/*
+ * File: jpeg.c
+ *
+ * Copyright (C) 1997 Raph Levien <raph@acm.org>
+ * Copyright (C) 2000-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.
+ */
+
+/*
+ * The jpeg decoder for dillo. It is responsible for decoding JPEG data
+ * and transferring it to the dicache. It uses libjpeg to do the actual
+ * decoding.
+ */
+
+#include <config.h>
+#ifdef ENABLE_JPEG
+
+#include <stdio.h>
+#include <setjmp.h>
+
+/* avoid a redefinition of HAVE_STDLIB_H with old jpeglib.h */
+#ifdef HAVE_STDLIB_H
+# undef HAVE_STDLIB_H
+#endif
+#include <jpeglib.h>
+#ifdef HAVE_STDLIB_H
+# undef HAVE_STDLIB_H
+#endif
+
+#include "image.hh"
+#include "web.hh"
+#include "cache.h"
+#include "dicache.h"
+
+#define DEBUG_LEVEL 6
+#include "debug.h"
+
+typedef enum {
+ DILLO_JPEG_INIT,
+ DILLO_JPEG_STARTING,
+ DILLO_JPEG_READING,
+ DILLO_JPEG_DONE,
+ DILLO_JPEG_ERROR
+} DilloJpegState;
+
+/* An implementation of a suspending source manager */
+
+typedef struct {
+ struct jpeg_source_mgr pub; /* public fields */
+ struct DilloJpeg *jpeg; /* a pointer back to the jpeg object */
+} my_source_mgr;
+
+struct my_error_mgr {
+ struct jpeg_error_mgr pub; /* "public" fields */
+ jmp_buf setjmp_buffer; /* for return to caller */
+};
+typedef struct my_error_mgr * my_error_ptr;
+
+typedef struct DilloJpeg {
+ DilloImage *Image;
+ DilloUrl *url;
+ int version;
+
+ my_source_mgr Src;
+
+ DilloJpegState state;
+ size_t Start_Ofs, Skip, NewStart;
+ char *Data;
+
+ uint_t y;
+
+ struct jpeg_decompress_struct cinfo;
+ struct my_error_mgr jerr;
+} DilloJpeg;
+
+/*
+ * Forward declarations
+ */
+static DilloJpeg *Jpeg_new(DilloImage *Image, DilloUrl *url, int version);
+static void Jpeg_callback(int Op, CacheClient_t *Client);
+static void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize);
+static void Jpeg_close(DilloJpeg *jpeg, CacheClient_t *Client);
+METHODDEF(void) Jpeg_errorexit (j_common_ptr cinfo);
+
+/* exported function */
+void *a_Jpeg_image(const char *Type, void *P, CA_Callback_t *Call,
+ void **Data);
+
+
+/* this is the routine called by libjpeg when it detects an error. */
+METHODDEF(void) Jpeg_errorexit (j_common_ptr cinfo)
+{
+ /* display message and return to setjmp buffer */
+ my_error_ptr myerr = (my_error_ptr) cinfo->err;
+ (*cinfo->err->output_message) (cinfo);
+ longjmp(myerr->setjmp_buffer, 1);
+}
+
+/*
+ * MIME handler for "image/jpeg" type
+ * (Sets Jpeg_callback or a_Dicache_callback as the cache-client)
+ */
+void *a_Jpeg_image(const char *Type, void *P, CA_Callback_t *Call,
+ void **Data)
+{
+ DilloWeb *web = P;
+ DICacheEntry *DicEntry;
+
+ if (!web->Image)
+ web->Image = a_Image_new(0, 0, NULL, 0);
+
+ /* Add an extra reference to the Image (for dicache usage) */
+ a_Image_ref(web->Image);
+
+ DicEntry = a_Dicache_get_entry(web->url);
+ if (!DicEntry) {
+ /* Let's create an entry for this image... */
+ DicEntry = a_Dicache_add_entry(web->url);
+
+ /* ... and let the decoder feed it! */
+ *Data = Jpeg_new(web->Image, DicEntry->url, DicEntry->version);
+ *Call = (CA_Callback_t) Jpeg_callback;
+ } else {
+ /* Let's feed our client from the dicache */
+ a_Dicache_ref(DicEntry->url, DicEntry->version);
+ *Data = web->Image;
+ *Call = (CA_Callback_t) a_Dicache_callback;
+ }
+ return (web->Image->dw);
+}
+
+/*
+ * Finish the decoding process
+ */
+static void Jpeg_close(DilloJpeg *jpeg, CacheClient_t *Client)
+{
+ a_Dicache_close(jpeg->url, jpeg->version, Client);
+
+ if (jpeg->state != DILLO_JPEG_DONE) {
+ jpeg_destroy_decompress(&(jpeg->cinfo));
+ }
+ dFree(jpeg);
+}
+
+static void init_source(j_decompress_ptr cinfo)
+{
+}
+
+static boolean fill_input_buffer(j_decompress_ptr cinfo)
+{
+ DilloJpeg *jpeg = ((my_source_mgr *) cinfo->src)->jpeg;
+
+ DEBUG_MSG(5, "fill_input_buffer\n");
+#if 0
+ if (!cinfo->src->bytes_in_buffer) {
+ DEBUG_MSG(5, "fill_input_buffer: %ld bytes in buffer\n",
+ (long)cinfo->src->bytes_in_buffer);
+
+ jpeg->Start_Ofs = (ulong_t) jpeg->cinfo.src->next_input_byte -
+ (ulong_t) jpeg->Data;
+#endif
+ if (jpeg->Skip) {
+ jpeg->Start_Ofs = jpeg->NewStart + jpeg->Skip - 1;
+ jpeg->Skip = 0;
+ } else {
+ jpeg->Start_Ofs = (ulong_t) jpeg->cinfo.src->next_input_byte -
+ (ulong_t) jpeg->Data;
+ }
+ return FALSE;
+#if 0
+ }
+ return TRUE;
+#endif
+}
+
+static void skip_input_data(j_decompress_ptr cinfo, long num_bytes)
+{
+ DilloJpeg *jpeg;
+
+ if (num_bytes < 1)
+ return;
+ jpeg = ((my_source_mgr *) cinfo->src)->jpeg;
+
+ DEBUG_MSG(5, "skip_input_data: Start_Ofs = %lu, num_bytes = %ld,"
+ " %ld bytes in buffer\n",
+ (ulong_t)jpeg->Start_Ofs, num_bytes,
+ (long)cinfo->src->bytes_in_buffer);
+
+ cinfo->src->next_input_byte += num_bytes;
+ if (num_bytes < (long)cinfo->src->bytes_in_buffer) {
+ cinfo->src->bytes_in_buffer -= num_bytes;
+ } else {
+ jpeg->Skip += num_bytes - cinfo->src->bytes_in_buffer + 1;
+ cinfo->src->bytes_in_buffer = 0;
+ }
+}
+
+static void term_source(j_decompress_ptr cinfo)
+{
+}
+
+static DilloJpeg *Jpeg_new(DilloImage *Image, DilloUrl *url, int version)
+{
+ my_source_mgr *src;
+ DilloJpeg *jpeg = dMalloc(sizeof(*jpeg));
+
+ jpeg->Image = Image;
+ jpeg->url = url;
+ jpeg->version = version;
+
+ jpeg->state = DILLO_JPEG_INIT;
+ jpeg->Start_Ofs = 0;
+ jpeg->Skip = 0;
+
+ /* decompression step 1 (see libjpeg.doc) */
+ jpeg->cinfo.err = jpeg_std_error(&(jpeg->jerr.pub));
+ jpeg->jerr.pub.error_exit = Jpeg_errorexit;
+
+ jpeg_create_decompress(&(jpeg->cinfo));
+
+ /* decompression step 2 (see libjpeg.doc) */
+ jpeg->cinfo.src = &jpeg->Src.pub;
+ src = &jpeg->Src;
+ src->pub.init_source = init_source;
+ src->pub.fill_input_buffer = fill_input_buffer;
+ src->pub.skip_input_data = skip_input_data;
+ src->pub.resync_to_restart = jpeg_resync_to_restart;/* use default method */
+ src->pub.term_source = term_source;
+ src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
+ src->pub.next_input_byte = NULL;/* until buffer loaded */
+
+ src->jpeg = jpeg;
+
+ /* decompression steps continue in write method */
+ return jpeg;
+}
+
+static void Jpeg_callback(int Op, CacheClient_t *Client)
+{
+ if (Op)
+ Jpeg_close(Client->CbData, Client);
+ else
+ Jpeg_write(Client->CbData, Client->Buf, Client->BufSize);
+}
+
+/*
+ * Receive and process new chunks of JPEG image data
+ */
+static void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize)
+{
+ DilloImgType type;
+ uchar_t *linebuf;
+ JSAMPLE *array[1];
+ int num_read;
+
+ DEBUG_MSG(5, "Jpeg_write: (%p) Bytes in buff: %ld Ofs: %lu\n", jpeg,
+ (long) BufSize, (ulong_t)jpeg->Start_Ofs);
+
+ /* See if we are supposed to skip ahead. */
+ if (BufSize <= jpeg->Start_Ofs)
+ return;
+
+ /* Concatenate with the partial input, if any. */
+ jpeg->cinfo.src->next_input_byte = (uchar_t *)Buf + jpeg->Start_Ofs;
+ jpeg->cinfo.src->bytes_in_buffer = BufSize - jpeg->Start_Ofs;
+ jpeg->NewStart = BufSize;
+ jpeg->Data = Buf;
+
+ if (setjmp(jpeg->jerr.setjmp_buffer)) {
+ /* If we get here, the JPEG code has signaled an error. */
+ jpeg->state = DILLO_JPEG_ERROR;
+ }
+
+ /* Process the bytes in the input buffer. */
+ if (jpeg->state == DILLO_JPEG_INIT) {
+
+ /* decompression step 3 (see libjpeg.doc) */
+ if (jpeg_read_header(&(jpeg->cinfo), TRUE) != JPEG_SUSPENDED) {
+ type = DILLO_IMG_TYPE_GRAY;
+ if (jpeg->cinfo.num_components == 1)
+ type = DILLO_IMG_TYPE_GRAY;
+ else if (jpeg->cinfo.num_components == 3)
+ type = DILLO_IMG_TYPE_RGB;
+ else
+ DEBUG_MSG(5, "jpeg: can't handle %d component images\n",
+ jpeg->cinfo.num_components);
+ a_Dicache_set_parms(jpeg->url, jpeg->version, jpeg->Image,
+ (uint_t)jpeg->cinfo.image_width,
+ (uint_t)jpeg->cinfo.image_height,
+ type);
+
+ /* decompression step 4 (see libjpeg.doc) */
+ jpeg->state = DILLO_JPEG_STARTING;
+ }
+ }
+ if (jpeg->state == DILLO_JPEG_STARTING) {
+ /* decompression step 5 (see libjpeg.doc) */
+ if (jpeg_start_decompress(&(jpeg->cinfo))) {
+ jpeg->y = 0;
+ jpeg->state = DILLO_JPEG_READING;
+ }
+ }
+ if (jpeg->state == DILLO_JPEG_READING) {
+ linebuf = dMalloc(jpeg->cinfo.image_width *
+ jpeg->cinfo.num_components);
+ array[0] = linebuf;
+ while (jpeg->y < jpeg->cinfo.image_height) {
+ num_read = jpeg_read_scanlines(&(jpeg->cinfo), array, 1);
+ if (num_read == 0)
+ break;
+ a_Dicache_write(jpeg->Image, jpeg->url, jpeg->version,
+ linebuf, 0, jpeg->y);
+
+ jpeg->y++;
+ }
+ if (jpeg->y == jpeg->cinfo.image_height) {
+ DEBUG_MSG(5, "height achieved\n");
+
+ jpeg_destroy_decompress(&(jpeg->cinfo));
+ jpeg->state = DILLO_JPEG_DONE;
+ }
+ dFree(linebuf);
+ }
+ if (jpeg->state == DILLO_JPEG_ERROR) {
+ jpeg_destroy_decompress(&(jpeg->cinfo));
+ jpeg->state = DILLO_JPEG_DONE;
+ }
+}
+
+#endif /* ENABLE_JPEG */
diff --git a/src/klist.c b/src/klist.c
new file mode 100644
index 00000000..113472b6
--- /dev/null
+++ b/src/klist.c
@@ -0,0 +1,118 @@
+/*
+ * File: klist.c
+ *
+ * Copyright 2001 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 simple ADT for Key-Data pairs
+ *
+ * NOTE: this ADT is not perfect. The possibility of holding a Key, after
+ * its data has been removed, long enough for the key-counter to reset and
+ * reuse the same key is very low, but EXISTS. So, the responsibility
+ * remains with the caller.
+ */
+
+#include "klist.h"
+
+
+/*
+ * Compare function for searching data by its key
+ */
+static int Klist_node_by_key_cmp(const void *Node, const void *key)
+{
+ return ((KlistNode_t *)Node)->Key - VOIDP2INT(key);
+}
+
+/*
+ * Return the data pointer for a given Key (or NULL if not found)
+ */
+void *a_Klist_get_data(Klist_t *Klist, int Key)
+{
+ void *aux;
+
+ if (!Klist)
+ return NULL;
+ aux = dList_find_sorted(Klist->List, INT2VOIDP(Key), Klist_node_by_key_cmp);
+ return (aux) ? ((KlistNode_t*)aux)->Data : NULL;
+}
+
+/*
+ * Insert a data pointer and return a key for it.
+ */
+int a_Klist_insert(Klist_t **Klist, void *Data)
+{
+ KlistNode_t *Node;
+
+ if (!*Klist) {
+ (*Klist) = dNew(Klist_t, 1);
+ (*Klist)->List = dList_new(32);
+ (*Klist)->Clean = 1;
+ (*Klist)->Counter = 0;
+ }
+
+ /* This avoids repeated keys at the same time */
+ do {
+ if (++((*Klist)->Counter) == 0) {
+ (*Klist)->Counter = 1;
+ (*Klist)->Clean = 0;
+ }
+ } while (!((*Klist)->Clean) &&
+ a_Klist_get_data((*Klist), (*Klist)->Counter));
+
+ Node = dNew(KlistNode_t, 1);
+ Node->Key = (*Klist)->Counter;
+ Node->Data = Data;
+ dList_insert_sorted((*Klist)->List, Node, Klist_node_by_key_cmp);
+ return (*Klist)->Counter;
+}
+
+/*
+ * Remove data by Key
+ */
+void a_Klist_remove(Klist_t *Klist, int Key)
+{
+ void *data;
+
+ data = dList_find_sorted(Klist->List, INT2VOIDP(Key),Klist_node_by_key_cmp);
+ if (data) {
+ dList_remove(Klist->List, data);
+ dFree(data);
+ }
+ if (dList_length(Klist->List) == 0)
+ Klist->Clean = 1;
+}
+
+/*
+ * Return the number of elements in the Klist
+ */
+int a_Klist_length(Klist_t *Klist)
+{
+ return dList_length(Klist->List);
+}
+
+/*
+ * Free a Klist
+ */
+void a_Klist_free(Klist_t **KlistPtr)
+{
+ void *node;
+ Klist_t *Klist = *KlistPtr;
+
+ if (!Klist)
+ return;
+
+ while (dList_length(Klist->List) > 0) {
+ node = dList_nth_data(Klist->List, 0);
+ dList_remove_fast(Klist->List, 0);
+ dFree(node);
+ }
+ dFree(Klist);
+ *KlistPtr = NULL;
+}
+
diff --git a/src/klist.h b/src/klist.h
new file mode 100644
index 00000000..53b2bc31
--- /dev/null
+++ b/src/klist.h
@@ -0,0 +1,40 @@
+#ifndef __KLIST_H__
+#define __KLIST_H__
+
+#include "../dlib/dlib.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct _KlistNode KlistNode_t;
+typedef struct _Klist Klist_t;
+
+struct _KlistNode {
+ int Key; /* primary key */
+ void *Data; /* data reference */
+};
+
+struct _Klist {
+ Dlist *List;
+ int Clean; /* check flag */
+ int Counter; /* counter (for making keys) */
+};
+
+
+/*
+ * Function prototypes
+ */
+void* a_Klist_get_data(Klist_t *Klist, int Key);
+int a_Klist_insert(Klist_t **Klist, void *Data);
+void a_Klist_remove(Klist_t *Klist, int Key);
+int a_Klist_length(Klist_t *Klist);
+void a_Klist_free(Klist_t **Klist);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __KLIST_H__ */
diff --git a/src/list.h b/src/list.h
new file mode 100644
index 00000000..8623bf09
--- /dev/null
+++ b/src/list.h
@@ -0,0 +1,49 @@
+/*
+ * Fast list methods
+ * Feb 2000 --Jcid
+ *
+ */
+
+#ifndef __LIST_H__
+#define __LIST_H__
+
+/*
+ * a_List_resize()
+ *
+ * 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_resize(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))); \
+ }
+
+
+/*
+ * a_List_add()
+ *
+ * Make sure there's space for one more item within the list.
+ */
+#define a_List_add(list,num_items,alloc_step) \
+ a_List_resize(list,num_items,alloc_step)
+
+
+/*
+ * a_List_remove()
+ *
+ * Quickly remove an item from the list
+ * ==> We preserve relative position, but not the element index <==
+ */
+#define a_List_remove(list, item, num_items) \
+ if (list && item < num_items) { \
+ list[item] = list[--num_items]; \
+ }
+
+
+#endif /* __LIST_H__ */
diff --git a/src/menu.cc b/src/menu.cc
new file mode 100644
index 00000000..1de42acf
--- /dev/null
+++ b/src/menu.cc
@@ -0,0 +1,358 @@
+/*
+ * File: menu.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.
+ */
+
+// Functions/Methods for menus
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <fltk/events.h>
+#include <fltk/PopupMenu.h>
+#include <fltk/Item.h>
+#include <fltk/Divider.h>
+
+#include "menu.hh"
+#include "uicmd.hh"
+#include "history.h"
+
+using namespace fltk;
+
+/*
+ * Local data
+ */
+
+// (This data can be encapsulated inside a class for each popup, but
+// as popups are modal, there's no need).
+// Weak reference to the popup's URL
+static DilloUrl *popup_url = NULL;
+// Weak reference to the popup's bw
+static BrowserWindow *popup_bw = NULL;
+// Weak reference to the page's HTML bugs
+static const char *popup_bugs = NULL;
+// History popup direction (-1 = back, 1 = forward).
+static int history_direction = -1;
+// History popup, list of URL-indexes.
+static int *history_list = NULL;
+
+/*
+ * Local sub class
+ * (Used to add the hint for history popup menus)
+ */
+
+class NewItem : public Item {
+public:
+ NewItem (const char* label) : Item(label) {};
+ void draw();
+};
+
+/*
+ * This adds a call to a_UIcmd_set_msg() to show the URL in the status bar
+ * TODO: erase the URL on popup close.
+ */
+void NewItem::draw() {
+ if (flags() & SELECTED) {
+ DilloUrl *url = a_History_get_url(history_list[((int)user_data())-1]);
+ a_UIcmd_set_msg(popup_bw, "%s", URL_STR(url));
+ }
+ Item::draw();
+}
+
+
+//--------------------------------------------------------------------------
+static void Menu_link_cb(Widget* , void *v_url)
+{
+ printf("Menu_link_cb: click! :-)\n");
+}
+
+/*
+ * Open URL in new window
+ */
+static void Menu_open_url_nw_cb(Widget* )
+{
+ printf("Open URL in new window cb: click! :-)\n");
+ a_UIcmd_open_url_nw(popup_bw, popup_url);
+}
+
+/*
+ * Add bookmark
+ */
+static void Menu_add_bookmark_cb(Widget* )
+{
+ a_UIcmd_add_bookmark(popup_bw, popup_url);
+}
+
+/*
+ * Find text
+ */
+static void Menu_find_text_cb(Widget* )
+{
+ a_UIcmd_fullscreen_toggle(popup_bw);
+}
+
+/*
+ * Save link
+ */
+static void Menu_save_link_cb(Widget* )
+{
+ a_UIcmd_save_link(popup_bw, popup_url);
+}
+
+/*
+ * Save current page
+ */
+static void Menu_save_page_cb(Widget* )
+{
+ a_UIcmd_save(popup_bw);
+}
+
+/*
+ * Save current page
+ */
+static void Menu_view_page_source_cb(Widget* )
+{
+ a_UIcmd_view_page_source(popup_url);
+}
+
+/*
+ * View current page's bugs
+ */
+static void Menu_view_page_bugs_cb(Widget* )
+{
+ a_UIcmd_view_page_bugs(popup_bw);
+}
+
+/*
+ * Validate URL with the W3C
+ */
+static void Menu_bugmeter_validate_w3c_cb(Widget* )
+{
+ Dstr *dstr = dStr_sized_new(128);
+
+ dStr_sprintf(dstr, "http://validator.w3.org/check?uri=%s",
+ URL_STR(popup_url));
+ a_UIcmd_open_urlstr(popup_bw, dstr->str);
+ dStr_free(dstr, 1);
+}
+
+/*
+ * Validate URL with the WDG
+ */
+static void Menu_bugmeter_validate_wdg_cb(Widget* )
+{
+ Dstr *dstr = dStr_sized_new(128);
+
+ dStr_sprintf(dstr,
+ "http://www.htmlhelp.org/cgi-bin/validate.cgi?url=%s&warnings=yes",
+ URL_STR(popup_url));
+ a_UIcmd_open_urlstr(popup_bw, dstr->str);
+ dStr_free(dstr, 1);
+}
+
+/*
+ * Show info page for the bug meter
+ */
+static void Menu_bugmeter_about_cb(Widget* )
+{
+ a_UIcmd_open_urlstr(popup_bw, "http://www.dillo.org/help/bug_meter.html");
+}
+
+/*
+ * Navigation History callback.
+ * Go to selected URL.
+ */
+static void Menu_history_cb(Widget *wid, void *data)
+{
+ int k = event_button();
+ int offset = history_direction * (int)data;
+
+ if (k == 2) {
+ /* middle button, open in a new window */
+ a_UIcmd_nav_jump(popup_bw, offset, 1);
+ } else {
+ a_UIcmd_nav_jump(popup_bw, offset, 0);
+ }
+}
+
+/*
+ * Page popup menu (construction & popup)
+ */
+void a_Menu_page_popup(BrowserWindow *bw, DilloUrl *url, const char *bugs_txt)
+{
+ // One menu for every browser window
+ static PopupMenu *pm = 0;
+ // Active/inactive control.
+ static Item *view_page_bugs_item = 0;
+
+ popup_bw = bw;
+ popup_url = url;
+ popup_bugs = bugs_txt;
+ if (!pm) {
+ Item *i;
+ pm = new PopupMenu(0,0,0,0,"&PAGE OPTIONS");
+ pm->begin();
+ i = new Item("View page Source");
+ i->callback(Menu_view_page_source_cb);
+ //i->shortcut(CTRL+'n');
+ i = view_page_bugs_item = new Item("View page Bugs");
+ i->callback(Menu_view_page_bugs_cb);
+ i = new Item("Bookmark this page");
+ i->callback(Menu_add_bookmark_cb);
+ new Divider();
+ i = new Item("&Find Text");
+ i->callback(Menu_find_text_cb);
+ i->shortcut(CTRL+'f');
+ i = new Item("Jump to...");
+ i->deactivate();
+ new Divider();
+ i = new Item("Save page As...");
+ i->callback(Menu_save_page_cb);
+
+ pm->type(PopupMenu::POPUP123);
+ pm->end();
+ }
+
+ if (bugs_txt == NULL)
+ view_page_bugs_item->deactivate();
+ else
+ view_page_bugs_item->activate();
+
+ // Make the popup a child of the calling UI object
+ ((Group *)bw->ui)->add(pm);
+
+ pm->popup();
+}
+
+/*
+ * Link popup menu (construction & popup)
+ */
+void a_Menu_link_popup(BrowserWindow *bw, DilloUrl *url)
+{
+ // One menu for every browser window
+ static PopupMenu *pm = 0;
+
+ popup_bw = bw;
+ popup_url = url;
+ if (!pm) {
+ Item *i;
+ pm = new PopupMenu(0,0,0,0,"&LINK OPTIONS");
+ //pm->callback(Menu_link_cb, url);
+ pm->begin();
+ i = new Item("Open Link in New Window");
+ i->callback(Menu_open_url_nw_cb);
+ i = new Item("Bookmark this Link");
+ i->callback(Menu_add_bookmark_cb);
+ i = new Item("Copy Link location");
+ i->callback(Menu_link_cb, url);
+ i->deactivate();
+ new Divider();
+ i = new Item("Save Link As...");
+ i->callback(Menu_save_link_cb);
+
+ pm->type(PopupMenu::POPUP123);
+ pm->end();
+ }
+
+ // Make the popup a child of the calling UI object
+ ((Group *)bw->ui)->add(pm);
+
+ pm->popup();
+}
+
+/*
+ * Bugmeter popup menu (construction & popup)
+ */
+void a_Menu_bugmeter_popup(BrowserWindow *bw, DilloUrl *url)
+{
+ // One menu for every browser window
+ static PopupMenu *pm = 0;
+
+ popup_bw = bw;
+ popup_url = url;
+ if (!pm) {
+ Item *i;
+ pm = new PopupMenu(0,0,0,0,"&BUG METER OPTIONS");
+ pm->begin();
+ i = new Item("Validate URL with W3C");
+ i->callback(Menu_bugmeter_validate_w3c_cb);
+ i = new Item("Validate URL with WDG");
+ i->callback(Menu_bugmeter_validate_wdg_cb);
+ new Divider();
+ i = new Item("About Bug Meter...");
+ i->callback(Menu_bugmeter_about_cb);
+
+ pm->type(PopupMenu::POPUP123);
+ pm->end();
+ }
+
+ // Make the popup a child of the calling UI object
+ ((Group *)bw->ui)->add(pm);
+
+ pm->popup();
+}
+
+/*
+ * Navigation History popup menu (construction & popup)
+ *
+ * direction: {backward = -1, forward = 1}
+ */
+void a_Menu_history_popup(BrowserWindow *bw, int direction)
+{
+ // One menu for every browser window
+ static PopupMenu *pm = 0;
+ Item *it;
+ int i;
+
+ popup_bw = bw;
+ history_direction = direction;
+
+ // TODO: hook popdown event with delete or similar.
+ if (pm)
+ delete(pm);
+ if (history_list)
+ dFree(history_list);
+
+ if (direction == -1) {
+ pm = new PopupMenu(0,0,0,0, "&PREVIOUS PAGES");
+ } else {
+ pm = new PopupMenu(0,0,0,0, "&FOLLOWING PAGES");
+ }
+
+ // Get a list of URLs for this popup
+ history_list = a_UIcmd_get_history(bw, direction);
+
+ pm->begin();
+ for (i = 0; history_list[i] != -1; i += 1) {
+ // TODO: restrict title size
+ it = new NewItem(a_History_get_title(history_list[i], 1));
+ it->callback(Menu_history_cb, (void*)(i+1));
+ }
+ pm->type(PopupMenu::POPUP123);
+ pm->end();
+
+ // Make the popup a child of the calling UI object
+ // I don't know whether this is necessary...
+ ((Group *)bw->ui)->add(pm);
+
+ pm->popup();
+}
+
+
+void a_Menu_popup_set_url(BrowserWindow *bw, const DilloUrl *url) { }
+void a_Menu_popup_set_url2(BrowserWindow *bw, const DilloUrl *url) { }
+void a_Menu_popup_clear_url2(void *menu_popup) { }
+
+DilloUrl *a_Menu_popup_get_url(BrowserWindow *bw) { return NULL; }
+
+void a_Menu_pagemarks_new (BrowserWindow *bw) { }
+void a_Menu_pagemarks_destroy (BrowserWindow *bw) { }
+void a_Menu_pagemarks_add(BrowserWindow *bw, void *page, void *style,
+ int level) { }
+void a_Menu_pagemarks_set_text(BrowserWindow *bw, const char *str) { }
+
diff --git a/src/menu.hh b/src/menu.hh
new file mode 100644
index 00000000..b9a2eb99
--- /dev/null
+++ b/src/menu.hh
@@ -0,0 +1,34 @@
+#ifndef __MENU_HH__
+#define __MENU_HH__
+
+#include "bw.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+void a_Menu_page_popup(BrowserWindow *bw, DilloUrl *url, const char *bugs_txt);
+void a_Menu_link_popup(BrowserWindow *bw, DilloUrl *url);
+void a_Menu_bugmeter_popup(BrowserWindow *bw, DilloUrl *url);
+void a_Menu_history_popup(BrowserWindow *bw, int direction);
+
+//---------------------
+void a_Menu_popup_set_url(BrowserWindow *bw, const DilloUrl *url);
+void a_Menu_popup_set_url2(BrowserWindow *bw, const DilloUrl *url);
+void a_Menu_popup_clear_url2(void *menu_popup);
+
+DilloUrl *a_Menu_popup_get_url(BrowserWindow *bw);
+
+void a_Menu_pagemarks_new (BrowserWindow *bw);
+void a_Menu_pagemarks_destroy (BrowserWindow *bw);
+void a_Menu_pagemarks_add(BrowserWindow *bw, void *page, void *style,
+ int level);
+void a_Menu_pagemarks_set_text(BrowserWindow *bw, const char *str);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* MENU_HH */
+
diff --git a/src/misc.c b/src/misc.c
new file mode 100644
index 00000000..f7ab00d3
--- /dev/null
+++ b/src/misc.c
@@ -0,0 +1,271 @@
+/*
+ * File: misc.c
+ *
+ * Copyright (C) 2000 Jorge Arellano Cid <jcid@dillo.org>,
+ * Jörgen Viksell <vsksga@hotmail.com>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "msg.h"
+#include "misc.h"
+
+
+/*
+ * Escape characters as %XX sequences.
+ * Return value: New string.
+ */
+char *a_Misc_escape_chars(const char *str, char *esc_set)
+{
+ static const char *hex = "0123456789ABCDEF";
+ char *p = NULL;
+ Dstr *dstr;
+ int i;
+
+ 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;
+}
+
+
+#define TAB_SIZE 8
+/*
+ * Takes a string and converts any tabs to spaces.
+ */
+char *a_Misc_expand_tabs(const char *str)
+{
+ Dstr *New = dStr_new("");
+ int len, i, j, pos, old_pos;
+ char *val;
+
+ if ((len = strlen(str))) {
+ for (pos = 0, i = 0; i < len; i++) {
+ if (str[i] == '\t') {
+ /* Fill with whitespaces until the next tab. */
+ old_pos = pos;
+ pos += TAB_SIZE - (pos % TAB_SIZE);
+ for (j = old_pos; j < pos; j++)
+ dStr_append_c(New, ' ');
+ } else {
+ dStr_append_c(New, str[i]);
+ pos++;
+ }
+ }
+ }
+ val = New->str;
+ dStr_free(New, FALSE);
+ return val;
+}
+
+/* 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;
+}
+
+/*
+ * Check the server-supplied 'Content-Type' against our detected type.
+ * (some servers seem to default to "text/plain").
+ *
+ * Return value:
+ * 0, if they match
+ * -1, if a mismatch is detected
+ *
+ * There're many MIME types Dillo doesn't know, they're handled
+ * as "application/octet-stream" (as the SPEC says).
+ *
+ * A mismatch happens when receiving a binary stream as
+ * "text/plain" or "text/html", or an image that's not an image of its kind.
+ *
+ * Note: this is a basic security procedure.
+ *
+ */
+int a_Misc_content_type_check(const char *EntryType, const char *DetectedType)
+{
+ int i;
+ int st = -1;
+
+ _MSG("Type check: [Srv: %s Det: %s]\n", EntryType, DetectedType);
+
+ if (!EntryType)
+ return 0; /* there's no mismatch without server type */
+
+ for (i = 1; MimeTypes[i].str; ++i)
+ if (dStrncasecmp(EntryType, MimeTypes[i].str, MimeTypes[i].len) == 0)
+ break;
+
+ if (!MimeTypes[i].str) {
+ /* type not found, no mismatch */
+ st = 0;
+ } else if (dStrncasecmp(EntryType, "image/", 6) == 0 &&
+ !dStrncasecmp(DetectedType,MimeTypes[i].str,MimeTypes[i].len)){
+ /* An image, and there's an exact match */
+ st = 0;
+ } else if (dStrncasecmp(EntryType, "text/", 5) ||
+ dStrncasecmp(DetectedType, "application/", 12)) {
+ /* Not an application sent as text */
+ st = 0;
+ }
+
+ return st;
+}
+
+/*
+ * Parse a geometry string.
+ */
+int a_Misc_parse_geometry(char *str, int *x, int *y, int *w, int *h)
+{
+ char *p, *t1, *t2;
+ int n1, n2;
+ int ret = 0;
+
+ if ((p = strchr(str, 'x')) || (p = strchr(str, 'X'))) {
+ n1 = strtol(str, &t1, 10);
+ n2 = strtol(++p, &t2, 10);
+ if (t1 != str && t2 != p) {
+ *w = n1;
+ *h = n2;
+ ret = 1;
+ /* parse x,y now */
+ p = t2;
+ n1 = strtol(p, &t1, 10);
+ n2 = strtol(t1, &t2, 10);
+ if (t1 != p && t2 != t1) {
+ *x = n1;
+ *y = n2;
+ }
+ }
+ }
+ _MSG("geom: w,h,x,y = (%d,%d,%d,%d)\n", *w, *h, *x, *y);
+ return ret;
+}
+
+/*
+ * Encodes string using base64 encoding.
+ * Return value: new string or NULL if input string is empty.
+ */
+char *a_Misc_encode_base64(const char *in)
+{
+ static const char *base64_hex = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+ char *out = NULL;
+ int len, i = 0;
+
+ if (in == NULL) return NULL;
+ len = strlen(in);
+
+ out = (char *)dMalloc((len + 2) / 3 * 4 + 1);
+
+ for (; len >= 3; len -= 3) {
+ out[i++] = base64_hex[in[0] >> 2];
+ out[i++] = base64_hex[((in[0]<<4) & 0x30) | (in[1]>>4)];
+ out[i++] = base64_hex[((in[1]<<2) & 0x3c) | (in[2]>>6)];
+ out[i++] = base64_hex[in[2] & 0x3f];
+ in += 3;
+ }
+
+ if (len > 0) {
+ unsigned char fragment;
+ out[i++] = base64_hex[in[0] >> 2];
+ fragment = (in[0] << 4) & 0x30;
+ if (len > 1) fragment |= in[1] >> 4;
+ out[i++] = base64_hex[fragment];
+ out[i++] = (len < 2) ? '=' : base64_hex[(in[1] << 2) & 0x3c];
+ out[i++] = '=';
+ }
+ out[i] = '\0';
+ return out;
+}
diff --git a/src/misc.h b/src/misc.h
new file mode 100644
index 00000000..71a00b68
--- /dev/null
+++ b/src/misc.h
@@ -0,0 +1,25 @@
+#ifndef __DILLO_MISC_H__
+#define __DILLO_MISC_H__
+
+#include <stddef.h> /* for size_t */
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+char *a_Misc_escape_chars(const char *str, char *esc_set);
+char *a_Misc_expand_tabs(const char *str);
+int a_Misc_get_content_type_from_data(void *Data, size_t Size,const char **PT);
+int a_Misc_content_type_check(const char *EntryType, const char *DetectedType);
+int a_Misc_parse_geometry(char *geom, int *x, int *y, int *w, int *h);
+char *a_Misc_encode_base64(const char *in);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __DILLO_MISC_H__ */
+
diff --git a/src/msg.h b/src/msg.h
new file mode 100644
index 00000000..cf5b8fed
--- /dev/null
+++ b/src/msg.h
@@ -0,0 +1,42 @@
+#ifndef __MSG_H__
+#define __MSG_H__
+
+#include <stdio.h>
+#include "prefs.h"
+
+/*
+ * You can disable any MSG* macro by adding the '_' prefix.
+ */
+#define _MSG(...)
+#define _MSG_WARN(...)
+#define _MSG_HTML(...)
+#define _MSG_HTTP(...)
+
+
+#define MSG(...) \
+ D_STMT_START { \
+ if (prefs.show_msg) \
+ printf(__VA_ARGS__); \
+ } D_STMT_END
+
+#define MSG_WARN(...) \
+ D_STMT_START { \
+ if (prefs.show_msg) \
+ printf("** WARNING **: " __VA_ARGS__); \
+ } D_STMT_END
+
+#define MSG_ERR(...) \
+ D_STMT_START { \
+ if (prefs.show_msg) \
+ printf("** ERROR **: " __VA_ARGS__); \
+ } D_STMT_END
+
+#define MSG_HTML(...) \
+ D_STMT_START { \
+ Html_msg(html, __VA_ARGS__); \
+ } D_STMT_END
+
+#define MSG_HTTP(...) \
+ printf("HTTP warning: " __VA_ARGS__)
+
+#endif /* __MSG_H__ */
diff --git a/src/nav.c b/src/nav.c
new file mode 100644
index 00000000..b2d1d679
--- /dev/null
+++ b/src/nav.c
@@ -0,0 +1,427 @@
+/*
+ * File: nav.c
+ *
+ * Copyright (C) 2000-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.
+ */
+
+/* Support for a navigation stack */
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include "msg.h"
+#include "list.h"
+#include "nav.h"
+#include "history.h"
+#include "web.hh"
+#include "uicmd.hh"
+#include "dialog.hh"
+#include "prefs.h"
+#include "capi.h"
+
+//#define DEBUG_LEVEL 3
+#include "debug.h"
+
+/*
+ * Forward declarations
+ */
+static void Nav_reload(BrowserWindow *bw);
+
+
+/*
+ * Free memory used by this module
+ */
+void a_Nav_free(BrowserWindow *bw)
+{
+ a_Nav_cancel_expect(bw);
+ dFree(bw->nav_stack);
+}
+
+
+/* Navigation stack methods ------------------------------------------------ */
+
+/*
+ * Return current nav_stack pointer [0 based; -1 = empty]
+ */
+int a_Nav_stack_ptr(BrowserWindow *bw)
+{
+ return bw->nav_stack_ptr;
+}
+
+/*
+ * Move the nav_stack pointer
+ */
+static void Nav_stack_move_ptr(BrowserWindow *bw, int offset)
+{
+ int nptr;
+
+ dReturn_if_fail (bw != NULL);
+ if (offset != 0) {
+ nptr = bw->nav_stack_ptr + offset;
+ dReturn_if_fail (nptr >= 0 && nptr < bw->nav_stack_size);
+ bw->nav_stack_ptr = nptr;
+ }
+}
+
+/*
+ * Return size of nav_stack [1 based]
+ */
+int a_Nav_stack_size(BrowserWindow *bw)
+{
+ return bw->nav_stack_size;
+}
+
+/*
+ * Add an URL-index in the navigation stack.
+ */
+static void Nav_stack_add(BrowserWindow *bw, int idx)
+{
+ dReturn_if_fail (bw != NULL);
+
+ ++bw->nav_stack_ptr;
+ if (bw->nav_stack_ptr == bw->nav_stack_size) {
+ a_List_add(bw->nav_stack, bw->nav_stack_size, bw->nav_stack_size_max);
+ ++bw->nav_stack_size;
+ } else {
+ bw->nav_stack_size = bw->nav_stack_ptr + 1;
+ }
+ bw->nav_stack[bw->nav_stack_ptr] = idx;
+}
+
+/*
+ * Remove an URL-index from the navigation stack.
+ */
+static void Nav_stack_remove(BrowserWindow *bw, int idx)
+{
+ int sz = a_Nav_stack_size(bw);
+
+ dReturn_if_fail (bw != NULL && idx >=0 && idx < sz);
+
+ for ( ; idx < sz - 1; ++idx)
+ bw->nav_stack[idx] = bw->nav_stack[idx + 1];
+ if (bw->nav_stack_ptr == --bw->nav_stack_size)
+ --bw->nav_stack_ptr;
+}
+
+/*
+ * Remove equal adyacent URLs at the top of the stack.
+ * (It may happen with redirections)
+ */
+static void Nav_stack_clean(BrowserWindow *bw)
+{
+ int i;
+
+ dReturn_if_fail (bw != NULL);
+
+ if ((i = a_Nav_stack_size(bw)) >= 2 &&
+ bw->nav_stack[i-2] == bw->nav_stack[i-1])
+ Nav_stack_remove(bw, i - 1);
+}
+
+
+/* General methods --------------------------------------------------------- */
+
+/*
+ * Create a DilloWeb structure for 'url' and ask the cache to send it back.
+ * - Also set a few things related to the browser window.
+ * This function requests the page's root-URL; images and related stuff
+ * are fetched directly by the HTML module.
+ */
+static void Nav_open_url(BrowserWindow *bw, const DilloUrl *url, int offset)
+{
+ DilloUrl *old_url = NULL;
+ bool_t MustLoad;
+ int ClientKey;
+ DilloWeb *Web;
+ char *loc_text;
+ bool_t ForceReload = (URL_FLAGS(url) & URL_E2EReload);
+
+ MSG("Nav_open_url: Url=>%s<\n", URL_STR_(url));
+
+ /* Get the url of the current page */
+ if (a_Nav_stack_ptr(bw) != -1)
+ old_url = a_History_get_url(NAV_TOP(bw));
+
+ /* Record current scrolling position
+ * (the strcmp check is necessary because of redirections) */
+ loc_text = a_UIcmd_get_location_text(bw);
+ if (old_url &&
+ !strcmp(URL_STR(old_url), loc_text)) {
+
+ //old_url->scrolling_position_x =
+ // a_Dw_gtk_scrolled_window_get_scrolling_position_x(
+ // GTK_DW_SCROLLED_WINDOW(bw->docwin));
+ //old_url->scrolling_position_y =
+ // a_Dw_gtk_scrolled_window_get_scrolling_position_y(
+ // GTK_DW_SCROLLED_WINDOW(bw->docwin));
+
+ a_Url_set_pos(old_url, 0, 0);
+ }
+
+ /* Update navigation-stack-pointer (offset may be zero) */
+ Nav_stack_move_ptr(bw, offset);
+
+ /* Page must be reloaded, if old and new url (without anchor) differ */
+ MustLoad = ForceReload || !old_url;
+ if (old_url){
+ MustLoad |= (a_Url_cmp(old_url, url) != 0);
+ MustLoad |= strcmp(URL_STR(old_url), a_UIcmd_get_location_text(bw));
+ }
+ dFree(loc_text);
+
+ if (MustLoad) {
+ a_Bw_stop_clients(bw, BW_Root + BW_Img);
+ a_Bw_cleanup(bw);
+
+ // a_Menu_pagemarks_new(bw);
+
+ Web = a_Web_new(url);
+ Web->bw = bw;
+ Web->flags |= WEB_RootUrl;
+ if ((ClientKey = a_Capi_open_url(Web, NULL, NULL)) != 0) {
+ a_Bw_add_client(bw, ClientKey, 1);
+ a_Bw_add_url(bw, url);
+ }
+ }
+
+ /* Jump to #anchor position */
+ if (URL_FRAGMENT_(url)) {
+ /* todo: push on stack */
+ char *pf = a_Url_decode_hex_str(URL_FRAGMENT_(url));
+ //a_Dw_render_layout_set_anchor(bw->render_layout, pf);
+ dFree(pf);
+ }
+}
+
+/*
+ * Cancel the last expected url if present. The responsibility
+ * for actually aborting the data stream remains with the caller.
+ */
+void a_Nav_cancel_expect(BrowserWindow *bw)
+{
+ if (bw->nav_expecting) {
+ if (bw->nav_expect_url) {
+ a_Url_free(bw->nav_expect_url);
+ bw->nav_expect_url = NULL;
+ }
+ bw->nav_expecting = FALSE;
+ }
+}
+
+/*
+ * We have an answer! Set things accordingly.
+ */
+void a_Nav_expect_done(BrowserWindow *bw)
+{
+ int idx;
+ DilloUrl *url;
+
+ dReturn_if_fail(bw != NULL);
+
+ if (bw->nav_expecting) {
+ url = bw->nav_expect_url;
+ /* unset E2EReload before adding this url to history */
+ a_Url_set_flags(url, URL_FLAGS(url) & ~URL_E2EReload);
+ idx = a_History_add_url(url);
+ Nav_stack_add(bw, idx);
+
+ a_Url_free(url);
+ bw->nav_expect_url = NULL;
+ bw->nav_expecting = FALSE;
+ }
+ Nav_stack_clean(bw);
+ a_UIcmd_set_buttons_sens(bw);
+}
+
+/*
+ * Make 'url' the current browsed page (upon data arrival)
+ * - Set bw to expect the URL data
+ * - Ask the cache to feed back the requested URL (via Nav_open_url)
+ */
+void a_Nav_push(BrowserWindow *bw, const DilloUrl *url)
+{
+ dReturn_if_fail (bw != NULL);
+
+ if (bw->nav_expecting && !a_Url_cmp(bw->nav_expect_url, url) &&
+ !strcmp(URL_FRAGMENT(bw->nav_expect_url),URL_FRAGMENT(url))) {
+ /* we're already expecting that url (most probably a double-click) */
+ return;
+ }
+ a_Nav_cancel_expect(bw);
+ bw->nav_expect_url = a_Url_dup(url);
+ bw->nav_expecting = TRUE;
+ Nav_open_url(bw, url, 0);
+}
+
+/*
+ * Same as a_Nav_push() but in a new window.
+ */
+void a_Nav_push_nw(BrowserWindow *bw, const DilloUrl *url)
+{
+ int w, h;
+ BrowserWindow *newbw;
+
+ a_UIcmd_get_wh(bw, &w, &h);
+ newbw = a_UIcmd_browser_window_new(w, h);
+ a_Nav_push(newbw, url);
+}
+
+/*
+ * Wraps a_Nav_push to match 'DwPage->link' function type
+ */
+void a_Nav_vpush(void *vbw, const DilloUrl *url)
+{
+ a_Nav_push(vbw, url);
+}
+
+/*
+ * Send the browser back to previous page
+ */
+void a_Nav_back(BrowserWindow *bw)
+{
+ int idx = a_Nav_stack_ptr(bw);
+
+ a_Nav_cancel_expect(bw);
+ if (--idx >= 0){
+ a_UIcmd_set_msg(bw, "");
+ Nav_open_url(bw, a_History_get_url(NAV_IDX(bw,idx)), -1);
+ }
+}
+
+/*
+ * Send the browser to next page in the history list
+ */
+void a_Nav_forw(BrowserWindow *bw)
+{
+ int idx = a_Nav_stack_ptr(bw);
+
+ a_Nav_cancel_expect(bw);
+ if (++idx < a_Nav_stack_size(bw)) {
+ a_UIcmd_set_msg(bw, "");
+ Nav_open_url(bw, a_History_get_url(NAV_IDX(bw,idx)), +1);
+ }
+}
+
+/*
+ * Redirect the browser to the HOME page!
+ */
+void a_Nav_home(BrowserWindow *bw)
+{
+ a_Nav_push(bw, prefs.home);
+}
+
+/*
+ * This one does a_Nav_reload's job!
+ */
+static void Nav_reload(BrowserWindow *bw)
+{
+ DilloUrl *url, *ReqURL;
+
+ a_Nav_cancel_expect(bw);
+ if (a_Nav_stack_size(bw)) {
+ url = a_History_get_url(NAV_TOP(bw));
+ ReqURL = a_Url_dup(a_History_get_url(NAV_TOP(bw)));
+ /* Let's make reload be end-to-end */
+ a_Url_set_flags(ReqURL, URL_FLAGS(ReqURL) | URL_E2EReload);
+ /* This is an explicit reload, so clear the SpamSafe flag */
+ a_Url_set_flags(ReqURL, URL_FLAGS(ReqURL) & ~URL_SpamSafe);
+ Nav_open_url(bw, ReqURL, 0);
+ a_Url_free(ReqURL);
+ }
+}
+
+/*
+ * Implement the RELOAD button functionality.
+ * (Currently it only reloads the page, not its images)
+ */
+void a_Nav_reload(BrowserWindow *bw)
+{
+ DilloUrl *url;
+ int choice;
+
+ a_Nav_cancel_expect(bw);
+ if (a_Nav_stack_size(bw)) {
+ url = a_History_get_url(NAV_TOP(bw));
+ if (URL_FLAGS(url) & URL_Post) {
+ /* Attempt to repost data, let's confirm... */
+ choice = a_Dialog_choice3("Repost form data?",
+ "Yes", "*No", "Cancel");
+ if (choice == 0) { /* "Yes" */
+ DEBUG_MSG(3, "Nav_reload_confirmed\n");
+ Nav_reload(bw);
+ }
+
+ } else {
+ Nav_reload(bw);
+ }
+ }
+}
+
+/*
+ * Jump to a URL in the Navigation stack.
+ */
+void a_Nav_jump(BrowserWindow *bw, int offset, int new_bw)
+{
+ int idx = a_Nav_stack_ptr(bw) + offset;
+
+ if (new_bw) {
+ a_Nav_push_nw(bw, a_History_get_url(NAV_IDX(bw,idx)));
+ } else {
+ a_Nav_cancel_expect(bw);
+ Nav_open_url(bw, a_History_get_url(NAV_IDX(bw,idx)), offset);
+ a_UIcmd_set_buttons_sens(bw);
+ }
+}
+
+
+/* Specific methods -------------------------------------------------------- */
+
+/*
+ * Receive data from the cache and save it to a local file
+ */
+static void Nav_save_cb(int Op, CacheClient_t *Client)
+{
+ DilloWeb *Web = Client->Web;
+ int Bytes;
+
+ if (Op){
+ struct stat st;
+
+ fflush(Web->stream);
+ fstat(fileno(Web->stream), &st);
+ fclose(Web->stream);
+ a_UIcmd_set_msg(Web->bw, "File saved (%d Bytes)", st.st_size);
+ } else {
+ if ((Bytes = Client->BufSize - Web->SavedBytes) > 0) {
+ Bytes = fwrite(Client->Buf + Web->SavedBytes, 1, Bytes, Web->stream);
+ Web->SavedBytes += Bytes;
+ }
+ }
+}
+
+/*
+ * Save a URL (from cache or from the net).
+ */
+void a_Nav_save_url(BrowserWindow *bw,
+ const DilloUrl *url, const char *filename)
+{
+ DilloWeb *Web = a_Web_new(url);
+ Web->bw = bw;
+ Web->filename = dStrdup(filename);
+ Web->flags |= WEB_Download;
+ /* todo: keep track of this client */
+ a_Capi_open_url(Web, Nav_save_cb, Web);
+}
+
+/*
+ * Wrapper for a_Capi_get_buf.
+ */
+int a_Nav_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize)
+{
+ return a_Capi_get_buf(Url, PBuf, BufSize);
+}
+
diff --git a/src/nav.h b/src/nav.h
new file mode 100644
index 00000000..7b6999ac
--- /dev/null
+++ b/src/nav.h
@@ -0,0 +1,40 @@
+#ifndef __NAV_H__
+#define __NAV_H__
+
+#include "bw.h"
+
+
+/* useful macros for the navigation stack */
+#define NAV_IDX(bw, i) (bw)->nav_stack[i]
+#define NAV_TOP(bw) (bw)->nav_stack[(bw)->nav_stack_ptr]
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+void a_Nav_push(BrowserWindow *bw, const DilloUrl *url);
+void a_Nav_push_nw(BrowserWindow *bw, const DilloUrl *url);
+void a_Nav_vpush(void *vbw, const DilloUrl *url);
+void a_Nav_back(BrowserWindow *bw);
+void a_Nav_forw(BrowserWindow *bw);
+void a_Nav_home(BrowserWindow *bw);
+void a_Nav_reload(BrowserWindow *bw);
+void a_Nav_jump(BrowserWindow *bw, int offset, int new_bw);
+void a_Nav_free(BrowserWindow *bw);
+void a_Nav_cancel_expect (BrowserWindow *bw);
+void a_Nav_expect_done(BrowserWindow *bw);
+int a_Nav_stack_ptr(BrowserWindow *bw);
+int a_Nav_stack_size(BrowserWindow *bw);
+
+void a_Nav_save_url(BrowserWindow *bw,
+ const DilloUrl *url, const char *filename);
+int a_Nav_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __NAV_H__ */
+
+
diff --git a/src/pixmaps.h b/src/pixmaps.h
new file mode 100644
index 00000000..57b05f97
--- /dev/null
+++ b/src/pixmaps.h
@@ -0,0 +1,1652 @@
+/*
+ * File: pixmaps.h
+ *
+ * Copyright (C) 2000, 2001 Jorge Arellano Cid <jcid@dillo.org>
+ * Design by John Grantham, Dipl.-Designer; http://www.grantham.de/
+ *
+ * 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.
+ */
+
+#ifndef __PIXMAPS_H__
+#define __PIXMAPS_H__
+
+/* XPM
+static char * history_xpm[] = {
+"11 20 6 1",
+" c None",
+". c #000000000000",
+"X c #9658A289BEFB",
+"O c #FFFF9A690000",
+"+ c #FFFFFFFF0000",
+"@ c #FFFFFFFFFFFF",
+" ",
+" . ",
+" .X. ",
+" .XXX. ",
+" .XXXXX. ",
+" .XXXXXXX. ",
+" ...XXX... ",
+" .XXX. ",
+" .XXX. ",
+" .XXX. ",
+" OOOOO ",
+" .XXX. ",
+" .XXX. ",
+" ...XXX... ",
+" .XXXXXXX. ",
+" .XXXXX. ",
+" .XXX. ",
+" .X. ",
+" . ",
+" "};
+*/
+/* XPM */
+static char * left_xpm[] = {
+"22 22 46 1",
+" c None",
+". c #000000",
+"+ c #FF7856",
+"@ c #FF8361",
+"# c #FF5325",
+"$ c #FF8C68",
+"% c #FF6636",
+"& c #FF6333",
+"* c #FF936B",
+"= c #FF6E39",
+"- c #FF5111",
+"; c #FF8C61",
+"> c #FFA685",
+", c #FF8659",
+"' c #FF996E",
+") c #FF773D",
+"! c #FF5C16",
+"~ c #FF570F",
+"{ c #EC510E",
+"] c #FF894C",
+"^ c #FF7E3B",
+"/ c #FF681A",
+"( c #FF6414",
+"_ c #EC5D13",
+": c #C56932",
+"< c #CB5A15",
+"[ c #F86E19",
+"} c #FF711A",
+"| c #EC6918",
+"1 c #9F5F31",
+"2 c #C56118",
+"3 c #F87A1E",
+"4 c #FF7D1F",
+"5 c #EC741D",
+"6 c #9F6532",
+"7 c #C56A1C",
+"8 c #F88523",
+"9 c #8C4B14",
+"0 c #582F0C",
+"a c #894A13",
+"b c #9F6B35",
+"c c #C57320",
+"d c #B96C1E",
+"e c #A47339",
+"f c #C77C23",
+"g c #93601C",
+" ",
+" ",
+" ",
+" ",
+" .. ",
+" .+. ",
+" .@#. ",
+" .$%&...... .... ",
+" .*=-=;>>>,. .,,. ",
+" .')!~~~~~~{. .{{. ",
+" .]^/(((((((_. .__. ",
+" .:<[}}}}}}}|. .||. ",
+" .1234444445. .55. ",
+" .67879000a. .aa. ",
+" .bcd...... .... ",
+" .ef. ",
+" .g. ",
+" .. ",
+" .....",
+" .>. ",
+" . ",
+" "};
+/* XPM */
+static char * right_xpm[] = {
+"22 22 46 1",
+" c None",
+". c #000000",
+"+ c #569A59",
+"@ c #228126",
+"# c #60A463",
+"$ c #2E9132",
+"% c #319335",
+"& c #65AE68",
+"* c #52AC55",
+"= c #80C282",
+"- c #5BB15E",
+"; c #319C35",
+"> c #07880C",
+", c #65B568",
+"' c #008905",
+") c #009405",
+"! c #07970C",
+"~ c #31A935",
+"{ c #65BF68",
+"] c #009705",
+"^ c #00A305",
+"/ c #07A60C",
+"( c #2AB22E",
+"_ c #3CB83F",
+": c #00A605",
+"< c #00B305",
+"[ c #00AE05",
+"} c #008E04",
+"| c #219424",
+"1 c #00B405",
+"2 c #00C205",
+"3 c #00BD05",
+"4 c #009604",
+"5 c #218124",
+"6 c #007003",
+"7 c #004802",
+"8 c #007303",
+"9 c #00A104",
+"0 c #00CB05",
+"a c #218924",
+"b c #00A304",
+"c c #00AD04",
+"d c #219024",
+"e c #00B804",
+"f c #219B24",
+"g c #008E03",
+" ",
+" ",
+" ",
+" ",
+" .. ",
+" .+. ",
+" .@#. ",
+" .... ......$%&. ",
+" .**. .*===-;>;,. ",
+" .''. .'))))))!~{. ",
+" .]]. .]^^^^^^^/(_. ",
+" .::. .:<<<<<<<[}|. ",
+" .11. .1222222345. ",
+" .66. .67778909a. ",
+" .... ......bcd. ",
+" .ef. ",
+" .g. ",
+" .. ",
+" .....",
+" .=. ",
+" . ",
+" "};
+/* XPM */
+static char * reload_xpm[] = {
+"22 22 147 2",
+" c None",
+". c #000000",
+"+ c #BABABA",
+"@ c #CCCCCC",
+"# c #CBCBCB",
+"$ c #5D5D5D",
+"% c #909090",
+"& c #9C9C9C",
+"* c #A0A0A0",
+"= c #EBEBEB",
+"- c #4C4C4C",
+"; c #949494",
+"> c #8D8D8D",
+", c #FFFFFF",
+"' c #E4E4E4",
+") c #424242",
+"! c #989898",
+"~ c #A4A4A4",
+"{ c #878787",
+"] c #FBFBFB",
+"^ c #E1E1E1",
+"/ c #393939",
+"( c #9B9B9B",
+"_ c #A8A8A8",
+": c #898989",
+"< c #ECECEC",
+"[ c #DEDEDE",
+"} c #313131",
+"| c #0B0B0B",
+"1 c #0C0C0C",
+"2 c #2B2B2B",
+"3 c #575757",
+"4 c #8B8B8B",
+"5 c #AEAEAE",
+"6 c #8E8E8E",
+"7 c #DDDDDD",
+"8 c #2C2C2C",
+"9 c #282828",
+"0 c #666666",
+"a c #A3A3A3",
+"b c #C0C0C0",
+"c c #7D7D7D",
+"d c #3F3F3F",
+"e c #1D1D1D",
+"f c #6B6B6B",
+"g c #929292",
+"h c #585858",
+"i c #BEBEBE",
+"j c #4F4F4F",
+"k c #A5A5A5",
+"l c #BDBDBD",
+"m c #B1B1B1",
+"n c #A9A9A9",
+"o c #AAAAAA",
+"p c #696969",
+"q c #0A0A0A",
+"r c #5F5F5F",
+"s c #515151",
+"t c #545454",
+"u c #686868",
+"v c #868686",
+"w c #B5B5B5",
+"x c #B4B4B4",
+"y c #7F907F",
+"z c #436B43",
+"A c #144F14",
+"B c #004800",
+"C c #1E1E1E",
+"D c #838383",
+"E c #9F9F9F",
+"F c #262626",
+"G c #9D9D9D",
+"H c #B8B8B8",
+"I c #839583",
+"J c #577A57",
+"K c #7E917E",
+"L c #A7AAA7",
+"M c #426D42",
+"N c #004600",
+"O c #A1A1A1",
+"P c #404040",
+"Q c #636363",
+"R c #C6C6C6",
+"S c #B7B7B7",
+"T c #728C72",
+"U c #B0B2B0",
+"V c #777777",
+"W c #323232",
+"X c #C2C2C2",
+"Y c #BCBCBC",
+"Z c #8EA18E",
+"` c #004B00",
+" . c #979797",
+".. c #0F0F0F",
+"+. c #D2D2D2",
+"@. c #AFAFAF",
+"#. c #C4C4C4",
+"$. c #B0B0B0",
+"%. c #D9D9D9",
+"&. c #C9C9C9",
+"*. c #7A5200",
+"=. c #B8AE9A",
+"-. c #101010",
+";. c #DFDFDF",
+">. c #CECECE",
+",. c #BFBFBF",
+"'. c #D4D4D4",
+"). c #7F5500",
+"!. c #D2D1CE",
+"~. c #B4A585",
+"{. c #E5E5E5",
+"]. c #845800",
+"^. c #A58A53",
+"/. c #C3B79F",
+"(. c #D9D7D3",
+"_. c #B09A6E",
+":. c #C6BBA5",
+"<. c #747474",
+"[. c #3A3A3A",
+"}. c #EAEAEA",
+"|. c #222222",
+"1. c #865A00",
+"2. c #CCC1AA",
+"3. c #AC915B",
+"4. c #936C1C",
+"5. c #E0E0E0",
+"6. c #414141",
+"7. c #787878",
+"8. c #F0F0F0",
+"9. c #8C8C8C",
+"0. c #C3C3C3",
+"a. c #E2E2E2",
+"b. c #F4F4F4",
+"c. c #646464",
+"d. c #9E9E9E",
+"e. c #DCDCDC",
+"f. c #6A6A6A",
+"g. c #555555",
+"h. c #0E0E0E",
+"i. c #F8F8F8",
+"j. c #E6E6E6",
+"k. c #191919",
+"l. c #2F2F2F",
+"m. c #464646",
+"n. c #444444",
+"o. c #242424",
+"p. c #343434",
+" . . . . . . . . . . . ",
+" . + @ @ @ @ @ @ @ # $ . ",
+" . % & & & & & & & * = - . ",
+" . ; * * * * * * * > , ' ) . ",
+" . ! ~ ~ ~ ~ ~ ~ ~ { ] , ^ / . ",
+" . ( _ _ _ _ _ _ _ : < , , [ } . ",
+" . | 1 1 1 2 3 4 5 6 < , , , 7 8 . ",
+" . 9 0 a b b a c d e f g : h h f * i . ",
+" . j k l m n n m l o p q r f s s t u v . ",
+" . - w x y z A A z y B w u C D & & & E * . ",
+" F G H I J K L K M N N H O P Q R R R R S . ",
+". $ i S T U w w w B B B S i V W @ @ @ @ l . ",
+". 6 X Y Z Y Y Y ` ` ` ` Y X ...+.+.+.+.X . ",
+". @.R #.#.#.#.#.#.#.#.#.#.R $...%.%.%.%.&.. ",
+". n #.@ *.*.*.*.@ @ @ =.@ #.o -.;.;.;.;.>.. ",
+". v ,.'.).).).'.'.'.!.~.'.,.% -.{.{.{.{.'.. ",
+". 3 w '.].].^./.(./._.:.'.w <.[.}.}.}.}.%.. ",
+" |.{ X 1.2.3.4.4.3.2.5.X > 6.7.8.8.8.8.[ . ",
+" . [.9.0.a.}.}.}.}.a.0.> $ 9 X b.b.b.b.a.. ",
+" . W c.d.R e.e.R d.f.g.h.; i.i.i.i.i.j.. ",
+" . k.l.m.j j n.[.o.h.p.3 3 3 3 3 3 { . ",
+" . . . . . . . . . . . . . . . . . "};
+/* XPM */
+static char * home_xpm[] = {
+"22 22 106 2",
+" c None",
+". c #190E0B",
+"+ c #180D09",
+"@ c #D8947D",
+"# c #000000",
+"$ c #190D09",
+"% c #CC6746",
+"& c #A7401F",
+"* c #C65834",
+"= c #1A0D09",
+"- c #D16746",
+"; c #A63F1F",
+"> c #160D09",
+", c #C13206",
+"' c #D86746",
+") c #AD3F1F",
+"! c #1C0F0A",
+"~ c #9B9376",
+"{ c #D2502A",
+"] c #1B0D09",
+"^ c #DF6746",
+"/ c #B43F1F",
+"( c #999174",
+"_ c #C1B588",
+": c #D34721",
+"< c #D8461E",
+"[ c #1C0D09",
+"} c #E76746",
+"| c #BB3F1F",
+"1 c #1D0F0A",
+"2 c #C5B98B",
+"3 c #C4B889",
+"4 c #93371F",
+"5 c #E14821",
+"6 c #1B0803",
+"7 c #1D0D09",
+"8 c #EE6746",
+"9 c #C33F1F",
+"0 c #1E0F0A",
+"a c #9E9678",
+"b c #CABD8F",
+"c c #CCBF8E",
+"d c #CFC290",
+"e c #1E0D09",
+"f c #F56746",
+"g c #CA3F1F",
+"h c #21110A",
+"i c #A09879",
+"j c #CDC091",
+"k c #D2C593",
+"l c #D8CA97",
+"m c #DCCE9A",
+"n c #1D0500",
+"o c #C63F21",
+"p c #9E361F",
+"q c #130300",
+"r c #988E6A",
+"s c #D1C492",
+"t c #D5C895",
+"u c #DDCF9A",
+"v c #E4D69F",
+"w c #E9DAA3",
+"x c #C9BC8C",
+"y c #DACC98",
+"z c #E0D29C",
+"A c #E8D9A2",
+"B c #EFE0A7",
+"C c #F2E3A9",
+"D c #5E5842",
+"E c #676047",
+"F c #F9E9AE",
+"G c #655F46",
+"H c #DBCD99",
+"I c #C16C4E",
+"J c #D3927C",
+"K c #C86F51",
+"L c #FCECB0",
+"M c #D6D6D6",
+"N c #D2D2D2",
+"O c #E3D49F",
+"P c #A82900",
+"Q c #B82C00",
+"R c #AB2A00",
+"S c #FEEEB1",
+"T c #6F6D6D",
+"U c #6D6B6B",
+"V c #E7D9A2",
+"W c #B52A00",
+"X c #C42D00",
+"Y c #B62A00",
+"Z c #FFEFB2",
+"` c #696249",
+" . c #676048",
+".. c #EADBA4",
+"+. c #C12A00",
+"@. c #D22D00",
+"#. c #C22A00",
+"$. c #ECDDA5",
+"%. c #CE2A00",
+"&. c #DF2D00",
+"*. c #DA2A00",
+"=. c #EC2D00",
+"-. c #898060",
+";. c #851800",
+">. c #551000",
+",. c #58523D",
+" ",
+" . . ",
+" + @ @ + # # # ",
+" $ % & & % $ # * # ",
+" = - ; > > ; - = # , # ",
+" = ' ) ! ~ ~ ! ) ' = { # ",
+" ] ^ / ! ( _ _ ( ! / : < # ",
+" [ } | 1 ~ 2 3 3 2 ~ 1 4 5 6 ",
+" 7 8 9 0 a b c d d c b a 0 9 8 7 ",
+" e f g h i j k l m m l k j i h g f e ",
+" n o p q r s t u v w w v u t s r q p o n ",
+" # # # # x y z A B C C B A z y x # # # # ",
+" # s D # # # E F G # # D s # ",
+" # H # I J K # L # M N # H # ",
+" # O # P Q R # S # T U # O # ",
+" # V # W X Y # Z ` # # .V # ",
+" # ..# +.@.#.# Z Z Z S S ..# ",
+" # $.# %.&.%.# Z Z Z Z Z $.# ",
+" # $.# *.=.*.# Z Z Z Z Z $.# ",
+" # -.# ;.>.;.# ,.,.,.,.,.-.# ",
+" # # # # # # # # # # # # # # ",
+" "};
+/* XPM */
+static char * save_xpm[] = {
+"22 22 59 1",
+" c None",
+". c #000000",
+"+ c #638163",
+"@ c #4D5A4D",
+"# c #A4BDA4",
+"$ c #184318",
+"% c #0E280E",
+"& c #4B7E4B",
+"* c #194419",
+"= c #0F290F",
+"- c #508350",
+"; c #1B481B",
+"> c #102B10",
+", c #568956",
+"' c #1D4C1D",
+") c #112D11",
+"! c #5D905D",
+"~ c #205020",
+"{ c #133013",
+"] c #639663",
+"^ c #235223",
+"/ c #153115",
+"( c #6B9E6B",
+"_ c #275627",
+": c #173317",
+"< c #73A673",
+"[ c #2B5A2B",
+"} c #2E612E",
+"| c #193619",
+"1 c #2E5D2E",
+"2 c #152A15",
+"3 c #326132",
+"4 c #838383",
+"5 c #9D9D9D",
+"6 c #B3B3B3",
+"7 c #979797",
+"8 c #366536",
+"9 c #585858",
+"0 c #565656",
+"a c #727272",
+"b c #6A6A6A",
+"c c #386838",
+"d c #848484",
+"e c #7A7A7A",
+"f c #3C6B3C",
+"g c #999999",
+"h c #8E8E8E",
+"i c #366036",
+"j c #3F6E3F",
+"k c #BDBDBD",
+"l c #BABABA",
+"m c #ADADAD",
+"n c #A0A0A0",
+"o c #162616",
+"p c #264126",
+"q c #949494",
+"r c #696969",
+"s c #424242",
+"t c #676767",
+" ",
+" ",
+" ................ ",
+" .+@############@+. ",
+" .$%&&&&&&&&&&&&%$. ",
+" .*=------------=*. ",
+" .;>,,,,,,,,,,,,>;. ",
+" .')!!!!!!!!!!!!)'. ",
+" .~{]]]]]]]]]]]]{~. ",
+" .^/((((((((((((/^. ",
+" ._:<<<<<<<<<<<<:_. ",
+" .[}||||||||||||}[. ",
+" .12............21. ",
+" .3.444566666667.3. ",
+" .8.9..0aaaaaaab.8. ",
+" .c.d..eddddddde.c. ",
+" .f.g..hgggggggh.i. ",
+" .j.k..lmmmmmmmn.o. ",
+" .p.qqqrssssssst.. ",
+" ............... ",
+" ",
+" "};
+
+/* XPM */
+static char * stop_xpm[] = {
+"22 22 77 1",
+" c None",
+". c #000000",
+"+ c #703434",
+"@ c #E57F7F",
+"# c #E68080",
+"$ c #6F2929",
+"% c #DB4343",
+"& c #D31A1A",
+"* c #CE0000",
+"= c #DC4343",
+"- c #D51A1A",
+"; c #D00000",
+"> c #702929",
+", c #DF4343",
+"' c #D71A1A",
+") c #D30000",
+"! c #6F1B1B",
+"~ c #E14343",
+"{ c #DA1A1A",
+"] c #D60000",
+"^ c #A66666",
+"/ c #841717",
+"( c #DA0404",
+"_ c #DD1A1A",
+": c #D90000",
+"< c #D56B6B",
+"[ c #FFFFFF",
+"} c #E7D5D5",
+"| c #861717",
+"1 c #CC0000",
+"2 c #DC0000",
+"3 c #E64C4C",
+"4 c #FADEDE",
+"5 c #EAD5D5",
+"6 c #722C2C",
+"7 c #CF0000",
+"8 c #E00000",
+"9 c #EB5C5C",
+"0 c #FBDFDF",
+"a c #FDF8F8",
+"b c #E40000",
+"c c #DE3535",
+"d c #FEF8F8",
+"e c #E70000",
+"f c #971C1C",
+"g c #EED5D5",
+"h c #FEFAFA",
+"i c #EB0000",
+"j c #AA1C1C",
+"k c #EFD5D5",
+"l c #FDE3E3",
+"m c #F69595",
+"n c #C50000",
+"o c #D50000",
+"p c #EF0000",
+"q c #F77777",
+"r c #FDE4E4",
+"s c #F56565",
+"t c #4D0000",
+"u c #9E0000",
+"v c #D70000",
+"w c #F20000",
+"x c #FAA7A7",
+"y c #F76969",
+"z c #420000",
+"A c #A00000",
+"B c #DA0000",
+"C c #F50000",
+"D c #A20000",
+"E c #DD0000",
+"F c #F80000",
+"G c #430000",
+"H c #A40000",
+"I c #DF0000",
+"J c #FB0000",
+"K c #380000",
+"L c #570000",
+" ",
+" ",
+" ......... ",
+" .+@#####@+. ",
+" .$%&*****&%$. ",
+" .$=-;;;;;;;-=$. ",
+" .>,')))))))))',>. ",
+" .!~{]]^/]]]/^]]{~!. ",
+" .(_::<[}|:|}[<::_(. ",
+" .122234[565[432221. ",
+" .7888890[a[0988887. ",
+" .)bbbbbcd[dcbbbbb). ",
+" .]eeeefg[h[gfeeee]. ",
+" .:iiijk[lml[kjiii:. ",
+" .noppq[rspsr[qppon. ",
+" .tuvwwxywwwyxwwvut. ",
+" .zABCCCCCCCCCBAz. ",
+" .zDEFFFFFFFEDz. ",
+" .GHIJJJJJIHG. ",
+" .KLLLLLLLK. ",
+" ......... ",
+" "};
+/* XPM */
+static char * bm_xpm[] = {
+"22 22 86 1",
+" c None",
+". c #000000",
+"+ c #150B0A",
+"@ c #A46C6A",
+"# c #B15652",
+"$ c #C68380",
+"% c #840600",
+"& c #8F0600",
+"* c #979797",
+"= c #B3B3B3",
+"- c #880600",
+"; c #930600",
+"> c #616161",
+", c #696969",
+"' c #595959",
+") c #8D0600",
+"! c #980600",
+"~ c #666666",
+"{ c #6E6E6E",
+"] c #5E5E5E",
+"^ c #920600",
+"/ c #9E0700",
+"( c #6B6B6B",
+"_ c #747474",
+": c #636363",
+"< c #970600",
+"[ c #A30700",
+"} c #727272",
+"| c #7B7B7B",
+"1 c #9D0600",
+"2 c #AA0700",
+"3 c #777777",
+"4 c #818181",
+"5 c #A40600",
+"6 c #B10700",
+"7 c #7F7F7F",
+"8 c #898989",
+"9 c #757575",
+"0 c #A90700",
+"a c #B70800",
+"b c #868686",
+"c c #919191",
+"d c #7C7C7C",
+"e c #BF0800",
+"f c #8E8E8E",
+"g c #999999",
+"h c #828282",
+"i c #C60900",
+"j c #959595",
+"k c #A1A1A1",
+"l c #BD0800",
+"m c #CC0900",
+"n c #9C9C9C",
+"o c #A9A9A9",
+"p c #909090",
+"q c #C40800",
+"r c #D40900",
+"s c #A3A3A3",
+"t c #B0B0B0",
+"u c #969696",
+"v c #CA0900",
+"w c #DA0A00",
+"x c #B7B7B7",
+"y c #D00900",
+"z c #E10A00",
+"A c #AFAFAF",
+"B c #BDBDBD",
+"C c #D70900",
+"D c #E80A00",
+"E c #B4B4B4",
+"F c #C3C3C3",
+"G c #A6A6A6",
+"H c #DB0900",
+"I c #E10900",
+"J c #454545",
+"K c #CD0900",
+"L c #A00700",
+"M c #7B1510",
+"N c #A40700",
+"O c #A20700",
+"P c #3E0300",
+"Q c #4A0300",
+"R c #A90800",
+"S c #540400",
+"T c #010000",
+"U c #660400",
+" ...... ",
+" +@#$$$#. ",
+" .......%&&&%.... ",
+" .*====.-;;;-.=*. ",
+" .>,',,.)!!!).,>. ",
+" .~{]{{.^///^.{~. ",
+" .(_:__.<[[[<._(. ",
+" .}|,||.12221.|}. ",
+" .34{44.56665.43. ",
+" .78988.0aaa0.87. ",
+" .bcdcc.6eee6.cb. ",
+" .fghgg.aiiia.gf. ",
+" .jk8kk.lmmml.kj. ",
+" .nopoo.qrrrq.on. ",
+" .stutt.vwwwv.ts. ",
+" .oxnxx.yzzzy.xo. ",
+" .ABkBB.CDDDC.BA. ",
+" .EFGFF.HIIIH.FE. ",
+" .(JJJJ.KLMNy.J(. ",
+" .......OP.QR.... ",
+" .S. TU. ",
+" . . "};
+
+/* Small icons here */
+
+/* XPM */
+static char * left_s_xpm[] = {
+"16 16 33 1",
+" c None",
+". c #000000",
+"+ c #1F120A",
+"@ c #FF9A59",
+"# c #DE7E42",
+"$ c #FF7A26",
+"% c #DE8247",
+"& c #FF8335",
+"* c #FF8232",
+"= c #FF6A0C",
+"- c #FF9D5E",
+"; c #FFB382",
+"> c #FF9755",
+", c #FF6605",
+"' c #EC5E05",
+") c #1F0F05",
+"! c #DE691E",
+"~ c #FF7F2E",
+"{ c #180A00",
+"] c #A44103",
+"^ c #CB5104",
+"/ c #F86305",
+"( c #120700",
+"_ c #7E3203",
+": c #C54F04",
+"< c #8C3803",
+"[ c #582302",
+"} c #893703",
+"| c #B94A04",
+"1 c #833403",
+"2 c #C75004",
+"3 c #110700",
+"4 c #933B03",
+" ",
+" .. ",
+" +@. ",
+" +#$. ",
+" +%&*.... ....",
+" +%&=&-;>. .>>.",
+" +%&=,,,,'. .''.",
+")!~=,,,,,'. .''.",
+"{]^/,,,,,'. .''.",
+" (_:/,,,,'. .''.",
+" (_:/:<[}. .}}.",
+" (_:|.... ....",
+" (12. ",
+" 34. .....",
+" .. .;. ",
+" . "};
+/* XPM */
+static char * right_s_xpm[] = {
+"16 16 56 1",
+" c None",
+". c #000000",
+"+ c #569A59",
+"@ c #0A120A",
+"# c #228126",
+"$ c #3F8342",
+"% c #09130A",
+"& c #2E9132",
+"* c #319335",
+"= c #448D47",
+"- c #09140A",
+"; c #52AC55",
+"> c #80C282",
+", c #5BB15E",
+"' c #319C35",
+") c #07880C",
+"! c #449447",
+"~ c #09150A",
+"{ c #008905",
+"] c #009405",
+"^ c #07970C",
+"/ c #31A935",
+"( c #449E47",
+"_ c #09160A",
+": c #009705",
+"< c #00A305",
+"[ c #07A60C",
+"} c #2AB22E",
+"| c #1B971E",
+"1 c #041505",
+"2 c #00A605",
+"3 c #00B305",
+"4 c #00AE05",
+"5 c #008E04",
+"6 c #007303",
+"7 c #001100",
+"8 c #00B405",
+"9 c #00C205",
+"0 c #00BD05",
+"a c #009604",
+"b c #006003",
+"c c #000D00",
+"d c #007003",
+"e c #004802",
+"f c #00A104",
+"g c #00CB05",
+"h c #006803",
+"i c #000E00",
+"j c #00A304",
+"k c #00AD04",
+"l c #006F03",
+"m c #000F00",
+"n c #00B804",
+"o c #007A03",
+"p c #001000",
+"q c #008E03",
+" ",
+" .. ",
+" .+@ ",
+" .#$% ",
+".... ....&*=- ",
+".;;. .;>,')'!~ ",
+".{{. .{]]]]^/(_ ",
+".::. .:<<<<<[}|1",
+".22. .2333334567",
+".88. .899990abc ",
+".dd. .de6fgfhi ",
+".... ....jklm ",
+" .nop ",
+" .qpb....",
+" .. .>. ",
+" . "};
+/* XPM */
+static char * home_s_xpm[] = {
+"16 16 54 1",
+" c None",
+". c #170E0B",
+"+ c #000000",
+"@ c #170C09",
+"# c #B0705B",
+"$ c #B35636",
+"% c #A13F20",
+"& c #661B03",
+"* c #AA3009",
+"= c #190C09",
+"- c #AC3F20",
+"; c #721C03",
+"> c #8E8875",
+", c #C24F2D",
+"' c #1A0C09",
+") c #B93F20",
+"! c #7C1C03",
+"~ c #8F876F",
+"{ c #A9A081",
+"] c #A72403",
+"^ c #CE4522",
+"/ c #1B0C09",
+"( c #C63F20",
+"_ c #861C03",
+": c #A9A080",
+"< c #FDEDB3",
+"[ c #631503",
+"} c #CC2903",
+"| c #1B0703",
+"1 c #1D0C08",
+"2 c #D03E20",
+"3 c #871902",
+"4 c #847C67",
+"5 c #FFEFB2",
+"6 c #847C68",
+"7 c #851902",
+"8 c #CF3A1B",
+"9 c #A81E02",
+"0 c #721402",
+"a c #9A9070",
+"b c #8B8163",
+"c c #9C926D",
+"d c #FDEDB1",
+"e c #EADBA4",
+"f c #696249",
+"g c #ECDDA5",
+"h c #C25834",
+"i c #FFFFFF",
+"j c #BE2D00",
+"k c #BF0000",
+"l c #898060",
+"m c #58523D",
+"n c #D90000",
+"o c #BB0000",
+" ",
+" .. +++ ",
+" @##@ +$+ ",
+" @%&&%@+*+ ",
+" =-;>>;-=,+ ",
+" ')!~{{~!]^+ ",
+" /(_~:<<:~[}| ",
+" 1234:<55<:6781 ",
+"+90a{<5555<:b09+",
+"+++cd555555dc+++",
+" +e5f+f5f+fe+ ",
+" +g5+h+5+i+g+ ",
+" +g5+j+5f+fg+ ",
+" +g5+k+5555g+ ",
+" +lm+n+mmmml+ ",
+" ++++o+++++++ "};
+/* XPM */
+static char * reload_s_xpm[] = {
+"16 16 74 1",
+" c None",
+". c #000000",
+"+ c #282828",
+"@ c #666666",
+"# c #9B9B9B",
+"$ c #BFBFBF",
+"% c #4F4F4F",
+"& c #A5A5A5",
+"* c #BDBDBD",
+"= c #B1B1B1",
+"- c #A9A9A9",
+"; c #4C4C4C",
+"> c #B5B5B5",
+", c #B4B4B4",
+"' c #7F907F",
+") c #436B43",
+"! c #144F14",
+"~ c #004800",
+"{ c #262626",
+"] c #9D9D9D",
+"^ c #B8B8B8",
+"/ c #839583",
+"( c #577A57",
+"_ c #7E917E",
+": c #A7AAA7",
+"< c #426D42",
+"[ c #004600",
+"} c #5D5D5D",
+"| c #BEBEBE",
+"1 c #B7B7B7",
+"2 c #728C72",
+"3 c #B0B2B0",
+"4 c #8E8E8E",
+"5 c #C2C2C2",
+"6 c #BCBCBC",
+"7 c #8EA18E",
+"8 c #004B00",
+"9 c #AFAFAF",
+"0 c #C6C6C6",
+"a c #C4C4C4",
+"b c #CCCCCC",
+"c c #7A5200",
+"d c #B8AE9A",
+"e c #868686",
+"f c #D4D4D4",
+"g c #7F5500",
+"h c #D2D1CE",
+"i c #B4A585",
+"j c #575757",
+"k c #845800",
+"l c #A58A53",
+"m c #C3B79F",
+"n c #D9D7D3",
+"o c #B09A6E",
+"p c #C6BBA5",
+"q c #222222",
+"r c #878787",
+"s c #865A00",
+"t c #CCC1AA",
+"u c #AC915B",
+"v c #936C1C",
+"w c #E0E0E0",
+"x c #3A3A3A",
+"y c #8C8C8C",
+"z c #C3C3C3",
+"A c #E2E2E2",
+"B c #EAEAEA",
+"C c #323232",
+"D c #646464",
+"E c #9E9E9E",
+"F c #DCDCDC",
+"G c #191919",
+"H c #2F2F2F",
+"I c #404040",
+" ...... ",
+" .+@#$$#@+. ",
+" .%&*=--=*&%. ",
+" .;>,')!!)'~>;. ",
+" {]^/(_:_<[[^]{ ",
+".}|123>>>~~~1|}.",
+".45676668888654.",
+".90aaaaaaaaaa09.",
+".-abccccbbbdba-.",
+".e$fgggfffhif$e.",
+".j>fkklmnmopf>j.",
+" qr5stuvvutw5rq ",
+" .xyzABBBBAzyx. ",
+" .CDE0FF0EDC. ",
+" .GHI%%IHG. ",
+" ...... "};
+/* XPM */
+static char * save_s_xpm[] = {
+"16 16 51 1",
+" c None",
+". c #000000",
+"+ c #638163",
+"@ c #8CA28C",
+"# c #A4BDA4",
+"$ c #184318",
+"% c #1A481A",
+"& c #4C7F4C",
+"* c #1A461A",
+"= c #1C4C1C",
+"- c #528552",
+"; c #1B4A1B",
+"> c #1D501D",
+", c #598C59",
+"' c #1F4E1F",
+") c #215421",
+"! c #619461",
+"~ c #225122",
+"{ c #255825",
+"] c #699C69",
+"^ c #275627",
+"/ c #2A5D2A",
+"( c #72A572",
+"_ c #2B5A2B",
+": c #2E612E",
+"< c #2F5E2F",
+"[ c #152A15",
+"} c #336233",
+"| c #838383",
+"1 c #9D9D9D",
+"2 c #B3B3B3",
+"3 c #979797",
+"4 c #376637",
+"5 c #5A5A5A",
+"6 c #585858",
+"7 c #757575",
+"8 c #6C6C6C",
+"9 c #3A6A3A",
+"0 c #8C8C8C",
+"a c #828282",
+"b c #345E34",
+"c c #3E6D3E",
+"d c #B6B6B6",
+"e c #A5A5A5",
+"f c #999999",
+"g c #162616",
+"h c #264126",
+"i c #919191",
+"j c #676767",
+"k c #414141",
+"l c #656565",
+"................",
+".+@##########@+.",
+".$%&&&&&&&&&&%$.",
+".*=----------=*.",
+".;>,,,,,,,,,,>;.",
+".')!!!!!!!!!!)'.",
+".~{]]]]]]]]]]{~.",
+".^/((((((((((/^.",
+"._::::::::::::_.",
+".<[..........[<.",
+".}.|||1222223.}.",
+".4.5..6777778.4.",
+".9.0..a00000a.b.",
+".c.d..2eeeeef.g.",
+".h.iiijkkkkkl.. ",
+" ............. "};
+/* XPM */
+static char * stop_s_xpm[] = {
+"16 16 65 1",
+" c None",
+". c #000000",
+"+ c #5A3435",
+"@ c #BF7F80",
+"# c #BF8081",
+"$ c #562929",
+"% c #A54344",
+"& c #911A1C",
+"* c #850002",
+"= c #582929",
+"- c #AA4344",
+"; c #981A1C",
+"> c #8C0002",
+", c #551B1C",
+"' c #B04345",
+") c #9F1A1D",
+"! c #940003",
+"~ c #926667",
+"{ c #661C1E",
+"] c #A00407",
+"^ c #A81A1D",
+"/ c #9E0003",
+"( c #B86B6D",
+"_ c #FFFFFF",
+": c #E2D5D5",
+"< c #581C1D",
+"[ c #9C0003",
+"} c #A90003",
+"| c #C24C4E",
+"1 c #F4DDDE",
+"2 c #E9DADA",
+"3 c #A70003",
+"4 c #B40003",
+"5 c #C4383A",
+"6 c #F5DEDE",
+"7 c #B10004",
+"8 c #BF0004",
+"9 c #9A1C1F",
+"0 c #F0D9D9",
+"a c #BB0004",
+"b c #CA0004",
+"c c #971C1F",
+"d c #ECD5D5",
+"e c #FAE6E6",
+"f c #AF0003",
+"g c #BE0004",
+"h c #D50004",
+"i c #E87779",
+"j c #FBE5E5",
+"k c #EA7E80",
+"l c #470001",
+"m c #930003",
+"n c #C70004",
+"o c #E00004",
+"p c #F5A7A9",
+"q c #EC6568",
+"r c #3F0001",
+"s c #990003",
+"t c #D00004",
+"u c #EA0004",
+"v c #410001",
+"w c #D70004",
+"x c #F20005",
+"y c #370001",
+"z c #560002",
+" ........ ",
+" .+@####@+. ",
+" .$%&****&%$. ",
+" .=-;>>>>>>;-=. ",
+".,')!~{!!{~!)',.",
+".]^/(_:<<:_(/^].",
+".[}}|1_22_1|}}[.",
+".344456__654443.",
+".788890__098887.",
+".abbcd_ee_dcbba.",
+".fghi_jkkj_ihgf.",
+".lmnopqooqponml.",
+" .rstuuuuuutsr. ",
+" .v/wxxxxw/v. ",
+" .yzzzzzzy. ",
+" ........ "};
+
+/* XPM */
+static char * bm_s_xpm[] = {
+"16 16 63 1",
+" c None",
+". c #000000",
+"+ c #B15652",
+"@ c #C68380",
+"# c #979797",
+"$ c #B3B3B3",
+"% c #999999",
+"& c #860600",
+"* c #910600",
+"= c #646464",
+"- c #6C6C6C",
+"; c #5C5C5C",
+"> c #8B0600",
+", c #960600",
+"' c #6A6A6A",
+") c #737373",
+"! c #626262",
+"~ c #920600",
+"{ c #9E0700",
+"] c #747474",
+"^ c #7D7D7D",
+"/ c #990600",
+"( c #A50700",
+"_ c #7E7E7E",
+": c #888888",
+"< c #A10600",
+"[ c #AE0700",
+"} c #939393",
+"| c #A90700",
+"1 c #B70800",
+"2 c #9F9F9F",
+"3 c #B30700",
+"4 c #C10800",
+"5 c #9D9D9D",
+"6 c #AAAAAA",
+"7 c #919191",
+"8 c #BB0800",
+"9 c #CA0900",
+"0 c #A8A8A8",
+"a c #B5B5B5",
+"b c #9A9A9A",
+"c c #C40800",
+"d c #D40900",
+"e c #B0B0B0",
+"f c #BEBEBE",
+"g c #A2A2A2",
+"h c #CD0900",
+"i c #DD0A00",
+"j c #444444",
+"k c #3A3A3A",
+"l c #D50900",
+"m c #DA0900",
+"n c #C80800",
+"o c #9C0700",
+"p c #680500",
+"q c #A00700",
+"r c #CB0900",
+"s c #3D0300",
+"t c #490300",
+"u c #A70800",
+"v c #540400",
+"w c #010000",
+"x c #660400",
+" ....... ",
+" ......+@@@+... ",
+" .#$%$.&***&.#. ",
+" .=-;-.>,,,>.=. ",
+" .')!).~{{{~.'. ",
+" .]^'^./(((/.]. ",
+" ._:]:.<[[[<._. ",
+" .:}^}.|111|.:. ",
+" .}2:2.34443.}. ",
+" .5676.89998.5. ",
+" .0aba.cdddc.0. ",
+" .efgf.hiiih.e. ",
+" .'jkj.lmmml.'. ",
+" ......nopqr... ",
+" .qs.tu. ",
+" .v. wx. "};
+
+/* XPM */
+static char * new_s_xpm[] = {
+"11 11 35 1",
+" c None",
+". c #000000",
+"+ c #482929",
+"@ c #0F0808",
+"# c #390606",
+"$ c #A04242",
+"% c #925050",
+"& c #0F0707",
+"* c #0B0000",
+"= c #500000",
+"- c #8C0101",
+"; c #944444",
+"> c #221515",
+", c #0A0000",
+"' c #560000",
+") c #A10909",
+"! c #AE3636",
+"~ c #230000",
+"{ c #AE0000",
+"] c #B40000",
+"^ c #180808",
+"/ c #B43535",
+"( c #C40000",
+"_ c #960000",
+": c #190606",
+"< c #C02B2B",
+"[ c #E00000",
+"} c #6B0000",
+"| c #120000",
+"1 c #590000",
+"2 c #A30000",
+"3 c #680000",
+"4 c #0F0000",
+"5 c #350000",
+"6 c #100000",
+" . . ",
+" .+@ @+. ",
+".#$%& &%$#.",
+" *=-;>;-=* ",
+" ,')!)', ",
+" ~{]{~ ",
+" ^/(_(/^ ",
+" :<[}|}[<: ",
+".1234 4321.",
+" .56 65. ",
+" . . "};
+
+/* XPM */
+static char * search_xpm[] = {
+"14 16 11 1",
+" c None",
+". c #000000",
+"+ c #EEEEEE",
+"[ c #EE0000",
+"} c #CC0000",
+"| c #BB0000",
+"1 c #AA0000",
+"2 c #880000",
+"3 c #660000",
+"4 c #440000",
+"5 c #330000",
+" ..... ",
+" . . ",
+" . + . ",
+". +++ . ",
+". + . ",
+". . ",
+". . ",
+" . . ",
+" . . ",
+" ...545 ",
+" 424 ",
+" 313 ",
+" 2|2 ",
+" 2}2 ",
+" 2}2 ",
+" 11 "};
+
+/* XPM */
+static char * full_screen_on_xpm[] = {
+"13 15 2 1",
+" c None",
+". c #000000",
+" ",
+".............",
+". . .",
+". ... .",
+". ..... .",
+". ....... .",
+". . .",
+". . .",
+". . .",
+". ....... .",
+". ..... .",
+". ... .",
+". . .",
+".............",
+" "};
+
+/* XPM */
+static char * full_screen_off_xpm[] = {
+"13 15 2 1",
+" c None",
+". c #000000",
+" ",
+".............",
+". . . . . . .",
+".. . . . . ..",
+".............",
+". .",
+". .",
+". .",
+". .",
+". .",
+". .",
+".............",
+". . . . . . .",
+".............",
+" "};
+
+/* XPM */
+static char * mini_bug_xpm[] = {
+"15 16 6 1",
+" c None",
+". c #000000000000",
+"X c #BEFBC30BBEFB",
+"o c #861782078617",
+"O c #FFFFFFFFFFFF",
+"+ c #30C230C230C2",
+" ",
+" . . ",
+" ... ",
+" X.....X ",
+" o.O...o ",
+" o.O...o.o ",
+" ..OoXo... ",
+" .....X..... ",
+" ....X.... ",
+" .o...X...o. ",
+" ...X... ",
+" .X..X..X. ",
+" .o. ",
+" .+++.",
+" .o. ",
+" . "};
+
+/* XPM */
+static char * mini_ok_xpm[] = {
+"15 15 5 1",
+"@ c #000000",
+"a c #808080",
+"b c #303030",
+"c c #606060",
+" s none m none c none",
+" ",
+" ",
+" @ ",
+" @@ ",
+" @@@ ",
+" @@@ ",
+" @@ @@@ ",
+" @@@ @@@ ",
+" @@@ @@@ ",
+" @@@@@@ ",
+" @@@@@ ",
+" @@@@ ",
+" @@ @bbb@",
+" @a@ ",
+" @ "
+};
+
+/* XPM */
+static char *left_i_xpm[] = {
+"22 22 3 1",
+" c None",
+". c #000000",
+"@ c gray70",
+" ",
+" ",
+" ",
+" ",
+" @@ ",
+" @@@ ",
+" @@@@ ",
+" @@@@@@@@@@ @@@@ ",
+" @@@@@@@@@@@ @@@@ ",
+" @@@@@@@@@@@@ @@@@ ",
+" @@@@@@@@@@@@@ @@@@ ",
+" @@@@@@@@@@@@@ @@@@ ",
+" @@@@@@@@@@@@ @@@@ ",
+" @@@@@@@@@@@ @@@@ ",
+" @@@@@@@@@@ @@@@ ",
+" @@@@ ",
+" @@@ ",
+" @@ ",
+" @@@@@",
+" @ @ ",
+" @ ",
+" "};
+
+/* XPM */
+static char *right_i_xpm[] = {
+"22 22 3 1",
+" c None",
+". c #000000",
+"@ c gray70",
+" ",
+" ",
+" ",
+" ",
+" @@ ",
+" @@@ ",
+" @@@@ ",
+" @@@@ @@@@@@@@@@ ",
+" @@@@ @@@@@@@@@@@ ",
+" @@@@ @@@@@@@@@@@@ ",
+" @@@@ @@@@@@@@@@@@@ ",
+" @@@@ @@@@@@@@@@@@@ ",
+" @@@@ @@@@@@@@@@@@ ",
+" @@@@ @@@@@@@@@@@ ",
+" @@@@ @@@@@@@@@@ ",
+" @@@@ ",
+" @@@ ",
+" @@ ",
+" @@@@@",
+" @ @ ",
+" @ ",
+" "};
+
+/* XPM */
+static char *stop_i_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"22 22 2 1",
+" c None",
+"@ c gray70",
+/* pixels */
+" ",
+" ",
+" @@@@@@@@@ ",
+" @@@@@@@@@@@ ",
+" @@@@@@@@@@@@@ ",
+" @@@@@@@@@@@@@@@ ",
+" @@@@@@@@@@@@@@@@@ ",
+" @@@@@@@@@@@@@@@@@@@ ",
+" @@@@@@ @@@ @@@@@@ ",
+" @@@@@@ @ @@@@@@ ",
+" @@@@@@@ @@@@@@@ ",
+" @@@@@@@@ @@@@@@@@ ",
+" @@@@@@@ @@@@@@@ ",
+" @@@@@@ @ @@@@@@ ",
+" @@@@@@ @@@ @@@@@@ ",
+" @@@@@@@@@@@@@@@@@@@ ",
+" @@@@@@@@@@@@@@@@@ ",
+" @@@@@@@@@@@@@@@ ",
+" @@@@@@@@@@@@@ ",
+" @@@@@@@@@@@ ",
+" @@@@@@@@@ ",
+" "
+};
+
+/* XPM */
+static char * stop_si_xpm[] = {
+"16 16 2 1",
+" c None",
+"@ c gray70",
+" @@@@@@@@ ",
+" @@@@@@@@@@ ",
+" @@@@@@@@@@@@ ",
+" @@@@@@@@@@@@@@ ",
+"@@@@@@@@@@@@@@@@",
+"@@@@@ @@@@ @@@@@",
+"@@@@@@ @@ @@@@@@",
+"@@@@@@@ @@@@@@@",
+"@@@@@@@ @@@@@@@",
+"@@@@@@ @@ @@@@@@",
+"@@@@@ @@@@ @@@@@",
+"@@@@@@@@@@@@@@@@",
+" @@@@@@@@@@@@@@ ",
+" @@@@@@@@@@@@ ",
+" @@@@@@@@@@ ",
+" @@@@@@@@ "};
+
+/* XPM */
+static char * left_si_xpm[] = {
+"16 16 2 1",
+" c None",
+"@ c gray70",
+" ",
+" @@ ",
+" @@@ ",
+" @@@@ ",
+" @@@@@@@@ @@@@",
+" @@@@@@@@@ @@@@",
+" @@@@@@@@@@ @@@@",
+"@@@@@@@@@@@ @@@@",
+"@@@@@@@@@@@ @@@@",
+" @@@@@@@@@@ @@@@",
+" @@@@@@@@@ @@@@",
+" @@@@@@@@ @@@@",
+" @@@@ ",
+" @@@ @@@@@",
+" @@ @ @ ",
+" @ "};
+
+/* XPM */
+static char * right_si_xpm[] = {
+"16 16 2 1",
+" c None",
+"@ c gray70",
+" ",
+" @@ ",
+" @@@ ",
+" @@@@ ",
+"@@@@ @@@@@@@@ ",
+"@@@@ @@@@@@@@@ ",
+"@@@@ @@@@@@@@@@ ",
+"@@@@ @@@@@@@@@@@",
+"@@@@ @@@@@@@@@@@",
+"@@@@ @@@@@@@@@@ ",
+"@@@@ @@@@@@@@@ ",
+"@@@@ @@@@@@@@ ",
+" @@@@ ",
+" @@@@@@@@",
+" @@ @ @ ",
+" @ "};
+
+#endif /* __PIXMAPS_H__ */
diff --git a/src/plain.cc b/src/plain.cc
new file mode 100644
index 00000000..be7b1e4c
--- /dev/null
+++ b/src/plain.cc
@@ -0,0 +1,233 @@
+/*
+ * File: plain.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.
+ */
+
+/*
+ * Module for decoding a text/plain object into a dw widget.
+ */
+
+#include <string.h> /* for memcpy and memmove */
+#include <math.h> /* for rint() */
+
+#include "prefs.h"
+#include "cache.h"
+#include "bw.h"
+#include "web.hh"
+#include "misc.h"
+#include "history.h"
+#include "nav.h"
+#include "menu.hh"
+
+#include "uicmd.hh"
+
+#include "dw/core.hh"
+#include "dw/textblock.hh"
+
+using namespace dw;
+using namespace dw::core;
+
+
+typedef struct _DilloPlain {
+ //DwWidget *dw;
+ Widget *dw;
+ size_t Start_Ofs; /* Offset of where to start reading next */
+ //DwStyle *style;
+ style::Style *widgetStyle;
+ BrowserWindow *bw;
+ int state;
+} DilloPlain;
+
+/* FSM states */
+enum {
+ ST_SeekingEol,
+ ST_Eol,
+ ST_Eof
+};
+
+/*
+ * Exported function with C linkage.
+ */
+extern "C" {
+void *a_Plain_text(const char *type, void *P, CA_Callback_t *Call,void **Data);
+}
+
+/*
+ * Forward declarations
+ */
+static void Plain_write(DilloPlain *plain, void *Buf, uint_t BufSize, int Eof);
+static void Plain_callback(int Op, CacheClient_t *Client);
+
+
+
+/*
+ * Popup the page menu ("button_press_event" callback of the viewport)
+ */
+//static int Plain_page_menu(GtkWidget *viewport, GdkEventButton *event,
+// BrowserWindow *bw)
+//{
+// if (event->button == 3) {
+// a_Menu_popup_set_url(bw, a_History_get_url(NAV_TOP(bw)));
+// gtk_menu_popup(GTK_MENU(bw->menu_popup.over_page), NULL, NULL,
+// NULL, NULL, event->button, event->time);
+// return TRUE;
+// } else
+// return FALSE;
+//}
+
+/*
+ * Create and initialize a new DilloPlain structure.
+ */
+static DilloPlain *Plain_new(BrowserWindow *bw)
+{
+ DilloPlain *plain;
+ //DwPage *page;
+ //DwStyle style_attrs;
+ //DwStyleFont font_attrs;
+ Textblock *textblock;
+ style::StyleAttrs styleAttrs;
+ style::FontAttrs fontAttrs;
+
+ plain = dNew(DilloPlain, 1);
+ plain->state = ST_SeekingEol;
+ plain->Start_Ofs = 0;
+ plain->bw = bw;
+ //plain->dw = a_Dw_page_new();
+ //page = (DwPage *) plain->dw;
+ textblock = new Textblock (false);
+ plain->dw = (Widget*) textblock;
+
+ /* Create the font and attribute for the page. */
+ //font_attrs.name = prefs.fw_fontname;
+ //font_attrs.size = rint(12.0 * prefs.font_factor);
+ //font_attrs.weight = 400;
+ //font_attrs.style = DW_STYLE_FONT_STYLE_NORMAL;
+ fontAttrs.name = "Courier";
+ fontAttrs.size = (int) rint(12.0 * prefs.font_factor);
+ fontAttrs.weight = 400;
+ fontAttrs.style = style::FONT_STYLE_NORMAL;
+
+ //a_Dw_style_init_values (&style_attrs);
+ //style_attrs.font =
+ // a_Dw_style_font_new (plain->bw->render_layout, &font_attrs);
+ //style_attrs.color =
+ // a_Dw_style_color_new (plain->bw->render_layout, prefs.text_color);
+ //plain->style = a_Dw_style_new (plain->bw->render_layout, &style_attrs);
+ //a_Dw_widget_set_style (plain->dw, plain->style);
+ Layout *layout = (Layout*)bw->render_layout;
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+ styleAttrs.font = style::Font::create (layout, &fontAttrs);
+ styleAttrs.color = style::Color::createSimple (layout, 0x0000ff);
+ styleAttrs.backgroundColor =
+ style::Color::createSimple (layout, 0xdcd1ba);
+ plain->widgetStyle = style::Style::create (layout, &styleAttrs);
+
+ /* The context menu */
+// gtk_signal_connect_while_alive
+// (GTK_OBJECT(GTK_BIN(plain->bw->render_main_scroll)->child),
+// "button_press_event", GTK_SIGNAL_FUNC(Plain_page_menu),
+// plain->bw, GTK_OBJECT (page));
+
+ return plain;
+}
+
+/*
+ * Set callback function and callback data for "text/" MIME major-type.
+ */
+void *a_Plain_text(const char *type, void *P, CA_Callback_t *Call, void **Data)
+{
+ DilloWeb *web = (DilloWeb*)P;
+ DilloPlain *plain = Plain_new(web->bw);
+
+ *Call = (CA_Callback_t)Plain_callback;
+ *Data = (void*)plain;
+
+ return (void*)plain->dw;
+}
+
+/*
+ * This function is a cache client
+ */
+static void Plain_callback(int Op, CacheClient_t *Client)
+{
+ DilloPlain *plain = (DilloPlain*)Client->CbData;
+ Textblock *textblock = (Textblock*)plain->dw;
+
+ if (Op) {
+ /* Do the last line: */
+ if (plain->Start_Ofs < Client->BufSize)
+ Plain_write(plain, Client->Buf, Client->BufSize, 1);
+ /* remove this client from our active list */
+ a_Bw_close_client(plain->bw, Client->Key);
+ /* set progress bar insensitive */
+ a_UIcmd_set_page_prog(plain->bw, 0, 0);
+
+ //a_Dw_style_unref (plain->bw->render_layout, plain->style);
+ plain->widgetStyle->unref();
+ dFree(plain);
+ } else {
+ Plain_write(plain, Client->Buf, Client->BufSize, 0);
+ }
+
+ textblock->flush();
+}
+
+/*
+ * Here we parse plain text and put it into the page structure.
+ * (This function is called by Plain_callback whenever there's new data)
+ */
+static void Plain_write(DilloPlain *plain, void *Buf, uint_t BufSize, int Eof)
+{
+ //DwPage *page = (DwPage *)plain->dw;
+ Textblock *textblock = (Textblock*)plain->dw;
+ char *Start;
+ char *data;
+ uint_t i, len, MaxBytes;
+
+ Start = (char*)Buf + plain->Start_Ofs;
+ MaxBytes = BufSize - plain->Start_Ofs;
+ i = len = 0;
+ while ( i < MaxBytes ) {
+ switch ( plain->state ) {
+ case ST_SeekingEol:
+ if (Start[i] == '\n' || Start[i] == '\r')
+ plain->state = ST_Eol;
+ else {
+ ++i; ++len;
+ }
+ break;
+ case ST_Eol:
+ data = dStrndup(Start + i - len, len);
+ //a_Dw_page_add_text(page, a_Misc_expand_tabs(data), plain->style);
+ //a_Dw_page_add_parbreak(page, 0, plain->style);
+ textblock->addText(a_Misc_expand_tabs(data), plain->widgetStyle);
+ textblock->addParbreak(0, plain->widgetStyle);
+ dFree(data);
+ if (Start[i] == '\r' && Start[i + 1] == '\n') ++i;
+ if (i < MaxBytes) ++i;
+ plain->state = ST_SeekingEol;
+ len = 0;
+ break;
+ }
+ }
+ plain->Start_Ofs += i - len;
+ if (Eof && len) {
+ data = dStrndup(Start + i - len, len);
+ //a_Dw_page_add_text(page, a_Misc_expand_tabs(data), plain->style);
+ //a_Dw_page_add_parbreak(page, 0, plain->style);
+ textblock->addText(a_Misc_expand_tabs(data), plain->widgetStyle);
+ textblock->addParbreak(0, plain->widgetStyle);
+ dFree(data);
+ plain->Start_Ofs += len;
+ }
+
+ if (plain->bw)
+ a_UIcmd_set_page_prog(plain->bw, plain->Start_Ofs, 1);
+}
diff --git a/src/png.c b/src/png.c
new file mode 100644
index 00000000..43ef5519
--- /dev/null
+++ b/src/png.c
@@ -0,0 +1,472 @@
+/*
+ * The png decoder for Dillo. It is responsible for decoding PNG data
+ * and transferring it to the dicache.
+ *
+ * Geoff Lane nov 1999 zzassgl@twirl.mcc.ac.uk
+ * Luca Rota, Jorge Arellano Cid, Eric Gaudet 2000
+ *
+ * "PNG: The Definitive Guide" by Greg Roelofs, O'Reilly
+ * ISBN 1-56592-542-4
+ */
+
+#include <config.h>
+#ifdef ENABLE_PNG
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h> /* For abort() */
+
+#include <zlib.h>
+
+#ifdef HAVE_LIBPNG_PNG_H
+#include <libpng/png.h>
+#else
+#include <png.h>
+#endif
+
+#include "msg.h"
+#include "image.hh"
+#include "web.hh"
+#include "cache.h"
+#include "dicache.h"
+#include "prefs.h"
+
+#define DEBUG_LEVEL 6
+#include "debug.h"
+
+enum prog_state {
+ IS_finished, IS_init, IS_nextdata
+};
+
+static char *prog_state_name[] =
+{
+ "IS_finished", "IS_init", "IS_nextdata"
+};
+
+/*
+ * This holds the data that must be saved between calls to this module.
+ * Each time it is called it is supplied with a vector of data bytes
+ * obtained from the web server. The module can process any amount of the
+ * supplied data. The next time the module is called, the vector may be
+ * extended with additional data bytes to be processed. The module must
+ * keep track of the current start and cursor position of the input data
+ * vector. As complete output rasters are determined they are sent out of
+ * the module for additional processing.
+ *
+ * NOTE: There is no external control of the splitting of the input data
+ * vector (only this module understands PNG format data.) This means that
+ * the complete state of a PNG image being processed must be held in the
+ * structure below so that processing can be suspended or resumed at any
+ * point within an input image.
+ *
+ * In the case of the libpng library, it maintains it's own state in
+ * png_ptr and into_ptr so the FSM is very simple - much simpler than the
+ * ones for XBM and PNM are.
+ */
+
+typedef
+struct _DilloPng {
+ DilloImage *Image; /* Image meta data */
+ DilloUrl *url; /* Primary Key for the dicache */
+ int version; /* Secondary Key for the dicache */
+
+ double display_exponent; /* gamma correction */
+ ulong_t width; /* png image width */
+ ulong_t height; /* png image height */
+ png_structp png_ptr; /* libpng private data */
+ png_infop info_ptr; /* libpng private info */
+ uchar_t *image_data; /* decoded image data */
+ uchar_t **row_pointers; /* pntr to row starts */
+ jmp_buf jmpbuf; /* png error processing */
+ int error; /* error flag */
+ int rowbytes; /* No. bytes in image row */
+ short passes;
+ short channels; /* No. image channels */
+
+/*
+ * 0 last byte
+ * +-------+-+-----------------------------------+-+
+ * | | | -- data to be processed -- | |
+ * +-------+-+-----------------------------------+-+
+ * ^ ^ ^
+ * ipbuf ipbufstart ipbufsize
+ */
+
+ uchar_t *ipbuf; /* image data in buffer */
+ int ipbufstart; /* first valid image byte */
+ int ipbufsize; /* size of valid data in */
+
+ enum prog_state state; /* FSM current state */
+
+ uchar_t *linebuf; /* o/p raster data */
+
+} DilloPng;
+
+#define DATASIZE (png->ipbufsize - png->ipbufstart)
+#define BLACK 0
+#define WHITE 255
+
+/*
+ * Forward declarations
+ */
+/* exported function */
+void *a_Png_image(const char *Type, void *Ptr, CA_Callback_t *Call,
+ void **Data);
+
+
+static
+void Png_error_handling(png_structp png_ptr, png_const_charp msg)
+{
+ DilloPng *png;
+
+ DEBUG_MSG(6, "Png_error_handling: %s\n", msg);
+ png = png_get_error_ptr(png_ptr);
+
+ png->error = 1;
+ png->state = IS_finished;
+
+ longjmp(png->jmpbuf, 1);
+}
+
+static void
+Png_datainfo_callback(png_structp png_ptr, png_infop info_ptr)
+{
+ DilloPng *png;
+ int color_type;
+ int bit_depth;
+ int interlace_type;
+ uint_t i;
+ double gamma;
+
+ DEBUG_MSG(5, "Png_datainfo_callback:\n");
+
+ png = png_get_progressive_ptr(png_ptr);
+ dReturn_if_fail (png != NULL);
+
+ png_get_IHDR(png_ptr, info_ptr, &png->width, &png->height,
+ &bit_depth, &color_type, &interlace_type, NULL, NULL);
+
+ DEBUG_MSG(5, "Png_datainfo_callback: png->width = %ld\n"
+ "Png_datainfo_callback: png->height = %ld\n",
+ png->width, png->height);
+
+ /* we need RGB/RGBA in the end */
+ if (color_type == PNG_COLOR_TYPE_PALETTE && bit_depth <= 8) {
+ /* Convert indexed images to RGB */
+ png_set_expand (png_ptr);
+ } else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {
+ /* Convert grayscale to RGB */
+ png_set_expand (png_ptr);
+ } else if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS)) {
+ /* We have transparency header, convert it to alpha channel */
+ png_set_expand(png_ptr);
+ } else if (bit_depth < 8) {
+ png_set_expand(png_ptr);
+ }
+
+ if (bit_depth == 16) {
+ png_set_strip_16(png_ptr);
+ }
+
+ /* Get and set gamma information. Beware: gamma correction 2.2 will
+ only work on PC's. todo: select screen gamma correction for other
+ platforms. */
+ if (png_get_gAMA(png_ptr, info_ptr, &gamma))
+ png_set_gamma(png_ptr, 2.2, gamma);
+
+ /* Convert gray scale to RGB */
+ if (color_type == PNG_COLOR_TYPE_GRAY ||
+ color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(png_ptr);
+ }
+
+ /* Interlaced */
+ if (interlace_type != PNG_INTERLACE_NONE) {
+ png->passes = png_set_interlace_handling(png_ptr);
+ }
+
+ /* get libpng to update it's state */
+ png_read_update_info(png_ptr, info_ptr);
+
+ png_get_IHDR(png_ptr, info_ptr, &png->width, &png->height,
+ &bit_depth, &color_type, &interlace_type, NULL, NULL);
+
+ png->rowbytes = png_get_rowbytes(png_ptr, info_ptr);
+ png->channels = png_get_channels(png_ptr, info_ptr);
+
+ /* init Dillo specifics */
+ DEBUG_MSG(5, "Png_datainfo_callback: rowbytes = %d\n"
+ "Png_datainfo_callback: width = %ld\n"
+ "Png_datainfo_callback: height = %ld\n",
+ png->rowbytes, png->width, png->height);
+
+ png->image_data = (uchar_t *) dMalloc(png->rowbytes * png->height);
+ png->row_pointers = (uchar_t **) dMalloc(png->height * sizeof(uchar_t *));
+
+ for (i = 0; i < png->height; i++)
+ png->row_pointers[i] = png->image_data + (i * png->rowbytes);
+
+ png->linebuf = dMalloc(3 * png->width);
+
+ /* Initialize the dicache-entry here */
+ a_Dicache_set_parms(png->url, png->version, png->Image,
+ (uint_t)png->width, (uint_t)png->height,
+ DILLO_IMG_TYPE_RGB);
+}
+
+static void
+ Png_datarow_callback(png_structp png_ptr, png_bytep new_row,
+ png_uint_32 row_num, int pass)
+{
+ DilloPng *png;
+ uint_t i;
+
+ if (!new_row) /* work to do? */
+ return;
+
+ DEBUG_MSG(5, "Png_datarow_callback: row_num = %ld\n", row_num);
+
+ png = png_get_progressive_ptr(png_ptr);
+
+ png_progressive_combine_row(png_ptr, png->row_pointers[row_num], new_row);
+
+ switch (png->channels) {
+ case 3:
+ a_Dicache_write(png->Image, png->url, png->version,
+ png->image_data + (row_num * png->rowbytes),
+ 0, (uint_t)row_num);
+ break;
+ case 4:
+ {
+ /* todo: get the backgound color from the parent
+ * of the image widget -- Livio. */
+ int a, bg_red, bg_green, bg_blue;
+ uchar_t *pl = png->linebuf;
+ uchar_t *data = png->image_data + (row_num * png->rowbytes);
+
+ /* todo: maybe change prefs.bg_color to `a_Dw_widget_get_bg_color`,
+ * when background colors are correctly implementated */
+ bg_blue = (png->Image->bg_color) & 0xFF;
+ bg_green = (png->Image->bg_color>>8) & 0xFF;
+ bg_red = (png->Image->bg_color>>16) & 0xFF;
+
+ for (i = 0; i < png->width; i++) {
+ a = *(data+3);
+
+ if (a == 255) {
+ *(pl++) = *(data++);
+ *(pl++) = *(data++);
+ *(pl++) = *(data++);
+ data++;
+ } else if (a == 0) {
+ *(pl++) = bg_red;
+ *(pl++) = bg_green;
+ *(pl++) = bg_blue;
+ data += 4;
+ } else {
+ png_composite(*(pl++), *(data++), a, bg_red);
+ png_composite(*(pl++), *(data++), a, bg_green);
+ png_composite(*(pl++), *(data++), a, bg_blue);
+ data++;
+ }
+ }
+ a_Dicache_write(png->Image, png->url, png->version,
+ png->linebuf, 0, (uint_t)row_num);
+ break;
+ }
+ default:
+ MSG("Png_datarow_callback: unexpected number of channels = %d\n",
+ png->channels);
+ abort();
+ }
+}
+
+static void
+ Png_dataend_callback(png_structp png_ptr, png_infop info_ptr)
+{
+ DilloPng *png;
+
+ DEBUG_MSG(5, "Png_dataend_callback:\n");
+
+ png = png_get_progressive_ptr(png_ptr);
+ png->state = IS_finished;
+}
+
+
+/*
+ * Op: Operation to perform.
+ * If (Op == 0)
+ * start or continue processing an image if image data exists.
+ * else
+ * terminate processing, cleanup any allocated memory,
+ * close down the decoding process.
+ *
+ * Client->CbData : pointer to previously allocated DilloPng work area.
+ * This holds the current state of the image processing and is saved
+ * across calls to this routine.
+ * Client->Buf : Pointer to data start.
+ * Client->BufSize : the size of the data buffer.
+ *
+ * You have to keep track of where you are in the image data and
+ * how much has been processed.
+ *
+ * It's entirely possible that you will not see the end of the data. The
+ * user may terminate transfer via a Stop button or there may be a network
+ * failure. This means that you can't just wait for all the data to be
+ * presented before starting conversion and display.
+ */
+static void Png_callback(int Op, CacheClient_t *Client)
+{
+ DilloPng *png = Client->CbData;
+
+ if (Op) {
+ /* finished - free up the resources for this image */
+ a_Dicache_close(png->url, png->version, Client);
+ dFree(png->image_data);
+ dFree(png->row_pointers);
+ dFree(png->linebuf);
+
+ if (setjmp(png->jmpbuf))
+ MSG_WARN("PNG: can't destroy read structure\n");
+ else if (png->png_ptr)
+ png_destroy_read_struct(&png->png_ptr, &png->info_ptr, NULL);
+ dFree(png);
+ return;
+ }
+
+ /* Let's make some sound if we have been called with no data */
+ dReturn_if_fail ( Client->Buf != NULL && Client->BufSize > 0 );
+
+ DEBUG_MSG(5, "Png_callback BufSize = %d\n", Client->BufSize);
+
+ /* Keep local copies so we don't have to pass multiple args to
+ * a number of functions. */
+ png->ipbuf = Client->Buf;
+ png->ipbufsize = Client->BufSize;
+
+ /* start/resume the FSM here */
+ while (png->state != IS_finished && DATASIZE) {
+ DEBUG_MSG(5, "State = %s\n", prog_state_name[png->state]);
+
+ switch (png->state) {
+ case IS_init:
+ if (DATASIZE < 8) {
+ return; /* need MORE data */
+ }
+ /* check the image signature - DON'T update ipbufstart! */
+ if (!png_check_sig(png->ipbuf, DATASIZE)) {
+ /* you lied to me about it being a PNG image */
+ png->state = IS_finished;
+ break;
+ }
+ /* OK, it looks like a PNG image, lets do some set up stuff */
+ png->png_ptr = png_create_read_struct(
+ PNG_LIBPNG_VER_STRING,
+ png,
+ (png_error_ptr)Png_error_handling,
+ (png_error_ptr)Png_error_handling);
+ dReturn_if_fail (png->png_ptr != NULL);
+ png->info_ptr = png_create_info_struct(png->png_ptr);
+ dReturn_if_fail (png->info_ptr != NULL);
+
+ setjmp(png->jmpbuf);
+ if (!png->error) {
+ png_set_progressive_read_fn(
+ png->png_ptr,
+ png,
+ Png_datainfo_callback, /* performs local init functions */
+ Png_datarow_callback, /* performs per row action */
+ Png_dataend_callback); /* performs cleanup actions */
+ png->state = IS_nextdata;
+ }
+ break;
+
+ case IS_nextdata:
+ if (setjmp(png->jmpbuf)) {
+ png->state = IS_finished;
+ } else if (!png->error) {
+ png_process_data( png->png_ptr,
+ png->info_ptr,
+ png->ipbuf + png->ipbufstart,
+ (png_size_t)DATASIZE);
+
+ png->ipbufstart += DATASIZE;
+ }
+ break;
+
+ default:
+ MSG_WARN("PNG decoder: bad state = %d\n", png->state);
+ abort();
+ }
+ }
+}
+
+/*
+ * Create the image state data that must be kept between calls
+ */
+static DilloPng *Png_new(DilloImage *Image, DilloUrl *url, int version)
+{
+ DilloPng *png = dNew0(DilloPng, 1);
+
+ png->Image = Image;
+ png->url = url;
+ png->version = version;
+ png->error = 0;
+ png->ipbuf = NULL;
+ png->ipbufstart = 0;
+ png->ipbufsize = 0;
+ png->state = IS_init;
+ png->linebuf = NULL;
+ png->image_data = NULL;
+ png->row_pointers = NULL;
+
+ return png;
+}
+
+/*
+ * MIME handler for "image/png" type
+ * (Sets Png_callback or a_Dicache_callback as the cache-client)
+ */
+void *a_Png_image(const char *Type, void *Ptr, CA_Callback_t *Call,
+ void **Data)
+{
+/*
+ * Type: MIME type
+ * Ptr: points to a Web structure
+ * Call: Dillo calls this with more data/eod
+ * Data: raw image data
+ */
+
+ DilloWeb *web = Ptr;
+ DICacheEntry *DicEntry;
+
+ DEBUG_MSG(5, "a_Png_image: Type = %s\n"
+ "a_Png_image: libpng - Compiled %s; using %s.\n"
+ "a_Png_image: zlib - Compiled %s; using %s.\n",
+ Type, PNG_LIBPNG_VER_STRING, png_libpng_ver,
+ ZLIB_VERSION, zlib_version);
+
+ if (!web->Image)
+ web->Image = a_Image_new(0, 0, NULL, prefs.bg_color);
+
+ /* Add an extra reference to the Image (for dicache usage) */
+ a_Image_ref(web->Image);
+
+ DicEntry = a_Dicache_get_entry(web->url);
+ if (!DicEntry) {
+ /* Let's create an entry for this image... */
+ DicEntry = a_Dicache_add_entry(web->url);
+
+ /* ... and let the decoder feed it! */
+ *Data = Png_new(web->Image, DicEntry->url, DicEntry->version);
+ *Call = (CA_Callback_t) Png_callback;
+ } else {
+ /* Let's feed our client from the dicache */
+ a_Dicache_ref(DicEntry->url, DicEntry->version);
+ *Data = web->Image;
+ *Call = (CA_Callback_t) a_Dicache_callback;
+ }
+ return (web->Image->dw);
+}
+
+#endif /* ENABLE_PNG */
diff --git a/src/prefs.c b/src/prefs.c
new file mode 100644
index 00000000..1d5e6053
--- /dev/null
+++ b/src/prefs.c
@@ -0,0 +1,434 @@
+/*
+ * Preferences for dillo
+ *
+ * Copyright (C) 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h> /* for strchr */
+#include <fcntl.h>
+#include <unistd.h>
+#include <locale.h> /* for setlocale */
+#include <ctype.h> /* for isspace */
+#include "prefs.h"
+#include "colors.h"
+#include "misc.h"
+#include "msg.h"
+
+#define RCNAME "dillorc2"
+
+
+/*
+ * Global Data
+ */
+DilloPrefs prefs;
+
+typedef struct SymNode_ SymNode_t;
+
+struct SymNode_ {
+ char *name;
+ RcToken_t token;
+};
+
+/* Symbol array, sorted alphabetically */
+SymNode_t symbols[] = {
+ { "allow_white_bg", DRC_TOKEN_ALLOW_WHITE_BG },
+ { "bg_color", DRC_TOKEN_BG_COLOR },
+ { "contrast_visited_color", DRC_TOKEN_CONTRAST_VISITED_COLOR },
+ { "enterpress_forces_submit", DRC_TOKEN_ENTERPRESS_FORCES_SUBMIT },
+ { "font_factor", DRC_TOKEN_FONT_FACTOR },
+ { "force_my_colors", DRC_TOKEN_FORCE_MY_COLORS },
+ { "fullwindow_start", DRC_TOKEN_FULLWINDOW_START },
+ { "fw_fontname", DRC_TOKEN_FW_FONT },
+ { "generate_submit", DRC_TOKEN_GENERATE_SUBMIT },
+ { "geometry", DRC_TOKEN_GEOMETRY },
+ { "home", DRC_TOKEN_HOME },
+ { "http_proxy", DRC_TOKEN_PROXY },
+ { "http_proxyuser", DRC_TOKEN_PROXYUSER },
+ { "limit_text_width", DRC_TOKEN_LIMIT_TEXT_WIDTH },
+ { "link_color", DRC_TOKEN_LINK_COLOR },
+ { "no_proxy", DRC_TOKEN_NOPROXY },
+ { "panel_size", DRC_TOKEN_PANEL_SIZE },
+ { "search_url", DRC_TOKEN_SEARCH_URL },
+ { "show_back", DRC_TOKEN_SHOW_BACK },
+ { "show_bookmarks", DRC_TOKEN_SHOW_BOOKMARKS },
+ { "show_clear_url", DRC_TOKEN_SHOW_CLEAR_URL },
+ { "show_extra_warnings", DRC_TOKEN_SHOW_EXTRA_WARNINGS },
+ { "show_forw", DRC_TOKEN_SHOW_FORW },
+ { "show_home", DRC_TOKEN_SHOW_HOME },
+ { "show_menubar", DRC_TOKEN_SHOW_MENUBAR },
+ { "show_msg", DRC_TOKEN_SHOW_MSG },
+ { "show_progress_box", DRC_TOKEN_SHOW_PROGRESS_BOX },
+ { "show_reload", DRC_TOKEN_SHOW_RELOAD },
+ { "show_save", DRC_TOKEN_SHOW_SAVE },
+ { "show_search", DRC_TOKEN_SHOW_SEARCH },
+ { "show_stop", DRC_TOKEN_SHOW_STOP },
+ { "show_tooltip", DRC_TOKEN_SHOW_TOOLTIP },
+ { "show_url", DRC_TOKEN_SHOW_URL },
+ { "small_icons", DRC_TOKEN_SMALL_ICONS },
+ { "start_page", DRC_TOKEN_START_PAGE },
+ { "text_color", DRC_TOKEN_TEXT_COLOR },
+ { "transient_dialogs", DRC_TOKEN_TRANSIENT_DIALOGS },
+ { "use_dicache", DRC_TOKEN_USE_DICACHE },
+ { "use_oblique", DRC_TOKEN_USE_OBLIQUE },
+ { "visited_color", DRC_TOKEN_VISITED_COLOR, },
+ { "vw_fontname", DRC_TOKEN_VW_FONT },
+ { "w3c_plus_heuristics", DRC_TOKEN_W3C_PLUS_HEURISTICS }
+};
+
+static const uint_t n_symbols = sizeof (symbols) / sizeof (symbols[0]);
+
+/*
+ *- Mini parser -------------------------------------------------------------
+ */
+
+/*
+ * Comparison function for binary search
+ */
+static int Prefs_symbol_cmp(const void *a, const void *b)
+{
+ return strcmp(((SymNode_t*)a)->name, ((SymNode_t*)b)->name);
+}
+
+/*
+ * Take a dillo rc line and return 'name' and 'value' pointers to it.
+ * Notes:
+ * - line is modified!
+ * - it skips blank lines and lines starting with '#'
+ *
+ * Return value: 0 on successful value/pair, -1 otherwise
+ */
+static int Prefs_get_pair(char **line, char **name, char **value)
+{
+ char *eq, *p;
+ int len, ret = -1;
+
+ dReturn_val_if_fail(*line, ret);
+
+ *name = NULL;
+ *value = NULL;
+ dStrstrip(*line);
+ if (*line[0] != '#' && (eq = strchr(*line, '='))) {
+ /* get name */
+ for (p = *line; *p && *p != '=' && !isspace(*p); ++p);
+ *p = 0;
+ *name = *line;
+
+ /* get value */
+ if (p == eq) {
+ for (++p; isspace(*p); ++p);
+ len = strlen(p);
+ if (len >= 2 && *p == '"' && p[len-1] == '"') {
+ p[len-1] = 0;
+ ++p;
+ }
+ *value = p;
+ ret = 0;
+ }
+ }
+
+ if (*line[0] && *line[0] != '#' && (!*name || !*value)) {
+ MSG("prefs: Syntax error in %s: name=\"%s\" value=\"%s\"\n",
+ RCNAME, *name, *value);
+ }
+ return ret;
+}
+
+/*
+ * Parse a name/value pair and set preferences accordingly.
+ */
+static int Prefs_parse_pair(char *name, char *value)
+{
+ int st;
+ SymNode_t key, *node;
+
+ key.name = name;
+ node = bsearch(&key, symbols, n_symbols,
+ sizeof(SymNode_t), Prefs_symbol_cmp);
+ if (!node) {
+ MSG("prefs: {%s} is not a recognized token.\n", name);
+ return -1;
+ }
+
+ switch (node->token) {
+ case DRC_TOKEN_GEOMETRY:
+ a_Misc_parse_geometry(value, &prefs.xpos, &prefs.ypos,
+ &prefs.width, &prefs.height);
+ break;
+ case DRC_TOKEN_PROXY:
+ a_Url_free(prefs.http_proxy);
+ prefs.http_proxy = a_Url_new(value, NULL, 0, 0, 0);
+ break;
+ case DRC_TOKEN_PROXYUSER:
+ dFree(prefs.http_proxyuser);
+ prefs.http_proxyuser = dStrdup(value);
+ break;
+ case DRC_TOKEN_NOPROXY:
+ dFree(prefs.no_proxy);
+ prefs.no_proxy = dStrdup(value);
+ break;
+ case DRC_TOKEN_LINK_COLOR:
+ prefs.link_color = a_Color_parse(value, prefs.link_color, &st);
+ break;
+ case DRC_TOKEN_VISITED_COLOR:
+ prefs.visited_color = a_Color_parse(value, prefs.visited_color, &st);
+ break;
+ case DRC_TOKEN_TEXT_COLOR:
+ prefs.text_color = a_Color_parse(value, prefs.text_color, &st);
+ break;
+ case DRC_TOKEN_BG_COLOR:
+ prefs.bg_color = a_Color_parse(value, prefs.bg_color, &st);
+ break;
+ case DRC_TOKEN_ALLOW_WHITE_BG:
+ prefs.allow_white_bg = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_FORCE_MY_COLORS:
+ prefs.force_my_colors = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_CONTRAST_VISITED_COLOR:
+ prefs.contrast_visited_color = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_USE_OBLIQUE:
+ prefs.use_oblique = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_PANEL_SIZE:
+ if (!dStrcasecmp(value, "tiny"))
+ prefs.panel_size = 1;
+ else if (!dStrcasecmp(value, "small"))
+ prefs.panel_size = 2;
+ else if (!dStrcasecmp(value, "medium"))
+ prefs.panel_size = 3;
+ else /* default to "large" */
+ prefs.panel_size = 4;
+ break;
+ case DRC_TOKEN_SMALL_ICONS:
+ prefs.small_icons = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_START_PAGE:
+ a_Url_free(prefs.start_page);
+ prefs.start_page = a_Url_new(value, NULL, 0, 0, 0);
+ break;
+ case DRC_TOKEN_HOME:
+ a_Url_free(prefs.home);
+ prefs.home = a_Url_new(value, NULL, 0, 0, 0);
+ break;
+ case DRC_TOKEN_SHOW_TOOLTIP:
+ prefs.show_tooltip = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_FONT_FACTOR:
+ prefs.font_factor = strtod(value, NULL);
+ break;
+ case DRC_TOKEN_LIMIT_TEXT_WIDTH:
+ prefs.limit_text_width = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_W3C_PLUS_HEURISTICS:
+ prefs.w3c_plus_heuristics = (strcmp(value,"YES") == 0);
+ break;
+ case DRC_TOKEN_USE_DICACHE:
+ prefs.use_dicache = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_SHOW_BACK:
+ prefs.show_back = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_SHOW_FORW:
+ prefs.show_forw = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_SHOW_HOME:
+ prefs.show_home = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_SHOW_RELOAD:
+ prefs.show_reload = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_SHOW_SAVE:
+ prefs.show_save = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_SHOW_STOP:
+ prefs.show_stop = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_SHOW_BOOKMARKS:
+ prefs.show_bookmarks = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_SHOW_MENUBAR:
+ prefs.show_menubar = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_SHOW_CLEAR_URL:
+ prefs.show_clear_url = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_SHOW_URL:
+ prefs.show_url = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_SHOW_SEARCH:
+ prefs.show_search = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_SHOW_PROGRESS_BOX:
+ prefs.show_progress_box = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_FULLWINDOW_START:
+ prefs.fullwindow_start = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_TRANSIENT_DIALOGS:
+ prefs.transient_dialogs = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_FW_FONT:
+ dFree(prefs.fw_fontname);
+ prefs.fw_fontname = dStrdup(value);
+ break;
+ case DRC_TOKEN_VW_FONT:
+ dFree(prefs.vw_fontname);
+ prefs.vw_fontname = dStrdup(value);
+ break;
+ case DRC_TOKEN_GENERATE_SUBMIT:
+ prefs.generate_submit = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_ENTERPRESS_FORCES_SUBMIT:
+ prefs.enterpress_forces_submit = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_SEARCH_URL:
+ dFree(prefs.search_url);
+ prefs.search_url = dStrdup(value);
+ break;
+ case DRC_TOKEN_SHOW_MSG:
+ prefs.show_msg = (strcmp(value, "YES") == 0);
+ break;
+ case DRC_TOKEN_SHOW_EXTRA_WARNINGS:
+ prefs.show_extra_warnings = (strcmp(value, "YES") == 0);
+ break;
+ default:
+ MSG_WARN("prefs: {%s} IS recognized but not handled!\n", name);
+ break; /* Not reached */
+ }
+
+ return 0;
+}
+
+/*
+ * Parse dillorc and set the values in the prefs structure.
+ */
+static int Prefs_parse_dillorc(void)
+{
+ FILE *F_in;
+ char *filename, *line, *name, *value;
+ int ret = -1;
+
+ filename = dStrconcat(dGethomedir(), "/.dillo/", RCNAME, NULL);
+ if (!(F_in = fopen(filename, "r"))) {
+ MSG("prefs: Can't open %s file: %s\n", RCNAME, filename);
+ if (!(F_in = fopen(DILLORC_SYS, "r"))) {
+ MSG("prefs: Can't open %s file: %s\n", RCNAME, DILLORC_SYS);
+ MSG("prefs: Using internal defaults.\n");
+ } else {
+ MSG("prefs: Using %s\n", DILLORC_SYS);
+ }
+ }
+
+ if (F_in) {
+ /* scan dillorc line by line */
+ while ((line = dGetline(F_in)) != NULL) {
+ if (Prefs_get_pair(&line, &name, &value) == 0){
+ _MSG("{%s}, {%s}\n", name, value);
+ Prefs_parse_pair(name, value);
+ }
+ dFree(line);
+ }
+ fclose(F_in);
+ ret = 0;
+ }
+ dFree(filename);
+
+ return ret;
+}
+
+/*---------------------------------------------------------------------------*/
+
+void a_Prefs_init(void)
+{
+ char *old_locale;
+
+ prefs.width = D_GEOMETRY_DEFAULT_WIDTH;
+ prefs.height = D_GEOMETRY_DEFAULT_HEIGHT;
+ prefs.xpos = D_GEOMETRY_DEFAULT_XPOS;
+ prefs.ypos = D_GEOMETRY_DEFAULT_YPOS;
+ prefs.http_proxy = NULL;
+ prefs.http_proxyuser = NULL;
+ prefs.no_proxy = NULL;
+ prefs.link_color = DW_COLOR_DEFAULT_BLUE;
+ prefs.visited_color = DW_COLOR_DEFAULT_PURPLE;
+ prefs.bg_color = DW_COLOR_DEFAULT_BGND;
+ prefs.text_color = DW_COLOR_DEFAULT_BLACK;
+ prefs.use_oblique = FALSE;
+ prefs.start_page = a_Url_new(DILLO_START_PAGE, NULL, 0, 0, 0);
+ prefs.home = a_Url_new(DILLO_HOME, NULL, 0, 0, 0);
+ prefs.allow_white_bg = TRUE;
+ prefs.force_my_colors = FALSE;
+ prefs.contrast_visited_color = FALSE;
+ prefs.show_tooltip = FALSE;
+ prefs.panel_size = 1;
+ prefs.small_icons = FALSE;
+ prefs.limit_text_width = FALSE;
+ prefs.w3c_plus_heuristics = TRUE;
+ prefs.font_factor = 1.0;
+ prefs.use_dicache = FALSE;
+ prefs.show_back=TRUE;
+ prefs.show_forw=TRUE;
+ prefs.show_home=TRUE;
+ prefs.show_reload=TRUE;
+ prefs.show_save=TRUE;
+ prefs.show_stop=TRUE;
+ prefs.show_bookmarks=TRUE;
+ prefs.show_menubar=TRUE;
+ prefs.show_clear_url=TRUE;
+ prefs.show_url=TRUE;
+ prefs.show_search=TRUE;
+ prefs.show_progress_box=TRUE;
+ prefs.fullwindow_start=FALSE;
+ prefs.transient_dialogs=FALSE;
+ prefs.vw_fontname = dStrdup("helvetica");
+ prefs.fw_fontname = dStrdup("courier");
+ prefs.generate_submit = FALSE;
+ prefs.enterpress_forces_submit = FALSE;
+ prefs.search_url = dStrdup("http://www.google.com/search?q=%s");
+ prefs.show_msg = TRUE;
+ prefs.show_extra_warnings = FALSE;
+
+ /* this locale stuff is to avoid parsing problems with float numbers */
+ old_locale = dStrdup (setlocale (LC_NUMERIC, NULL));
+ setlocale (LC_NUMERIC, "C");
+
+ Prefs_parse_dillorc();
+
+ setlocale (LC_NUMERIC, old_locale);
+ dFree (old_locale);
+
+}
+
+/*
+ * Preferences memory-deallocation
+ * (Call this one at exit time)
+ */
+void a_Prefs_freeall(void)
+{
+ dFree(prefs.http_proxyuser);
+ dFree(prefs.no_proxy);
+ a_Url_free(prefs.http_proxy);
+ dFree(prefs.fw_fontname);
+ dFree(prefs.vw_fontname);
+ a_Url_free(prefs.start_page);
+ a_Url_free(prefs.home);
+ dFree(prefs.search_url);
+}
diff --git a/src/prefs.h b/src/prefs.h
new file mode 100644
index 00000000..79f06bc1
--- /dev/null
+++ b/src/prefs.h
@@ -0,0 +1,130 @@
+#ifndef __PREFS_H__
+#define __PREFS_H__
+
+#include "url.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define DILLO_START_PAGE "about:splash"
+#define DILLO_HOME "http://www.dillo.org/"
+#define D_GEOMETRY_DEFAULT_WIDTH 640
+#define D_GEOMETRY_DEFAULT_HEIGHT 550
+#define D_GEOMETRY_DEFAULT_XPOS -9999
+#define D_GEOMETRY_DEFAULT_YPOS -9999
+
+#define DW_COLOR_DEFAULT_GREY 0xd6d6d6
+#define DW_COLOR_DEFAULT_BLACK 0x000000
+#define DW_COLOR_DEFAULT_BLUE 0x0000ff
+#define DW_COLOR_DEFAULT_PURPLE 0x800080
+#define DW_COLOR_DEFAULT_BGND 0xd6d6c0
+
+
+/* define enumeration values to be returned for specific symbols */
+typedef enum {
+ DRC_TOKEN_ALLOW_WHITE_BG,
+ DRC_TOKEN_BG_COLOR,
+ DRC_TOKEN_CONTRAST_VISITED_COLOR,
+ DRC_TOKEN_ENTERPRESS_FORCES_SUBMIT,
+ DRC_TOKEN_FONT_FACTOR,
+ DRC_TOKEN_FORCE_MY_COLORS,
+ DRC_TOKEN_FULLWINDOW_START,
+ DRC_TOKEN_FW_FONT,
+ DRC_TOKEN_GENERATE_SUBMIT,
+ DRC_TOKEN_GEOMETRY,
+ DRC_TOKEN_HOME,
+ DRC_TOKEN_LIMIT_TEXT_WIDTH,
+ DRC_TOKEN_LINK_COLOR,
+ DRC_TOKEN_NOPROXY,
+ DRC_TOKEN_PANEL_SIZE,
+ DRC_TOKEN_PROXY,
+ DRC_TOKEN_PROXYUSER,
+ DRC_TOKEN_SEARCH_URL,
+ DRC_TOKEN_SHOW_BACK,
+ DRC_TOKEN_SHOW_BOOKMARKS,
+ DRC_TOKEN_SHOW_CLEAR_URL,
+ DRC_TOKEN_SHOW_EXTRA_WARNINGS,
+ DRC_TOKEN_SHOW_FORW,
+ DRC_TOKEN_SHOW_HOME,
+ DRC_TOKEN_SHOW_MENUBAR,
+ DRC_TOKEN_SHOW_MSG,
+ DRC_TOKEN_SHOW_PROGRESS_BOX,
+ DRC_TOKEN_SHOW_RELOAD,
+ DRC_TOKEN_SHOW_SAVE,
+ DRC_TOKEN_SHOW_SEARCH,
+ DRC_TOKEN_SHOW_STOP,
+ DRC_TOKEN_SHOW_TOOLTIP,
+ DRC_TOKEN_SHOW_URL,
+ DRC_TOKEN_SMALL_ICONS,
+ DRC_TOKEN_START_PAGE,
+ DRC_TOKEN_TEXT_COLOR,
+ DRC_TOKEN_TRANSIENT_DIALOGS,
+ DRC_TOKEN_USE_DICACHE,
+ DRC_TOKEN_USE_OBLIQUE,
+ DRC_TOKEN_VISITED_COLOR,
+ DRC_TOKEN_VW_FONT,
+ DRC_TOKEN_W3C_PLUS_HEURISTICS
+} RcToken_t;
+
+typedef struct _DilloPrefs DilloPrefs;
+
+struct _DilloPrefs {
+ int width;
+ int height;
+ int xpos;
+ int ypos;
+ DilloUrl *http_proxy;
+ char *http_proxyuser;
+ char *no_proxy;
+ DilloUrl *start_page;
+ DilloUrl *home;
+ int32_t link_color;
+ int32_t visited_color;
+ int32_t bg_color;
+ int32_t text_color;
+ bool_t allow_white_bg;
+ bool_t use_oblique;
+ bool_t force_my_colors;
+ bool_t contrast_visited_color;
+ bool_t show_tooltip;
+ int panel_size;
+ bool_t small_icons;
+ bool_t limit_text_width;
+ bool_t w3c_plus_heuristics;
+ double font_factor;
+ bool_t use_dicache;
+ bool_t show_back;
+ bool_t show_forw;
+ bool_t show_home;
+ bool_t show_reload;
+ bool_t show_save;
+ bool_t show_stop;
+ bool_t show_bookmarks;
+ bool_t show_menubar;
+ bool_t show_clear_url;
+ bool_t show_url;
+ bool_t show_search;
+ bool_t show_progress_box;
+ bool_t fullwindow_start;
+ bool_t transient_dialogs;
+ char *vw_fontname;
+ char *fw_fontname;
+ bool_t generate_submit;
+ bool_t enterpress_forces_submit;
+ char *search_url;
+ bool_t show_msg;
+ bool_t show_extra_warnings;
+};
+
+/* Global Data */
+extern DilloPrefs prefs;
+
+void a_Prefs_init(void);
+void a_Prefs_freeall(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __PREFS_H__ */
diff --git a/src/srch b/src/srch
new file mode 100755
index 00000000..06266382
--- /dev/null
+++ b/src/srch
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Find a token within source files ( *.[ch] )
+# Enjoy!
+# Jorge.-
+
+if [ $# = 1 ]; then
+ FLAGS="-H"
+elif [ $# = 2 ]; then
+ FLAGS="-H $1"
+ shift 1
+else
+ echo "Usage:"
+ echo " srch [-options] <token>"
+ exit 0
+fi
+
+# find "./" -name "*.[ch]" -print -exec grep $1 {} \;
+egrep $FLAGS "$1" *.[ch][ch]
+egrep $FLAGS "$1" *.[ch]
+egrep $FLAGS "$1" IO/*.[ch][ch]*
+egrep $FLAGS "$1" IO/*.[ch]
+egrep $FLAGS "$1" ../dpi/*.[ch]
+egrep $FLAGS "$1" ../dpi/*.[ch][ch]
+egrep $FLAGS "$1" ../dpid/*.[ch]
+egrep $FLAGS "$1" ../dpip/*.[ch]
+egrep $FLAGS "$1" ../dlib/*.[ch]
+
+#egrep $FLAGS "$1" dw/*[ch]
+#egrep $FLAGS "$1" lout/*[ch]
+egrep $FLAGS "$1" ../../dw-testbed/dw/*[ch]
+egrep $FLAGS "$1" ../../dw-testbed/lout/*[ch]
+
diff --git a/src/timeout.cc b/src/timeout.cc
new file mode 100644
index 00000000..da944803
--- /dev/null
+++ b/src/timeout.cc
@@ -0,0 +1,46 @@
+/*
+ * File: timeout.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.
+ */
+
+// Simple ADT for timeout functions
+
+#include <fltk/run.h>
+#include "timeout.hh"
+
+using namespace fltk;
+
+
+// C++ functions with C linkage ----------------------------------------------
+
+/*
+ * Hook a one-time timeout function 'cb' after 't' seconds
+ * with 'cbdata" as its data.
+ */
+void a_Timeout_add(float t, TimeoutCb_t cb, void *cbdata)
+{
+ add_timeout(t, cb, cbdata);
+}
+
+/*
+ * To be called from iside the 'cb' function when it wants to keep running
+ */
+void a_Timeout_repeat(float t, TimeoutCb_t cb, void *cbdata)
+{
+ add_timeout(t, cb, cbdata);
+}
+
+/*
+ * Stop running a timeout function
+ */
+void a_Timeout_remove()
+{
+ /* in FLTK, timeouts run one time by default */
+}
+
diff --git a/src/timeout.hh b/src/timeout.hh
new file mode 100644
index 00000000..5b7f4759
--- /dev/null
+++ b/src/timeout.hh
@@ -0,0 +1,20 @@
+#ifndef __TIMEOUT_HH__
+#define __TIMEOUT_HH__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef void (*TimeoutCb_t)(void *data);
+
+void a_Timeout_add(float t, TimeoutCb_t cb, void *cbdata);
+void a_Timeout_repeat(float t, TimeoutCb_t cb, void *cbdata);
+void a_Timeout_remove();
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __TIMEOUT_HH__ */
+
diff --git a/src/ui.cc b/src/ui.cc
new file mode 100644
index 00000000..54523b88
--- /dev/null
+++ b/src/ui.cc
@@ -0,0 +1,912 @@
+/*
+ * File: ui.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.
+ */
+
+// UI for Dillo
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <fltk/Window.h>
+#include <fltk/Widget.h>
+#include <fltk/Button.h>
+#include <fltk/HighlightButton.h>
+#include <fltk/Input.h>
+#include <fltk/Output.h>
+#include <fltk/run.h>
+#include <fltk/BarGroup.h>
+#include <fltk/PackedGroup.h>
+#include <fltk/xpmImage.h>
+#include <fltk/MultiImage.h>
+#include <fltk/events.h> // for mouse buttons
+#include <fltk/Box.h>
+#include <fltk/InvisibleBox.h>
+#include <fltk/PopupMenu.h>
+#include <fltk/Item.h>
+#include <fltk/Divider.h>
+
+#include "ui.hh"
+#include "msg.h"
+
+using namespace fltk;
+
+
+// Include image data
+#include "pixmaps.h"
+
+#include "uicmd.hh"
+
+/*
+ * Local sub class
+ * (Used to avoid certain shortcuts in the location bar)
+ */
+
+class NewInput : public Input {
+public:
+ NewInput (int x, int y, int w, int h, const char* l=0) :
+ Input(x,y,w,h,l) {};
+ int handle(int e);
+};
+
+/*
+ * Disable: UpKey, DownKey, PageUpKey, PageDownKey and CTRL+{o,HomeKey,EndKey}
+ */
+int NewInput::handle(int e)
+{
+ int k = event_key();
+ bool ctrl = event_state(CTRL);
+
+ _MSG("NewInput::handle event=%d", e);
+ if (ctrl && (k == 'o' || k == HomeKey || k == EndKey))
+ return 0;
+ if ((e == KEY || e == KEYUP) &&
+ k == UpKey || k == DownKey || k == PageUpKey || k == PageDownKey) {
+ _MSG(" {Up|Down|PgUp|PgDn} ret = 1\n");
+ // todo: one way to handle this is to send(SHORTCUT) to the viewport
+ // and return 1 here. A cleaner approach may be to ignore the event and
+ // only allow the location and render area to take focus.
+ //
+ // Currently, zero is returned, so the parent gets the event and moves
+ // focus to a button widget that's not interested in these keys;
+ // once this new widget is focused, Up and Down start to work.
+ return 0;
+
+ //this->window()->send(SHORTCUT);
+ //this->window()->send(e);
+ //return 1;
+ }
+ _MSG("\n");
+
+ return Input::handle(e);
+}
+
+//----------------------------------------------------------------------------
+
+/*
+ * Used to handle "paste" within the toolbar's Clear button.
+ */
+class NewHighlightButton : public HighlightButton {
+public:
+ NewHighlightButton(int x, int y, int w, int h, const char *l=0) :
+ HighlightButton(x,y,w,h,l) {};
+ int handle(int e);
+};
+
+int NewHighlightButton::handle(int e)
+{
+ if (e == PASTE) {
+ const char* t = event_text();
+ if (t && *t) {
+ a_UIcmd_set_location_text(this->window()->user_data(), t);
+ a_UIcmd_open_urlstr(this->window()->user_data(), t);
+ return 1;
+ }
+ }
+ return HighlightButton::handle(e);
+}
+
+//
+// Toolbar buttons -----------------------------------------------------------
+//
+//static const char *button_names[] = {
+// "Back", "Forward", "Home", "Reload", "Save", "Stop", "Bookmarks",
+// "Clear", "Search"
+//};
+
+//
+// Global event handler function ---------------------------------------------
+//
+int global_event_handler(int e, Window *win)
+{
+ int ret = 0;
+
+ if (e == SHORTCUT) {
+ if (event_state(CTRL)) {
+ if (event_key() == 'l') {
+ a_UIcmd_open_url_dialog(win->user_data());
+ ret = 1;
+ } else if (event_key() == 'n') {
+ a_UIcmd_browser_window_new(win->w(), win->h());
+ ret = 1;
+ } else if (event_key() == 'o') {
+ a_UIcmd_open_file(win->user_data());
+ ret = 1;
+ } else if (event_key() == 'q') {
+ a_UIcmd_close_bw(win->user_data());
+ ret = 1;
+ } else if (event_key() == 's') {
+ a_UIcmd_search_dialog(win->user_data());
+ ret = 1;
+ }
+ }
+
+ if (event_state(ALT) && event_key() == 'q') {
+ a_UIcmd_close_all_bw();
+ }
+ }
+ return ret;
+}
+
+//
+// Callback functions --------------------------------------------------------
+//
+
+/*
+ * Callback handler for the close window event.
+ */
+void close_window_cb(Widget *wid, void *data)
+{
+ a_UIcmd_close_bw(data);
+}
+
+/*
+ * Callback for the search button.
+ */
+static void search_cb(Widget *wid, void *data)
+{
+ int k = event_key();
+ if (k && k <= 7)
+ MSG("[Search], mouse button %d was pressed\n", k);
+
+ if (k == 1) {
+ a_UIcmd_search_dialog(wid->window()->user_data());
+ } else if (k == 2) {
+ ((UI*)data)->color_change_cb_i();
+ } else if (k == 3) {
+ ((UI*)data)->panel_cb_i();
+ }
+}
+
+/*
+ * Callback for the location's clear-button.
+ */
+void clear_cb(Widget *w, void *data)
+{
+ UI *ui = (UI*)data;
+
+ int k = event_key();
+ if (k && k <= 7)
+ MSG("[Clear], mouse button %d was pressed\n", k);
+ if (k == 1) {
+ ui->set_location("");
+ ui->focus_location();
+ } if (k == 2) {
+ ui->paste_url();
+ }
+}
+
+/*
+ * Change the color of the location bar.
+ *
+static void color_change_cb(Widget *wid, void *data)
+{
+ ((UI*)data)->color_change_cb_i();
+}
+ */
+
+
+/*
+ * Send the browser to the new URL in the location.
+ */
+void location_cb(Widget *wid, void *data)
+{
+ Input *i = (Input*)wid;
+
+ /* This test is necessary because WHEN_ENTER_KEY also includes
+ * other events we're not interested in */
+ if (event_key() == ReturnKey) {
+ a_UIcmd_open_urlstr(i->window()->user_data(), i->value());
+ }
+}
+
+
+/*
+ * Callback handler for button press on the panel
+ */
+void b1_cb(Widget *wid, void *cb_data)
+{
+ int bn = (int)cb_data;
+ int k = event_key();
+ if (k && k <= 7) {
+ _MSG("[%s], mouse button %d was pressed\n", button_names[bn], k);
+ MSG("mouse button %d was pressed\n", k);
+ }
+ switch (bn) {
+ case UI_BACK:
+ if (k == 1) {
+ a_UIcmd_back(wid->window()->user_data());
+ } else if (k == 3) {
+ a_UIcmd_back_popup(wid->window()->user_data());
+ }
+ break;
+ case UI_FORW:
+ if (k == 1) {
+ a_UIcmd_forw(wid->window()->user_data());
+ } else if (k == 3) {
+ a_UIcmd_forw_popup(wid->window()->user_data());
+ }
+ break;
+ case UI_HOME:
+ if (k == 1) {
+ a_UIcmd_home(wid->window()->user_data());
+ }
+ break;
+ case UI_RELOAD:
+ if (k == 1) {
+ a_UIcmd_reload(wid->window()->user_data());
+ }
+ break;
+ case UI_SAVE:
+ if (k == 1) {
+ a_UIcmd_save(wid->window()->user_data());
+ }
+ break;
+ case UI_STOP:
+ if (k == 1) {
+ a_UIcmd_stop(wid->window()->user_data());
+ }
+ break;
+ case UI_BOOK:
+ if (k == 1) {
+ a_UIcmd_book(wid->window()->user_data());
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * Callback handler for fullscreen button press
+ */
+void fullscreen_cb(Widget *wid, void *data)
+{
+ ((UI*)data)->fullscreen_cb_i();
+}
+
+/*
+ * Callback for the bug meter button.
+ */
+void bugmeter_cb(Widget *w, void *data)
+{
+ int k = event_key();
+ if (k && k <= 7)
+ MSG("[BugMeter], mouse button %d was pressed\n", k);
+ if (k == 1) {
+ a_UIcmd_view_page_bugs(((UI*)data)->user_data());
+ } else if (k == 3) {
+ a_UIcmd_bugmeter_popup(((UI*)data)->user_data());
+ }
+}
+
+/*
+ * File menu item callback.
+ */
+void menu_cb(Widget* w, void*)
+{
+ Menu* menu = (Menu*)w;
+ Widget* item = menu->get_item();
+ MSG("Callback for %s, item is %s\n",
+ menu->label() ? menu->label() : "menu bar",
+ item ? item->label() ? item->label() : "unnamed" : "none");
+ //if (item) item->do_callback();
+ menu->value(-1);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// UI class methods
+//
+
+//----------------------------
+// Panel construction methods
+//----------------------------
+
+/*
+ * Create the archetipic browser buttons
+ */
+PackedGroup *UI::make_toolbar(int tw, int th)
+{
+ HighlightButton *b;
+ MultiImage *multi;
+ PackedGroup *p1=new PackedGroup(0,0,tw,th);
+ p1->begin();
+ Back = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Back" : 0);
+ ImgLeftIns = new xpmImage(Small_Icons ? left_si_xpm : left_i_xpm);
+ ImgLeftSens = new xpmImage(Small_Icons ? left_s_xpm : left_xpm);
+ multi = new MultiImage(*ImgLeftSens, INACTIVE_R, *ImgLeftIns);
+ b->image(multi);
+ b->tooltip("Previous page");
+ b->callback(b1_cb, (void *)UI_BACK);
+ HighlightButton::default_style->highlight_color(CuteColor);
+
+ Forw = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Forw" : 0);
+ ImgRightIns = new xpmImage(Small_Icons ? right_si_xpm : right_i_xpm);
+ ImgRightSens = new xpmImage(Small_Icons ? right_s_xpm : right_xpm);
+ multi = new MultiImage(*ImgRightSens, INACTIVE_R, *ImgRightIns);
+ b->image(multi);
+ b->tooltip("Next page");
+ b->callback(b1_cb, (void *)UI_FORW);
+
+ Home = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Home" : 0);
+ b->image(new xpmImage(Small_Icons ? home_s_xpm : home_xpm));
+ b->tooltip("Go to the Home page");
+ b->callback(b1_cb, (void *)UI_HOME);
+
+ Reload = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Reload" : 0);
+ b->image(new xpmImage(Small_Icons ? reload_s_xpm : reload_xpm));
+ b->tooltip("Reload");
+ b->callback(b1_cb, (void *)UI_RELOAD);
+
+ Save = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Save" : 0);
+ b->image(new xpmImage(Small_Icons ? save_s_xpm : save_xpm));
+ b->tooltip("Save this page");
+ b->callback(b1_cb, (void *)UI_SAVE);
+
+ Stop = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Stop" : 0);
+ ImgStopIns = new xpmImage(Small_Icons ? stop_si_xpm : stop_i_xpm);
+ ImgStopSens = new xpmImage(Small_Icons ? stop_s_xpm : stop_xpm);
+ multi = new MultiImage(*ImgStopSens, INACTIVE_R, *ImgStopIns);
+ b->image(multi);
+ b->tooltip("Stop loading");
+ b->callback(b1_cb, (void *)UI_STOP);
+
+ Bookmarks = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Book" : 0);
+ b->image(new xpmImage(Small_Icons ? bm_s_xpm : bm_xpm));
+ b->tooltip("View bookmarks");
+ b->callback(b1_cb, (void *)UI_BOOK);
+
+ p1->type(PackedGroup::ALL_CHILDREN_VERTICAL);
+ p1->end();
+
+ return p1;
+}
+
+/*
+ * Create the location box (Clear/Input/Search)
+ */
+PackedGroup *UI::make_location()
+{
+ Button *b;
+ PackedGroup *pg = new PackedGroup(0,0,0,0);
+ pg->begin();
+ Clear = b = new NewHighlightButton(2,2,16,22,0);
+ b->image(new xpmImage(new_s_xpm));
+ b->tooltip("Clear the URL box.\nMiddle-click to paste a URL.");
+ //b->callback(b1_cb, (void *)UI_CLEAR);
+ b->callback(clear_cb, (void *)this);
+
+ Input *i = Location = new NewInput(0,0,0,0,0);
+ i->tooltip("Location");
+ i->color(CuteColor);
+ i->when(WHEN_ENTER_KEY);
+ i->callback(location_cb, this);
+
+ Search = b = new HighlightButton(0,0,16,22,0);
+ b->image(new xpmImage(search_xpm));
+ b->tooltip("Search the Web");
+ //b->callback(b1_cb, (void *)UI_SEARCH);
+ b->callback(search_cb, (void *)this);
+
+ pg->type(PackedGroup::ALL_CHILDREN_VERTICAL);
+ pg->resizable(i);
+ pg->end();
+
+ return pg;
+}
+
+/*
+ * Create the progress bars
+ */
+PackedGroup *UI::make_progress_bars(int wide, int thin_up)
+{
+ PackedGroup *pg = new PackedGroup(0,0,0,0);
+ pg->begin();
+ // Images
+ IProg = new InvisibleBox(0,0,pr_w,0,
+ wide ? "Images\n0 of 0" : "0 of 0");
+ IProg->box(thin_up ? THIN_UP_BOX : EMBOSSED_BOX);
+ IProg->labelcolor(GRAY10);
+ // Page
+ PProg = new InvisibleBox(0,0,pr_w,0,
+ wide ? "Page\n0.0KB" : "0.0KB");
+ PProg->box(thin_up ? THIN_UP_BOX : EMBOSSED_BOX);
+ PProg->labelcolor(GRAY10);
+
+ pg->type(PackedGroup::ALL_CHILDREN_VERTICAL);
+ pg->end();
+
+ return pg;
+}
+
+/*
+ * Create the "File" menu
+ */
+Group *UI::make_menu(int tiny)
+{
+ Item *i;
+
+ PopupMenu *pm = new PopupMenu(2,2,30,fh-4, tiny ? "&F" : "&File");
+ pm->callback(menu_cb);
+ pm->begin();
+ i = new Item("&New Browser");
+ i->shortcut(CTRL+'n');
+ i = new Item("&Open File...");
+ i->shortcut(CTRL+'o');
+ i = new Item("Open UR&L...");
+ i->shortcut(CTRL+'l');
+ i = new Item("Close Window");
+ i->shortcut(CTRL+'q');
+ new Divider();
+ i = new Item("Exit Dillo");
+ i->shortcut(ALT+'q');
+
+ pm->end();
+
+ return pm;
+}
+
+/*
+ * Create the control panel
+ */
+Group *UI::make_panel(int ww)
+{
+ Widget *w;
+ Group *g1, *g2, *g3;
+ PackedGroup *pg;
+
+ if (PanelSize > P_large) {
+ PanelSize = P_tiny;
+ Small_Icons = !Small_Icons;
+ }
+
+ if (PanelSize == P_tiny) {
+ if (Small_Icons)
+ xpos = 0, bw = 22, bh = 22, fh = 0, lh = 22, lbl = 0, pr_w = 45;
+ else
+ xpos = 0, bw = 28, bh = 28, fh = 0, lh = 28, lbl = 0, pr_w = 45;
+ } else if (PanelSize == P_small) {
+ if (Small_Icons)
+ xpos = 0, bw = 20, bh = 20, fh = 0, lh = 20, lbl = 0, pr_w = 45;
+ else
+ xpos = 0, bw = 28, bh = 28, fh = 0, lh = 28, lbl = 0, pr_w = 45;
+ } else if (PanelSize == P_medium) {
+ if (Small_Icons)
+ xpos = 0, bw = 42, bh = 36, fh = 0, lh = 22, lbl = 1, pr_w = 60;
+ else
+ xpos = 0, bw = 45, bh = 45, fh = 0, lh = 28, lbl = 1, pr_w = 60;
+ } else { // P_large
+ if (Small_Icons)
+ xpos = 0, bw = 42, bh = 36, fh = 22, lh = 22, lbl = 1, pr_w = 60;
+ else
+ xpos = 0, bw = 45, bh = 45, fh = 28, lh = 28, lbl = 1, pr_w = 60;
+ }
+
+ if (PanelSize == P_tiny) {
+ g1 = new Group(0,0,ww,bh);
+ g1->begin();
+ // Toolbar
+ pg = make_toolbar(ww,bh);
+ pg->box(EMBOSSED_BOX);
+ //pg->box(BORDER_FRAME);
+ w = make_location();
+ pg->add(w);
+ pg->resizable(w);
+ w = make_progress_bars(0,1);
+ pg->add(w);
+
+ g1->resizable(pg);
+ g1->end();
+
+ } else {
+ g1 = new Group(0,0,ww,fh+lh+bh);
+ g1->begin();
+ // File menu
+ if (PanelSize == P_large) {
+ g2 = new Group(0,0,ww,fh);
+ g2->begin();
+ make_menu(0);
+ g2->box(EMBOSSED_BOX);
+ g2->end();
+ }
+
+ // Location
+ g2 = new Group(0,fh,ww,lh);
+ g2->begin();
+ pg = make_location();
+ pg->resize(ww,lh);
+ g2->resizable(pg);
+ g2->end();
+
+ // Toolbar
+ g3 = new Group(0,fh+lh,ww,bh);
+ g3->begin();
+ pg = make_toolbar(ww,bh);
+ //w = new InvisibleBox(0,0,0,0,"i n v i s i b l e");
+ w = new InvisibleBox(0,0,0,0,0);
+ pg->add(w);
+ pg->resizable(w);
+
+ if (PanelSize == P_small) {
+ w = make_progress_bars(0,0);
+ } else {
+ w = make_progress_bars(1,0);
+ }
+ pg->add(w);
+
+ g3->resizable(pg); // Better than 'w3' and it also works
+ pg->box(BORDER_FRAME);
+ //g3->box(EMBOSSED_BOX);
+ g3->end();
+
+ g1->resizable(g3);
+ g1->end();
+ }
+
+ return g1;
+}
+
+/*
+ * User Interface constructor
+ */
+UI::UI(int win_w, int win_h, const char* label) :
+ Window(win_w, win_h, label)
+{
+ int s_h = 20;
+ resizable(this);
+ begin();
+ TopGroup = this;
+
+ // Set handler for the close window event
+ // (the argument is set later via user_data())
+ TopGroup->callback(close_window_cb);
+
+ // Set some default values
+ //PanelSize = P_tiny, CuteColor = 26, Small_Icons = 0;
+ PanelSize = P_medium, CuteColor = 206, Small_Icons = 0;
+
+ // Control panel
+ Panel = make_panel(win_w);
+
+ // Render area
+ Main = new Widget(0,Panel->h(),win_w,win_h-Panel->h()-s_h,"Welcome...");
+ Main->box(FLAT_BOX);
+ Main->color(GRAY15);
+ Main->labelfont(HELVETICA_BOLD_ITALIC);
+ Main->labelsize(36);
+ Main->labeltype(SHADOW_LABEL);
+ Main->labelcolor(WHITE);
+ TopGroup->resizable(Main);
+ MainIdx = TopGroup->find(Main);
+
+ // Status Panel
+ StatusPanel = new Group(0, win_h-s_h, win_w, s_h, 0);
+ StatusPanel->begin();
+ // Status box
+ int bm_w = 16;
+ Status = new Output(0, 0, win_w-bm_w, s_h, 0);
+ Status->value("");
+ //Status->box(UP_BOX);
+ Status->box(THIN_DOWN_BOX);
+ Status->clear_click_to_focus();
+ Status->clear_tab_to_focus();
+ //Status->throw_focus();
+
+ // Bug Meter
+ BugMeter = new HighlightButton(win_w-bm_w,0,bm_w,s_h,0);
+ ImgMeterOK = new xpmImage(mini_ok_xpm);
+ ImgMeterBug = new xpmImage(mini_bug_xpm);
+ BugMeter->image(ImgMeterOK);
+ BugMeter->box(THIN_DOWN_BOX);
+ BugMeter->align(ALIGN_INSIDE|ALIGN_CLIP|ALIGN_LEFT);
+ BugMeter->tooltip("Show HTML bugs\n(right-click for menu)");
+ BugMeter->callback(bugmeter_cb, (void *)this);
+
+ StatusPanel->resizable(Status);
+ StatusPanel->end();
+
+ end();
+
+ // Make the full screen button (to be attached to the viewport later)
+ // TODO: attach to the viewport
+ FullScreen = new HighlightButton(0,0,15,15);
+ ImgFullScreenOn = new xpmImage(full_screen_on_xpm);
+ ImgFullScreenOff = new xpmImage(full_screen_off_xpm);
+ FullScreen->image(ImgFullScreenOn);
+ FullScreen->tooltip("Hide Controls");
+ FullScreen->callback(fullscreen_cb, (void *)this);
+
+ add_event_handler(global_event_handler);
+
+ //show();
+}
+
+/*
+ * FLTK event handler for this window.
+ */
+int UI::handle(int event)
+{
+ if (event == KEY || event == KEYUP) {
+ _MSG ("UI::handle %s key=%d\n",
+ event == KEY ? "KEY" : "KEYUP", event_key());
+ return 0;
+ }
+ return Window::handle(event);
+}
+
+
+//----------------------------
+// API for the User Interface
+//----------------------------
+
+/*
+ * Get the text from the location input-box.
+ */
+const char *UI::get_location()
+{
+ return Location->value();
+}
+
+/*
+ * Set a new URL in the location input-box.
+ */
+void UI::set_location(const char *str)
+{
+ Location->static_value("");
+ Location->insert(str);
+}
+
+/*
+ * Focus location entry.
+ */
+void UI::focus_location()
+{
+ Location->take_focus();
+}
+
+/*
+ * Set a new message in the status bar.
+ */
+void UI::set_status(const char *str)
+{
+ Status->value(str);
+}
+
+/*
+ * Set the page progress text
+ * cmd: 0 Deactivate, 1 Update, 2 Clear
+ */
+void UI::set_page_prog(size_t nbytes, int cmd)
+{
+ char str[32];
+
+ if (cmd == 0) {
+ PProg->deactivate();
+ } else {
+ PProg->activate();
+ if (cmd == 1) {
+ snprintf(str, 32, "%s%.1f KB",
+ (PanelSize == 0) ? "" : "Page\n", nbytes/1024.0);
+ } else if (cmd == 2) {
+ str[0] = '\0';
+ }
+ PProg->copy_label(str);
+ PProg->redraw_label();
+ }
+}
+
+/*
+ * Set the image progress text
+ * cmd: 0 Deactivate, 1 Update, 2 Clear
+ */
+void UI::set_img_prog(int n_img, int t_img, int cmd)
+{
+ char str[32];
+
+ if (cmd == 0) {
+ IProg->deactivate();
+ } else {
+ IProg->activate();
+ if (cmd == 1) {
+ snprintf(str, 32, "%s%d of %d",
+ (PanelSize == 0) ? "" : "Images\n", n_img, t_img);
+ } else if (cmd == 2) {
+ str[0] = '\0';
+ }
+ IProg->copy_label(str);
+ IProg->redraw_label();
+ }
+}
+
+/*
+ * Set the bug meter progress text
+ */
+void UI::set_bug_prog(int n_bug)
+{
+ char str[32];
+ int new_w = 16;
+
+ if (n_bug == 0) {
+ BugMeter->image(ImgMeterOK);
+ BugMeter->label("");
+ } else if (n_bug >= 1) {
+ if (n_bug == 1)
+ BugMeter->image(ImgMeterBug);
+ snprintf(str, 32, "%d", n_bug);
+ BugMeter->copy_label(str);
+ BugMeter->redraw_label();
+ new_w = strlen(str)*8 + 20;
+ }
+ Status->resize(0,0,StatusPanel->w()-new_w,Status->h());
+ BugMeter->resize(StatusPanel->w()-new_w, 0, new_w, BugMeter->h());
+ StatusPanel->init_sizes();
+}
+
+/*
+ * Customize the UI's panel (show/hide buttons)
+ */
+void UI::customize(int flags)
+{
+ Save->hide();
+}
+
+/*
+ * On-the-fly panel style change
+ */
+void UI::panel_cb_i()
+{
+ Group *NewPanel;
+
+ // Create a new Panel
+ ++PanelSize;
+ NewPanel = make_panel(TopGroup->w());
+ TopGroup->replace(*Panel, *NewPanel);
+ delete(Panel);
+ Panel = NewPanel;
+ // Scale the viewport
+ int p_h = Panel->h();
+ Main->resize(0, p_h, TopGroup->w(), TopGroup->h() - p_h - Status->h());
+ TopGroup->init_sizes();
+
+ Location->take_focus();
+}
+
+/*
+ * On-the-fly color style change
+ */
+void UI::color_change_cb_i()
+{
+ static int ncolor = 0, cols[] = {7,17,26,51,140,156,205,206,215,-1};
+
+ ncolor = (cols[ncolor+1] < 0) ? 0 : ncolor + 1;
+ CuteColor = cols[ncolor];
+ MSG("Location color %d\n", CuteColor);
+ Location->color(CuteColor);
+ Location->redraw();
+ HighlightButton::default_style->highlight_color(CuteColor);
+}
+
+/*
+ * Toggle the Control Panel out of the way
+ */
+void UI::fullscreen_cb_i()
+{
+#if 0
+ // Works, but for unknown reasons it resizes hidden widgets on "Maximize"
+ if (Panel->visible_r()) {
+ Panel->hide();
+ Status->hide();
+ // Scale the viewport
+ Main->resize(0, 0, TopGroup->w(), TopGroup->h());
+ TopGroup->init_sizes();
+ } else {
+ // Scale the viewport
+ int p_h = Panel->h();
+ Main->resize(0, p_h, TopGroup->w(), TopGroup->h() - p_h - Status->h());
+ Panel->show();
+ Status->show();
+ TopGroup->init_sizes();
+ }
+#else
+ if (Panel->w() != 0) {
+ Panel_h = Panel->h();
+ Status_h = Status->h();
+ Panel->resize(0, 0);
+ StatusPanel->resize(0, 0);
+ // Scale the viewport
+ Main->resize(0, 0, TopGroup->w(), TopGroup->h());
+ TopGroup->init_sizes();
+ } else {
+ Panel->resize(TopGroup->w(), Panel_h);
+ Main->resize(0,Panel_h,TopGroup->w(),TopGroup->h()-Panel_h-Status_h);
+ StatusPanel->resize(0,TopGroup->h()-Status_h, TopGroup->w(), Status_h);
+ TopGroup->init_sizes();
+ }
+#endif
+}
+
+/*
+ * Set 'nw' as the main render area widget
+ */
+void UI::set_render_layout(Widget &nw)
+{
+ // BUG: replace() is not working as it should.
+ // In our case, replacing the rendering area leaves the vertical
+ // scrollbar without events.
+ //
+ // We'll use a workaround in a_UIcmd_browser_window_new() instead.
+
+ TopGroup->replace(MainIdx, nw);
+ delete(Main);
+ Main = &nw;
+ //TopGroup->box(DOWN_BOX);
+ //TopGroup->box(BORDER_FRAME);
+ TopGroup->resizable(TopGroup->child(MainIdx));
+}
+
+/*
+ * Set the window title
+ */
+void UI::set_page_title(const char *label)
+{
+ char title[128];
+
+ snprintf(title, 128, "Dillo: %s", label);
+ this->copy_label(title);
+ this->redraw_label();
+}
+
+/*
+ * Set button sensitivity (Back/Forw/Stop)
+ */
+void UI::button_set_sens(UIButton btn, int sens)
+{
+ switch (btn) {
+ case UI_BACK:
+ (sens) ? Back->activate() : Back->deactivate();
+ break;
+ case UI_FORW:
+ (sens) ? Forw->activate() : Forw->deactivate();
+ break;
+ case UI_STOP:
+ (sens) ? Stop->activate() : Stop->deactivate();
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * Paste a middle-click-selection into "Clear" button as URL
+ */
+void UI::paste_url()
+{
+ paste(*Clear, false);
+}
+
diff --git a/src/ui.hh b/src/ui.hh
new file mode 100644
index 00000000..7e3527ab
--- /dev/null
+++ b/src/ui.hh
@@ -0,0 +1,109 @@
+#ifndef __UI_HH__
+#define __UI_HH__
+
+// UI for dillo --------------------------------------------------------------
+
+#include <fltk/Window.h>
+#include <fltk/Widget.h>
+#include <fltk/Button.h>
+#include <fltk/Input.h>
+#include <fltk/PackedGroup.h>
+#include <fltk/Output.h>
+#include <fltk/xpmImage.h>
+
+using namespace fltk;
+
+// Panel sizes
+enum { P_tiny = 0, P_small, P_medium, P_large };
+
+typedef enum {
+ UI_BACK = 0,
+ UI_FORW,
+ UI_HOME,
+ UI_RELOAD,
+ UI_SAVE,
+ UI_STOP,
+ UI_BOOK,
+ UI_CLEAR,
+ UI_SEARCH
+} UIButton;
+
+//
+// UI class definition -------------------------------------------------------
+//
+class UI : public fltk::Window {
+ Group *TopGroup;
+ Button *Back, *Forw, *Home, *Reload, *Save, *Stop, *Bookmarks,
+ *Clear, *Search, *FullScreen, *BugMeter;
+ Input *Location;
+ Widget *PProg, *IProg;
+ Image *ImgLeftIns, *ImgLeftSens, *ImgRightIns, *ImgRightSens,
+ *ImgStopIns, *ImgStopSens, *ImgFullScreenOn, *ImgFullScreenOff,
+ *ImgMeterOK, *ImgMeterBug;
+ Group *Panel, *StatusPanel;
+ Widget *Main;
+ Output *Status;
+
+ int MainIdx;
+ // Panel customization variables
+ int PanelSize, CuteColor, Small_Icons;
+ int xpos, bw, bh, fh, lh, lbl, pr_w;
+
+ // TODO: Hack for fullscreen mode
+ int Panel_h, Status_h;
+
+ PackedGroup *make_toolbar(int tw, int th);
+ PackedGroup *make_location();
+ PackedGroup *make_progress_bars(int wide, int thin_up);
+ Group *make_menu(int tiny);
+ Group *make_panel(int ww);
+
+
+public:
+
+ UI(int w, int h, const char* label = 0);
+ ~UI() {} // TODO: implement destructor
+
+ // To manage what events to catch and which to let pass
+ int handle(int event);
+
+ const char *get_location();
+ void set_location(const char *str);
+ void focus_location();
+ void set_status(const char *str);
+ void set_page_prog(size_t nbytes, int cmd);
+ void set_img_prog(int n_img, int t_img, int cmd);
+ void set_bug_prog(int n_bug);
+ void set_render_layout(Widget &nw);
+ void set_page_title(const char *label);
+ void customize(int flags);
+ void button_set_sens(UIButton btn, int sens);
+ void paste_url();
+
+ // Workaround functions for a non-working replace() in FLTK2
+ void set_render_layout_begin() {
+ TopGroup->remove(MainIdx);
+ TopGroup->remove(TopGroup->find(StatusPanel));
+ delete(Main);
+ Main = NULL;
+ TopGroup->begin();
+ }
+ void set_render_layout_end() {
+ TopGroup->resizable(TopGroup->child(MainIdx));
+ Main = TopGroup->child(MainIdx);
+ TopGroup->add(*StatusPanel);
+ TopGroup->end();
+ }
+ int panel_h() { return Panel->h(); };
+ int status_h() { return Status->h(); };
+ Widget *fullscreen_button() { return FullScreen; }
+ void fullscreen_toggle() { FullScreen->do_callback(); }
+
+ // Hooks to method callbacks
+ void panel_cb_i();
+ void color_change_cb_i();
+ void toggle_cb_i();
+ void fullscreen_cb_i();
+};
+
+#endif // __UI_HH__
diff --git a/src/uicmd.cc b/src/uicmd.cc
new file mode 100644
index 00000000..d5f44e6b
--- /dev/null
+++ b/src/uicmd.cc
@@ -0,0 +1,644 @@
+/*
+ * File: uicmd.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.
+ */
+
+// Functions/Methods for commands triggered from the UI
+
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <fltk/Widget.h>
+
+#include "dir.h"
+#include "ui.hh"
+#include "uicmd.hh"
+#include "timeout.hh"
+#include "menu.hh"
+#include "dialog.hh"
+#include "bookmark.h"
+#include "history.h"
+#include "msg.h"
+#include "prefs.h"
+
+#include "dw/fltkviewport.hh"
+
+#include "nav.h"
+
+// Platform idependent part
+using namespace dw::core;
+// FLTK related
+using namespace dw::fltk;
+
+typedef struct {
+ UI *ui;
+ BrowserWindow *bw;
+} Uibw;
+
+/*
+ * Local data
+ */
+// A matching table for all open ui/bw pairs
+// BUG: must be dynamic.
+static Uibw uibws[32];
+static int uibws_num = 0, uibws_max = 32;
+
+static char *save_dir = NULL;
+
+using namespace fltk;
+
+
+/*
+ * Create a new UI and its associated BrowserWindow data structure.
+ */
+BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh)
+{
+ if (ww <= 0 || wh <= 0) {
+ // TODO: set default geometry from dillorc.
+ ww = 780;
+ wh = 580;
+ }
+
+ // Create and set the UI
+ UI *new_ui = new UI(ww, wh, "Dillo: UI");
+ new_ui->set_status("http://www.dillo.org/");
+ //new_ui->set_location("http://dillo.org/");
+ //new_ui->customize(12);
+
+ // Now create the Dw render layout and viewport
+ FltkPlatform *platform = new FltkPlatform ();
+ Layout *layout = new Layout (platform);
+
+ // BUG: This is a workaround for FLTK's non-working replace().
+ new_ui->set_render_layout_begin();
+ int p_h = new_ui->panel_h();
+ int s_h = new_ui->status_h();
+ FltkViewport *viewport = new FltkViewport (0, p_h, ww, wh-p_h-s_h);
+ layout->attachView (viewport);
+ //viewport->addGadget(new_ui->fullscreen_button());
+ new_ui->set_render_layout_end();
+ // This was the original code.
+ // Set the render_layout widget into the UI
+ // new_ui->set_render_layout(*viewport);
+
+ // Now, create a new browser window structure
+ BrowserWindow *new_bw = a_Bw_new(ww, wh, 0);
+
+ // Set new_bw as callback data for UI
+ new_ui->user_data(new_bw);
+ // Reference the UI from the bw
+ new_bw->ui = (void *)new_ui;
+ // Copy the layout pointer into the bw data
+ new_bw->render_layout = (void*)layout;
+
+ // insert the new ui/bw pair in the table
+ if (uibws_num < uibws_max) {
+ uibws[uibws_num].ui = new_ui;
+ uibws[uibws_num].bw = new_bw;
+ uibws_num++;
+ }
+
+ new_ui->show();
+
+ return new_bw;
+}
+
+/*
+ * Close one browser window
+ */
+void a_UIcmd_close_bw(void *vbw)
+{
+ BrowserWindow *bw = (BrowserWindow *)vbw;
+ UI *ui = (UI*)bw->ui;
+ Layout *layout = (Layout*)bw->render_layout;
+
+ MSG("a_UIcmd_close_bw\n");
+ ui->destroy();
+ delete(layout);
+ a_Bw_free(bw);
+}
+
+/*
+ * Close all the browser windows
+ */
+void a_UIcmd_close_all_bw()
+{
+ BrowserWindow *bw;
+
+ while ((bw = a_Bw_get()))
+ a_UIcmd_close_bw((void*)bw);
+}
+
+/*
+ * Open a new URL in the given browser window.
+ *
+ * our custom "file:" URIs are normalized here too.
+ */
+void a_UIcmd_open_urlstr(void *vbw, const char *urlstr)
+{
+ char *new_urlstr;
+ DilloUrl *url;
+ int ch;
+ BrowserWindow *bw = (BrowserWindow*)vbw;
+
+ if (urlstr && *urlstr) {
+ /* Filter URL string */
+ new_urlstr = a_Url_string_strip_delimiters(urlstr);
+
+ if (!dStrncasecmp(new_urlstr, "file:", 5)) {
+ /* file URI */
+ ch = new_urlstr[5];
+ if (!ch || ch == '.') {
+ url = a_Url_new(a_Dir_get_owd(), "file:", 0, 0, 0);
+ } else if (ch == '~') {
+ url = a_Url_new(dGethomedir(), "file:", 0, 0, 0);
+ } else {
+ url = a_Url_new(new_urlstr, "file:", 0, 0, 0);
+ }
+
+ } else {
+ /* common case */
+ url = a_Url_new(new_urlstr, NULL, 0, 0, 0);
+ }
+ dFree(new_urlstr);
+
+ if (url) {
+ a_Nav_push(bw, url);
+ a_Url_free(url);
+ }
+ }
+
+ /* let the rendered area have focus */
+ //gtk_widget_grab_focus(GTK_BIN(bw->render_main_scroll)->child);
+}
+
+/*
+ * Open a new URL in the given browser window
+ */
+void a_UIcmd_open_url_nw(BrowserWindow *bw, DilloUrl *url)
+{
+ a_Nav_push_nw(bw, url);
+}
+
+/*
+ * Send the browser back to previous page
+ */
+void a_UIcmd_back(void *vbw)
+{
+ a_Nav_back((BrowserWindow*)vbw);
+}
+
+/*
+ * Popup the navigation menu of the Back button
+ */
+void a_UIcmd_back_popup(void *vbw)
+{
+ a_Menu_history_popup((BrowserWindow*)vbw, -1);
+}
+
+/*
+ * Send the browser to next page in the history list
+ */
+void a_UIcmd_forw(void *vbw)
+{
+ a_Nav_forw((BrowserWindow*)vbw);
+}
+
+/*
+ * Popup the navigation menu of the Forward button
+ */
+void a_UIcmd_forw_popup(void *vbw)
+{
+ a_Menu_history_popup((BrowserWindow*)vbw, 1);
+}
+
+/*
+ * Send the browser to home URL
+ */
+void a_UIcmd_home(void *vbw)
+{
+ a_Nav_home((BrowserWindow*)vbw);
+}
+
+/*
+ * Reload current URL
+ */
+void a_UIcmd_reload(void *vbw)
+{
+ a_Nav_reload((BrowserWindow*)vbw);
+}
+
+/*
+ * Return a suitable filename for a given URL.
+ */
+char *UIcmd_make_save_filename(const char *urlstr)
+{
+ size_t MaxLen = 64;
+ char *FileName, *name;
+ const char *dir = a_UIcmd_get_save_dir();
+
+ if ((name = strrchr(urlstr, '/'))) {
+ if (strlen(++name) > MaxLen) {
+ name = name + strlen(name) - MaxLen;
+ }
+ FileName = dStrconcat(dir ? dir : "", name, NULL);
+ } else {
+ FileName = dStrconcat(dir ? dir : "", urlstr, NULL);
+ }
+ return FileName;
+}
+
+/*
+ * Get the default directory for saving files.
+ */
+const char *a_UIcmd_get_save_dir()
+{
+ return save_dir;
+}
+
+/*
+ * Set the default directory for saving files.
+ */
+void a_UIcmd_set_save_dir(const char *dir)
+{
+ char *p;
+
+ if (dir && (p = strrchr(dir, '/'))) {
+ dFree(save_dir);
+ // assert a trailing '/'
+ save_dir = dStrconcat(dir, (p[1] != 0) ? "/" : "", NULL);
+ }
+}
+
+/*
+ * Save current URL
+ */
+void a_UIcmd_save(void *vbw)
+{
+ const char *name;
+ char *SuggestedName, *urlstr;
+ DilloUrl *url;
+
+ // BUG: this should be set by preferences.
+ a_UIcmd_set_save_dir("/tmp/k/");
+
+ urlstr = a_UIcmd_get_location_text((BrowserWindow*)vbw);
+ url = a_Url_new(urlstr, NULL, 0, 0, 0);
+ SuggestedName = UIcmd_make_save_filename(urlstr);
+ name = a_Dialog_save_file("Save Page as File", NULL, SuggestedName);
+ MSG("a_UIcmd_save: %s\n", name);
+ dFree(SuggestedName);
+ dFree(urlstr);
+
+ if (name) {
+ a_Nav_save_url((BrowserWindow*)vbw, url, name);
+ }
+
+ a_Url_free(url);
+}
+
+/*
+ * Stop network activity on this bw.
+ * The stop button was pressed: stop page (and images) downloads.
+ */
+void a_UIcmd_stop(void *vbw)
+{
+ BrowserWindow *bw = (BrowserWindow *)vbw;
+
+ MSG("a_UIcmd_stop()\n");
+ a_Bw_stop_clients(bw, BW_Root + BW_Img + Bw_Force);
+ a_UIcmd_set_buttons_sens(bw);
+}
+
+/*
+ * Open URL with dialog chooser
+ */
+void a_UIcmd_open_file(void *vbw)
+{
+ char *name;
+ DilloUrl *url;
+
+ name = a_Dialog_open_file("Open File", NULL, "");
+
+ if (name) {
+ url = a_Url_new(name, "file:", 0, 0, 0);
+ a_Nav_push((BrowserWindow*)vbw, url);
+ a_Url_free(url);
+ dFree(name);
+ }
+}
+
+/*
+ * Get an URL from a dialog and open it
+ */
+void a_UIcmd_open_url_dialog(void *vbw)
+{
+ const char *urlstr;
+
+ if ((urlstr = a_Dialog_input("Please enter a URL:"))) {
+ a_UIcmd_open_urlstr(vbw, urlstr);
+ }
+}
+
+/*
+ * Returns a newly allocated string holding a search url generated from
+ * a string of keywords (separarated by blanks) and prefs.search_url.
+ * The search string is urlencoded.
+ */
+char *UIcmd_make_search_str(const char *str)
+{
+ char *keys = a_Url_encode_hex_str(str), *c = prefs.search_url;
+ Dstr *ds = dStr_sized_new(128);
+ char *search_url;
+
+ for (; *c; c++) {
+ if (*c == '%')
+ switch(*++c) {
+ case 's':
+ dStr_append(ds, keys); break;;
+ case '%':
+ dStr_append_c(ds, '%'); break;;
+ case 0:
+ MSG_WARN("search_url ends with '%%'\n"); c--; break;;
+ default:
+ MSG_WARN("illegal specifier '%%%c' in search_url\n", *c);
+ }
+ else
+ dStr_append_c(ds, *c);
+ }
+ dFree(keys);
+
+ search_url = ds->str;
+ dStr_free(ds, 0);
+ return search_url;
+}
+
+/*
+ * Get a query from a dialog and open it
+ */
+void a_UIcmd_search_dialog(void *vbw)
+{
+ const char *query, *url_str;
+
+ if ((query = a_Dialog_input("Search the Web:"))) {
+ url_str = UIcmd_make_search_str(query);
+ a_UIcmd_open_urlstr(vbw, url_str);
+ }
+}
+
+/*
+ * Save link URL
+ */
+void a_UIcmd_save_link(BrowserWindow *bw, const DilloUrl *url)
+{
+ const char *name;
+ char *SuggestedName;
+
+ // BUG: this should be set by preferences.
+ a_UIcmd_set_save_dir("/tmp/k/");
+
+ SuggestedName = UIcmd_make_save_filename(URL_STR(url));
+ name = a_Dialog_save_file("Save Link as File", NULL, SuggestedName);
+ MSG("a_UIcmd_save_link: %s\n", name);
+ dFree(SuggestedName);
+
+ if (name) {
+ a_Nav_save_url(bw, url, name);
+ }
+}
+
+/*
+ * Request the bookmarks page
+ */
+void a_UIcmd_book(void *vbw)
+{
+ DilloUrl *url = a_Url_new("dpi:/bm/", NULL, 0, 0, 0);
+ a_Nav_push((BrowserWindow*)vbw, url);
+ a_Url_free(url);
+}
+
+/*
+ * Add a bookmark for a certain URL
+ */
+void a_UIcmd_add_bookmark(BrowserWindow *bw, DilloUrl *url)
+{
+ a_Bookmarks_add(bw, url);
+}
+
+
+/*
+ * Popup the page menu
+ */
+void a_UIcmd_page_popup(void *vbw, DilloUrl *url, const char *bugs_txt)
+{
+ a_Menu_page_popup((BrowserWindow*)vbw, url, bugs_txt);
+}
+
+/*
+ * Popup the link menu
+ */
+void a_UIcmd_link_popup(void *vbw, DilloUrl *url)
+{
+ a_Menu_link_popup((BrowserWindow*)vbw, url);
+}
+
+/*
+ * Show a text window with the URL's source
+ */
+void a_UIcmd_view_page_source(DilloUrl *url)
+{
+ char *buf;
+ int buf_size;
+
+ if (a_Nav_get_buf(url, &buf, &buf_size)) {
+ a_Dialog_text_window(buf, "View Page source");
+ }
+}
+
+/*
+ * Show a text window with the URL's source
+ */
+void a_UIcmd_view_page_bugs(void *vbw)
+{
+ BrowserWindow *bw = (BrowserWindow*)vbw;
+
+ if (bw->num_page_bugs > 0) {
+ a_Dialog_text_window(bw->page_bugs->str, "Detected HTML errors");
+ } else {
+ a_Dialog_msg("Zero detected HTML errors!");
+ }
+}
+
+/*
+ * Popup the bug meter menu
+ */
+void a_UIcmd_bugmeter_popup(void *vbw)
+{
+ BrowserWindow *bw = (BrowserWindow*)vbw;
+
+ a_Menu_bugmeter_popup(bw, a_History_get_url(NAV_TOP(bw)));
+}
+
+/*
+ * Make a list of URL indexes for the history popup
+ * based on direction (-1 = back, 1 = forward)
+ */
+int *a_UIcmd_get_history(BrowserWindow *bw, int direction)
+{
+ int i, j, n;
+ int *hlist;
+
+ // Count the number of URLs
+ i = a_Nav_stack_ptr(bw) + direction;
+ for (n = 0 ; i >= 0 && i < a_Nav_stack_size(bw); i+=direction)
+ ++n;
+ hlist = dNew(int, n + 1);
+
+ // Fill the list
+ i = a_Nav_stack_ptr(bw) + direction;
+ for (j = 0 ; i >= 0 && i < a_Nav_stack_size(bw); i+=direction, j += 1) {
+ hlist[j] = NAV_IDX(bw,i);
+ }
+ hlist[j] = -1;
+
+ return hlist;
+}
+
+/*
+ * Jump to a certain URL in the navigation stack.
+ */
+void a_UIcmd_nav_jump(BrowserWindow *bw, int offset, int new_bw)
+{
+ a_Nav_jump(bw, offset, new_bw);
+}
+
+// UI binding functions -------------------------------------------------------
+
+#define BW2UI(bw) ((UI*)(bw->ui))
+
+/*
+ * Return browser window width and height
+ */
+void a_UIcmd_get_wh(BrowserWindow *bw, int *w, int *h)
+{
+ *w = BW2UI(bw)->w();
+ *h = BW2UI(bw)->h();
+ _MSG("a_UIcmd_wh: w=%d, h=%d\n", *w, *h);
+}
+
+/*
+ * Get location's text
+ */
+char *a_UIcmd_get_location_text(BrowserWindow *bw)
+{
+ return dStrdup(BW2UI(bw)->get_location());
+}
+
+/*
+ * Set location's text
+ */
+void a_UIcmd_set_location_text(void *vbw, const char *text)
+{
+ BrowserWindow *bw = (BrowserWindow*)vbw;
+ BW2UI(bw)->set_location(text);
+}
+
+/*
+ * Set the page progress bar
+ * cmd: 0 Deactivate, 1 Update, 2 Clear
+ */
+void a_UIcmd_set_page_prog(BrowserWindow *bw, size_t nbytes, int cmd)
+{
+ BW2UI(bw)->set_page_prog(nbytes, cmd);
+}
+
+/*
+ * Set the images progress bar
+ * cmd: 0 Deactivate, 1 Update, 2 Clear
+ */
+void a_UIcmd_set_img_prog(BrowserWindow *bw, int n_img, int t_img, int cmd)
+{
+ BW2UI(bw)->set_img_prog(n_img, t_img, cmd);
+}
+
+/*
+ * Set the bug meter progress label
+ */
+void a_UIcmd_set_bug_prog(BrowserWindow *bw, int n_bug)
+{
+ BW2UI(bw)->set_bug_prog(n_bug);
+}
+
+/*
+ * Set the page title.
+ * now it goes to the window titlebar (maybe to TAB label in the future).
+ */
+void a_UIcmd_set_page_title(BrowserWindow *bw, const char *label)
+{
+ BW2UI(bw)->set_page_title(label);
+}
+
+/*
+ * Set a printf-like status string on the bottom of the dillo window.
+ * Beware: The safe way to set an arbitrary string is
+ * a_UIcmd_set_msg(bw, "%s", str)
+ */
+void a_UIcmd_set_msg(BrowserWindow *bw, const char *format, ...)
+{
+ va_list argp;
+ Dstr *ds = dStr_sized_new(128);
+ va_start(argp, format);
+ dStr_vsprintf(ds, format, argp);
+ va_end(argp);
+ BW2UI(bw)->set_status(ds->str);
+ dStr_free(ds, 1);
+}
+
+/*
+ * Set the sensitivity of back/forw/stop buttons.
+ */
+static void a_UIcmd_set_buttons_sens_cb(void *vbw)
+{
+ int sens;
+ BrowserWindow *bw = (BrowserWindow*)vbw;
+
+ // Stop
+ sens = (dList_length(bw->ImageClients) || dList_length(bw->RootClients));
+ BW2UI(bw)->button_set_sens(UI_STOP, sens);
+ // Back
+ sens = (a_Nav_stack_ptr(bw) > 0);
+ BW2UI(bw)->button_set_sens(UI_BACK, sens);
+ // Forward
+ sens = (a_Nav_stack_ptr(bw) < a_Nav_stack_size(bw) - 1 &&
+ !bw->nav_expecting);
+ BW2UI(bw)->button_set_sens(UI_FORW, sens);
+
+ bw->sens_idle_up = 0;
+}
+
+
+/*
+ * Set the timeout function for button sensitivity
+ */
+void a_UIcmd_set_buttons_sens(BrowserWindow *bw)
+{
+ if (bw->sens_idle_up == 0) {
+ a_Timeout_add(0.0, a_UIcmd_set_buttons_sens_cb, bw);
+ bw->sens_idle_up = 1;
+ }
+}
+
+/*
+ * Toggle control panel (aka. fullscreen)
+ */
+void a_UIcmd_fullscreen_toggle(BrowserWindow *bw)
+{
+ BW2UI(bw)->fullscreen_toggle();
+}
+
diff --git a/src/uicmd.hh b/src/uicmd.hh
new file mode 100644
index 00000000..8ebbb086
--- /dev/null
+++ b/src/uicmd.hh
@@ -0,0 +1,62 @@
+#ifndef __UICMD_HH__
+#define __UICMD_HH__
+
+#include "bw.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh);
+void a_UIcmd_open_urlstr(void *vbw, const char *urlstr);
+void a_UIcmd_open_url_nw(BrowserWindow *bw, DilloUrl *url);
+void a_UIcmd_back(void *vbw);
+void a_UIcmd_back_popup(void *vbw);
+void a_UIcmd_forw(void *vbw);
+void a_UIcmd_forw_popup(void *vbw);
+void a_UIcmd_home(void *vbw);
+void a_UIcmd_reload(void *vbw);
+void a_UIcmd_save(void *vbw);
+void a_UIcmd_stop(void *vbw);
+void a_UIcmd_save_link(BrowserWindow *bw, const DilloUrl *url);
+void a_UIcmd_open_file(void *vbw);
+void a_UIcmd_open_url_dialog(void *vbw);
+void a_UIcmd_search_dialog(void *vbw);
+void a_UIcmd_book(void *vbw);
+void a_UIcmd_add_bookmark(BrowserWindow *bw, DilloUrl *url);
+void a_UIcmd_fullscreen_toggle(BrowserWindow *bw);
+void a_UIcmd_page_popup(void *vbw, DilloUrl *url, const char *bugs_txt);
+void a_UIcmd_link_popup(void *vbw, DilloUrl *url);
+void a_UIcmd_view_page_source(DilloUrl *url);
+void a_UIcmd_view_page_bugs(void *vbw);
+void a_UIcmd_bugmeter_popup(void *vbw);
+int *a_UIcmd_get_history(BrowserWindow *bw, int direction);
+void a_UIcmd_nav_jump(BrowserWindow *bw, int offset, int new_bw);
+
+void a_UIcmd_close_bw(void *vbw);
+void a_UIcmd_close_all_bw();
+
+const char *a_UIcmd_get_save_dir();
+void a_UIcmd_set_save_dir(const char *dir);
+
+
+// UI binding functions -------------------------------------------------------
+
+void a_UIcmd_get_wh(BrowserWindow *bw, int *w, int *h);
+char *a_UIcmd_get_location_text(BrowserWindow *bw);
+void a_UIcmd_set_location_text(void *vbw, const char *text);
+void a_UIcmd_set_page_prog(BrowserWindow *bw, size_t nbytes, int cmd);
+void a_UIcmd_set_img_prog(BrowserWindow *bw, int n_img, int t_img, int cmd);
+void a_UIcmd_set_bug_prog(BrowserWindow *bw, int n_bug);
+void a_UIcmd_set_page_title(BrowserWindow *bw, const char *label);
+void a_UIcmd_set_msg(BrowserWindow *bw, const char *format, ...);
+void a_UIcmd_set_buttons_sens(BrowserWindow *bw);
+void a_UIcmd_fullscreen_toggle(BrowserWindow *bw);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif // __UICMD_HH__
diff --git a/src/url.c b/src/url.c
new file mode 100644
index 00000000..6e1805bf
--- /dev/null
+++ b/src/url.c
@@ -0,0 +1,632 @@
+/*
+ * File: url.c
+ *
+ * Copyright (C) 2001 Jorge Arellano Cid <jcid@dillo.org>
+ * 2001 Livio Baldini Soares <livio@linux.ime.usp.br>
+ *
+ * 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.
+ */
+
+/*
+ * Parse and normalize all URL's inside Dillo.
+ * - <scheme> <authority> <path> <query> and <fragment> point to 'buffer'.
+ * - 'url_string' is built upon demand (transparent to the caller).
+ * - 'hostname' and 'port' are also being handled on demand.
+ */
+
+/*
+ * Regular Expression as given in RFC2396 for URL parsing.
+ *
+ * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ * 12 3 4 5 6 7 8 9
+ *
+ * scheme = $2
+ * authority = $4
+ * path = $5
+ * query = $7
+ * fragment = $9
+ *
+ *
+ * RFC-2396 BNF:
+ *
+ * absoluteURI = scheme ":" (hier_part | opaque_part)
+ * hier_part = (net_path | abs_path) ["?" query]
+ * net_path = "//" authority[abs_path]
+ * abs_path = "/" path_segments
+ *
+ * Notes:
+ * - "undefined" means "preceeding separator does not appear".
+ * - path is never "undefined" though it may be "empty".
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "url.h"
+
+//#define DEBUG_LEVEL 2
+#include "debug.h"
+
+
+/*
+ * Return the url as a string.
+ * (initializing 'url_string' camp if necessary)
+ */
+char *a_Url_str(const DilloUrl *u)
+{
+ /* Internal url handling IS transparent to the caller */
+ DilloUrl *url = (DilloUrl *) u;
+
+ dReturn_val_if_fail (url != NULL, NULL);
+
+ if (!url->url_string) {
+ url->url_string = dStr_sized_new(60);
+ dStr_sprintf(
+ url->url_string, "%s%s%s%s%s%s%s%s%s%s",
+ url->scheme ? url->scheme : "",
+ url->scheme ? ":" : "",
+ url->authority ? "//" : "",
+ url->authority ? url->authority : "",
+ // (url->path && url->path[0] != '/' && url->authority) ? "/" : "",
+ (url->authority && (!url->path || *url->path != '/')) ? "/" : "",
+ url->path ? url->path : "",
+ url->query ? "?" : "",
+ url->query ? url->query : "",
+ url->fragment ? "#" : "",
+ url->fragment ? url->fragment : "");
+ }
+
+ return url->url_string->str;
+}
+
+/*
+ * Return the hostname as a string.
+ * (initializing 'hostname' and 'port' camps if necessary)
+ * Note: a similar approach can be taken for user:password auth.
+ */
+const char *a_Url_hostname(const DilloUrl *u)
+{
+ char *p;
+ /* Internal url handling IS transparent to the caller */
+ DilloUrl *url = (DilloUrl *) u;
+
+ if (!url->hostname && url->authority) {
+ if ((p = strchr(url->authority, ':'))) {
+ url->port = strtol(p + 1, NULL, 10);
+ url->hostname = dStrndup(url->authority,(uint_t)(p - url->authority));
+ } else
+ url->hostname = url->authority;
+ }
+
+ return url->hostname;
+}
+
+/*
+ * Create a DilloUrl object and initialize it.
+ * (buffer, scheme, authority, path, query and fragment).
+ */
+static DilloUrl *Url_object_new(const char *uri_str)
+{
+ DilloUrl *url;
+ char *s, *p;
+
+ dReturn_val_if_fail (uri_str != NULL, NULL);
+
+ url = dNew0(DilloUrl, 1);
+
+ /* remove leading & trailing space from buffer */
+ url->buffer = dStrstrip(dStrdup(uri_str));
+
+ s = (char *) url->buffer;
+ p = strpbrk(s, ":/?#");
+ if (p && p[0] == ':' && p > s) { /* scheme */
+ *p = 0;
+ url->scheme = s;
+ s = ++p;
+ }
+ /* p = strpbrk(s, "/"); */
+ if (p == s && p[0] == '/' && p[1] == '/') { /* authority */
+ s = p + 2;
+ p = strpbrk(s, "/?#");
+ if (p) {
+ memmove(s - 2, s, (size_t)MAX(p - s, 1));
+ url->authority = s - 2;
+ p[-2] = 0;
+ s = p;
+ } else if (*s) {
+ url->authority = s;
+ return url;
+ }
+ }
+
+ p = strpbrk(s, "?#");
+ if (p) { /* path */
+ url->path = (p > s) ? s : NULL;
+ s = p;
+ } else if (*s) {
+ url->path = s;
+ return url;
+ }
+
+ p = strpbrk(s, "?#");
+ if (p && p[0] == '?') { /* query */
+ *p = 0;
+ s = p + 1;
+ url->query = s;
+ p = strpbrk(s, "#");
+ }
+ if (p && p[0] == '#') { /* fragment */
+ *p = 0;
+ s = p + 1;
+ url->fragment = s;
+ }
+
+ return url;
+}
+
+/*
+ * Free a DilloUrl
+ */
+void a_Url_free(DilloUrl *url)
+{
+ if (url) {
+ if (url->url_string)
+ dStr_free(url->url_string, TRUE);
+ if (url->hostname != url->authority)
+ dFree((char *)url->hostname);
+ dFree((char *)url->buffer);
+ dFree((char *)url->data);
+ dFree((char *)url->alt);
+ dFree(url);
+ }
+}
+
+/*
+ * Resolve the URL as RFC2396 suggests.
+ */
+static Dstr *Url_resolve_relative(const char *RelStr,
+ DilloUrl *BaseUrlPar,
+ const char *BaseStr)
+{
+ char *p, *s, *e;
+ int i;
+ Dstr *SolvedUrl, *Path;
+ DilloUrl *RelUrl, *BaseUrl = NULL;
+
+ /* parse relative URL */
+ RelUrl = Url_object_new(RelStr);
+
+ if (BaseUrlPar) {
+ BaseUrl = BaseUrlPar;
+ } else if (RelUrl->scheme == NULL) {
+ /* only required when there's no <scheme> in RelStr */
+ BaseUrl = Url_object_new(BaseStr);
+ }
+
+ SolvedUrl = dStr_sized_new(64);
+ Path = dStr_sized_new(64);
+
+ /* path empty && scheme, authority and query undefined */
+ if (!RelUrl->path && !RelUrl->scheme &&
+ !RelUrl->authority && !RelUrl->query) {
+ dStr_append(SolvedUrl, BaseStr);
+
+ if (RelUrl->fragment) { /* fragment */
+ if (BaseUrl->fragment)
+ dStr_truncate(SolvedUrl, BaseUrl->fragment-BaseUrl->buffer-1);
+ dStr_append_c(SolvedUrl, '#');
+ dStr_append(SolvedUrl, RelUrl->fragment);
+ }
+ goto done;
+
+ } else if (RelUrl->scheme) { /* scheme */
+ dStr_append(SolvedUrl, RelStr);
+ goto done;
+
+ } else if (RelUrl->authority) { /* authority */
+ // Set the Path buffer and goto "STEP 7";
+ if (RelUrl->path)
+ dStr_append(Path, RelUrl->path);
+
+ } else if (RelUrl->path && RelUrl->path[0] == '/') { /* path */
+ dStr_append(Path, RelUrl->path);
+
+ } else {
+ // solve relative path
+ if (BaseUrl->path) {
+ dStr_append(Path, BaseUrl->path);
+ for (i = Path->len; --i >= 0 && Path->str[i] != '/'; );
+ if (Path->str[i] == '/')
+ dStr_truncate(Path, ++i);
+ }
+ if (RelUrl->path)
+ dStr_append(Path, RelUrl->path);
+
+ // erase "./"
+ while ((p=strstr(Path->str, "./")) &&
+ (p == Path->str || p[-1] == '/'))
+ dStr_erase(Path, p - Path->str, 2);
+ // erase last "."
+ if (Path->len && Path->str[Path->len - 1] == '.' &&
+ (Path->len == 1 || Path->str[Path->len - 2] == '/'))
+ dStr_truncate(Path, Path->len - 1);
+
+ // erase "<segment>/../" and "<segment>/.."
+ s = p = Path->str;
+ while ( (p = strstr(p, "/..")) != NULL ) {
+ if ((p[3] == '/' || !p[3]) && (p - s)) { // "/../" | "/.."
+
+ for (e = p + 3 ; p[-1] != '/' && p > s; --p);
+ if (p[0] != '.' || p[1] != '.' || p[2] != '/') {
+ dStr_erase(Path, p - Path->str, e - p + (*e != 0));
+ p -= (p > Path->str);
+ } else
+ p = e;
+ } else
+ p += 3;
+ }
+ }
+
+ /* STEP 7
+ */
+
+ /* scheme */
+ if (BaseUrl->scheme) {
+ dStr_append(SolvedUrl, BaseUrl->scheme);
+ dStr_append_c(SolvedUrl, ':');
+ }
+
+ /* authority */
+ if (RelUrl->authority) {
+ dStr_append(SolvedUrl, "//");
+ dStr_append(SolvedUrl, RelUrl->authority);
+ } else if (BaseUrl->authority) {
+ dStr_append(SolvedUrl, "//");
+ dStr_append(SolvedUrl, BaseUrl->authority);
+ }
+
+ /* path */
+ if ((RelUrl->authority || BaseUrl->authority) &&
+ ((Path->len == 0 && (RelUrl->query || RelUrl->fragment)) ||
+ (Path->len && Path->str[0] != '/')))
+ dStr_append_c(SolvedUrl, '/'); /* hack? */
+ dStr_append(SolvedUrl, Path->str);
+
+ /* query */
+ if (RelUrl->query) {
+ dStr_append_c(SolvedUrl, '?');
+ dStr_append(SolvedUrl, RelUrl->query);
+ }
+
+ /* fragment */
+ if (RelUrl->fragment) {
+ dStr_append_c(SolvedUrl, '#');
+ dStr_append(SolvedUrl, RelUrl->fragment);
+ }
+
+done:
+ dStr_free(Path, TRUE);
+ a_Url_free(RelUrl);
+ if (BaseUrl != BaseUrlPar)
+ a_Url_free(BaseUrl);
+ return SolvedUrl;
+}
+
+/*
+ * Transform (and resolve) an URL string into the respective DilloURL.
+ * If URL = "http://dillo.sf.net:8080/index.html?long#part2"
+ * then the resulting DilloURL should be:
+ * DilloURL = {
+ * url_string = "http://dillo.sf.net:8080/index.html?long#part2"
+ * scheme = "http"
+ * authority = "dillo.sf.net:8080:
+ * path = "/index.html"
+ * query = "long"
+ * fragment = "part2"
+ * hostname = "dillo.sf.net"
+ * port = 8080
+ * flags = 0
+ * data = NULL
+ * alt = NULL
+ * ismap_url_len = 0
+ * scrolling_position = 0
+ * }
+ *
+ * Return NULL if URL is badly formed.
+ */
+DilloUrl* a_Url_new(const char *url_str, const char *base_url,
+ int flags, int32_t posx, int32_t posy)
+{
+ DilloUrl *url;
+ char *urlstr = (char *)url_str; /* auxiliar variable, don't free */
+ char *p, *str1 = NULL, *str2 = NULL;
+ Dstr *SolvedUrl;
+ int i, n_ic, n_ic_spc;
+
+ dReturn_val_if_fail (url_str != NULL, NULL);
+
+ /* Count illegal characters (0x00-0x1F, 0x7F and space) */
+ n_ic = n_ic_spc = 0;
+ for (p = (char*)url_str; *p; p++) {
+ n_ic_spc += (*p == ' ') ? 1 : 0;
+ n_ic += (*p != ' ' && *p > 0x1F && *p != 0x7F) ? 0 : 1;
+ }
+ if (n_ic) {
+ /* Strip illegal characters (they could also be encoded).
+ * There's no standard for illegal chars; we chose to strip. */
+ p = str1 = dNew(char, strlen(url_str)); /* Yes, enough memory! */
+ for (i = 0; url_str[i]; ++i)
+ if (url_str[i] > 0x1F && url_str[i] != 0x7F && url_str[i] != ' ')
+ *p++ = url_str[i];
+ *p = 0;
+ urlstr = str1;
+ }
+
+ /* let's use a heuristic to set http: as default */
+ if (!base_url) {
+ base_url = "http:";
+ if (urlstr[0] != '/') {
+ p = strpbrk(urlstr, "/#?:");
+ if (!p || *p != ':')
+ urlstr = str2 = dStrconcat("//", urlstr, NULL);
+ } else if (urlstr[1] != '/')
+ urlstr = str2 = dStrconcat("/", urlstr, NULL);
+ }
+
+ /* Resolve the URL */
+ SolvedUrl = Url_resolve_relative(urlstr, NULL, base_url);
+ DEBUG_MSG(2, "SolvedUrl = %s\n", SolvedUrl->str);
+
+ /* Fill url data */
+ url = Url_object_new(SolvedUrl->str);
+ url->url_string = SolvedUrl;
+ url->flags = flags;
+ url->scrolling_position_x = posx;
+ url->scrolling_position_y = posy;
+ url->illegal_chars = n_ic;
+ url->illegal_chars_spc = n_ic_spc;
+
+ dFree(str1);
+ dFree(str2);
+ return url;
+}
+
+
+/*
+ * Duplicate a Url structure
+ */
+DilloUrl* a_Url_dup(const DilloUrl *ori)
+{
+ DilloUrl *url;
+
+ url = Url_object_new(URL_STR_(ori));
+ dReturn_val_if_fail (url != NULL, NULL);
+
+ url->url_string = dStr_new(URL_STR(ori));
+ url->port = ori->port;
+ url->flags = ori->flags;
+ url->data = dStrdup(ori->data);
+ url->alt = dStrdup(ori->alt);
+ url->ismap_url_len = ori->ismap_url_len;
+ url->scrolling_position_x = ori->scrolling_position_x;
+ url->scrolling_position_y = ori->scrolling_position_y;
+ url->illegal_chars = ori->illegal_chars;
+ url->illegal_chars_spc = ori->illegal_chars_spc;
+
+ return url;
+}
+
+/*
+ * Compare two Url's to check if they're the same, or which one is bigger.
+ *
+ * The fields which are compared here are:
+ * <scheme>, <authority>, <path>, <query> and <data>
+ * Other fields are left for the caller to check
+ *
+ * Return value: 0 if equal, > 0 if A > B, < 0 if A < B.
+ *
+ * Note: this function defines a sorting order different from strcmp!
+ */
+int a_Url_cmp(const DilloUrl *A, const DilloUrl *B)
+{
+ int st;
+
+ dReturn_val_if_fail(A && B, 1);
+
+ if (A == B ||
+ ((st = URL_STRCAMP_I_CMP(A->authority, B->authority)) == 0 &&
+ (st = strcmp(A->path ? A->path + (*A->path == '/') : "",
+ B->path ? B->path + (*B->path == '/') : "")) == 0 &&
+ //(st = URL_STRCAMP_CMP(A->path, B->path)) == 0 &&
+ (st = URL_STRCAMP_CMP(A->query, B->query)) == 0 &&
+ (st = URL_STRCAMP_CMP(A->data, B->data)) == 0 &&
+ (st = URL_STRCAMP_I_CMP(A->scheme, B->scheme) == 0)))
+ return 0;
+ return st;
+}
+
+/*
+ * Set DilloUrl flags
+ */
+void a_Url_set_flags(DilloUrl *u, int flags)
+{
+ if (u)
+ u->flags = flags;
+}
+
+/*
+ * Set DilloUrl data (like POST info, etc.)
+ */
+void a_Url_set_data(DilloUrl *u, char *data)
+{
+ if (u) {
+ dFree((char *)u->data);
+ u->data = dStrdup(data);
+ }
+}
+
+/*
+ * Set DilloUrl alt (alternate text to the URL. Used by image maps)
+ */
+void a_Url_set_alt(DilloUrl *u, const char *alt)
+{
+ if (u) {
+ dFree((char *)u->alt);
+ u->alt = dStrdup(alt);
+ }
+}
+
+/*
+ * Set DilloUrl scrolling position
+ */
+void a_Url_set_pos(DilloUrl *u, int32_t posx, int32_t posy)
+{
+ if (u) {
+ u->scrolling_position_x = posx;
+ u->scrolling_position_y = posy;
+ }
+}
+
+/*
+ * Set DilloUrl ismap coordinates
+ * (this is optimized for not hogging the CPU)
+ */
+void a_Url_set_ismap_coords(DilloUrl *u, char *coord_str)
+{
+ dReturn_if_fail (u && coord_str);
+
+ if (!u->ismap_url_len) {
+ /* Save base-url length (without coords) */
+ u->ismap_url_len = URL_STR_(u) ? u->url_string->len : 0;
+ a_Url_set_flags(u, URL_FLAGS(u) | URL_Ismap);
+ }
+ if (u->url_string) {
+ dStr_truncate(u->url_string, u->ismap_url_len);
+ dStr_append(u->url_string, coord_str);
+ u->query = u->url_string->str + u->ismap_url_len + 1;
+ }
+}
+
+/*
+ * 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)
+{
+ char *new_str, *dest;
+ int i, val;
+
+ if (!str)
+ return NULL;
+
+ /* most cases won't have hex octets */
+ if (!strchr(str, '%'))
+ return dStrdup(str);
+
+ 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));
+ return new_str;
+}
+
+/*
+ * Urlencode 'str'
+ * -RL :: According to the RFC 1738, only alphanumerics, the special
+ * characters "$-_.+!*'(),", and reserved characters ";/?:@=&" used
+ * for their *reserved purposes* may be used unencoded within a URL.
+ * We'll escape everything but alphanumeric and "-_.*" (as lynx). --Jcid
+ *
+ * Note: the content type "application/x-www-form-urlencoded" is used:
+ * i.e., ' ' -> '+' and '\n' -> CR LF (see HTML 4.01, Sec. 17.13.4)
+ */
+char *a_Url_encode_hex_str(const char *str)
+{
+ static const char *verbatim = "-_.*";
+ static const char *hex = "0123456789ABCDEF";
+ char *newstr, *c;
+
+ if (!str)
+ return NULL;
+
+ newstr = dNew(char, 6*strlen(str)+1);
+
+ for (c = newstr; *str; str++)
+ if ((isalnum(*str) && !(*str & 0x80)) || strchr(verbatim, *str))
+ /* we really need isalnum for the "C" locale */
+ *c++ = *str;
+ else if (*str == ' ')
+ *c++ = '+';
+ else if (*str == '\n') {
+ *c++ = '%';
+ *c++ = '0';
+ *c++ = 'D';
+ *c++ = '%';
+ *c++ = '0';
+ *c++ = 'A';
+ } else {
+ *c++ = '%';
+ *c++ = hex[(*str >> 4) & 15];
+ *c++ = hex[*str & 15];
+ }
+ *c = 0;
+
+ return newstr;
+}
+
+
+/*
+ * RFC-2396 suggests this stripping when "importing" URLs from other media.
+ * Strip: "URL:", enclosing < >, and embedded whitespace.
+ * (We also strip illegal chars: 00-1F and 7F)
+ */
+char *a_Url_string_strip_delimiters(const char *str)
+{
+ char *p, *new_str, *text;
+
+ new_str = text = dStrdup(str);
+
+ if (new_str) {
+ if (strncmp(new_str, "URL:", 4) == 0)
+ text += 4;
+ if (*text == '<')
+ text++;
+
+ for (p = new_str; *text; text++)
+ if (*text > 0x1F && *text != 0x7F && *text != ' ')
+ *p++ = *text;
+ if (p > new_str && p[-1] == '>')
+ --p;
+ *p = 0;
+ }
+ return new_str;
+}
diff --git a/src/url.h b/src/url.h
new file mode 100644
index 00000000..5f331881
--- /dev/null
+++ b/src/url.h
@@ -0,0 +1,144 @@
+/*
+ * File : url.h - Dillo
+ *
+ * Copyright (C) 2001 Jorge Arellano Cid <jcid@dillo.org>
+ * 2001 Livio Baldini Soares <livio@linux.ime.usp.br>
+ *
+ * Parse and normalize all URL's inside Dillo.
+ */
+
+#ifndef __URL_H__
+#define __URL_H__
+
+#include <string.h> /* for strcmp */
+#include "d_size.h"
+#include "../dlib/dlib.h"
+
+
+#define DILLO_URL_HTTP_PORT 80
+#define DILLO_URL_HTTPS_PORT 443
+#define DILLO_URL_FTP_PORT 21
+#define DILLO_URL_MAILTO_PORT 25
+#define DILLO_URL_NEWS_PORT 119
+#define DILLO_URL_TELNET_PORT 23
+#define DILLO_URL_GOPHER_PORT 70
+
+
+/*
+ * Values for DilloUrl->flags.
+ * Specifies which which action to perform with an URL.
+ */
+#define URL_Get (1 << 0)
+#define URL_Post (1 << 1)
+#define URL_ISindex (1 << 2)
+#define URL_Ismap (1 << 3)
+#define URL_RealmAccess (1 << 4)
+
+#define URL_E2EReload (1 << 5)
+#define URL_ReloadImages (1 << 6)
+#define URL_ReloadPage (1 << 7)
+#define URL_ReloadFromCache (1 << 8)
+
+#define URL_ReloadIncomplete (1 << 9)
+#define URL_SpamSafe (1 << 10)
+
+/*
+ * Access methods to fields inside DilloURL.
+ * (non '_'-ended macros MUST use these for initialization sake)
+ */
+/* these MAY return NULL: */
+#define URL_SCHEME_(u) u->scheme
+#define URL_AUTHORITY_(u) u->authority
+#define URL_PATH_(u) u->path
+#define URL_QUERY_(u) u->query
+#define URL_FRAGMENT_(u) u->fragment
+#define URL_HOST_(u) a_Url_hostname(u)
+#define URL_DATA_(u) u->data
+#define URL_ALT_(u) u->alt
+#define URL_STR_(u) a_Url_str(u)
+/* these return an integer */
+#define URL_PORT_(u) (URL_HOST(u) ? u->port : u->port)
+#define URL_FLAGS_(u) u->flags
+#define URL_POSX_(u) u->scrolling_position_x
+#define URL_POSY_(u) u->scrolling_position_y
+#define URL_ILLEGAL_CHARS_(u) url->illegal_chars
+#define URL_ILLEGAL_CHARS_SPC_(u) url->illegal_chars_spc
+
+/*
+ * Access methods that always return a string:
+ * When the "empty" and "undefined" concepts of RFC-2396 are irrelevant to
+ * the caller, and a string is required, use these methods instead:
+ */
+#define NPTR2STR(p) ((p) ? (p) : "")
+#define URL_SCHEME(u) NPTR2STR(URL_SCHEME_(u))
+#define URL_AUTHORITY(u) NPTR2STR(URL_AUTHORITY_(u))
+#define URL_PATH(u) NPTR2STR(URL_PATH_(u))
+#define URL_QUERY(u) NPTR2STR(URL_QUERY_(u))
+#define URL_FRAGMENT(u) NPTR2STR(URL_FRAGMENT_(u))
+#define URL_HOST(u) NPTR2STR(URL_HOST_(u))
+#define URL_DATA(u) NPTR2STR(URL_DATA_(u))
+#define URL_ALT(u) NPTR2STR(URL_ALT_(u))
+#define URL_STR(u) NPTR2STR(URL_STR_(u))
+#define URL_PORT(u) URL_PORT_(u)
+#define URL_FLAGS(u) URL_FLAGS_(u)
+#define URL_POSX(u) URL_POSX_(u)
+#define URL_POSY(u) URL_POSY_(u)
+#define URL_ILLEGAL_CHARS(u) URL_ILLEGAL_CHARS_(u)
+#define URL_ILLEGAL_CHARS_SPC(u) URL_ILLEGAL_CHARS_SPC_(u)
+
+
+/* URL-camp compare methods */
+#define URL_STRCAMP_CMP(s1,s2) \
+ (s1) && (s2) ? strcmp(s1,s2) : !(s1) && !(s2) ? 0 : (s1) ? 1 : -1
+#define URL_STRCAMP_I_CMP(s1,s2) \
+ (s1) && (s2) ? dStrcasecmp(s1,s2) : !(s1) && !(s2) ? 0 : (s1) ? 1 : -1
+
+
+typedef struct _DilloUrl DilloUrl;
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+struct _DilloUrl {
+ Dstr *url_string;
+ const char *buffer;
+ const char *scheme; //
+ const char *authority; //
+ const char *path; // These are references only
+ const char *query; // (no need to free them)
+ const char *fragment; //
+ const char *hostname; //
+ int port;
+ int flags;
+ const char *data; /* POST */
+ const char *alt; /* "alt" text (used by image maps) */
+ int ismap_url_len; /* Used by server side image maps */
+ int32_t scrolling_position_x, /* remember position of visited urls */
+ scrolling_position_y;
+ int illegal_chars; /* number of illegal chars */
+ int illegal_chars_spc; /* number of illegal space chars */
+};
+
+
+DilloUrl* a_Url_new(const char *url_str, const char *base_url,
+ int flags, int32_t posx, int32_t posy);
+void a_Url_free(DilloUrl *u);
+char *a_Url_str(const DilloUrl *url);
+const char *a_Url_hostname(const DilloUrl *u);
+DilloUrl* a_Url_dup(const DilloUrl *u);
+int a_Url_cmp(const DilloUrl *A, const DilloUrl *B);
+void a_Url_set_flags(DilloUrl *u, int flags);
+void a_Url_set_data(DilloUrl *u, char *data);
+void a_Url_set_alt(DilloUrl *u, const char *alt);
+void a_Url_set_pos(DilloUrl *u, int32_t posx, int32_t posy);
+void a_Url_set_ismap_coords(DilloUrl *u, char *coord_str);
+char *a_Url_decode_hex_str(const char *str);
+char *a_Url_encode_hex_str(const char *str);
+char *a_Url_string_strip_delimiters(const char *str);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __URL_H__ */
diff --git a/src/web.cc b/src/web.cc
new file mode 100644
index 00000000..345a3263
--- /dev/null
+++ b/src/web.cc
@@ -0,0 +1,175 @@
+/*
+ * File: web.cc
+ *
+ * Copyright 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h> /* for rint */
+
+#include "msg.h"
+#include "nav.h"
+
+#include "uicmd.hh"
+
+#include "IO/IO.h"
+#include "IO/mime.h"
+
+#include "dw/core.hh"
+#include "prefs.h"
+#include "web.hh"
+
+#define DEBUG_LEVEL 5
+#include "debug.h"
+
+// Platform idependent part
+using namespace dw::core;
+
+
+/*
+ * Local data
+ */
+static Dlist *ValidWebs; /* Active web structures list; it holds
+ * pointers to DilloWeb structures. */
+
+/*
+ * Initialize local data
+ */
+void a_Web_init(void)
+{
+ ValidWebs = dList_new(32);
+}
+
+/*
+ * Given the MIME content type, and a fd to read it from,
+ * this function connects the proper MIME viewer to it.
+ * Return value:
+ * 1 on success (and Call and Data properly set).
+ * -1 for unhandled MIME types (and Call and Data untouched).
+ */
+int a_Web_dispatch_by_type (const char *Type, DilloWeb *Web,
+ CA_Callback_t *Call, void **Data)
+{
+ Widget *dw = NULL;
+ style::StyleAttrs styleAttrs;
+ style::Style *widgetStyle;
+ style::FontAttrs fontAttrs;
+
+ DEBUG_MSG(1, "a_Web_dispatch_by_type\n");
+
+ dReturn_val_if_fail(Web->bw != NULL, -1);
+
+ // get the Layout object from the bw structure.
+ Layout *layout = (Layout*)Web->bw->render_layout;
+
+ if (Web->flags & WEB_RootUrl) {
+ /* We have RootUrl! */
+ dw = (Widget*) a_Mime_set_viewer(Type, Web, Call, Data);
+ if (dw == NULL)
+ return -1;
+
+ /* Set a style for the widget */
+ fontAttrs.name = "Bitstream Charter";
+ fontAttrs.size = (int) rint(12.0 * prefs.font_factor);
+ fontAttrs.weight = 400;
+ fontAttrs.style = style::FONT_STYLE_NORMAL;
+
+ styleAttrs.initValues ();
+ styleAttrs.margin.setVal (5);
+ styleAttrs.font = style::Font::create (layout, &fontAttrs);
+ styleAttrs.color = style::Color::createSimple (layout, 0xff0000);
+ styleAttrs.backgroundColor =
+ style::Color::createSimple (layout, 0xdcd1ba);
+ widgetStyle = style::Style::create (layout, &styleAttrs);
+ dw->setStyle (widgetStyle);
+ widgetStyle->unref ();
+
+ /* This method frees the old dw if any */
+ layout->setWidget(dw);
+
+ if (URL_POSX(Web->url) || URL_POSY(Web->url)) {
+ layout->scrollTo(HPOS_LEFT, VPOS_TOP,
+ URL_POSX(Web->url), URL_POSY(Web->url),
+ 0, 0);
+ } else {
+ char *pf = a_Url_decode_hex_str(URL_FRAGMENT_(Web->url));
+ if (pf) {
+ layout->setAnchor(pf);
+ dFree(pf);
+ }
+ }
+
+ /* Clear the title bar for pages without a <TITLE> tag */
+ a_UIcmd_set_page_title(Web->bw, "");
+ a_UIcmd_set_location_text(Web->bw, URL_STR(Web->url));
+ /* Reset both progress bars */
+ a_UIcmd_set_page_prog(Web->bw, 0, 2);
+ a_UIcmd_set_img_prog(Web->bw, 0, 0, 2);
+ /* Reset the bug meter */
+ a_UIcmd_set_bug_prog(Web->bw, 0);
+
+ /* Let the Nav module know... */
+ a_Nav_expect_done(Web->bw);
+
+ } else {
+ /* A non-RootUrl. At this moment we only handle image-children */
+ if (!dStrncasecmp(Type, "image/", 6))
+ dw = (Widget*) a_Mime_set_viewer(Type, Web, Call, Data);
+ }
+
+ if (!dw) {
+ MSG_HTTP("unhandled MIME type: \"%s\"\n", Type);
+ }
+ return (dw ? 1 : -1);
+}
+
+
+/*
+ * Allocate and set safe values for a DilloWeb structure
+ */
+DilloWeb* a_Web_new(const DilloUrl *url)
+{
+ DilloWeb *web= dNew(DilloWeb, 1);
+
+ _MSG(" a_Web_new: ValidWebs ==> %d\n", dList_length(ValidWebs));
+ web->url = a_Url_dup(url);
+ web->bw = NULL;
+ web->flags = 0;
+ web->Image = NULL;
+ web->filename = NULL;
+ web->stream = NULL;
+ web->SavedBytes = 0;
+
+ dList_append(ValidWebs, (void *)web);
+ return web;
+}
+
+/*
+ * Validate a DilloWeb pointer
+ */
+int a_Web_valid(DilloWeb *web)
+{
+ return (dList_find(ValidWebs, web) != NULL);
+}
+
+/*
+ * Deallocate a DilloWeb structure
+ */
+void a_Web_free(DilloWeb *web)
+{
+ if (!web) return;
+ if (web->url)
+ a_Url_free(web->url);
+ if (web->Image)
+ a_Image_unref(web->Image);
+ dFree(web->filename);
+ dList_remove(ValidWebs, (void *)web);
+ dFree(web);
+}
+
diff --git a/src/web.hh b/src/web.hh
new file mode 100644
index 00000000..a5e05a2f
--- /dev/null
+++ b/src/web.hh
@@ -0,0 +1,45 @@
+#ifndef __WEB_H__
+#define __WEB_H__
+
+#include <stdio.h> /* for FILE */
+#include "bw.h" /* for BrowserWindow */
+#include "cache.h" /* for CA_Callback_t */
+#include "image.hh" /* for DilloImage */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * Flag defines
+ */
+#define WEB_RootUrl 1
+#define WEB_Image 2
+#define WEB_Download 4 /* Half implemented... */
+
+
+typedef struct _DilloWeb DilloWeb;
+
+struct _DilloWeb {
+ DilloUrl *url; /* Requested URL */
+ BrowserWindow *bw; /* The requesting browser window [reference] */
+ int flags; /* Additional info */
+
+ DilloImage *Image; /* For image urls [reference] */
+
+ char *filename; /* Variables for Local saving */
+ FILE *stream;
+ int SavedBytes;
+};
+
+void a_Web_init(void);
+DilloWeb* a_Web_new (const DilloUrl* url);
+int a_Web_valid(DilloWeb *web);
+void a_Web_free (DilloWeb*);
+int a_Web_dispatch_by_type (const char *Type, DilloWeb *web,
+ CA_Callback_t *Call, void **Data);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* __WEB_H__ */