From 93715c46a99c96d6c866968312691ec9ab0f6a03 Mon Sep 17 00:00:00 2001 From: jcid Date: Sun, 7 Oct 2007 00:36:34 +0200 Subject: Initial revision --- src/IO/IO.c | 412 +++++ src/IO/IO.h | 45 + src/IO/Makefile.am | 14 + src/IO/Url.h | 40 + src/IO/about.c | 344 ++++ src/IO/dpi.c | 779 ++++++++ src/IO/http.c | 494 +++++ src/IO/iowatch.cc | 35 + src/IO/iowatch.hh | 25 + src/IO/mime.c | 152 ++ src/IO/mime.h | 58 + src/IO/proto.c | 13 + src/Makefile.am | 87 + src/binaryconst.h | 38 + src/bitvec.c | 59 + src/bitvec.h | 36 + src/bookmark.c | 89 + src/bookmark.h | 19 + src/bw.c | 248 +++ src/bw.h | 96 + src/cache.c | 932 ++++++++++ src/cache.h | 75 + src/capi.c | 587 ++++++ src/capi.h | 29 + src/chain.c | 128 ++ src/chain.h | 69 + src/chg | 28 + src/colors.c | 366 ++++ src/colors.h | 15 + src/cookies.c | 332 ++++ src/cookies.h | 24 + src/debug.h | 149 ++ src/dialog.cc | 116 ++ src/dialog.hh | 22 + src/dicache.c | 451 +++++ src/dicache.h | 70 + src/dillo.cc | 108 ++ src/dir.c | 48 + src/dir.h | 19 + src/dns.c | 535 ++++++ src/dns.h | 31 + src/dpiapi.c | 82 + src/dpiapi.h | 3 + src/form.cc | 98 + src/form.hh | 87 + src/gif.c | 1054 +++++++++++ src/history.c | 125 ++ src/history.h | 24 + src/html.cc | 5123 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/html.hh | 279 +++ src/image.cc | 226 +++ src/image.hh | 79 + src/jpeg.c | 334 ++++ src/klist.c | 118 ++ src/klist.h | 40 + src/list.h | 49 + src/menu.cc | 358 ++++ src/menu.hh | 34 + src/misc.c | 271 +++ src/misc.h | 25 + src/msg.h | 42 + src/nav.c | 427 +++++ src/nav.h | 40 + src/pixmaps.h | 1652 +++++++++++++++++ src/plain.cc | 233 +++ src/png.c | 472 +++++ src/prefs.c | 434 +++++ src/prefs.h | 130 ++ src/srch | 33 + src/timeout.cc | 46 + src/timeout.hh | 20 + src/ui.cc | 912 ++++++++++ src/ui.hh | 109 ++ src/uicmd.cc | 644 +++++++ src/uicmd.hh | 62 + src/url.c | 632 +++++++ src/url.h | 144 ++ src/web.cc | 175 ++ src/web.hh | 45 + 79 files changed, 21878 insertions(+) create mode 100644 src/IO/IO.c create mode 100644 src/IO/IO.h create mode 100644 src/IO/Makefile.am create mode 100644 src/IO/Url.h create mode 100644 src/IO/about.c create mode 100644 src/IO/dpi.c create mode 100644 src/IO/http.c create mode 100644 src/IO/iowatch.cc create mode 100644 src/IO/iowatch.hh create mode 100644 src/IO/mime.c create mode 100644 src/IO/mime.h create mode 100644 src/IO/proto.c create mode 100644 src/Makefile.am create mode 100644 src/binaryconst.h create mode 100644 src/bitvec.c create mode 100644 src/bitvec.h create mode 100644 src/bookmark.c create mode 100644 src/bookmark.h create mode 100644 src/bw.c create mode 100644 src/bw.h create mode 100644 src/cache.c create mode 100644 src/cache.h create mode 100644 src/capi.c create mode 100644 src/capi.h create mode 100644 src/chain.c create mode 100644 src/chain.h create mode 100755 src/chg create mode 100644 src/colors.c create mode 100644 src/colors.h create mode 100644 src/cookies.c create mode 100644 src/cookies.h create mode 100644 src/debug.h create mode 100644 src/dialog.cc create mode 100644 src/dialog.hh create mode 100644 src/dicache.c create mode 100644 src/dicache.h create mode 100644 src/dillo.cc create mode 100644 src/dir.c create mode 100644 src/dir.h create mode 100644 src/dns.c create mode 100644 src/dns.h create mode 100644 src/dpiapi.c create mode 100644 src/dpiapi.h create mode 100644 src/form.cc create mode 100644 src/form.hh create mode 100644 src/gif.c create mode 100644 src/history.c create mode 100644 src/history.h create mode 100644 src/html.cc create mode 100644 src/html.hh create mode 100644 src/image.cc create mode 100644 src/image.hh create mode 100644 src/jpeg.c create mode 100644 src/klist.c create mode 100644 src/klist.h create mode 100644 src/list.h create mode 100644 src/menu.cc create mode 100644 src/menu.hh create mode 100644 src/misc.c create mode 100644 src/misc.h create mode 100644 src/msg.h create mode 100644 src/nav.c create mode 100644 src/nav.h create mode 100644 src/pixmaps.h create mode 100644 src/plain.cc create mode 100644 src/png.c create mode 100644 src/prefs.c create mode 100644 src/prefs.h create mode 100755 src/srch create mode 100644 src/timeout.cc create mode 100644 src/timeout.hh create mode 100644 src/ui.cc create mode 100644 src/ui.hh create mode 100644 src/uicmd.cc create mode 100644 src/uicmd.hh create mode 100644 src/url.c create mode 100644 src/url.h create mode 100644 src/web.cc create mode 100644 src/web.hh (limited to 'src') 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#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 +#include + +#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 + * + * 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 + +/* + * HTML text for startup screen + */ +const char *AboutSplash= +"\n" +"\n" +"\n" +"Splash screen for dillo-" VERSION "\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +"
\n" +" \n" +" \n" +"
\n" +"

 Welcome to Dillo " VERSION " 

\n" +"
\n" +"
\n" +"
\n" +"\n" +"
\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"
\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +"
Dillo\n" +"
\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \n" +" Help\n" +"
  \n" +" \n" +" Home\n" +"
  \n" +" \n" +" \n" +" Objectives\n" +"
  \n" +" \n" +" \n" +" ChangeLog\n" +"
  \n" +" \n" +" \n" +" Interview\n" +"
  \n" +" \n" +" \n" +" Authors\n" +"
  \n" +" \n" +" \n" +" Donate\n" +"
\n" +"
\n" +"
\n" +"
\n" +"\n" +"
\n" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +"
Magazines\n" +"\n" +"
\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
  \n" +" \n" +" LWN\n" +"
  \n" +" \n" +" Slashdot\n" +"
  \n" +" \n" +" KuroShin\n" +"
  \n" +" \n" +" Nexus M.\n" +"
  \n" +" \n" +" Monster News\n" +"
  \n" +" \n" +" The Register\n" +"
\n" +"
\n" +"
\n" +"
\n" +"\n" +"
\n" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +"
Additional Stuff\n" +"\n" +"
\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
  \n" +" Google\n" +"
  \n" +" Wikipedia\n" +"
  \n" +" P. Gutenberg\n" +"
  \n" +" FreshMeat\n" +"
  \n" +" GNU\n" +" project\n" +"
  \n" +" LinuxFund\n" +"
\n" +"
\n" +"
\n" +"
\n" +"\n" +"
\n" +"\n" +"\n" +"\n" +"
\n" +" \n" +" \n" +" \n" +"
Essential Readings\n" +"\n" +"
\n" +"
\n" +" \n" +"
  \n" +" Peace&Violence\n" +"
  \n" +" " +" Right to Read\n" +"
\n" +"
\n" +"
\n" +"
\n" +"\n" +"
\n" +"\n" +"\n" +"\n" +"\n" +"
\n" +"\n" +"\n" +"\n" +"\n" +"
\n" +"\n" +"\n" +" \n" +"
\n" +"

Free Software

\n" +"
\n" +"
\n" +"

\n" +" Dillo is Free Software in the terms of the GPL.\n" +" This means you have four basic freedoms:\n" +"

    \n" +"
  • Freedom to use the program any way you see fit.\n" +"
  • Freedom to study and modify the source code.\n" +"
  • Freedom to make backup copies.\n" +"
  • Freedom to redistribute it.\n" +"
\n" +" The GPL\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.
\n" +"
\n" +"
\n" +"
\n" +"\n" +"
\n" +"\n" +"
\n" +"\n" +"\n" +" \n" +"
\n" +"

Release overview

\n" +" ??, 2005\n" +"
\n" +" \n" +" \n" +"
\n" +"

\n" +"[...]\n" +"

\n" +"Remember that dillo project uses a release model where every new\n" +"browser shall be better than the former.\n" +"Keep up with the latest one!\n" +"

\n" +"
\n" +"
\n" +"\n" +"
\n" +"\n" +"
\n" +"\n" +"\n" +" \n" +"
\n" +"

ChangeLog highlights

\n" +" (Extracted from the\n" +" full\n" +" ChangeLog)\n" +"
\n" +" \n" +" \n" +"
\n" +"
    \n" +"
  • [...]\n" +"
\n" +"
\n" +"
\n" +"
\n" +"\n" +"
\n" +"\n" +"
\n" +"\n" +"\n" +" \n" +"
\n" +"

Notes

\n" +"
\n" +" \n" +" \n" +"
\n" +"
    \n" +"
  • There's a\n" +" dillorc\n" +" (readable config) file within the tarball; It is well commented\n" +" and has plenty of options to customize dillo, so copy\n" +" it to your ~/.dillo/ directory, and\n" +" modify to your taste.\n" +"
  • There's documentation for developers in the /doc\n" +" dir within the tarball; you can find directions on everything\n" +" else at the home page.\n" +"
  • 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" +"
  • Dillo behaves very nicely when browsing local files, images, and HTML.\n" +" It's also very good for Internet searching (try Google!).\n" +"
  • This release is mainly intended for developers\n" +" and advanced users.\n" +"
  • Frames, Java and Javascript are not supported.\n" +"
\n" +"
\n" +"
\n" +"
\n" +"
\n" +"\n" +"
\n" +"\n" +"\n" +"\n" +"\n" +"
\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"
\n" +"\n" +"\n" +"\n" +"

\n" +"
\n" +"
\n" +"
\n" +"\n" +"\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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include /* for errno */ + +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 + +#include +#include /* for errno */ +#include +#include +#include +#include +#include /* for lots of socket stuff */ +#include /* for ntohl and stuff */ +#include /* 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 + * + * 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 +#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 + * + * 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 + * + * 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 +#include + +#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 , + * + * 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 + * + * 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 + * + * 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 +#include +#include +#include + +#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 + * + * 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 + +#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 + * + * 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 /* for tolower */ +#include + +#include +#include +#include +#include +#include + +#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 + * + * 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 + +#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 + * + * 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 + */ + + +/* + * 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 " + echo " (this script changes 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 + * + * 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 +#include +#include +#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 + * Jörgen Viksell + * + * 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 +#include +#include +#include +#include +#include +#include +#include /* for time() and time_t */ +#include + +#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 + * #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 +#include + + +# 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 + * + * 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 +#include +#include +#include +#include +#include + +#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 +/* + * 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 + * + * 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 /* for libc5 compatibility */ +#include /* for memset */ +#include +#include + +#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 + * + * 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 +#include + +#include +#include + +#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 + * + * 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 + +#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 + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 (true); +} + +Form::~Form () +{ + delete resources; +} + +void Form::clicked (ButtonResource *resource, int buttonNo) +{ +/* + for (container::typed::Iterator 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 *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 + * Copyright (C) 2000-2002 Jorge Arellano Cid + * + * 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 +#ifdef ENABLE_GIF + +#include /* for sprintf */ +#include /* 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 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: + * ::= 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, * Trailer + * ::= | + * ::= Application Extension | Comment Extension + * ::= [Graphic Control Extension] + * ::= | Plain Text Extension + * + * * --> GIF_Block + * --> while (...) + * --> Gif_do_extension + * Graphic Control Extension --> Gif_do_extension + * Plain Text Extension --> Gif_do_extension + * --> 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: + * ::= Header * Trailer + * + * --> Gif_process_bytes + * Header --> State 0 + * --> State 1 + * * --> State 2 + * Trailer --> State > 3 + * + * State == 3 is special... this is inside of 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 * 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 + * + * 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 + * + * 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 /* for isspace and tolower */ +#include /* for memcpy and memmove */ +#include +#include /* for sprintf */ +#include /* for rint */ +#include + +#include /* 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 (1); + + html_lb->links = new misc::SimpleVector (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 (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

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_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 (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

\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  ) */
+      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: . -- expected \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 = 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);
+}
+
+/*
+ * 
+ */
+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);
+}
+
+/*
+ * 

+ * 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); +} + +/* + * + */ +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
and + */ +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(" or outside \n"); + return; + + case DILLO_HTML_TABLE_MODE_TOP: + MSG_HTML("\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 +} + + +/* + * + */ +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: outside
or outside
+ */ +static void Html_tag_open_td(DilloHtml *html, char *tag, int tagsize) +{ + Html_tag_open_table_cell (html, tag, tagsize, TEXT_ALIGN_LEFT); +} + + +/* + * + */ +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); +} + + +/* + *
\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 +} + +/* + * ,