diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/IO/IO.c | 412 | ||||
-rw-r--r-- | src/IO/IO.h | 45 | ||||
-rw-r--r-- | src/IO/Makefile.am | 14 | ||||
-rw-r--r-- | src/IO/Url.h | 40 | ||||
-rw-r--r-- | src/IO/about.c | 344 | ||||
-rw-r--r-- | src/IO/dpi.c | 779 | ||||
-rw-r--r-- | src/IO/http.c | 494 | ||||
-rw-r--r-- | src/IO/iowatch.cc | 35 | ||||
-rw-r--r-- | src/IO/iowatch.hh | 25 | ||||
-rw-r--r-- | src/IO/mime.c | 152 | ||||
-rw-r--r-- | src/IO/mime.h | 58 | ||||
-rw-r--r-- | src/IO/proto.c | 13 | ||||
-rw-r--r-- | src/Makefile.am | 87 | ||||
-rw-r--r-- | src/binaryconst.h | 38 | ||||
-rw-r--r-- | src/bitvec.c | 59 | ||||
-rw-r--r-- | src/bitvec.h | 36 | ||||
-rw-r--r-- | src/bookmark.c | 89 | ||||
-rw-r--r-- | src/bookmark.h | 19 | ||||
-rw-r--r-- | src/bw.c | 248 | ||||
-rw-r--r-- | src/bw.h | 96 | ||||
-rw-r--r-- | src/cache.c | 932 | ||||
-rw-r--r-- | src/cache.h | 75 | ||||
-rw-r--r-- | src/capi.c | 587 | ||||
-rw-r--r-- | src/capi.h | 29 | ||||
-rw-r--r-- | src/chain.c | 128 | ||||
-rw-r--r-- | src/chain.h | 69 | ||||
-rwxr-xr-x | src/chg | 28 | ||||
-rw-r--r-- | src/colors.c | 366 | ||||
-rw-r--r-- | src/colors.h | 15 | ||||
-rw-r--r-- | src/cookies.c | 332 | ||||
-rw-r--r-- | src/cookies.h | 24 | ||||
-rw-r--r-- | src/debug.h | 149 | ||||
-rw-r--r-- | src/dialog.cc | 116 | ||||
-rw-r--r-- | src/dialog.hh | 22 | ||||
-rw-r--r-- | src/dicache.c | 451 | ||||
-rw-r--r-- | src/dicache.h | 70 | ||||
-rw-r--r-- | src/dillo.cc | 108 | ||||
-rw-r--r-- | src/dir.c | 48 | ||||
-rw-r--r-- | src/dir.h | 19 | ||||
-rw-r--r-- | src/dns.c | 535 | ||||
-rw-r--r-- | src/dns.h | 31 | ||||
-rw-r--r-- | src/dpiapi.c | 82 | ||||
-rw-r--r-- | src/dpiapi.h | 3 | ||||
-rw-r--r-- | src/form.cc | 98 | ||||
-rw-r--r-- | src/form.hh | 87 | ||||
-rw-r--r-- | src/gif.c | 1054 | ||||
-rw-r--r-- | src/history.c | 125 | ||||
-rw-r--r-- | src/history.h | 24 | ||||
-rw-r--r-- | src/html.cc | 5123 | ||||
-rw-r--r-- | src/html.hh | 279 | ||||
-rw-r--r-- | src/image.cc | 226 | ||||
-rw-r--r-- | src/image.hh | 79 | ||||
-rw-r--r-- | src/jpeg.c | 334 | ||||
-rw-r--r-- | src/klist.c | 118 | ||||
-rw-r--r-- | src/klist.h | 40 | ||||
-rw-r--r-- | src/list.h | 49 | ||||
-rw-r--r-- | src/menu.cc | 358 | ||||
-rw-r--r-- | src/menu.hh | 34 | ||||
-rw-r--r-- | src/misc.c | 271 | ||||
-rw-r--r-- | src/misc.h | 25 | ||||
-rw-r--r-- | src/msg.h | 42 | ||||
-rw-r--r-- | src/nav.c | 427 | ||||
-rw-r--r-- | src/nav.h | 40 | ||||
-rw-r--r-- | src/pixmaps.h | 1652 | ||||
-rw-r--r-- | src/plain.cc | 233 | ||||
-rw-r--r-- | src/png.c | 472 | ||||
-rw-r--r-- | src/prefs.c | 434 | ||||
-rw-r--r-- | src/prefs.h | 130 | ||||
-rwxr-xr-x | src/srch | 33 | ||||
-rw-r--r-- | src/timeout.cc | 46 | ||||
-rw-r--r-- | src/timeout.hh | 20 | ||||
-rw-r--r-- | src/ui.cc | 912 | ||||
-rw-r--r-- | src/ui.hh | 109 | ||||
-rw-r--r-- | src/uicmd.cc | 644 | ||||
-rw-r--r-- | src/uicmd.hh | 62 | ||||
-rw-r--r-- | src/url.c | 632 | ||||
-rw-r--r-- | src/url.h | 144 | ||||
-rw-r--r-- | src/web.cc | 175 | ||||
-rw-r--r-- | src/web.hh | 45 |
79 files changed, 21878 insertions, 0 deletions
diff --git a/src/IO/IO.c b/src/IO/IO.c new file mode 100644 index 00000000..060fe200 --- /dev/null +++ b/src/IO/IO.c @@ -0,0 +1,412 @@ +/* + * File: IO.c + * + * Copyright (C) 2000-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * Dillo's event driven IO engine + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/uio.h> +#include <sys/socket.h> +#include "../msg.h" +#include "../chain.h" +#include "../klist.h" +#include "../list.h" +#include "IO.h" +#include "iowatch.hh" + +#define DEBUG_LEVEL 5 +//#define DEBUG_LEVEL 1 +#include "../debug.h" + +/* + * Symbolic defines for shutdown() function + * (Not defined in the same header file, for all distros --Jcid) + */ +#define IO_StopRd 0 +#define IO_StopWr 1 +#define IO_StopRdWr 2 + + +typedef struct { + int Key; /* Primary Key (for klist) */ + int Op; /* IORead | IOWrite */ + int FD; /* Current File Descriptor */ + int Flags; /* Flag array (look definitions above) */ + int Status; /* errno code */ + Dstr *Buf; /* Internal buffer */ + + void *Info; /* CCC Info structure for this IO */ + int events; /* FLTK events for this IO */ +} IOData_t; + + +/* + * Local data + */ +static Klist_t *ValidIOs = NULL; /* Active IOs list. It holds pointers to + * IOData_t structures. */ + +/* + * Forward declarations + */ +void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2); + + +/* IO API - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* + * Return a newly created, and initialized, 'io' struct + */ +static IOData_t *IO_new(int op, int fd) +{ + IOData_t *io = dNew0(IOData_t, 1); + io->Op = op; + io->FD = fd; + io->Flags = 0; + io->Key = 0; + io->Buf = dStr_sized_new(IOBufLen); + + return io; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* + * Register an IO in ValidIOs + */ +static void IO_ins(IOData_t *io) +{ + if (io->Key == 0) { + io->Key = a_Klist_insert(&ValidIOs, io); + } +} + +/* + * Remove an IO from ValidIOs + */ +static void IO_del(IOData_t *io) +{ + if (io->Key != 0) { + a_Klist_remove(ValidIOs, io->Key); + } + io->Key = 0; + _MSG(" -->ValidIOs: %d\n", a_Klist_length(ValidIOs)); +} + +/* + * Return a io by its Key (NULL if not found) + */ +static IOData_t *IO_get(int Key) +{ + return (IOData_t *)a_Klist_get_data(ValidIOs, Key); +} + +/* + * Free an 'io' struct + */ +static void IO_free(IOData_t *io) +{ + dStr_free(io->Buf, 1); + dFree(io); +} + +/* + * Close an open FD, and remove io controls. + * (This function can be used for Close and Abort operations) + */ +static void IO_close_fd(IOData_t *io, int CloseCode) +{ + int st; + + /* With HTTP, if we close the writing part, the reading one also gets + * closed! (other clients may set 'IOFlag_ForceClose') */ + if ((io->Flags & IOFlag_ForceClose) || (CloseCode == IO_StopRdWr)) { + do + st = close(io->FD); + while (st < 0 && errno == EINTR); + } + /* Remove this IOData_t reference, from our ValidIOs list + * We don't deallocate it here, just remove from the list.*/ + IO_del(io); + + /* Stop the polling on this FD */ + a_IOwatch_remove_fd(io->FD, io->events); +} + +/* + * Read data from a file descriptor into a specific buffer + */ +static bool_t IO_read(IOData_t *io) +{ + char Buf[IOBufLen]; + ssize_t St; + bool_t ret = FALSE; + + DEBUG_MSG(3, " IO_read\n"); + + /* this is a new read-buffer */ + dStr_truncate(io->Buf, 0); + io->Status = 0; + + while (1) { + St = read(io->FD, Buf, IOBufLen); + if (St > 0) { + dStr_append_l(io->Buf, Buf, St); + continue; + } else if (St < 0) { + if (errno == EINTR) { + continue; + } else if (errno == EAGAIN) { + ret = TRUE; + break; + } else { + io->Status = errno; + break; + } + } else { /* St == 0 */ + break; + } + } + + if (io->Buf->len > 0) { + /* send what we've got so far */ + a_IO_ccc(OpSend, 2, FWD, io->Info, io, NULL); + } + if (St == 0) { + /* All data read (EOF) */ + a_IO_ccc(OpEnd, 2, FWD, io->Info, io, NULL); + } + return ret; +} + +/* + * Write data, from a specific buffer, into a file descriptor + */ +static bool_t IO_write(IOData_t *io) +{ + ssize_t St; + bool_t ret = FALSE; + + DEBUG_MSG(3, " IO_write\n"); + io->Status = 0; + + while (1) { + St = write(io->FD, io->Buf->str, io->Buf->len); + if (St < 0) { + /* Error */ + if (errno == EINTR) { + continue; + } else if (errno == EAGAIN) { + ret = TRUE; + break; + } else { + io->Status = errno; + break; + } + } else if (St < io->Buf->len) { + /* Not all data written */ + dStr_erase (io->Buf, 0, St); + } else { + /* All data in buffer written */ + dStr_truncate(io->Buf, 0); + break; + } + } + + return ret; +} + +/* + * Handle background IO for a given FD (reads | writes) + * (This function gets called when there's activity in the FD) + */ +static int IO_callback(int fd, IOData_t *io) +{ + bool_t ret = FALSE; + + _MSG("IO_callback:: (%s) FD = %d\n", + (io->Op == IORead) ? "IORead" : "IOWrite", io->FD); + + if (io->Op == IORead) { /* Read */ + ret = IO_read(io); + } else if (io->Op == IOWrite) { /* Write */ + ret = IO_write(io); + } + return (ret) ? 1 : 0; +} + +/* + * Handle the READ event of a FD. + */ +static void IO_fd_read_cb(int fd, void *data) +{ + int io_key = (int)data; + IOData_t *io = IO_get(io_key); + + /* There should be no more events on already closed FDs --Jcid */ + if (io == NULL) { + MSG_ERR("IO_fd_read_cb: call on already closed io!\n"); + a_IOwatch_remove_fd(fd, DIO_READ); + + } else { + if (IO_callback(fd, io) == 0) + a_IOwatch_remove_fd(fd, DIO_READ); + } +} + +/* + * Handle the WRITE event of a FD. + */ +static void IO_fd_write_cb(int fd, void *data) +{ + int io_key = (int)data; + IOData_t *io = IO_get(io_key); + + if (io == NULL) { + /* There must be no more events on already closed FDs --Jcid */ + MSG_ERR("IO_fd_write_cb: call on already closed io!\n"); + a_IOwatch_remove_fd(fd, DIO_WRITE); + + } else { + if (IO_callback(fd, io) == 0) + a_IOwatch_remove_fd(fd, DIO_WRITE); + } +} + +/* + * Receive an IO request (IORead | IOWrite), + * Set a watch for it, and let it flow! + */ +static void IO_submit(IOData_t *r_io) +{ + /* Insert this IO in ValidIOs */ + IO_ins(r_io); + + _MSG("IO_submit:: (%s) FD = %d\n", + (io->Op == IORead) ? "IORead" : "IOWrite", io->FD); + + /* Set FD to background and to close on exec. */ + fcntl(r_io->FD, F_SETFL, O_NONBLOCK | fcntl(r_io->FD, F_GETFL)); + fcntl(r_io->FD, F_SETFD, FD_CLOEXEC | fcntl(r_io->FD, F_GETFD)); + + if (r_io->Op == IORead) { + r_io->events = DIO_READ; + a_IOwatch_add_fd(r_io->FD, r_io->events, + IO_fd_read_cb, (void*)(r_io->Key)); + + } else if (r_io->Op == IOWrite) { + r_io->events = DIO_WRITE; + a_IOwatch_add_fd(r_io->FD, r_io->events, + IO_fd_write_cb, (void*)(r_io->Key)); + } +} + +/* + * CCC function for the IO module + * ( Data1 = IOData_t* ; Data2 = NULL ) + */ +void a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2) +{ + IOData_t *io; + DataBuf *dbuf; + + a_Chain_debug_msg("a_IO_ccc", Op, Branch, Dir); + + if (Branch == 1) { + if (Dir == BCK) { + /* Write data using select */ + switch (Op) { + case OpStart: + io = IO_new(IOWrite, *(int*)Data1); /* SockFD */ + Info->LocalKey = io; + break; + case OpSend: + io = Info->LocalKey; + dbuf = Data1; + dStr_append_l(io->Buf, dbuf->Buf, dbuf->Size); + IO_submit(io); + break; + case OpEnd: + case OpAbort: + io = Info->LocalKey; + if (io->Buf->len > 0) { + MSG_WARN("IO_write, closing with pending data not sent\n"); + MSG_WARN(" \"%s\"\n", io->Buf->str); + } + /* close FD, remove from ValidIOs and remove its watch */ + IO_close_fd(io, IO_StopRdWr); + IO_free(io); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* FWD */ + /* Write-data status */ + switch (Op) { + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + + } else if (Branch == 2) { + if (Dir == BCK) { + /* This part catches the reader's messages */ + switch (Op) { + case OpStart: + io = IO_new(IORead, *(int*)Data2); /* SockFD */ + Info->LocalKey = io; + io->Info = Info; + IO_submit(io); + break; + case OpAbort: + io = Info->LocalKey; + IO_close_fd(io, IO_StopRdWr); + IO_free(io); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* FWD */ + /* Send read-data */ + io = Data1; + switch (Op) { + case OpSend: + dbuf = a_Chain_dbuf_new(io->Buf->str, io->Buf->len, 0); + a_Chain_fcb(OpSend, Info, dbuf, NULL); + dFree(dbuf); + break; + case OpEnd: + a_Chain_fcb(OpEnd, Info, NULL, NULL); + IO_close_fd(io, IO_StopRdWr); + IO_free(io); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + } +} + diff --git a/src/IO/IO.h b/src/IO/IO.h new file mode 100644 index 00000000..71ed25b4 --- /dev/null +++ b/src/IO/IO.h @@ -0,0 +1,45 @@ +#ifndef __IO_H__ +#define __IO_H__ + +#include <unistd.h> +#include <sys/uio.h> + +#include "d_size.h" +#include "../../dlib/dlib.h" +#include "../chain.h" +#include "iowatch.hh" + +/* + * IO Operations + */ +#define IORead 0 +#define IOWrite 1 +#define IOClose 2 +#define IOAbort 3 + +/* + * IO Flags (unused) + */ +#define IOFlag_ForceClose (1 << 1) +#define IOFlag_SingleWrite (1 << 2) + +/* + * IO constants + */ +#define IOBufLen 8192 + + +/* + * Exported functions + */ +/* Note: a_IO_ccc() is defined in Url.h together with the *_ccc() set */ + + +/* + * Exported data + */ +extern const char *AboutSplash; + + +#endif /* __IO_H__ */ + diff --git a/src/IO/Makefile.am b/src/IO/Makefile.am new file mode 100644 index 00000000..bff4667f --- /dev/null +++ b/src/IO/Makefile.am @@ -0,0 +1,14 @@ +noinst_LIBRARIES = libDiof.a + +libDiof_a_SOURCES = \ + mime.c \ + mime.h \ + about.c \ + Url.h \ + proto.c \ + http.c \ + dpi.c \ + IO.c \ + iowatch.cc \ + iowatch.hh \ + IO.h diff --git a/src/IO/Url.h b/src/IO/Url.h new file mode 100644 index 00000000..91a9c1bd --- /dev/null +++ b/src/IO/Url.h @@ -0,0 +1,40 @@ +#ifndef __IO_URL_H__ +#define __IO_URL_H__ + +#include "../chain.h" +#include "../url.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * External functions + */ +extern void a_Http_freeall(void); +int a_Http_init(void); +int a_Http_proxy_auth(void); +void a_Http_set_proxy_passwd(char *str); +char *a_Http_make_query_str(const DilloUrl *url, bool_t use_proxy); + +void a_Http_ccc (int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2); +void a_About_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2); +void a_IO_ccc (int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2); +void a_Dpi_ccc (int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2); + +char *a_Dpi_send_blocking_cmd(const char *server_name, const char *cmd); +void a_Dpi_bye_dpid(void); +void a_Dpi_init(void); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __IO_URL_H__ */ + diff --git a/src/IO/about.c b/src/IO/about.c new file mode 100644 index 00000000..a3a0c420 --- /dev/null +++ b/src/IO/about.c @@ -0,0 +1,344 @@ +/* + * File: about.c + * + * Copyright (C) 1999-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include <config.h> + +/* + * HTML text for startup screen + */ +const char *AboutSplash= +"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n" +"<html>\n" +"<head>\n" +"<title>Splash screen for dillo-" VERSION "</title>\n" +"</head>\n" +"<body bgcolor='#778899' text='#000000' link='#000000' vlink='#000000'>\n" +"\n" +"\n" +"<!-- the head of the page -->\n" +"\n" +"<table width='100%' border='0' cellspacing='1' cellpadding='3'>\n" +" <tr><td>\n" +" <table border='1' cellspacing='1' cellpadding='0'>\n" +" <tr>\n" +" <td bgcolor='#000000'>\n" +" <table width='100%' border='0' bgcolor='#ffffff'>\n" +" <tr>\n" +" <td valign='top' align='left'>\n" +" <h1> Welcome to Dillo " VERSION " </h1>\n" +" </table>\n" +" </table>\n" +"</table>\n" +"\n" +"<br>\n" +"\n" +"\n" +"<!-- the main layout table, definition -->\n" +"\n" +"<table width='100%' border='0' cellspacing='0' cellpadding='0'>\n" +"<tr><td valign='top' width='150' align='center'>\n" +"\n" +"\n" +"<!-- The navigation bar -->\n" +"\n" +"<table border='0' cellspacing='0' cellpadding='0' width='140' bgcolor='#000000'>\n" +"<tr>\n" +" <td>\n" +" <table width='100%' border='0' cellspacing='1' cellpadding='3'>\n" +" <tr>\n" +" <td colspan='1' bgcolor='#CCCCCC'>Dillo\n" +" <tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n" +" <table border='0' cellspacing='0' cellpadding='2'><tr>\n" +" <td>\n" +" <td>\n" +" <a href='http://www.dillo.org/dillo-help.html'>\n" +" Help</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.dillo.org/'>Home</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.dillo.org/funding/objectives.html'>\n" +" Objectives</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.dillo.org/ChangeLog.html'>\n" +" ChangeLog</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.dillo.org/interview.html'>\n" +" Interview</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.dillo.org/D_authors.html'>\n" +" Authors</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.dillo.org/donations.html'>\n" +" Donate</a>\n" +" </table>\n" +" </table>\n" +" </table>\n" +"</table>\n" +"\n" +"<br>\n" +"\n" +"<table border='0' cellspacing='0' cellpadding='0' width='140' bgcolor='#000000'>\n" +"<tr>\n" +" <td>\n" +" <table width='100%' border='0' cellspacing='1' cellpadding='3'>\n" +" <tr>\n" +" <td colspan='1' bgcolor='#CCCCCC'>Magazines\n" +"\n" +" <tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n" +" <table border='0' cellpadding='2'>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://lwn.net/'>LWN</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://slashdot.org/'>Slashdot</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.kuro5hin.org/?op=section;section=__all__'>KuroShin</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.nexusmagazine.com/'>Nexus M.</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.gnu-darwin.org/update.html'>Monster News</a>\n" +" <tr>\n" +" <td> \n" +" <td>\n" +" <a href='http://www.theregister.co.uk/index.html'>The Register</a>\n" +" </table>\n" +" </table>\n" +" </table>\n" +"</table>\n" +"\n" +"<br>\n" +"\n" +"<table border='0' cellspacing='0' cellpadding='0' width='140' bgcolor='#000000'>\n" +"<tr>\n" +" <td>\n" +" <table width='100%' border='0' cellspacing='1' cellpadding='3'>\n" +" <tr>\n" +" <td colspan='1' bgcolor='#CCCCCC'>Additional Stuff\n" +"\n" +" <tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n" +" <table border='0' cellpadding='2'><tr>\n" +" <td> \n" +" <td><a href='http://www.google.com/'>Google</a>\n" +" <tr>\n" +" <td> \n" +" <td><a href='http://www.wikipedia.org/'>Wikipedia</a>\n" +" <tr>\n" +" <td> \n" +" <td><a href='http://www.gutenberg.org/'>P. Gutenberg</a>\n" +" <tr>\n" +" <td> \n" +" <td><a href='http://freshmeat.net/'>FreshMeat</a>\n" +" <tr>\n" +" <td> \n" +" <td><a href='http://www.gnu.org/gnu/thegnuproject.html'>GNU\n" +" project</a>\n" +" <tr>\n" +" <td> \n" +" <td><a href='http://www.linuxfund.org/'>LinuxFund</a>\n" +" </table>\n" +" </table>\n" +" </table>\n" +"</table>\n" +"\n" +"<br>\n" +"\n" +"<table border='0' cellspacing='0' cellpadding='0' width='140' bgcolor='#000000'>\n" +"<tr>\n" +" <td>\n" +" <table width='100%' border='0' cellspacing='1' cellpadding='3'>\n" +" <tr>\n" +" <td colspan='1' bgcolor='#CCCCCC'>Essential Readings\n" +"\n" +" <tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n" +" <table border='0' cellpadding='2'>\n" +" <tr><td> \n" +" <td><a href='http://www.violence.de'>Peace&Violence</a>\n" +" <tr><td> \n" +" <td><a href='http://www.fsf.org/philosophy/right-to-read.html'>" +" Right to Read</a>\n" +" </table>\n" +" </table>\n" +" </table>\n" +"</table>\n" +"\n" +"<table border='0' width='100%' cellpadding='0' cellspacing='0'><tr><td height='10'></table>\n" +"\n" +"\n" +"<!-- the main layout table, a small vertical spacer -->\n" +"\n" +"<td width='20'><td valign='top'>\n" +"\n" +"\n" +"<!-- Main Part of the page -->\n" +"\n" +"<table border='0' cellpadding='0' cellspacing='0' align='center' bgcolor='#000000' width='100%'><tr><td>\n" +"<table border='0' cellpadding='5' cellspacing='1' width='100%'>\n" +"<tr>\n" +" <td bgcolor='#CCCCCC'>\n" +" <h4>Free Software</h4>\n" +"<tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'><tr><td>\n" +" <p>\n" +" Dillo is Free Software in the terms of the GPL.\n" +" This means you have four basic freedoms:\n" +" <ul>\n" +" <li>Freedom to use the program any way you see fit.\n" +" <li>Freedom to study and modify the source code.\n" +" <li>Freedom to make backup copies.\n" +" <li>Freedom to redistribute it.\n" +" </ul>\n" +" The <a href='http://www.gnu.org/licenses/gpl.html'>GPL</a>\n" +" is the legal mechanism that gives you these freedoms.\n" +" It also protects them from being taken away: any derivative work\n" +" based on the program must be under the GPL.<br>\n" +" </table>\n" +"</table>\n" +"</table>\n" +"\n" +"<br>\n" +"\n" +"<table border='0' cellpadding='0' cellspacing='0' align='center' bgcolor='#000000' width='100%'><tr><td>\n" +"<table border='0' cellpadding='5' cellspacing='1' width='100%'>\n" +"<tr>\n" +" <td bgcolor='#CCCCCC'>\n" +" <h4>Release overview</h4>\n" +" ??, 2005\n" +"<tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'>\n" +" <tr>\n" +" <td>\n" +"<p>\n" +"[...]\n" +"<p>\n" +"Remember that dillo project uses a release model where every new\n" +"browser shall be better than the former.\n" +"<EM>Keep up with the latest one!</EM>\n" +" </table>\n" +"</table>\n" +"</table>\n" +"\n" +"<br>\n" +"\n" +"<table border='0' cellpadding='0' cellspacing='0' align='center' bgcolor='#000000' width='100%'><tr><td>\n" +"<table border='0' cellpadding='5' cellspacing='1' width='100%'>\n" +"<tr>\n" +" <td bgcolor='#CCCCCC'>\n" +" <h4>ChangeLog highlights</h4>\n" +" (Extracted from the\n" +" <a href='http://www.dillo.org/ChangeLog.html'>full\n" +" ChangeLog</a>)\n" +"<tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'>\n" +" <tr>\n" +" <td>\n" +"<ul>\n" +"<li>[...]\n" +"</ul>\n" +" </table>\n" +"</table>\n" +"</table>\n" +"\n" +"<br>\n" +"\n" +"<table border='0' cellpadding='0' cellspacing='0' align='center' bgcolor='#000000' width='100%'><tr><td>\n" +"<table border='0' cellpadding='5' cellspacing='1' width='100%'>\n" +"<tr>\n" +" <td bgcolor='#CCCCCC'>\n" +" <h4>Notes</h4>\n" +"<tr>\n" +" <td bgcolor='#FFFFFF'>\n" +" <table border='0' cellspacing='0' cellpadding='5'>\n" +" <tr>\n" +" <td>\n" +"<ul>\n" +" <li> There's a\n" +" <a href='http://www.dillo.org/dillorc'>dillorc</a>\n" +" (readable config) file within the tarball; It is well commented\n" +" and has plenty of options to customize dillo, so <STRONG>copy\n" +" it</STRONG> to your <STRONG>~/.dillo/</STRONG> directory, and\n" +" modify to your taste.\n" +" <li> There's documentation for developers in the <CODE>/doc</CODE>\n" +" dir within the tarball; you can find directions on everything\n" +" else at the home page.\n" +" <li> Dillo has context sensitive menus using the\n" +" right mouse button (available on pages, links, images,\n" +" the Back and Forward buttons, and bug meter).\n" +" <li> Dillo behaves very nicely when browsing local files, images, and HTML.\n" +" It's also very good for Internet searching (try Google!).\n" +" <li> This release is mainly intended <strong>for developers</strong>\n" +" and <em>advanced users</em>.\n" +" <li> Frames, Java and Javascript are not supported.\n" +"</ul>\n" +"<br>\n" +" </table>\n" +"</table>\n" +"</table>\n" +"\n" +"<table border='0' width='100%' cellpadding='0' cellspacing='0'><tr><td height='10'></table>\n" +"\n" +"\n" +"<!-- the main layout table, a small vertical spacer -->\n" +"\n" +"<td width='20'>\n" +"\n" +"\n" +"\n" +"<!-- The right column (info) -->\n" +"<td valign='top' align='center'>\n" +"\n" +"\n" +"\n" +"<!-- end of the main layout table -->\n" +"\n" +"\n" +"</table>\n" +"\n" +"<!-- footnotes -->\n" +"\n" +"<br><br><center>\n" +"<hr size='2'>\n" +"<hr size='2'>\n" +"</center>\n" +"</body>\n" +"</html>\n"; + diff --git a/src/IO/dpi.c b/src/IO/dpi.c new file mode 100644 index 00000000..13cd1f74 --- /dev/null +++ b/src/IO/dpi.c @@ -0,0 +1,779 @@ +/* + * File: dpi.c + * + * Copyright (C) 2002-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * Dillo plugins (small programs that interact with dillo) + * + * Dillo plugins are designed to handle: + * bookmarks, cookies, FTP, downloads, files, preferences, https, + * datauri and a lot of any-to-html filters. + */ + + +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> /* for errno */ + +#include <stdio.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include "../msg.h" +#include "../klist.h" +#include "IO.h" +#include "Url.h" +#include "../misc.h" +#include "../../dpip/dpip.h" + +/* #define DEBUG_LEVEL 2 */ +#define DEBUG_LEVEL 4 +#include "../debug.h" + +/* This one is tricky, some sources state it should include the byte + * for the terminating NULL, and others say it shouldn't. */ +# define D_SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + + strlen ((ptr)->sun_path)) + +/* Solaris may not have this one... */ +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#endif + + +typedef struct { + int InTag; + int Send2EOF; + + int DataTotalSize; + int DataRecvSize; + + Dstr *Buf; + + int BufIdx; + int TokIdx; + int TokSize; + int TokIsTag; + + ChainLink *InfoRecv; + int Key; +} dpi_conn_t; + + +/* + * Local data + */ +static Klist_t *ValidConns = NULL; /* Active connections list. It holds + * pointers to dpi_conn_t structures. */ + + +/* + * Initialize local data + */ +void a_Dpi_init(void) +{ + /* empty */ +} + +/* + * Close a FD handling EINTR + */ +static void Dpi_close_fd(int fd) +{ + int st; + + do + st = close(fd); + while (st < 0 && errno == EINTR); +} + +/* + * Create a new connection data structure + */ +static dpi_conn_t *Dpi_conn_new(ChainLink *Info) +{ + dpi_conn_t *conn = dNew0(dpi_conn_t, 1); + + conn->Buf = dStr_sized_new(8*1024); + conn->InfoRecv = Info; + conn->Key = a_Klist_insert(&ValidConns, conn); + + return conn; +} + +/* + * Free a connection data structure + */ +static void Dpi_conn_free(dpi_conn_t *conn) +{ + a_Klist_remove(ValidConns, conn->Key); + dStr_free(conn->Buf, 1); + dFree(conn); +} + +/* + * Check whether a conn is still valid. + * Return: 1 if found, 0 otherwise + */ +int Dpi_conn_valid(int key) +{ + return (a_Klist_get_data(ValidConns, key)) ? 1 : 0; +} + +/* + * Append the new buffer in 'dbuf' to Buf in 'conn' + */ +static void Dpi_append_dbuf(dpi_conn_t *conn, DataBuf *dbuf) +{ + if (dbuf->Code == 0 && dbuf->Size > 0) { + dStr_append_l(conn->Buf, dbuf->Buf, dbuf->Size); + } +} + +/* + * Split the data stream into tokens. + * Here, a token is either: + * a) a dpi tag + * b) a raw data chunk + * + * Return Value: 0 upon a new token, -1 on not enough data. + * + * TODO: define an API and move this function into libDpip.a. +*/ +static int Dpi_get_token(dpi_conn_t *conn) +{ + int i, resp = -1; + char *buf = conn->Buf->str; + + if (conn->BufIdx == conn->Buf->len) { + dStr_truncate(conn->Buf, 0); + conn->BufIdx = 0; + return resp; + } + + if (conn->Send2EOF) { + conn->TokIdx = conn->BufIdx; + conn->TokSize = conn->Buf->len - conn->BufIdx; + conn->BufIdx = conn->Buf->len; + return 0; + } + + _MSG("conn->BufIdx = %d; conn->Buf->len = %d\nbuf: [%s]\n", + conn->BufIdx,conn->Buf->len, conn->Buf->str + conn->BufIdx); + + if (!conn->InTag) { + /* search for start of tag */ + while (conn->BufIdx < conn->Buf->len && buf[conn->BufIdx] != '<') + ++conn->BufIdx; + if (conn->BufIdx < conn->Buf->len) { + /* found */ + conn->InTag = 1; + conn->TokIdx = conn->BufIdx; + } else { + MSG_ERR("[Dpi_get_token] Can't find token start\n"); + } + } + + if (conn->InTag) { + /* search for end of tag (EOT=" '>") */ + for (i = conn->BufIdx; i < conn->Buf->len; ++i) + if (buf[i] == '>' && i >= 2 && buf[i-1] == '\'' && buf[i-2] == ' ') + break; + conn->BufIdx = i; + + if (conn->BufIdx < conn->Buf->len) { + /* found EOT */ + conn->TokIsTag = 1; + conn->TokSize = conn->BufIdx - conn->TokIdx + 1; + ++conn->BufIdx; + conn->InTag = 0; + resp = 0; + } + } + + return resp; +} + +/* + * Parse a dpi tag and take the appropriate actions + */ +static void Dpi_parse_token(dpi_conn_t *conn) +{ + char *tag, *cmd, *msg, *urlstr; + DataBuf *dbuf; + char *Tok = conn->Buf->str + conn->TokIdx; + + if (conn->Send2EOF) { + /* we're receiving data chunks from a HTML page */ + dbuf = a_Chain_dbuf_new(Tok, conn->TokSize, 0); + a_Chain_fcb(OpSend, conn->InfoRecv, dbuf, "send_page_2eof"); + dFree(dbuf); + return; + } + + tag = dStrndup(Tok, (size_t)conn->TokSize); + _MSG("Dpi_parse_token: {%s}\n", tag); + + cmd = a_Dpip_get_attr(Tok, conn->TokSize, "cmd"); + if (strcmp(cmd, "send_status_message") == 0) { + msg = a_Dpip_get_attr(Tok, conn->TokSize, "msg"); + a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd); + dFree(msg); + + } else if (strcmp(cmd, "chat") == 0) { + msg = a_Dpip_get_attr(Tok, conn->TokSize, "msg"); + a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd); + dFree(msg); + + } else if (strcmp(cmd, "dialog") == 0) { + /* For now will send the dpip tag... */ + a_Chain_fcb(OpSend, conn->InfoRecv, tag, cmd); + + } else if (strcmp(cmd, "start_send_page") == 0) { + conn->Send2EOF = 1; + urlstr = a_Dpip_get_attr(Tok, conn->TokSize, "url"); + a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd); + dFree(urlstr); + /* todo: a_Dpip_get_attr(Tok, conn->TokSize, "send_mode") */ + + } else if (strcmp(cmd, "reload_request") == 0) { + urlstr = a_Dpip_get_attr(Tok, conn->TokSize, "url"); + a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd); + dFree(urlstr); + } + dFree(cmd); + + dFree(tag); +} + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* + * Get a new data buffer (within a 'dbuf'), save it into local data, + * split in tokens and parse the contents. + */ +static void Dpi_process_dbuf(int Op, void *Data1, dpi_conn_t *conn) +{ + DataBuf *dbuf = Data1; + int key = conn->Key; + + /* Very useful for debugging: show the data stream as received. */ + /* fwrite(dbuf->Buf, dbuf->Size, 1, stdout); */ + + if (Op == IORead) { + Dpi_append_dbuf(conn, dbuf); + /* 'conn' has to be validated because Dpi_parse_token() MAY call abort */ + while (Dpi_conn_valid(key) && Dpi_get_token(conn) != -1) { + Dpi_parse_token(conn); + } + + } else if (Op == IOClose) { + /* unused */ + } +} + +/* + * Start dpid. + * Return: 0 starting now, 1 Error. + */ +static int Dpi_start_dpid(void) +{ + pid_t pid; + int st_pipe[2], n, ret = 1; + char buf[16]; + + /* create a pipe to track our child's status */ + if (pipe(st_pipe)) + return 1; + + pid = fork(); + if (pid == 0) { + /* This is the child process. Execute the command. */ + char *path1 = dStrconcat(dGethomedir(), "/.dillo/dpid", NULL); + Dpi_close_fd(st_pipe[0]); + if (execl(path1, "dpid", NULL) == -1) { + dFree(path1); + if (execlp("dpid", "dpid", NULL) == -1) { + DEBUG_MSG(4, "Dpi_start_dpid (child): %s\n", dStrerror(errno)); + do + n = write(st_pipe[1], "ERROR", 5); + while (n == -1 && errno == EINTR); + Dpi_close_fd(st_pipe[1]); + _exit (EXIT_FAILURE); + } + } + } else if (pid < 0) { + /* The fork failed. Report failure. */ + DEBUG_MSG(4, "Dpi_start_dpid: %s\n", dStrerror(errno)); + /* close the unused pipe */ + Dpi_close_fd(st_pipe[0]); + Dpi_close_fd(st_pipe[1]); + + } else { + /* This is the parent process, check our child status... */ + Dpi_close_fd(st_pipe[1]); + do + n = read(st_pipe[0], buf, 16); + while (n == -1 && errno == EINTR); + DEBUG_MSG(2, "Dpi_start_dpid: n = %d\n", n); + if (n != 5) { + ret = 0; + } else { + DEBUG_MSG(4, "Dpi_start_dpid: %s\n", dStrerror(errno)); + } + } + + return ret; +} + +/* + * Make a connection test for a UDS. + * Return: 0 OK, 1 Not working. + */ +static int Dpi_check_uds(char *uds_name) +{ + struct sockaddr_un pun; + int SockFD, ret = 1; + + if (access(uds_name, W_OK) == 0) { + /* socket connection test */ + memset(&pun, 0, sizeof(struct sockaddr_un)); + pun.sun_family = AF_LOCAL; + strncpy(pun.sun_path, uds_name, sizeof (pun.sun_path)); + + if ((SockFD = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1 || + connect(SockFD, (void*)&pun, D_SUN_LEN(&pun)) == -1) { + DEBUG_MSG(4, "Dpi_check_uds: %s %s\n", dStrerror(errno), uds_name); + } else { + Dpi_close_fd(SockFD); + ret = 0; + } + } + return ret; +} + +/* + * Return the directory where the UDS are in, + * NULL if it can't be found. + */ +static char *Dpi_get_dpid_uds_dir(void) +{ + FILE *in; + char *saved_name_filename; /* :) */ + char dpid_uds_dir[256], *p = NULL; + + saved_name_filename = + dStrconcat(dGethomedir(), "/.dillo/dpi_socket_dir", NULL); + in = fopen(saved_name_filename, "r"); + dFree(saved_name_filename); + + if (in != NULL) { + fgets(dpid_uds_dir, 256, in); + fclose(in); + if ((p = strchr(dpid_uds_dir, '\n'))) { + *p = 0; + } + if (access(dpid_uds_dir, F_OK) == 0) { + p = dStrdup(dpid_uds_dir); + _MSG("Dpi_get_dpid_uds_dir:: %s\n", p); + } + } + + _MSG("Dpi_get_dpid_uds_dir: %s \n", dStrerror(errno)); + return p; +} + +/* + * Return the dpid's UDS name, NULL on failure. + */ +static char *Dpi_get_dpid_uds_name(void) +{ + char *dpid_uds_dir, *dpid_uds_name = NULL; + + if ((dpid_uds_dir = Dpi_get_dpid_uds_dir()) != NULL) + dpid_uds_name= dStrconcat(dpid_uds_dir, "/", "dpid.srs", NULL); + + dFree(dpid_uds_dir); + return dpid_uds_name; +} + +/* + * Confirm that the dpid is running. If not, start it. + * Return: 0 running OK, 1 starting (EAGAIN), 2 Error. + */ +static int Dpi_check_dpid(int num_tries) +{ + static int starting = 0; + char *dpid_uds_name; + int check_st = 1, ret = 2; + + if ((dpid_uds_name = Dpi_get_dpid_uds_name())) + check_st = Dpi_check_uds(dpid_uds_name); + + _MSG("Dpi_check_dpid: dpid_uds_name=%s, check_st=%d\n", + dpid_uds_name, check_st); + + if (check_st == 0) { + /* connection test with dpi server passed */ + starting = 0; + ret = 0; + } else if (!dpid_uds_name || check_st) { + if (!starting) { + /* start dpid */ + if (Dpi_start_dpid() == 0) { + starting = 1; + ret = 1; + } + } else if (++starting < num_tries) { + ret = 1; + } else { + /* we waited too much, report an error... */ + starting = 0; + } + } + + dFree(dpid_uds_name); + DEBUG_MSG(2, "Dpi_check_dpid:: %s\n", + (ret == 0) ? "OK" : (ret == 1 ? "EAGAIN" : "ERROR")); + return ret; +} + +/* + * Confirm that the dpid is running. If not, start it. + * Return: 0 running OK, 2 Error. + */ +static int Dpi_blocking_start_dpid(void) +{ + int cst, try = 0, + n_tries = 12; /* 3 seconds */ + + /* test the dpid, and wait a bit for it to start if necessary */ + while ((cst = Dpi_check_dpid(n_tries)) == 1) { + MSG("Dpi_blocking_start_dpid: try %d\n", ++try); + usleep(250000); /* 1/4 sec */ + } + return cst; +} + +/* + * Return the UDS name of a dpi server. + * (A query is sent to dpid and then its answer parsed) + * note: as the available servers and/or the dpi socket directory can + * change at any time, we'll ask each time. If someday we find + * that connecting each time significantly degrades performance, + * an optimized approach can be tried. + */ +static char *Dpi_get_server_uds_name(const char *server_name) +{ + char *dpid_uds_dir, *dpid_uds_name = NULL, + *server_uds_name = NULL; + int st; + + dReturn_val_if_fail (server_name != NULL, NULL); + DEBUG_MSG(2, "Dpi_get_server_uds_name:: server_name = [%s]\n", server_name); + + dpid_uds_dir = Dpi_get_dpid_uds_dir(); + if (dpid_uds_dir) { + struct sockaddr_un dpid; + int sock, req_sz, rdlen; + char buf[128], *cmd, *request, *rply; + size_t buflen; + + /* Get the server's uds name from dpid */ + sock = socket(AF_LOCAL, SOCK_STREAM, 0); + dpid.sun_family = AF_LOCAL; + dpid_uds_name = dStrconcat(dpid_uds_dir, "/", "dpid.srs", NULL); + _MSG("dpid_uds_name = [%s]\n", dpid_uds_name); + strncpy(dpid.sun_path, dpid_uds_name, sizeof(dpid.sun_path)); + + if (connect(sock, (struct sockaddr *) &dpid, sizeof(dpid)) == -1) + perror("connect"); + /* ask dpid to check the server plugin and send its UDS name back */ + request = a_Dpip_build_cmd("cmd=%s msg=%s", "check_server", server_name); + DEBUG_MSG(2, "[%s]\n", request); + do + st = write(sock, request, strlen(request)); + while (st < 0 && errno == EINTR); + if (st < 0 && errno != EINTR) + perror("writing request"); + dFree(request); + shutdown(sock, 1); /* signals no more writes to dpid */ + + /* Get the reply */ + rply = NULL; + buf[0] = '\0'; + buflen = sizeof(buf)/sizeof(buf[0]); + for (req_sz = 0; (rdlen = read(sock, buf, buflen)) != 0; + req_sz += rdlen) { + if (rdlen == -1 && errno == EINTR) + continue; + if (rdlen == -1) { + perror(" ** Dpi_get_server_uds_name **"); + break; + } + rply = dRealloc(rply, (uint_t)(req_sz + rdlen + 1)); + if (req_sz == 0) + rply[0] = '\0'; + strncat(rply, buf, (size_t)rdlen); + } + Dpi_close_fd(sock); + DEBUG_MSG(2, "rply = [%s]\n", rply); + + /* Parse reply */ + if (rdlen == 0 && rply) { + cmd = a_Dpip_get_attr(rply, (int)strlen(rply), "cmd"); + if (strcmp(cmd, "send_data") == 0) + server_uds_name = a_Dpip_get_attr(rply, (int)strlen(rply), "msg"); + dFree(cmd); + dFree(rply); + } + } + dFree(dpid_uds_dir); + dFree(dpid_uds_name); + DEBUG_MSG(2, "Dpi_get_server_uds_name:: %s\n", server_uds_name); + return server_uds_name; +} + + +/* + * Connect a socket to a dpi server and return the socket's FD. + * We have to ask 'dpid' (dpi daemon) for the UDS of the target dpi server. + * Once we have it, then the proper file descriptor is returned (-1 on error). + */ +static int Dpi_connect_socket(const char *server_name, int retry) +{ + char *server_uds_name; + struct sockaddr_un pun; + int SockFD, err; + + /* Query dpid for the UDS name for this server */ + server_uds_name = Dpi_get_server_uds_name(server_name); + DEBUG_MSG(2, "server_uds_name = [%s]\n", server_uds_name); + + if (access(server_uds_name, F_OK) != 0) { + MSG("server socket was NOT found\n"); + return -1; + } + + /* connect with this server's socket */ + memset(&pun, 0, sizeof(struct sockaddr_un)); + pun.sun_family = AF_LOCAL; + strncpy(pun.sun_path, server_uds_name, sizeof (pun.sun_path)); + dFree(server_uds_name); + + if ((SockFD = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) + perror("[dpi::socket]"); + else if (connect(SockFD, (void*)&pun, D_SUN_LEN(&pun)) == -1) { + err = errno; + SockFD = -1; + MSG("[dpi::connect] errno:%d %s\n", errno, dStrerror(errno)); + if (retry) { + switch (err) { + case ECONNREFUSED: case EBADF: case ENOTSOCK: case EADDRNOTAVAIL: + /* the server may crash and its socket name survive */ + unlink(pun.sun_path); + SockFD = Dpi_connect_socket(server_name, FALSE); + break; + } + } + } + + return SockFD; +} + + +/* + * CCC function for the Dpi module + */ +void a_Dpi_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2) +{ + dpi_conn_t *conn; + int SockFD = -1, st; + + a_Chain_debug_msg("a_Dpi_ccc", Op, Branch, Dir); + + if (Branch == 1) { + if (Dir == BCK) { + /* Send commands to dpi-server */ + switch (Op) { + case OpStart: + if ((st = Dpi_blocking_start_dpid()) == 0) { + SockFD = Dpi_connect_socket(Data1, TRUE); + if (SockFD != -1) { + int *fd = dNew(int, 1); + *fd = SockFD; + Info->LocalKey = fd; + a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 1, 1); + a_Chain_bcb(OpStart, Info, Info->LocalKey, NULL); + /* tell the capi to start the receiving branch */ + a_Chain_fcb(OpSend, Info, Info->LocalKey, "SockFD"); + } + } + + if (st == 0 && SockFD != -1) { + a_Chain_fcb(OpSend, Info, NULL, "DpidOK"); + } else { + MSG_ERR("dpi.c: can't start dpi daemon\n"); + a_Dpi_ccc(OpAbort, 1, FWD, Info, NULL, "DpidERROR"); + } + break; + case OpSend: + a_Chain_bcb(OpSend, Info, Data1, NULL); + break; + case OpEnd: + a_Chain_bcb(OpEnd, Info, NULL, NULL); + dFree(Info->LocalKey); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* FWD */ + /* Send commands to dpi-server (status) */ + switch (Op) { + case OpAbort: + a_Chain_fcb(OpAbort, Info, NULL, Data2); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + + } else if (Branch == 2) { + if (Dir == FWD) { + /* Receiving from server */ + switch (Op) { + case OpSend: + /* Data1 = dbuf */ + Dpi_process_dbuf(IORead, Data1, Info->LocalKey); + break; + case OpEnd: + a_Chain_fcb(OpEnd, Info, NULL, NULL); + Dpi_conn_free(Info->LocalKey); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* BCK */ + switch (Op) { + case OpStart: + conn = Dpi_conn_new(Info); + Info->LocalKey = conn; + + /* Hack: for receiving HTTP through the DPI framework */ + if (strcmp(Data2, "http") == 0) { + conn->Send2EOF = 1; + } + + a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 2, 2); + a_Chain_bcb(OpStart, Info, NULL, Data1); /* IORead, SockFD */ + break; + case OpAbort: + a_Chain_bcb(OpAbort, Info, NULL, NULL); + Dpi_conn_free(Info->LocalKey); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + } +} + +/*! Send DpiBye to dpid + * Note: currently disabled. Maybe it'd be better to have a + * dpid_idle_timeout variable in the config file. + */ +void a_Dpi_bye_dpid() +{ + char *DpiBye_cmd; + struct sockaddr_un sa; + size_t sun_path_len, addr_len; + char *srs_name; + int new_socket; + + srs_name = Dpi_get_dpid_uds_name(); + sun_path_len = sizeof(sa.sun_path); + addr_len = sizeof(sa); + + sa.sun_family = AF_LOCAL; + + if ((new_socket = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { + DEBUG_MSG(4, "a_Dpi_bye_dpid: %s\n", dStrerror(errno)); + } + strncpy(sa.sun_path, srs_name, sizeof (sa.sun_path)); + if (connect(new_socket, (struct sockaddr *) &sa, addr_len) == -1) { + DEBUG_MSG(4, "a_Dpi_bye_dpid: %s\n", dStrerror(errno)); + MSG("%s\n", sa.sun_path); + } + DpiBye_cmd = a_Dpip_build_cmd("cmd=%s", "DpiBye"); + (void) write(new_socket, DpiBye_cmd, strlen(DpiBye_cmd)); + dFree(DpiBye_cmd); + Dpi_close_fd(new_socket); +} + + +/* + * Send a command to a dpi server, and block until the answer is got. + * Return value: the dpip tag answer as an string, NULL on error. + */ +char *a_Dpi_send_blocking_cmd(const char *server_name, const char *cmd) +{ + int cst, SockFD; + ssize_t st; + char buf[16384], *retval = NULL; + + /* test the dpid, and wait a bit for it to start if necessary */ + if ((cst = Dpi_blocking_start_dpid()) != 0) { + return retval; + } + + SockFD = Dpi_connect_socket(server_name, TRUE); + if (SockFD != -1) { + /* todo: handle the case of (st < strlen(cmd)) */ + do + st = write(SockFD, cmd, strlen(cmd)); + while (st == -1 && errno == EINTR); + + /* todo: if the answer is too long... */ + do + st = read(SockFD, buf, 16384); + while (st < 0 && errno == EINTR); + + if (st == -1) + perror("[a_Dpi_send_blocking_cmd]"); + else if (st > 0) + retval = dStrndup(buf, (size_t)st); + + Dpi_close_fd(SockFD); + + } else { + perror("[a_Dpi_send_blocking_cmd]"); + } + + return retval; +} + diff --git a/src/IO/http.c b/src/IO/http.c new file mode 100644 index 00000000..f9b483fa --- /dev/null +++ b/src/IO/http.c @@ -0,0 +1,494 @@ +/* + * File: http.c + * + * Copyright (C) 2000, 2001 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * HTTP connect functions + */ + + +#include <config.h> + +#include <unistd.h> +#include <errno.h> /* for errno */ +#include <stdlib.h> +#include <signal.h> +#include <fcntl.h> +#include <sys/wait.h> +#include <sys/socket.h> /* for lots of socket stuff */ +#include <netinet/in.h> /* for ntohl and stuff */ +#include <arpa/inet.h> /* for inet_ntop */ + +#include "IO.h" +#include "Url.h" +#include "../msg.h" +#include "../klist.h" +#include "../dns.h" +#include "../cache.h" +#include "../web.hh" +#include "../cookies.h" +#include "../prefs.h" +#include "../misc.h" + +#include "../uicmd.hh" + +/* Used to send a message to the bw's status bar */ +#define MSG_BW(web, root, ...) \ +D_STMT_START { \ + if (a_Web_valid((web)) && (!(root) || (web)->flags & WEB_RootUrl)) \ + a_UIcmd_set_msg((web)->bw, __VA_ARGS__); \ +} D_STMT_END + +#define _MSG_BW(web, root, ...) + +#define DEBUG_LEVEL 5 +#include "../debug.h" + + + +/* 'Url' and 'web' are just references (no need to deallocate them here). */ +typedef struct { + int SockFD; + const DilloUrl *Url; /* reference to original URL */ + uint_t port; /* need a separate port in order to support PROXY */ + bool_t use_proxy; /* indicates whether to use proxy or not */ + DilloWeb *web; /* reference to client's web structure */ + Dlist *addr_list; /* Holds the DNS answer */ + int Err; /* Holds the errno of the connect() call */ + ChainLink *Info; /* Used for CCC asynchronous operations */ +} SocketData_t; + + +/* + * Local data + */ +static Klist_t *ValidSocks = NULL; /* Active sockets list. It holds pointers to + * SocketData_t structures. */ + +static DilloUrl *HTTP_Proxy = NULL; +static char *HTTP_Proxy_Auth_base64 = NULL; + +/* + * Initialize proxy vars. + */ +int a_Http_init(void) +{ + char *env_proxy = getenv("http_proxy"); + + if (env_proxy && strlen(env_proxy)) + HTTP_Proxy = a_Url_new(env_proxy, NULL, 0, 0, 0); + if (!HTTP_Proxy && prefs.http_proxy) + HTTP_Proxy = a_Url_dup(prefs.http_proxy); + +/* This allows for storing the proxy password in "user:passwd" format + * in dillorc, but as this constitutes a security problem, it was disabled. + * + if (HTTP_Proxy && prefs.http_proxyuser && strchr(prefs.http_proxyuser, ':')) + HTTP_Proxy_Auth_base64 = a_Misc_encode_base64(prefs.http_proxyuser); + */ + return 0; +} + +/* + * Tell whether the proxy auth is already set (user:password) + * Return: 1 Yes, 0 No + */ +int a_Http_proxy_auth(void) +{ + return (HTTP_Proxy_Auth_base64 ? 1 : 0); +} + +/* + * Activate entered proxy password for HTTP. + */ +void a_Http_set_proxy_passwd(char *str) +{ + char *http_proxyauth = dStrconcat(prefs.http_proxyuser, ":", str, NULL); + HTTP_Proxy_Auth_base64 = a_Misc_encode_base64(http_proxyauth); + dFree(http_proxyauth); +} + +/* + * Create and init a new SocketData_t struct, insert into ValidSocks, + * and return a primary key for it. + */ +static int Http_sock_new(void) +{ + SocketData_t *S = dNew0(SocketData_t, 1); + return a_Klist_insert(&ValidSocks, S); +} + +/* + * Free SocketData_t struct + */ +static void Http_socket_free(int SKey) +{ + SocketData_t *S; + + if ((S = a_Klist_get_data(ValidSocks, SKey))) { + a_Klist_remove(ValidSocks, SKey); + dFree(S); + } +} + +/* + * Close the socket's FD + */ +static void Http_socket_close(SocketData_t *S) +{ + int st; + do + st = close(S->SockFD); + while (st < 0 && errno == EINTR); +} + +/* + * Make the http query string + */ +char *a_Http_make_query_str(const DilloUrl *url, bool_t use_proxy) +{ + char *str, *ptr, *cookies; + Dstr *s_port = dStr_new(""), + *query = dStr_new(""), + *full_path = dStr_new(""), + *proxy_auth = dStr_new(""); + + /* Sending the default port in the query may cause a 302-answer. --Jcid */ + if (URL_PORT(url) && URL_PORT(url) != DILLO_URL_HTTP_PORT) + dStr_sprintfa(s_port, ":%d", URL_PORT(url)); + + if (use_proxy) { + dStr_sprintfa(full_path, "%s%s", + URL_STR(url), + (URL_PATH_(url) || URL_QUERY_(url)) ? "" : "/"); + if ((ptr = strrchr(full_path->str, '#'))) + dStr_truncate(full_path, ptr - full_path->str); + if (HTTP_Proxy_Auth_base64) + dStr_sprintf(proxy_auth, "Proxy-Authorization: Basic %s\r\n", + HTTP_Proxy_Auth_base64); + } else { + dStr_sprintfa(full_path, "%s%s%s%s", + URL_PATH(url), + URL_QUERY_(url) ? "?" : "", + URL_QUERY(url), + (URL_PATH_(url) || URL_QUERY_(url)) ? "" : "/"); + } + + cookies = a_Cookies_get(url); + if (URL_FLAGS(url) & URL_Post) { + dStr_sprintfa( + query, + "POST %s HTTP/1.0\r\n" + "Accept-Charset: utf-8, iso-8859-1\r\n" + "Host: %s%s\r\n" + "%s" + "User-Agent: Dillo/%s\r\n" + "Cookie2: $Version=\"1\"\r\n" + "%s" + "Content-type: application/x-www-form-urlencoded\r\n" + "Content-length: %ld\r\n" + "\r\n" + "%s", + full_path->str, URL_HOST(url), s_port->str, + proxy_auth->str, VERSION, cookies, + (long)strlen(URL_DATA(url)), + URL_DATA(url)); + + } else { + dStr_sprintfa( + query, + "GET %s HTTP/1.0\r\n" + "%s" + "Accept-Charset: utf-8, iso-8859-1\r\n" + "Host: %s%s\r\n" + "%s" + "User-Agent: Dillo/%s\r\n" + "Cookie2: $Version=\"1\"\r\n" + "%s" + "\r\n", + full_path->str, + (URL_FLAGS(url) & URL_E2EReload) ? + "Cache-Control: no-cache\r\nPragma: no-cache\r\n" : "", + URL_HOST(url), s_port->str, + proxy_auth->str, + VERSION, + cookies); + } + dFree(cookies); + + str = query->str; + dStr_free(query, FALSE); + dStr_free(s_port, TRUE); + dStr_free(full_path, TRUE); + dStr_free(proxy_auth, TRUE); + DEBUG_MSG(4, "Query:\n%s", str); + return str; +} + +/* + * Create and submit the HTTP query to the IO engine + */ +static void Http_send_query(ChainLink *Info, SocketData_t *S) +{ + char *query; + DataBuf *dbuf; + + /* Create the query */ + query = a_Http_make_query_str(S->Url, S->use_proxy); + dbuf = a_Chain_dbuf_new(query, (int)strlen(query), 0); + + /* actually this message is sent too early. + * It should go when the socket is ready for writing (i.e. connected) */ + _MSG_BW(S->web, 1, "Sending query to %s...", URL_HOST_(S->Url)); + + /* send query */ + a_Chain_link_new(Info, a_Http_ccc, BCK, a_IO_ccc, 1, 1); + a_Chain_bcb(OpStart, Info, &S->SockFD, NULL); + a_Chain_bcb(OpSend, Info, dbuf, NULL); + dFree(dbuf); + dFree(query); + + /* Tell the cache to start the receiving CCC for the answer */ + a_Chain_fcb(OpSend, Info, &S->SockFD, "SockFD"); +} + +/* + * This function gets called after the DNS succeeds solving a hostname. + * Task: Finish socket setup and start connecting the socket. + * Return value: 0 on success; -1 on error. + */ +static int Http_connect_socket(ChainLink *Info) +{ + int status; +#ifdef ENABLE_IPV6 + struct sockaddr_in6 name; +#else + struct sockaddr_in name; +#endif + SocketData_t *S; + DilloHost *dh; + socklen_t socket_len = 0; + + S = a_Klist_get_data(ValidSocks, VOIDP2INT(Info->LocalKey)); + + /* TODO: iterate this address list until success, or end-of-list */ + dh = dList_nth_data(S->addr_list, 0); + + if ((S->SockFD = socket(dh->af, SOCK_STREAM, IPPROTO_TCP)) < 0) { + S->Err = errno; + DEBUG_MSG(5, "Http_connect_socket ERROR: %s\n", dStrerror(errno)); + return -1; + } + /* set NONBLOCKING and close on exec. */ + fcntl(S->SockFD, F_SETFL, O_NONBLOCK | fcntl(S->SockFD, F_GETFL)); + fcntl(S->SockFD, F_SETFD, FD_CLOEXEC | fcntl(S->SockFD, F_GETFD)); + + /* Some OSes require this... */ + memset(&name, 0, sizeof(name)); + /* Set remaining parms. */ + switch (dh->af) { + case AF_INET: + { + struct sockaddr_in *sin = (struct sockaddr_in *)&name; + socket_len = sizeof(struct sockaddr_in); + sin->sin_family = dh->af; + sin->sin_port = S->port ? htons(S->port) : htons(DILLO_URL_HTTP_PORT); + memcpy(&sin->sin_addr, dh->data, (size_t)dh->alen); + if (a_Web_valid(S->web) && (S->web->flags & WEB_RootUrl)) + DEBUG_MSG(5, "Connecting to %s\n", inet_ntoa(sin->sin_addr)); + break; + } +#ifdef ENABLE_IPV6 + case AF_INET6: + { + char buf[128]; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&name; + socket_len = sizeof(struct sockaddr_in6); + sin6->sin6_family = dh->af; + sin6->sin6_port = S->port ? htons(S->port) : htons(DILLO_URL_HTTP_PORT); + memcpy(&sin6->sin6_addr, dh->data, dh->alen); + inet_ntop(dh->af, dh->data, buf, sizeof(buf)); + if (a_Web_valid(S->web) && (S->web->flags & WEB_RootUrl)) + DEBUG_MSG(5, "Connecting to %s\n", buf); + break; + } +#endif + } + + MSG_BW(S->web, 1, "Contacting host..."); + status = connect(S->SockFD, (struct sockaddr *)&name, socket_len); + if (status == -1 && errno != EINPROGRESS) { + S->Err = errno; + Http_socket_close(S); + DEBUG_MSG(5, "Http_connect_socket ERROR: %s\n", dStrerror(S->Err)); + return -1; + } else { + Http_send_query(S->Info, S); + } + + return 0; /* Success */ +} + +/* + * Test proxy settings and check the no_proxy domains list + * Return value: whether to use proxy or not. + */ +static int Http_must_use_proxy(const DilloUrl *url) +{ + char *np, *p, *tok; + int ret = 0; + + if (HTTP_Proxy) { + ret = 1; + if (prefs.no_proxy) { + np = dStrdup(prefs.no_proxy); + for (p = np; (tok = dStrsep(&p, " ")); ) { + if (dStristr(URL_AUTHORITY(url), tok)) { + ret = 0; + break; + } + } + dFree(np); + } + } + return ret; +} + +/* + * Callback function for the DNS resolver. + * Continue connecting the socket, or abort upon error condition. + */ +void a_Http_dns_cb(int Status, Dlist *addr_list, void *data) +{ + int SKey = VOIDP2INT(data); + SocketData_t *S; + + S = a_Klist_get_data(ValidSocks, SKey); + if (S) { + if (Status == 0 && addr_list) { + /* Successful DNS answer; save the IP */ + S->addr_list = addr_list; + /* start connecting the socket */ + if (Http_connect_socket(S->Info) < 0) { + MSG_BW(S->web, 1, "ERROR: %s", dStrerror(S->Err)); + a_Chain_fcb(OpAbort, S->Info, NULL, NULL); + dFree(S->Info); + Http_socket_free(SKey); + } + + } else { + /* DNS wasn't able to resolve the hostname */ + MSG_BW(S->web, 0, "ERROR: Dns can't resolve %s", + (S->use_proxy) ? URL_HOST_(HTTP_Proxy) : URL_HOST_(S->Url)); + a_Chain_fcb(OpAbort, S->Info, NULL, NULL); + dFree(S->Info); + Http_socket_free(SKey); + } + } +} + +/* + * Asynchronously create a new http connection for 'Url' + * We'll set some socket parameters; the rest will be set later + * when the IP is known. + * ( Data1 = Web structure ) + * Return value: 0 on success, -1 otherwise + */ +static int Http_get(ChainLink *Info, void *Data1) +{ + SocketData_t *S; + char *hostname; + + S = a_Klist_get_data(ValidSocks, VOIDP2INT(Info->LocalKey)); + /* Reference Web data */ + S->web = Data1; + /* Reference URL data */ + S->Url = S->web->url; + /* Reference Info data */ + S->Info = Info; + + /* Proxy support */ + if (Http_must_use_proxy(S->Url)) { + hostname = dStrdup(URL_HOST(HTTP_Proxy)); + S->port = URL_PORT(HTTP_Proxy); + S->use_proxy = TRUE; + } else { + hostname = dStrdup(URL_HOST(S->Url)); + S->port = URL_PORT(S->Url); + S->use_proxy = FALSE; + } + + /* Let the user know what we'll do */ + MSG_BW(S->web, 1, "DNS resolving %s", URL_HOST_(S->Url)); + + /* Let the DNS engine resolve the hostname, and when done, + * we'll try to connect the socket from the callback function */ + a_Dns_resolve(hostname, a_Http_dns_cb, Info->LocalKey); + + dFree(hostname); + return 0; +} + +/* + * CCC function for the HTTP module + */ +void a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2) +{ + int SKey = VOIDP2INT(Info->LocalKey); + + a_Chain_debug_msg("a_Http_ccc", Op, Branch, Dir); + + if (Branch == 1) { + if (Dir == BCK) { + /* HTTP query branch */ + switch (Op) { + case OpStart: + /* ( Data1 = Web ) */ + SKey = Http_sock_new(); + Info->LocalKey = INT2VOIDP(SKey); + Http_get(Info, Data1); + break; + case OpEnd: + /* finished the HTTP query branch */ + a_Chain_bcb(OpEnd, Info, NULL, NULL); + Http_socket_free(SKey); + dFree(Info); + break; + case OpAbort: + /* something bad happened... */ + a_Chain_bcb(OpAbort, Info, NULL, NULL); + Http_socket_free(SKey); + dFree(Info); + break; + } + } else { /* FWD */ + /* HTTP send-query status branch */ + switch (Op) { + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + } +} + + + +/* + * Deallocate memory used by http module + * (Call this one at exit time) + */ +void a_Http_freeall(void) +{ + a_Klist_free(&ValidSocks); + a_Url_free(HTTP_Proxy); + dFree(HTTP_Proxy_Auth_base64); +} diff --git a/src/IO/iowatch.cc b/src/IO/iowatch.cc new file mode 100644 index 00000000..2c1465ba --- /dev/null +++ b/src/IO/iowatch.cc @@ -0,0 +1,35 @@ +/* + * File: iowatch.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +// Simple ADT for watching file descriptor activity + +#include <fltk/run.h> +#include "iowatch.hh" + +using namespace fltk; + + +// +// Hook a Callback for a certain activities in a FD +// +void a_IOwatch_add_fd(int fd, int when, FileHandler Callback, void *usr_data=0) +{ + add_fd(fd, when, Callback, usr_data); +} + +// +// Remove a Callback for a given FD (or just remove some events) +// +void a_IOwatch_remove_fd(int fd, int when) +{ + remove_fd(fd, when); +} + diff --git a/src/IO/iowatch.hh b/src/IO/iowatch.hh new file mode 100644 index 00000000..681d0080 --- /dev/null +++ b/src/IO/iowatch.hh @@ -0,0 +1,25 @@ +#ifndef __IO_WATCH_H__ +#define __IO_WATCH_H__ + +/* + * BUG: enum {READ = 1, WRITE = 4, EXCEPT = 8} borrowed from fltk/run.h + */ +#define DIO_READ 1 +#define DIO_WRITE 4 +#define DIO_EXCEPT 8 + +typedef void (*CbFunction_t)(int fd, void *data); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void a_IOwatch_add_fd(int fd,int when,CbFunction_t Callback,void *usr_data); +void a_IOwatch_remove_fd(int fd,int when); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __IO_WATCH_H__ */ + diff --git a/src/IO/mime.c b/src/IO/mime.c new file mode 100644 index 00000000..3a8040ae --- /dev/null +++ b/src/IO/mime.c @@ -0,0 +1,152 @@ +/* + * File: mime.c + * + * Copyright (C) 2000 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include "mime.h" +#include "../msg.h" +#include "../list.h" + + +typedef struct { + const char *Name; /* MIME type name */ + Viewer_t Data; /* Pointer to a function */ +} MimeItem_t; + + +/* + * Local data + */ +static int MimeMinItemsSize = 0, MimeMinItemsMax = 8; +static MimeItem_t *MimeMinItems = NULL; + +static int MimeMajItemsSize = 0, MimeMajItemsMax = 8; +static MimeItem_t *MimeMajItems = NULL; + + +/* + * Add a specific MIME type (as "image/png") to our viewer list + * 'Key' is the content-type string that identifies the MIME type + * 'Method' is the function that handles it + */ +static int Mime_add_minor_type(const char *Key, Viewer_t Method) +{ + a_List_add(MimeMinItems, MimeMinItemsSize, MimeMinItemsMax); + MimeMinItems[MimeMinItemsSize].Name = Key; + MimeMinItems[MimeMinItemsSize].Data = Method; + MimeMinItemsSize++; + return 0; +} + +/* + * Add a major MIME type (as "text") to our viewer list + * 'Key' is the content-type string that identifies the MIME type + * 'Method' is the function that handles it + */ +static int Mime_add_major_type(const char *Key, Viewer_t Method) +{ + a_List_add(MimeMajItems, MimeMajItemsSize, MimeMajItemsMax); + MimeMajItems[MimeMajItemsSize].Name = Key; + MimeMajItems[MimeMajItemsSize].Data = Method; + MimeMajItemsSize++; + return 0; +} + +/* + * Search the list of specific MIME viewers, for a Method that matches 'Key' + * 'Key' is the content-type string that identifies the MIME type + */ +static Viewer_t Mime_minor_type_fetch(const char *Key, uint_t Size) +{ + int i; + + if (Size) { + for ( i = 0; i < MimeMinItemsSize; ++i ) + if (dStrncasecmp(Key, MimeMinItems[i].Name, Size) == 0) + return MimeMinItems[i].Data; + } + return NULL; +} + +/* + * Search the list of major MIME viewers, for a Method that matches 'Key' + * 'Key' is the content-type string that identifies the MIME type + */ +static Viewer_t Mime_major_type_fetch(const char *Key, uint_t Size) +{ + int i; + + if (Size) { + for ( i = 0; i < MimeMajItemsSize; ++i ) + if (dStrncasecmp(Key, MimeMajItems[i].Name, Size) == 0) + return MimeMajItems[i].Data; + } + return NULL; +} + + +/* + * Initializes Mime module and, sets the supported Mime types. + */ +void a_Mime_init() +{ +#ifdef ENABLE_GIF + Mime_add_minor_type("image/gif", a_Gif_image); +#endif +#ifdef ENABLE_JPEG + Mime_add_minor_type("image/jpeg", a_Jpeg_image); + Mime_add_minor_type("image/pjpeg", a_Jpeg_image); + Mime_add_minor_type("image/jpg", a_Jpeg_image); +#endif +#ifdef ENABLE_PNG + Mime_add_minor_type("image/png", a_Png_image); + Mime_add_minor_type("image/x-png", a_Png_image); /* deprecated */ +#endif + Mime_add_minor_type("text/html", a_Html_text); + + /* Add a major type to handle all the text stuff */ + Mime_add_major_type("text", a_Plain_text); +} + + +/* + * Call the handler for the MIME type to set Call and Data as appropriate + * + * Return Value: + * On success: a new Dw (and Call and Data properly set). + * On failure: NULL (and Call and Data untouched). + */ +void *a_Mime_set_viewer(const char *content_type, void *Ptr, + CA_Callback_t *Call, void **Data) +{ + + Viewer_t viewer; + uint_t MinSize, MajSize, i; + const char *str = content_type; + + MajSize = 0; + for (i = 0; str[i] && str[i] != ' ' && str[i] != ';'; ++i) { + if (str[i] == '/' && !MajSize) + MajSize = i; + } + MinSize = i; + + /* Try minor type */ + viewer = Mime_minor_type_fetch(content_type, MinSize); + if (viewer) + return viewer(content_type, Ptr, Call, Data); + + /* Try major type */ + viewer = Mime_major_type_fetch(content_type, MajSize); + if (viewer) + return viewer(content_type, Ptr, Call, Data); + + /* Type not handled */ + return NULL; +} diff --git a/src/IO/mime.h b/src/IO/mime.h new file mode 100644 index 00000000..0f51a1e2 --- /dev/null +++ b/src/IO/mime.h @@ -0,0 +1,58 @@ +/* + * File: mime.h + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __MIME_H__ +#define __MIME_H__ + +#include <config.h> +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#include "../cache.h" + +typedef void* (*Viewer_t) (const char*, void*, CA_Callback_t*, void**); + +/* + * Function prototypes defined elsewhere + */ +void *a_Html_text (const char *Type,void *web, CA_Callback_t *Call, + void **Data); +void *a_Plain_text(const char *Type,void *web, CA_Callback_t *Call, + void **Data); +#ifdef ENABLE_JPEG +void *a_Jpeg_image(const char *Type,void *web, CA_Callback_t *Call, + void **Data); +#endif +#ifdef ENABLE_PNG +void *a_Png_image (const char *Type,void *web, CA_Callback_t *Call, + void **Data); +#endif +#ifdef ENABLE_GIF +void *a_Gif_image (const char *Type,void *web, CA_Callback_t *Call, + void **Data); +#endif + +/* + * Functions defined inside Mime module + */ +void a_Mime_init(void); +void *a_Mime_set_viewer(const char *content_type, void *Ptr, + CA_Callback_t *Call, void **Data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MIME_H__ */ diff --git a/src/IO/proto.c b/src/IO/proto.c new file mode 100644 index 00000000..3a9e2177 --- /dev/null +++ b/src/IO/proto.c @@ -0,0 +1,13 @@ +/* + * File: proto.c + * + * Copyright (C) 2003 Jorge Arellano Cid <jcid@dillo.org>, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* This module may be programmed to manage dpi-programs. */ + diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..a2afa02e --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,87 @@ +AM_CPPFLAGS=-DDILLORC_SYS='"$(sysconfdir)/dillorc"' @LIBJPEG_CPPFLAGS@ +AM_CFLAGS = -I../../dw-testbed @LIBPNG_CFLAGS@ +AM_CXXFLAGS = -I../../dw-testbed @LIBPNG_CFLAGS@ @LIBFLTK_CXXFLAGS@ + +SUBDIRS = IO + +bin_PROGRAMS = dillo-fltk + +dillo_fltk_LDADD = \ + ../dlib/libDlib.a \ + ../dpip/libDpip.a \ + IO/libDiof.a \ + ../../dw-testbed/dw/libDw-widgets.a \ + ../../dw-testbed/dw/libDw-fltk.a \ + ../../dw-testbed/dw/libDw-core.a \ + ../../dw-testbed/lout/liblout.a \ + @LIBJPEG_LIBS@ @LIBPNG_LIBS@ @LIBFLTK_LIBS@ @LIBZ_LIBS@ + +dillo_fltk_SOURCES = \ + dillo.cc \ + dir.c \ + dir.h \ + ui.cc \ + ui.hh \ + uicmd.cc \ + uicmd.hh \ + bw.h \ + bw.c \ + cookies.c \ + cookies.h \ + colors.c \ + colors.h \ + binaryconst.h \ + misc.c \ + misc.h \ + history.h \ + history.c \ + prefs.c \ + prefs.h \ + debug.h \ + msg.h \ + list.h \ + url.c \ + url.h \ + bitvec.c \ + bitvec.h \ + klist.c \ + klist.h \ + chain.c \ + chain.h \ + timeout.cc \ + timeout.hh \ + dialog.cc \ + dialog.hh \ + \ + \ + web.cc \ + web.hh \ + nav.c \ + nav.h \ + cache.c \ + cache.h \ + dicache.c \ + dicache.h \ + capi.c \ + capi.h \ + plain.cc \ + form.cc \ + form.hh \ + html.cc \ + html.hh \ + bookmark.c \ + bookmark.h \ + dns.c \ + dns.h \ + gif.c \ + jpeg.c \ + png.c \ + image.cc \ + image.hh \ + menu.hh \ + menu.cc \ + dpiapi.c \ + dpiapi.h \ + pixmaps.h + +EXTRA_DIST = chg srch diff --git a/src/binaryconst.h b/src/binaryconst.h new file mode 100644 index 00000000..b5b9735b --- /dev/null +++ b/src/binaryconst.h @@ -0,0 +1,38 @@ +#ifndef __BINARYCONST_H__ +#define __BINARYCONST_H__ + +/* Macros for allowing binary constants in C + * By Tom Torfs - donated to the public domain */ + +#define HEX__(n) 0x##n##LU + +/* 8-bit conversion function */ +#define B8__(x) ((x&0x0000000FLU)?1:0) \ + +((x&0x000000F0LU)?2:0) \ + +((x&0x00000F00LU)?4:0) \ + +((x&0x0000F000LU)?8:0) \ + +((x&0x000F0000LU)?16:0) \ + +((x&0x00F00000LU)?32:0) \ + +((x&0x0F000000LU)?64:0) \ + +((x&0xF0000000LU)?128:0) + + +/* + * *** USER MACROS *** + */ + +/* for upto 8-bit binary constants */ +#define B8(d) ((unsigned char)B8__(HEX__(d))) + +/* for upto 16-bit binary constants, MSB first */ +#define B16(dmsb,dlsb) (((unsigned short)B8(dmsb)<<8) + B8(dlsb)) + +/* + * Sample usage: + * B8(01010101) = 85 + * B16(10101010,01010101) = 43605 + */ + + +#endif /* __BINARYCONST_H__ */ + diff --git a/src/bitvec.c b/src/bitvec.c new file mode 100644 index 00000000..fc308fc6 --- /dev/null +++ b/src/bitvec.c @@ -0,0 +1,59 @@ +/* + * File: bitvec.c + * + * Copyright 2001 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * A simple ADT for bit arrays + */ + +#include "../dlib/dlib.h" +#include "bitvec.h" + + +/* + * Create a new bitvec with 'num_bits' size + */ +bitvec_t *a_Bitvec_new(int num_bits) +{ + bitvec_t *bvec = dNew(bitvec_t, 1); + + bvec->vec = dNew0(uchar_t, num_bits/BVEC_SIZE + 1); + bvec->len = num_bits; + return bvec; +} + +/* + * Free a bitvec + */ +void a_Bitvec_free(bitvec_t *bvec) +{ + if (bvec) { + dFree(bvec->vec); + dFree(bvec); + } +} + +/* + * Get a bit + */ +int a_Bitvec_get_bit(bitvec_t *bvec, int pos) +{ + dReturn_val_if_fail (pos < bvec->len, 0); + return (bvec->vec[pos/BVEC_SIZE] & 1 << pos % BVEC_SIZE); +} + +/* + * Set a bit + */ +void a_Bitvec_set_bit(bitvec_t *bvec, int pos) +{ + dReturn_if_fail (pos < bvec->len); + bvec->vec[pos/BVEC_SIZE] |= 1 << (pos % BVEC_SIZE); +} diff --git a/src/bitvec.h b/src/bitvec.h new file mode 100644 index 00000000..ab6797ce --- /dev/null +++ b/src/bitvec.h @@ -0,0 +1,36 @@ +#ifndef __BITVEC_H__ +#define __BITVEC_H__ + +#include "d_size.h" + +#define BVEC_TYPE uchar_t +#define BVEC_SIZE sizeof(BVEC_TYPE) + +typedef struct _bitvec bitvec_t; + +struct _bitvec { + BVEC_TYPE *vec; + int len; /* number of bits [1 based] */ +}; + + +/* + * Function prototypes + */ +bitvec_t *a_Bitvec_new(int bits); +void a_Bitvec_free(bitvec_t *bvec); +int a_Bitvec_get_bit(bitvec_t *bvec, int pos); +void a_Bitvec_set_bit(bitvec_t *bvec, int pos); + +/* +#define a_Bitvec_get_bit(bvec,pos) \ + ((bvec)->vec[(pos)/BVEC_SIZE] & 1 << (pos) % BVEC_SIZE) + +#define a_Bitvec_set_bit(bvec,pos) \ + ((bvec)->vec[(pos)/BVEC_SIZE] |= 1 << (pos) % BVEC_SIZE) +*/ +#define a_Bitvec_clear_bit(bvec,pos) \ + ((bvec)->vec[(pos)/BVEC_SIZE] &= ~(1 << (pos) % BVEC_SIZE)) + + +#endif /* __BITVEC_H__ */ diff --git a/src/bookmark.c b/src/bookmark.c new file mode 100644 index 00000000..af37bfed --- /dev/null +++ b/src/bookmark.c @@ -0,0 +1,89 @@ +/* + * File: bookmark.c + * + * Copyright 2002 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "msg.h" +#include "history.h" +#include "capi.h" +#include "bookmark.h" /* for prototypes */ +#include "../dpip/dpip.h" + + + +/* + * Have a short chat with the bookmarks server, + * and finally ask it to add a new bookmark. + * (this is an example of dpi chat) + */ +void a_Bookmarks_chat_add(BrowserWindow *Bw, char *Cmd, char *answer) +{ + static char *cmd1 = NULL, *cmd2 = NULL, *cmd3 = NULL, *cmd4 = NULL; + static BrowserWindow *bw = NULL; + + if (!cmd1) { + cmd1 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat", "Hi server"); + cmd2 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat", + "I want to set a bookmark"); + cmd3 = a_Dpip_build_cmd("cmd=%s msg=%s", "chat", "Sure it is!"); + } + + _MSG("a_Bookmarks_chat_add\n answer=%s\n", answer ? answer : "(null)"); + + if (Bw) + bw = Bw; + if (!cmd4 && Cmd) + cmd4 = dStrdup(Cmd); + + if (!answer) { + a_Capi_dpi_send_cmd(NULL, bw, cmd1, "bookmarks", 1); + + } else { + /* we have an answer */ + if (answer) { + if (*answer == 'H') { + /* "Hi browser" */ + a_Capi_dpi_send_cmd(NULL, bw, cmd2, "bookmarks", 0); + } else if (*answer == 'I') { + /* "Is it worth?" */ + a_Capi_dpi_send_cmd(NULL, bw, cmd3, "bookmarks", 0); + } else if (*answer == 'O') { + /* "OK, send it!" */ + a_Capi_dpi_send_cmd(NULL, bw, cmd4, "bookmarks", 0); + dFree(cmd4); + cmd4 = NULL; + } + } + } +} + +/* + * Add the new bookmark through the bookmarks server + */ +void a_Bookmarks_add(BrowserWindow *bw, DilloUrl *url) +{ + const char *title; + char *cmd; + + dReturn_if_fail(url != NULL); + + /* if the page has no title, we'll use the url string */ + title = a_History_get_title_by_url(url, 1); + + cmd = a_Dpip_build_cmd("cmd=%s url=%s title=%s", + "add_bookmark", URL_STR(url), title); + a_Bookmarks_chat_add(bw, cmd, NULL); + dFree(cmd); +} + diff --git a/src/bookmark.h b/src/bookmark.h new file mode 100644 index 00000000..e1053204 --- /dev/null +++ b/src/bookmark.h @@ -0,0 +1,19 @@ +#ifndef __BOOKMARK_H__ +#define __BOOKMARK_H__ + +#include "bw.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void a_Bookmarks_add(BrowserWindow *bw, DilloUrl *url); + +/* todo: this is for testing purposes */ +void a_Bookmarks_chat_add(BrowserWindow *Bw, char *Cmd, char *answer); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __BOOKMARK_H__ */ diff --git a/src/bw.c b/src/bw.c new file mode 100644 index 00000000..f207557b --- /dev/null +++ b/src/bw.c @@ -0,0 +1,248 @@ +/* + * File: bw.c + * + * Copyright (C) 2006 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* Data structures for each browser window */ + + +#include "bw.h" +#include "list.h" +#include "capi.h" +#include "uicmd.hh" + + +/* + * Local Data + */ +/* A list of working browser windows */ +static BrowserWindow **bws; +static int num_bws, num_bws_max; + + +/* + * Initialize global data + */ +void a_Bw_init(void) +{ + num_bws = 0; + num_bws_max = 16; + bws = NULL; +} + +/* + * Create a new browser window and return it. + * (the new window is stored in browser_window[]) + */ +BrowserWindow *a_Bw_new(int width, int height, uint32_t xid) +{ + BrowserWindow *bw; + + /* We use dNew0() to zero the memory */ + bw = dNew0(BrowserWindow, 1); + a_List_add(bws, num_bws, num_bws_max); + bws[num_bws++] = bw; + + /* Initialize nav_stack */ + bw->nav_stack_size = 0; + bw->nav_stack_size_max = 16; + bw->nav_stack = NULL; + bw->nav_stack_ptr = -1; + bw->nav_expecting = FALSE; + bw->nav_expect_url = NULL; + +// if (!xid) +// bw->main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); +// else +// bw->main_window = gtk_plug_new(xid); + + + bw->redirect_level = 0; + bw->sens_idle_up = 0; + + bw->RootClients = dList_new(8); + bw->ImageClients = dList_new(8); + bw->NumImages = 0; + bw->NumImagesGot = 0; + bw->PageUrls = dList_new(8); + + bw->question_dialog_data = NULL; + + bw->num_page_bugs = 0; + bw->page_bugs = dStr_new(""); + + /* now that the bw is made, let's customize it.. */ + //Interface_browser_window_customize(bw); + + return bw; +} + +/* + * Free resources associated to a bw. + */ +void a_Bw_free(BrowserWindow *bw) +{ + int i, j; + + for (i = 0; i < num_bws; i++) { + if (bws[i] == bw) { + a_List_remove(bws, i, num_bws); + + dList_free(bw->RootClients); + dList_free(bw->ImageClients); + + for (j = 0; j < dList_length(bw->PageUrls); ++j) + a_Url_free(dList_nth_data(bw->PageUrls, j)); + dList_free(bw->PageUrls); + + dFree(bw->nav_stack); + dStr_free(bw->page_bugs, 1); + dFree(bw); + break; + } + } +} + +/*- Clients ----------------------------------------------------------------*/ +/* + * Add a reference to a cache-client. It is kept int this bw's list. + * This helps us keep track of which are active in the window so that it's + * possible to abort/stop them. + * (Root: Flag, whether a Root URL or not) + * + * TODO: Make NumImages count different images. + */ +void a_Bw_add_client(BrowserWindow *bw, int Key, int Root) +{ + dReturn_if_fail ( bw != NULL ); + + if (Root) { + dList_append(bw->RootClients, INT2VOIDP(Key)); + } else { + dList_append(bw->ImageClients, INT2VOIDP(Key)); + bw->NumImages++; + /* --Images progress-bar stuff-- */ + a_UIcmd_set_img_prog(bw, bw->NumImagesGot, bw->NumImages, 1); + } +} + +/* + * Remove the cache-client from the bw's list + * (client can be a image or a html page) + * Return: 0 if found, 1 otherwise. + */ +int a_Bw_remove_client(BrowserWindow *bw, int ClientKey) +{ + void *data; + + if ((data = dList_find(bw->RootClients, INT2VOIDP(ClientKey)))) { + dList_remove_fast(bw->RootClients, data); + } else if ((data = dList_find(bw->ImageClients, INT2VOIDP(ClientKey)))) { + dList_remove_fast(bw->ImageClients, data); + ++bw->NumImagesGot; + } + return data ? 0 : 1; +} + +/* + * Close a cache-client upon successful retrieval. + * Remove the cache-client from the bw list and update the meters. + * (client can be a image or a html page) + */ +void a_Bw_close_client(BrowserWindow *bw, int ClientKey) +{ + if (a_Bw_remove_client(bw, ClientKey) == 0) { + a_UIcmd_set_img_prog(bw, bw->NumImagesGot, bw->NumImages, 1); + if (bw->NumImagesGot == bw->NumImages) + a_UIcmd_set_img_prog(bw, 0, 0, 0); + if (dList_length(bw->RootClients) == 0) + a_UIcmd_set_buttons_sens(bw); + } +} + +/* + * Stop the active clients of this bw's top page. + * Note: rendering stops, but the cache continues to be fed. + */ +void a_Bw_stop_clients(BrowserWindow *bw, int flags) +{ + void *data; + + if (flags & BW_Root) { + /* Remove root clients */ + while ((data = dList_nth_data(bw->RootClients, 0))) { + a_Capi_stop_client(VOIDP2INT(data), (flags & Bw_Force)); + dList_remove_fast(bw->RootClients, data); + } + } + + if (flags & BW_Img) { + /* Remove image clients */ + while ((data = dList_nth_data(bw->ImageClients, 0))) { + a_Capi_stop_client(VOIDP2INT(data), (flags & Bw_Force)); + dList_remove_fast(bw->ImageClients, data); + } + } +} + +/*- PageUrls ---------------------------------------------------------------*/ +/* + * Add an URL to the browser window's list. + * This helps us keep track of page-requested URLs so that it's + * possible to stop, abort and reload them. + */ +void a_Bw_add_url(BrowserWindow *bw, const DilloUrl *Url) +{ + dReturn_if_fail ( bw != NULL && Url != NULL ); + + if (!dList_find_custom(bw->PageUrls, Url, (dCompareFunc)a_Url_cmp)) { + dList_append(bw->PageUrls, a_Url_dup(Url)); + } +} + +/*- Cleanup ----------------------------------------------------------------*/ +/* + * Empty RootClients, ImageClients and PageUrls lists and + * reset progress bar data. + */ +void a_Bw_cleanup(BrowserWindow *bw) +{ + void *data; + + /* Remove root clients */ + while ((data = dList_nth_data(bw->RootClients, 0))) { + dList_remove_fast(bw->RootClients, data); + } + /* Remove image clients */ + while ((data = dList_nth_data(bw->ImageClients, 0))) { + dList_remove_fast(bw->ImageClients, data); + } + /* Remove PageUrls */ + while ((data = dList_nth_data(bw->PageUrls, 0))) { + a_Url_free(data); + dList_remove_fast(bw->PageUrls, data); + } + + /* Zero image-progress data */ + bw->NumImages = 0; + bw->NumImagesGot = 0; +} + +/*--------------------------------------------------------------------------*/ + +/* + * TODO: remove this Hack. + */ +BrowserWindow *a_Bw_get() +{ + if (num_bws > 0) + return bws[0]; + return NULL; +} + diff --git a/src/bw.h b/src/bw.h new file mode 100644 index 00000000..42cb97d6 --- /dev/null +++ b/src/bw.h @@ -0,0 +1,96 @@ +#ifndef __BW_H__ +#define __BW_H__ + +#include <sys/types.h> + +#include "url.h" /* for DilloUrl */ + +/* + * Flag Defines for a_Bw_stop_clients() + */ +#define BW_Root (1) /* Root URLs */ +#define BW_Img (2) /* Image URLs */ +#define Bw_Force (4) /* Stop connection too */ + + +typedef struct _BrowserWindow BrowserWindow; + + +/* browser_window contains the specific data for a single window */ +struct _BrowserWindow +{ + /* Pointer to the UI object this bw belongs to */ + void *ui; + + /* All the rendering is done by this. + * It is defined as a void pointer to avoid C++ in this structure. + * C++ sources have to include browser.h and cast it into an object. */ + void *render_layout; + + /* A list of active cache clients in the window (The primary Key) */ + Dlist *RootClients; + /* Image Keys for all active connections in the window */ + Dlist *ImageClients; + /* Number of images in the page */ + int NumImages; + /* Number of images already loaded */ + int NumImagesGot; + /* List of all Urls requested by this page (and its types) */ + Dlist *PageUrls; + + /* The navigation stack (holds indexes to history list) */ + int *nav_stack; + int nav_stack_size; /* [1 based] */ + int nav_stack_size_max; + /* 'nav_stack_ptr' refers to what's being displayed */ + int nav_stack_ptr; /* [0 based] */ + /* When the user clicks a link, the URL isn't pushed directly to history; + * nav_expect_url holds it until the first answer-bytes are got. Only then + * it is sent to history and referenced in 'nav_stack[++nav_stack_ptr]' */ + DilloUrl *nav_expect_url; + /* 'nav_expecting' is true if the last URL is being loaded for + * the first time and has not gotten the dw yet. */ + bool_t nav_expecting; + + /* Counter for the number of hops on a redirection. Used to stop + * redirection loops (accounts for WEB_RootUrl only) */ + int redirect_level; + + /* flag for button sens idle function */ + int sens_idle_up; + + /* TODO: remove me */ + void *question_dialog_data; + void *question_dialog_answer; + + /* TODO: maybe this fits better in the linkblock. + * Although having it here avoids having a signal for handling it. */ + int num_page_bugs; + Dstr *page_bugs; +}; + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +void a_Bw_init(void); +BrowserWindow *a_Bw_new(int width, int height, uint32_t xid); +void a_Bw_free(BrowserWindow *bw); +BrowserWindow *a_Bw_get(); + +void a_Bw_add_client(BrowserWindow *bw, int Key, int Root); +int a_Bw_remove_client(BrowserWindow *bw, int ClientKey); +void a_Bw_close_client(BrowserWindow *bw, int ClientKey); +void a_Bw_stop_clients(BrowserWindow *bw, int flags); +void a_Bw_add_url(BrowserWindow *bw, const DilloUrl *Url); +void a_Bw_cleanup(BrowserWindow *bw); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __BROWSER_H__ */ + diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 00000000..efe7d0c1 --- /dev/null +++ b/src/cache.c @@ -0,0 +1,932 @@ +/* + * File: cache.c + * + * Copyright 2000, 2001, 2002, 2003, 2004 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * Dillo's cache module + */ + +#include <ctype.h> /* for tolower */ +#include <sys/types.h> + +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> + +#include "msg.h" +#include "list.h" +#include "IO/Url.h" +#include "IO/IO.h" +#include "web.hh" +#include "dicache.h" +#include "nav.h" +#include "cookies.h" +#include "misc.h" +#include "capi.h" + +#include "timeout.hh" +#include "uicmd.hh" + +#define NULLKey 0 + +#define DEBUG_LEVEL 5 +#include "debug.h" + +/* Maximun initial size for the automatically-growing data buffer */ +#define MAX_INIT_BUF 1024*1024 +/* Maximum filesize for a URL, before offering a download */ +#define HUGE_FILESIZE 5*1024*1024 + +/* + * Local data types + */ + +typedef struct { + const DilloUrl *Url; /* Cached Url. Url is used as a primary Key */ + char *TypeDet; /* MIME type string (detected from data) */ + char *TypeHdr; /* MIME type string as from the HTTP Header */ + Dstr *Header; /* HTTP header */ + const DilloUrl *Location; /* New URI for redirects */ + Dstr *Data; /* Pointer to raw data */ + int TotalSize; /* Goal size of the whole data (0 if unknown) */ + uint_t Flags; /* Look Flag Defines in cache.h */ +} CacheEntry_t; + + +/* + * Local data + */ +/* A sorted list for cached data. Holds pointers to CacheEntry_t structs */ +static Dlist *CachedURLs; + +/* A list for cache clients. + * Although implemented as a list, we'll call it ClientQueue --Jcid */ +static Dlist *ClientQueue; + +/* A list for delayed clients (it holds weak pointers to cache entries, + * which are used to make deferred calls to Cache_process_queue) */ +static Dlist *DelayedQueue; +static uint_t DelayedQueueIdleId = 0; + + +/* + * Forward declarations + */ +static void Cache_process_queue(CacheEntry_t *entry); +static void Cache_delayed_process_queue(CacheEntry_t *entry); + + +/* + * Determine if two cache entries are equal (used by CachedURLs) + */ +static int Cache_entry_cmp(const void *v1, const void *v2) +{ + const CacheEntry_t *d1 = v1, *d2 = v2; + + return a_Url_cmp(d1->Url, d2->Url); +} + +/* + * Determine if two cache entries are equal, using a URL as key. + */ +static int Cache_entry_by_url_cmp(const void *v1, const void *v2) +{ + const DilloUrl *u1 = ((CacheEntry_t*)v1)->Url; + const DilloUrl *u2 = v2; + + return a_Url_cmp(u1, u2); +} + +/* + * Initialize dicache data + */ +void a_Cache_init(void) +{ + ClientQueue = dList_new(32); + DelayedQueue = dList_new(32); + CachedURLs = dList_new(256); + + /* inject the splash screen in the cache */ + { + DilloUrl *url = a_Url_new("about:splash", NULL, 0, 0, 0); + Dstr *ds = dStr_new(AboutSplash); + a_Cache_entry_inject(url, ds); + dStr_free(ds, 1); + a_Url_free(url); + } +} + +/* Client operations ------------------------------------------------------ */ + +/* + * Make a unique primary-key for cache clients + */ +static int Cache_client_make_key(void) +{ + static int ClientKey = 0; /* Provide a primary key for each client */ + + if (++ClientKey < 0) + ClientKey = 1; + return ClientKey; +} + +/* + * Add a client to ClientQueue. + * - Every client-camp is just a reference (except 'Web'). + * - Return a unique number for identifying the client. + */ +static int Cache_client_enqueue(const DilloUrl *Url, DilloWeb *Web, + CA_Callback_t Callback, void *CbData) +{ + int ClientKey; + CacheClient_t *NewClient; + + NewClient = dNew(CacheClient_t, 1); + ClientKey = Cache_client_make_key(); + NewClient->Key = ClientKey; + NewClient->Url = Url; + NewClient->Buf = NULL; + NewClient->Callback = Callback; + NewClient->CbData = CbData; + NewClient->Web = Web; + + dList_append(ClientQueue, NewClient); + + return ClientKey; +} + +/* + * Compare function for searching a Client by its key + */ +static int Cache_client_by_key_cmp(const void *client, const void *key) +{ + return ((CacheClient_t *)client)->Key - VOIDP2INT(key); +} + +/* + * Remove a client from the queue + */ +static void Cache_client_dequeue(CacheClient_t *Client, int Key) +{ + if (!Client) { + Client = dList_find_custom(ClientQueue, INT2VOIDP(Key), + Cache_client_by_key_cmp); + } + if (Client) { + dList_remove(ClientQueue, Client); + a_Web_free(Client->Web); + dFree(Client); + } +} + + +/* Entry operations ------------------------------------------------------- */ + +/* + * Set safe values for a new cache entry + */ +static void Cache_entry_init(CacheEntry_t *NewEntry, const DilloUrl *Url) +{ + NewEntry->Url = a_Url_dup(Url); + NewEntry->TypeDet = NULL; + NewEntry->TypeHdr = NULL; + NewEntry->Header = dStr_new(""); + NewEntry->Location = NULL; + NewEntry->Data = dStr_sized_new(8*1024); + NewEntry->TotalSize = 0; + NewEntry->Flags = 0; +} + +/* + * Get the data structure for a cached URL (using 'Url' as the search key) + * If 'Url' isn't cached, return NULL + */ +static CacheEntry_t *Cache_entry_search(const DilloUrl *Url) +{ + return dList_find_sorted(CachedURLs, Url, Cache_entry_by_url_cmp); +} + +/* + * Allocate and set a new entry in the cache list + */ +static CacheEntry_t *Cache_entry_add(const DilloUrl *Url) +{ + CacheEntry_t *old_entry, *new_entry; + + if ((old_entry = Cache_entry_search(Url))) { + MSG_WARN("Cache_entry_add, leaking an entry.\n"); + dList_remove(CachedURLs, old_entry); + } + + new_entry = dNew(CacheEntry_t, 1); + Cache_entry_init(new_entry, Url); /* Set safe values */ + dList_insert_sorted(CachedURLs, new_entry, Cache_entry_cmp); + return new_entry; +} + +/* + * Inject full page content directly into the cache. + * Used for "about:splash". May be used for "about:cache" too. + */ +void a_Cache_entry_inject(const DilloUrl *Url, Dstr *data_ds) +{ + CacheEntry_t *entry; + + if (!(entry = Cache_entry_search(Url))) + entry = Cache_entry_add(Url); + entry->Flags |= CA_GotData + CA_GotHeader + CA_GotLength + CA_InternalUrl; + dStr_truncate(entry->Data, 0); + dStr_append_l(entry->Data, data_ds->str, data_ds->len); + dStr_fit(entry->Data); + entry->TotalSize = entry->Data->len; +} + +/* + * Free the components of a CacheEntry_t struct. + */ +static void Cache_entry_free(CacheEntry_t *entry) +{ + a_Url_free((DilloUrl *)entry->Url); + dFree(entry->TypeDet); + dFree(entry->TypeHdr); + dStr_free(entry->Header, TRUE); + a_Url_free((DilloUrl *)entry->Location); + dStr_free(entry->Data, 1); + dFree(entry); +} + +/* + * Remove an entry, from the cache. + * All the entry clients are removed too! (it may stop rendering of this + * same resource on other windows, but nothing more). + */ +void Cache_entry_remove(CacheEntry_t *entry, DilloUrl *url) +{ + int i; + CacheClient_t *Client; + + if (!entry && !(entry = Cache_entry_search(url))) + return; + if (entry->Flags & CA_InternalUrl) + return; + + /* remove all clients for this entry */ + for (i = 0; (Client = dList_nth_data(ClientQueue, i)); ++i) { + if (Client->Url == entry->Url) { + a_Cache_stop_client(Client->Key); + --i; + } + } + + /* remove from DelayedQueue */ + dList_remove(DelayedQueue, entry); + + /* remove from dicache */ + a_Dicache_invalidate_entry(entry->Url); + + /* remove from cache */ + dList_remove(CachedURLs, entry); + Cache_entry_free(entry); +} + +/* + * Wrapper for capi. + */ +void a_Cache_entry_remove_by_url(DilloUrl *url) +{ + Cache_entry_remove(NULL, url); +} + +/* Misc. operations ------------------------------------------------------- */ + +/* + * Try finding the url in the cache. If it hits, send the cache contents + * from there. If it misses, set up a new connection. + * + * - 'Web' is an auxiliar data structure with misc. parameters. + * - 'Call' is the callback that receives the data + * - 'CbData' is custom data passed to 'Call' + * Note: 'Call' and/or 'CbData' can be NULL, in that case they get set + * later by a_Web_dispatch_by_type, based on content/type and 'Web' data. + * + * Return value: A primary key for identifying the client, + * 0 if the client is aborted in the process. + */ +int a_Cache_open_url(void *web, CA_Callback_t Call, void *CbData) +{ + int ClientKey; + CacheEntry_t *entry; + DilloWeb *Web = web; + DilloUrl *Url = Web->url; + + if (URL_FLAGS(Url) & URL_E2EReload) { + /* remove current entry */ + Cache_entry_remove(NULL, Url); + } + + if ((entry = Cache_entry_search(Url))) { + /* URL is cached: feed our client with cached data */ + ClientKey = Cache_client_enqueue(entry->Url, Web, Call, CbData); + Cache_delayed_process_queue(entry); + + } else { + /* URL not cached: create an entry, send our client to the queue, + * and open a new connection */ + entry = Cache_entry_add(Url); + ClientKey = Cache_client_enqueue(entry->Url, Web, Call, CbData); + } + + return ClientKey; +} + +/* + * Get the pointer to the URL document, and its size, from the cache entry. + * Return: 1 cached, 0 not cached. + */ +int a_Cache_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize) +{ + int i; + CacheEntry_t *entry; + + for (i = 0; (entry = Cache_entry_search(Url)); ++i) { + + /* Test for a redirection loop */ + if (entry->Flags & CA_RedirectLoop || i == 3) { + MSG_WARN("Redirect loop for URL: >%s<\n", URL_STR_(Url)); + break; + } + /* Test for a working redirection */ + if (entry && entry->Flags & CA_Redirect && entry->Location) { + Url = entry->Location; + } else + break; + } + + *BufSize = (entry) ? entry->Data->len : 0; + *PBuf = (entry) ? entry->Data->str : NULL; + return (entry ? 1 : 0); +} + +/* + * Extract a single field from the header, allocating and storing the value + * in 'field'. ('fieldname' must not include the trailing ':') + * Return a new string with the field-content if found (NULL on error) + * (This function expects a '\r' stripped header) + */ +static char *Cache_parse_field(const char *header, const char *fieldname) +{ + char *field; + uint_t i, j; + + for (i = 0; header[i]; i++) { + /* Search fieldname */ + for (j = 0; fieldname[j]; j++) + if (tolower(fieldname[j]) != tolower(header[i + j])) + break; + if (fieldname[j]) { + /* skip to next line */ + for ( i += j; header[i] != '\n'; i++); + continue; + } + + i += j; + while (header[i] == ' ') i++; + if (header[i] == ':') { + /* Field found! */ + while (header[++i] == ' '); + for (j = 0; header[i + j] != '\n'; j++); + field = dStrndup(header + i, j); + return field; + } + } + return NULL; +} + +#ifndef DISABLE_COOKIES +/* + * Extract multiple fields from the header. + */ +static Dlist *Cache_parse_multiple_fields(const char *header, + const char *fieldname) +{ + uint_t i, j; + Dlist *fields = dList_new(8); + char *field; + + for (i = 0; header[i]; i++) { + /* Search fieldname */ + for (j = 0; fieldname[j]; j++) + if (tolower(fieldname[j]) != tolower(header[i + j])) + break; + if (fieldname[j]) { + /* skip to next line */ + for (i += j; header[i] != '\n'; i++); + continue; + } + + i += j; + for ( ; header[i] == ' '; i++); + if (header[i] == ':') { + /* Field found! */ + while (header[++i] == ' '); + for (j = 0; header[i + j] != '\n'; j++); + field = dStrndup(header + i, j); + dList_append(fields, field); + } + } + return fields; +} +#endif /* !DISABLE_COOKIES */ + +/* + * Scan, allocate, and set things according to header info. + * (This function needs the whole header to work) + */ +static void Cache_parse_header(CacheEntry_t *entry, + const char *buf, size_t buf_size, int HdrLen) +{ + char *header = entry->Header->str; + char *Length, *Type, *location_str; +#ifndef DISABLE_COOKIES + Dlist *Cookies; + void *data; + int i; +#endif + + if (HdrLen < 12) { + /* Not enough info. */ + + } if (header[9] == '3' && header[10] == '0') { + /* 30x: URL redirection */ + entry->Flags |= CA_Redirect; + if (header[11] == '1') + entry->Flags |= CA_ForceRedirect; /* 301 Moved Permanently */ + else if (header[11] == '2') + entry->Flags |= CA_TempRedirect; /* 302 Temporal Redirect */ + + location_str = Cache_parse_field(header, "Location"); + entry->Location = a_Url_new(location_str, URL_STR_(entry->Url), 0, 0, 0); + dFree(location_str); + + } else if (strncmp(header + 9, "404", 3) == 0) { + entry->Flags |= CA_NotFound; + } + + if ((Length = Cache_parse_field(header, "Content-Length")) != NULL) { + entry->Flags |= CA_GotLength; + entry->TotalSize = strtol(Length, NULL, 10); + if (entry->TotalSize < 0) + entry->TotalSize = 0; + dFree(Length); + } + +#ifndef DISABLE_COOKIES + /* BUG: If a server feels like mixing Set-Cookie2 and Set-Cookie + * responses which aren't identical, then we have a problem. I don't + * know if that is a real issue though. */ + if ((Cookies = Cache_parse_multiple_fields(header, "Set-Cookie2")) || + (Cookies = Cache_parse_multiple_fields(header, "Set-Cookie"))) { + a_Cookies_set(Cookies, entry->Url); + for (i = 0; (data = dList_nth_data(Cookies, i)); ++i) + dFree(data); + dList_free(Cookies); + } +#endif /* !DISABLE_COOKIES */ + + if (entry->TotalSize > 0) { + if (entry->TotalSize > HUGE_FILESIZE) { + entry->Flags |= CA_HugeFile; + } + /* Avoid some reallocs. With MAX_INIT_BUF we avoid a SEGFAULT + * with huge files (e.g. iso files). + * Note: the buffer grows automatically. */ + dStr_free(entry->Data, 1); + entry->Data = dStr_sized_new(MIN(entry->TotalSize+1, MAX_INIT_BUF)); + } + dStr_append_l(entry->Data, buf + HdrLen, (int)buf_size - HdrLen); + + /* Get Content-Type */ + if ((Type = Cache_parse_field(header, "Content-Type")) == NULL) { + MSG_HTTP("Server didn't send Content-Type in header.\n"); + } else { + entry->TypeHdr = Type; + /* This Content-Type is not trusted. It's checked against real data + * in Cache_process_queue(); only then CA_GotContentType becomes true. + */ + } +} + +/* + * Consume bytes until the whole header is got (up to a "\r\n\r\n" sequence) + * (Also strip '\r' chars from header) + */ +static int Cache_get_header(CacheEntry_t *entry, + const char *buf, size_t buf_size) +{ + size_t N, i; + Dstr *hdr = entry->Header; + + /* Header finishes when N = 2 */ + N = (hdr->len && hdr->str[hdr->len - 1] == '\n'); + for (i = 0; i < buf_size && N < 2; ++i) { + if (buf[i] == '\r' || !buf[i]) + continue; + N = (buf[i] == '\n') ? N + 1 : 0; + dStr_append_c(hdr, buf[i]); + } + + if (N == 2) { + /* Got whole header */ + _MSG("Header [buf_size=%d]\n%s", i, hdr->str); + entry->Flags |= CA_GotHeader; + dStr_fit(hdr); + /* Return number of header bytes in 'buf' [1 based] */ + return i; + } + return 0; +} + +/* + * Receive new data, update the reception buffer (for next read), update the + * cache, and service the client queue. + * + * This function gets called whenever the IO has new data. + * 'Op' is the operation to perform + * 'VPtr' is a (void) pointer to the IO control structure + */ +void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, + const DilloUrl *Url) +{ + int len; + CacheEntry_t *entry = Cache_entry_search(Url); + + /* Assert a valid entry (not aborted) */ + if (!entry) + return; + + if (Op == IOClose) { + if (entry->Flags & CA_GotLength && entry->TotalSize != entry->Data->len){ + MSG_HTTP("Content-Length does NOT match message body,\n" + " at: %s\n", URL_STR_(entry->Url)); + } + entry->Flags |= CA_GotData; + entry->Flags &= ~CA_Stopped; /* it may catch up! */ + entry->TotalSize = entry->Data->len; + dStr_fit(entry->Data); /* fit buffer size! */ + Cache_process_queue(entry); + return; + } else if (Op == IOAbort) { + /* unused */ + MSG("a_Cache_process_dbuf Op = IOAbort; not implemented!\n"); + return; + } + + if (!(entry->Flags & CA_GotHeader)) { + /* Haven't got the whole header yet */ + len = Cache_get_header(entry, buf, buf_size); + if (entry->Flags & CA_GotHeader) { + /* Let's scan, allocate, and set things according to header info */ + Cache_parse_header(entry, buf, buf_size, len); + /* Now that we have it parsed, let's update our clients */ + Cache_process_queue(entry); + } + return; + } + + dStr_append_l(entry->Data, buf, (int)buf_size); + Cache_process_queue(entry); +} + +/* + * Process redirections (HTTP 30x answers) + * (This is a work in progress --not finished yet) + */ +static int Cache_redirect(CacheEntry_t *entry, int Flags, BrowserWindow *bw) +{ + DilloUrl *NewUrl; + + _MSG(" Cache_redirect: redirect_level = %d\n", bw->redirect_level); + + /* if there's a redirect loop, stop now */ + if (bw->redirect_level >= 5) + entry->Flags |= CA_RedirectLoop; + + if (entry->Flags & CA_RedirectLoop) { + a_UIcmd_set_msg(bw, "ERROR: redirect loop for: %s", URL_STR_(entry->Url)); + bw->redirect_level = 0; + return 0; + } + + if ((entry->Flags & CA_Redirect && entry->Location) && + (entry->Flags & CA_ForceRedirect || entry->Flags & CA_TempRedirect || + !entry->Data->len || entry->Data->len < 1024)) { + + _MSG(">>>Redirect from: %s\n to %s\n", + URL_STR_(entry->Url), URL_STR_(entry->Location)); + _MSG("%s", entry->Header->str); + + if (Flags & WEB_RootUrl) { + /* Redirection of the main page */ + NewUrl = a_Url_new(URL_STR_(entry->Location), URL_STR_(entry->Url), + 0, 0, 0); + if (entry->Flags & CA_TempRedirect) + a_Url_set_flags(NewUrl, URL_FLAGS(NewUrl) | URL_E2EReload); + a_Nav_push(bw, NewUrl); + a_Url_free(NewUrl); + } else { + /* Sub entity redirection (most probably an image) */ + if (!entry->Data->len) { + DEBUG_MSG(3,">>>Image redirection without entity-content<<<\n"); + } else { + DEBUG_MSG(3, ">>>Image redirection with entity-content<<<\n"); + } + } + } + return 0; +} + +/* + * Check whether a URL scheme is downloadable. + * Return: 1 enabled, 0 disabled. + */ +int Cache_download_enabled(const DilloUrl *url) +{ + if (!dStrcasecmp(URL_SCHEME(url), "http") || + !dStrcasecmp(URL_SCHEME(url), "https") || + !dStrcasecmp(URL_SCHEME(url), "ftp")) + return 1; + return 0; +} + +/* + * Don't process data any further, but let the cache fill the entry. + * (Currently used to handle WEB_RootUrl redirects, + * and to ignore image redirects --Jcid) + */ +static void Cache_null_client(int Op, CacheClient_t *Client) +{ + DilloWeb *Web = Client->Web; + + /* make the stop button insensitive when done */ + if (Op == CA_Close) { + if (Web->flags & WEB_RootUrl) { + /* Remove this client from our active list */ + a_Bw_close_client(Web->bw, Client->Key); + } + } + + /* else ignore */ + + return; +} + +/* + * Update cache clients for a single cache-entry + * Tasks: + * - Set the client function (if not already set) + * - Look if new data is available and pass it to client functions + * - Remove clients when done + * - Call redirect handler + * + * todo: Implement CA_Abort Op in client callback + */ +static void Cache_process_queue(CacheEntry_t *entry) +{ + uint_t i; + int st; + const char *Type; + CacheClient_t *Client; + DilloWeb *ClientWeb; + BrowserWindow *Client_bw = NULL; + static bool_t Busy = FALSE; + bool_t AbortEntry = FALSE; + bool_t OfferDownload = FALSE; + bool_t TypeMismatch = FALSE; + + if (Busy) + MSG_ERR("FATAL!: >>>> Cache_process_queue Caught busy!!! <<<<\n"); + if (!(entry->Flags & CA_GotHeader)) + return; + if (!(entry->Flags & CA_GotContentType)) { + st = a_Misc_get_content_type_from_data( + entry->Data->str, entry->Data->len, &Type); + if (st == 0 || entry->Flags & CA_GotData) { + if (a_Misc_content_type_check(entry->TypeHdr, Type) < 0) { + MSG_HTTP("Content-Type '%s' doesn't match the real data.\n", + entry->TypeHdr); + TypeMismatch = TRUE; + } + entry->TypeDet = dStrdup(Type); + entry->Flags |= CA_GotContentType; + } else + return; /* i.e., wait for more data */ + } + + Busy = TRUE; + for (i = 0; (Client = dList_nth_data(ClientQueue, i)); ++i) { + if (Client->Url == entry->Url) { + ClientWeb = Client->Web; /* It was a (void*) */ + Client_bw = ClientWeb->bw; /* 'bw' in a local var */ + + if (ClientWeb->flags & WEB_RootUrl) { + if (!(entry->Flags & CA_MsgErased)) { + /* clear the "expecting for reply..." message */ + a_UIcmd_set_msg(Client_bw, ""); + entry->Flags |= CA_MsgErased; + } + if (TypeMismatch) { + a_UIcmd_set_msg(Client_bw,"HTTP warning: Content-Type '%s' " + "doesn't match the real data.", entry->TypeHdr); + } + if (entry->Flags & CA_Redirect) { + if (!Client->Callback) { + Client->Callback = Cache_null_client; + Client_bw->redirect_level++; + } + } else { + Client_bw->redirect_level = 0; + } + if (entry->Flags & CA_HugeFile) { + a_UIcmd_set_msg(Client_bw,"Huge file! (%dMB)", + entry->TotalSize / (1024*1024)); + AbortEntry = OfferDownload = TRUE; + } + } else { + /* For non root URLs, ignore redirections and 404 answers */ + if (entry->Flags & CA_Redirect || entry->Flags & CA_NotFound) + Client->Callback = Cache_null_client; + } + + /* Set the client function */ + if (!Client->Callback) { + Client->Callback = Cache_null_client; + if (TypeMismatch) { + AbortEntry = TRUE; + } else { + st = a_Web_dispatch_by_type( + entry->TypeHdr ? entry->TypeHdr : entry->TypeDet, + ClientWeb, &Client->Callback, &Client->CbData); + if (st == -1) { + /* MIME type is not viewable */ + if (ClientWeb->flags & WEB_RootUrl) { + /* prepare a download offer... */ + AbortEntry = OfferDownload = TRUE; + } else { + /* TODO: Resource Type not handled. + * Not aborted to avoid multiple connections on the same + * resource. A better idea is to abort the connection and + * to keep a failed-resource flag in the cache entry. */ + } + } + } + if (AbortEntry) { + a_Bw_remove_client(Client_bw, Client->Key); + Cache_client_dequeue(Client, NULLKey); + --i; /* Keep the index value in the next iteration */ + continue; + } + } + + /* Send data to our client */ + if ((Client->BufSize = entry->Data->len) > 0) { + Client->Buf = entry->Data->str; + (Client->Callback)(CA_Send, Client); + } + + /* Remove client when done */ + if (entry->Flags & CA_GotData) { + /* Copy flags to a local var */ + int flags = ClientWeb->flags; + /* We finished sending data, let the client know */ + (Client->Callback)(CA_Close, Client); + Cache_client_dequeue(Client, NULLKey); + --i; /* Keep the index value in the next iteration */ + + /* within CA_GotData, we assert just one redirect call */ + if (entry->Flags & CA_Redirect) + Cache_redirect(entry, flags, Client_bw); + } + } + } /* for */ + + if (AbortEntry) { + /* Abort the entry, remove it from cache, and maybe offer download. */ + DilloUrl *url = a_Url_dup(entry->Url); + a_Capi_conn_abort_by_url(url); + Cache_entry_remove(entry, NULL); + if (OfferDownload && Cache_download_enabled(url)) { + a_UIcmd_save_link(Client_bw, url); + } + a_Url_free(url); + } + + /* Trigger cleanup when there're no cache clients */ + if (dList_length(ClientQueue) == 0) { + MSG(" a_Dicache_cleanup()\n"); + a_Dicache_cleanup(); + } + + Busy = FALSE; + _MSG("QueueSize ====> %d\n", dList_length(ClientQueue)); +} + +/* + * Callback function for Cache_delayed_process_queue. + */ +static void Cache_delayed_process_queue_callback(void *data) +{ + void *entry; + + while ((entry = dList_nth_data(DelayedQueue, 0))) { + Cache_process_queue((CacheEntry_t *)entry); + /* note that if Cache_process_queue removes the entry, + * the following dList_remove has no effect. */ + dList_remove(DelayedQueue, entry); + } + DelayedQueueIdleId = 0; + a_Timeout_remove(); +} + +/* + * Set a call to Cache_process_queue from the main cycle. + */ +static void Cache_delayed_process_queue(CacheEntry_t *entry) +{ + /* there's no need to repeat entries in the queue */ + if (!dList_find(DelayedQueue, entry)) + dList_append(DelayedQueue, entry); + + if (DelayedQueueIdleId == 0) { + _MSG(" Setting timeout callback\n"); + a_Timeout_add(0.0, Cache_delayed_process_queue_callback, NULL); + DelayedQueueIdleId = 1; + } +} + +/* + * Last Client for this entry? + * Return: Client if true, NULL otherwise + * (cache.c has only one call to a capi function. This avoids a second one) + */ +CacheClient_t *a_Cache_client_get_if_unique(int Key) +{ + int i, n = 0; + CacheClient_t *Client, *iClient; + + if ((Client = dList_find_custom(ClientQueue, INT2VOIDP(Key), + Cache_client_by_key_cmp))) { + for (i = 0; (iClient = dList_nth_data(ClientQueue, i)); ++i) { + if (iClient->Url == Client->Url) { + ++n; + } + } + } + return (n == 1) ? Client : NULL; +} + +/* + * Remove a client from the client queue + * todo: notify the dicache and upper layers + */ +void a_Cache_stop_client(int Key) +{ + CacheClient_t *Client; + + if ((Client = dList_find_custom(ClientQueue, INT2VOIDP(Key), + Cache_client_by_key_cmp))) { + Cache_client_dequeue(Client, NULLKey); + } else { + _MSG("WARNING: Cache_stop_client, inexistent client\n"); + } +} + + +/* + * Memory deallocator (only called at exit time) + */ +void a_Cache_freeall(void) +{ + CacheClient_t *Client; + void *data; + + /* free the client queue */ + while ((Client = dList_nth_data(ClientQueue, 0))) + Cache_client_dequeue(Client, NULLKey); + + /* Remove every cache entry */ + while ((data = dList_nth_data(CachedURLs, 0))) { + dList_remove(CachedURLs, data); + Cache_entry_free(data); + } + /* Remove the cache list */ + dList_free(CachedURLs); +} diff --git a/src/cache.h b/src/cache.h new file mode 100644 index 00000000..e3891740 --- /dev/null +++ b/src/cache.h @@ -0,0 +1,75 @@ +#ifndef __CACHE_H__ +#define __CACHE_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#include "chain.h" +#include "url.h" + +/* + * Cache Op codes + */ +#define CA_Send (0) /* Normal update */ +#define CA_Close (1) /* Successful operation close */ +#define CA_Abort (2) /* Operation abort */ + +/* + * Flag Defines + */ +#define CA_GotHeader (1) /* True after header is completely got */ +#define CA_GotContentType (2) /* True after Content-Type is known */ +#define CA_GotLength (4) /* True if Content-Length is known */ +#define CA_GotData (8) /* True if we have all Data in cache */ +#define CA_FreeData (16) /* Free the cache Data on close */ +#define CA_Redirect (32) /* Data actually points to a redirect */ +#define CA_ForceRedirect (64) /* Unconditional redirect */ +#define CA_TempRedirect (128) /* Temporal redirect */ +#define CA_NotFound (256) /* True if remote server didn't find the URL */ +#define CA_Stopped (512) /* True if the entry has been stopped */ +#define CA_MsgErased (1024) /* Used to erase the bw's status bar */ +#define CA_RedirectLoop (2048) /* Redirect loop */ +#define CA_InternalUrl (4096) /* URL content is generated by dillo */ +#define CA_HugeFile (8192) /* URL content is too big */ + +/* + * Callback type for cache clients + */ +typedef struct _CacheClient CacheClient_t; +typedef void (*CA_Callback_t)(int Op, CacheClient_t *Client); + +/* + * Data structure for cache clients. + */ +struct _CacheClient { + int Key; /* Primary Key for this client */ + const DilloUrl *Url; /* Pointer to a cache entry Url */ + void *Buf; /* Pointer to cache-data */ + uint_t BufSize; /* Valid size of cache-data */ + CA_Callback_t Callback; /* Client function */ + void *CbData; /* Client function data */ + void *Web; /* Pointer to the Web structure of our client */ +}; + +/* + * Function prototypes + */ +void a_Cache_init(void); +int a_Cache_open_url(void *Web, CA_Callback_t Call, void *CbData); +int a_Cache_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize); +void a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size, + const DilloUrl *Url); +void a_Cache_entry_inject(const DilloUrl *Url, Dstr *data_ds); +void a_Cache_entry_remove_by_url(DilloUrl *url); +void a_Cache_freeall(void); +CacheClient_t *a_Cache_client_get_if_unique(int Key); +void a_Cache_stop_client(int Key); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __CACHE_H__ */ + diff --git a/src/capi.c b/src/capi.c new file mode 100644 index 00000000..2b77614d --- /dev/null +++ b/src/capi.c @@ -0,0 +1,587 @@ +/* + * File: capi.c + * + * Copyright 2002-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * Cache API + * This is the module that manages the cache and starts the CCC chains + * to get the requests served. Kind of a broker. + */ + +#include <string.h> + +#include "msg.h" +#include "capi.h" +#include "IO/IO.h" /* for IORead &friends */ +#include "IO/Url.h" +#include "chain.h" +#include "list.h" +#include "history.h" +#include "nav.h" +#include "misc.h" +#include "dpiapi.h" +#include "uicmd.hh" +#include "../dpip/dpip.h" + +/* for testing dpi chat */ +#include "bookmark.h" + +#define DEBUG_LEVEL 5 +#include "debug.h" + +typedef struct { + DilloUrl *url; /* local copy of web->url */ + void *bw; + char *server; + char *datastr; + int SockFD; + int Flags; + ChainLink *InfoSend; + ChainLink *InfoRecv; + + int Ref; +} capi_conn_t; + +/* Flags for conn */ +enum { + PENDING = 1, + TIMEOUT = 2, /* unused */ + ABORTED = 4 +}; + +/* + * Local data + */ +/* Data list for active dpi connections */ +static capi_conn_t **DpiConn = NULL; +static int DpiConnSize; +static int DpiConnMax = 4; + + +/* + * Forward declarations + */ +void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2); + + +/* ------------------------------------------------------------------------- */ + +/* + * Initialize capi&cache data + */ +void a_Capi_init(void) +{ + /* nothing to do for capi yet, just for cache */ + a_Cache_init(); +} + +/* ------------------------------------------------------------------------- */ + +/* + * Create a new connection data structure + */ +static capi_conn_t * + Capi_conn_new(DilloUrl *url, void *bw, char *server, char *datastr) +{ + capi_conn_t *conn; + + conn = dNew(capi_conn_t, 1); + conn->url = url ? a_Url_dup(url) : NULL; + conn->bw = bw; + conn->server = dStrdup(server); + conn->datastr = dStrdup(datastr); + conn->SockFD = -1; + conn->Flags = (strcmp(server, "http") != 0) ? PENDING : 0; + conn->InfoSend = NULL; + conn->InfoRecv = NULL; + conn->Ref = 0; /* Reference count */ + return conn; +} + +/* + * Increment the reference count and add to the list if not present + */ +static void Capi_conn_ref(capi_conn_t *conn) +{ + if (++conn->Ref == 1) { + /* add the connection data to list */ + a_List_add(DpiConn, DpiConnSize, DpiConnMax); + DpiConn[DpiConnSize] = conn; + DpiConnSize++; + } + _MSG(" Capi_conn_ref #%d %p\n", conn->Ref, conn); +} + +/* + * Decrement the reference count (and remove from list when zero) + */ +static void Capi_conn_unref(capi_conn_t *conn) +{ + int i, j; + + _MSG(" Capi_conn_unref #%d %p\n", conn->Ref - 1, conn); + + if (--conn->Ref == 0) { + for (i = 0; i < DpiConnSize; ++i) { + if (DpiConn[i] == conn) { + /* remove conn preserving the list order */ + for (j = i; j + 1 < DpiConnSize; ++j) + DpiConn[j] = DpiConn[j + 1]; + --DpiConnSize; + /* free dynamic memory */ + a_Url_free(conn->url); + dFree(conn->server); + dFree(conn->datastr); + dFree(conn); + break; + } + } + } +} + +/* + * Find connection data by server + */ +static capi_conn_t *Capi_conn_find(char *server) +{ + int i; + + for (i = 0; i < DpiConnSize; ++i) + if (strcmp(server, DpiConn[i]->server) == 0) + return DpiConn[i]; + + return NULL; +} + +/* + * Resume connections that were waiting for dpid to start. + */ +static void Capi_conn_resume(void) +{ + int i; + DataBuf *dbuf; + + for (i = 0; i < DpiConnSize; ++i) { + if (DpiConn[i]->Flags & PENDING) { + capi_conn_t *conn = DpiConn[i]; + dbuf = a_Chain_dbuf_new(conn->datastr,(int)strlen(conn->datastr), 0); + a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL); + dFree(dbuf); + conn->Flags &= ~PENDING; + } + } +} + +/* + * Abort the connection for a given url, using its CCC. + */ +void a_Capi_conn_abort_by_url(const DilloUrl *url) +{ + int i; + + for (i = 0; i < DpiConnSize; ++i) { + if (a_Url_cmp(DpiConn[i]->url, url) == 0) { + capi_conn_t *conn = DpiConn[i]; + if (conn->InfoSend) { + a_Capi_ccc(OpAbort, 1, BCK, conn->InfoSend, NULL, NULL); + } + if (conn->InfoRecv) { + a_Capi_ccc(OpAbort, 2, BCK, conn->InfoRecv, NULL, NULL); + } + break; + } + } +} + +/* ------------------------------------------------------------------------- */ + +/* + * Safety test: only allow dpi-urls from dpi-generated pages. + */ +static int Capi_dpi_verify_request(DilloWeb *web) +{ + DilloUrl *referer; + int allow = FALSE; + + /* test POST and GET */ + if (dStrcasecmp(URL_SCHEME(web->url), "dpi") == 0 && + (strchr(URL_STR(web->url), '?') || URL_DATA_(web->url))) { + /* only allow dpi requests from dpi-generated urls */ + if (a_Nav_stack_size(web->bw)) { + referer = a_History_get_url(NAV_TOP(web->bw)); + if (dStrcasecmp(URL_SCHEME(referer), "dpi") == 0) { + allow = TRUE; + } + } + } else { + allow = TRUE; + } + + if (!allow) { + MSG("Capi_dpi_verify_request: Permission Denied!\n"); + MSG(" URL_STR : %s\n", URL_STR(web->url)); + if (URL_DATA_(web->url)) + MSG(" URL_DATA: %s\n", URL_DATA(web->url)); + } + return allow; +} + +/* + * If the url belongs to a dpi server, return its name. + */ +int Capi_url_uses_dpi(DilloUrl *url, char **server_ptr) +{ + char *p, *server = NULL, *url_str = URL_STR(url); + + if (dStrncasecmp(url_str, "dpi:/", 5) == 0) { + /* dpi prefix, get this server's name */ + if ((p = strchr(url_str + 5, '/')) != NULL) { + server = dStrndup(url_str + 5, (uint_t)(p - url_str - 5)); + } else { + server = dStrdup("?"); + } + if (strcmp(server, "bm") == 0) { + dFree(server); + server = dStrdup("bookmarks"); + } + + } else if (dStrncasecmp(url_str, "ftp:/", 5) == 0) { + server = dStrdup("ftp"); + + } else if (dStrncasecmp(url_str, "https:/", 7) == 0) { + server = dStrdup("https"); + } else if (dStrncasecmp(url_str, "file:", 5) == 0) { + server = dStrdup("file"); + } else if (dStrncasecmp(url_str, "data:", 5) == 0) { + server = dStrdup("datauri"); + } + + return ((*server_ptr = server) ? 1 : 0); +} + +/* + * Build the dpip command tag, according to URL and server. + * todo: make it PROXY-aware (AFAIS, it should be easy) + */ +static char *Capi_dpi_build_cmd(DilloWeb *web, char *server) +{ + char *cmd, *http_query; + + if (strcmp(server, "https") == 0) { + /* Let's be kind and make the HTTP query string for the dpi */ + http_query = a_Http_make_query_str(web->url, FALSE); + cmd = a_Dpip_build_cmd("cmd=%s url=%s query=%s", + "open_url", URL_STR(web->url), http_query); + dFree(http_query); + + } else if (strcmp(server, "downloads") == 0) { + /* let the downloads server get it */ + cmd = a_Dpip_build_cmd("cmd=%s url=%s destination=%s", + "download", URL_STR(web->url), web->filename); + + } else { + /* For everyone else, the url string is enough... */ + cmd = a_Dpip_build_cmd("cmd=%s url=%s", "open_url", URL_STR(web->url)); + } + return cmd; +} + +/* + * Most used function for requesting a URL. + * todo: clean up the ad-hoc bindings with an API that allows dynamic + * addition of new plugins. + * + * Return value: A primary key for identifying the client, + * 0 if the client is aborted in the process. + */ +int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData) +{ + capi_conn_t *conn; + int buf_size, reload; + char *cmd, *server, *buf; + const char *scheme = URL_SCHEME(web->url); + int safe = 0, ret = 0, use_cache = 0; + + /* reload test */ + reload = (!a_Capi_get_buf(web->url, &buf, &buf_size) || + (URL_FLAGS(web->url) & URL_E2EReload)); + + if (web->flags & WEB_Download) { + /* donwload request: if cached save from cache, else + * for http, ftp or https, use the downloads dpi */ + if (a_Capi_get_buf(web->url, &buf, &buf_size)) { + if (web->filename && (web->stream = fopen(web->filename, "w"))) { + use_cache = 1; + } + } else { + if (!dStrcasecmp(scheme, "https") || + !dStrcasecmp(scheme, "http") || + !dStrcasecmp(scheme, "ftp")) { + server = "downloads"; + cmd = Capi_dpi_build_cmd(web, server); + a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1); + dFree(cmd); + } + } + + } else if (Capi_url_uses_dpi(web->url, &server)) { + /* dpi request */ + if ((safe = Capi_dpi_verify_request(web))) { + if (dStrcasecmp(scheme, "dpi") == 0) { + /* make "dpi:/" prefixed urls always reload. */ + a_Url_set_flags(web->url, URL_FLAGS(web->url) | URL_E2EReload); + reload = 1; + } + if (reload) { + a_Capi_conn_abort_by_url(web->url); + /* Send dpip command */ + cmd = Capi_dpi_build_cmd(web, server); + a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1); + dFree(cmd); + } + use_cache = 1; + } + dFree(server); + + } else if (!dStrcasecmp(scheme, "http")) { + /* http request */ + if (reload) { + a_Capi_conn_abort_by_url(web->url); + /* create a new connection and start the CCC operations */ + conn = Capi_conn_new(web->url, web->bw, "http", "none"); + a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, web); + } + use_cache = 1; + + } else if (!dStrcasecmp(scheme, "about")) { + /* internal request */ + use_cache = 1; + } + + if (use_cache) { + ret = a_Cache_open_url(web, Call, CbData); + } else { + a_Web_free(web); + } + return ret; +} + +/* + * Get the cache's buffer for the URL, and its size. + * Return: 1 cached, 0 not cached. + */ +int a_Capi_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize) +{ + return a_Cache_get_buf(Url, PBuf, BufSize); +} + +/* + * Send a dpi cmd. + * (For instance: add_bookmark, open_url, send_preferences, ...) + */ +int a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server, + int flags) +{ + capi_conn_t *conn; + DataBuf *dbuf; + + if (flags & 1) { + /* open a new connection to server */ + + /* Create a new connection data struct and add it to the list */ + conn = Capi_conn_new(url, bw, server, cmd); + /* start the CCC operations */ + a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, server); + + } else { + /* Re-use an open connection */ + conn = Capi_conn_find(server); + if (conn) { + /* found */ + dbuf = a_Chain_dbuf_new(cmd, (int)strlen(cmd), 0); + a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL); + dFree(dbuf); + } else { + MSG(" ERROR: [a_Capi_dpi_send_cmd] No open connection found\n"); + } + } + + return 0; +} + +/* + * Remove a client from the cache client queue. + * force = also abort the CCC if this is the last client. + */ +void a_Capi_stop_client(int Key, int force) +{ + CacheClient_t *Client; + + if (force && (Client = a_Cache_client_get_if_unique(Key))) { + a_Capi_conn_abort_by_url(Client->Url); + } + a_Cache_stop_client(Key); +} + +/* + * CCC function for the CAPI module + */ +void a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2) +{ + capi_conn_t *conn; + + a_Chain_debug_msg("a_Capi_ccc", Op, Branch, Dir); + + if (Branch == 1) { + if (Dir == BCK) { + /* Command sending branch */ + switch (Op) { + case OpStart: + /* Data1 = conn; Data2 = {Web | server} */ + conn = Data1; + Capi_conn_ref(conn); + Info->LocalKey = conn; + conn->InfoSend = Info; + if (strcmp(conn->server, "http") == 0) { + a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Http_ccc, 1, 1); + a_Chain_bcb(OpStart, Info, Data2, NULL); + } else { + a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 1, 1); + a_Chain_bcb(OpStart, Info, Data2, NULL); + } + break; + case OpSend: + /* Data1 = dbuf */ + a_Chain_bcb(OpSend, Info, Data1, NULL); + break; + case OpEnd: + conn = Info->LocalKey; + conn->InfoSend = NULL; + a_Chain_bcb(OpEnd, Info, NULL, NULL); + Capi_conn_unref(conn); + dFree(Info); + break; + case OpAbort: + conn = Info->LocalKey; + conn->InfoSend = NULL; + a_Chain_bcb(OpAbort, Info, NULL, NULL); + Capi_conn_unref(conn); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* FWD */ + /* Command sending branch (status) */ + switch (Op) { + case OpSend: + if (!Data2) { + MSG_WARN("Capi.c: Opsend [1F] Data2 = NULL\n"); + } else if (strcmp(Data2, "SockFD") == 0) { + /* start the receiving branch */ + capi_conn_t *conn = Info->LocalKey; + conn->SockFD = *(int*)Data1; + a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(), Info->LocalKey, + conn->server); + } else if (strcmp(Data2, "DpidOK") == 0) { + /* resume pending dpi requests */ + Capi_conn_resume(); + } + break; + case OpAbort: + conn = Info->LocalKey; + conn->InfoSend = NULL; + /* remove the cache entry for this URL */ + a_Cache_entry_remove_by_url(conn->url); + if (Data2 && !strcmp(Data2, "DpidERROR")) + a_UIcmd_set_msg(conn->bw, "ERROR: can't start dpid daemon!"); + /* finish conn */ + Capi_conn_unref(conn); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + + } else if (Branch == 2) { + if (Dir == BCK) { + /* Server listening branch (status) + * (Data1 = conn; Data2 = {"HttpFD" | "DpiFD"}) */ + switch (Op) { + case OpStart: + conn = Data1; + Capi_conn_ref(conn); + Info->LocalKey = conn; + conn->InfoRecv = Info; + a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 2, 2); + a_Chain_bcb(OpStart, Info, &conn->SockFD, Data2); + break; + case OpAbort: + conn = Info->LocalKey; + conn->InfoRecv = NULL; + a_Chain_bcb(OpAbort, Info, NULL, NULL); + Capi_conn_unref(conn); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } else { /* FWD */ + /* Server listening branch */ + switch (Op) { + case OpSend: + conn = Info->LocalKey; + if (strcmp(Data2, "send_page_2eof") == 0) { + /* Data1 = dbuf */ + DataBuf *dbuf = Data1; + a_Cache_process_dbuf(IORead, dbuf->Buf, dbuf->Size, conn->url); + } else if (strcmp(Data2, "send_status_message") == 0) { + a_UIcmd_set_msg(conn->bw, "%s", Data1); + } else if (strcmp(Data2, "chat") == 0) { + a_UIcmd_set_msg(conn->bw, "%s", Data1); + a_Bookmarks_chat_add(NULL, NULL, Data1); + } else if (strcmp(Data2, "dialog") == 0) { + a_Dpiapi_dialog(conn->bw, conn->server, Data1); + } else if (strcmp(Data2, "reload_request") == 0) { + a_Nav_reload(conn->bw); + } else if (strcmp(Data2, "start_send_page") == 0) { + /* prepare the cache to receive the data stream for this URL + * + * a_Capi_open_url() already added a new cache entry, + * and a client for it. + */ + } + break; + case OpEnd: + conn = Info->LocalKey; + conn->InfoRecv = NULL; + + a_Cache_process_dbuf(IOClose, NULL, 0, conn->url); + + if (conn->InfoSend) { + /* Propagate OpEnd to the sending branch too */ + a_Capi_ccc(OpEnd, 1, BCK, conn->InfoSend, NULL, NULL); + } + Capi_conn_unref(conn); + dFree(Info); + break; + default: + MSG_WARN("Unused CCC\n"); + break; + } + } + } +} diff --git a/src/capi.h b/src/capi.h new file mode 100644 index 00000000..e61d815b --- /dev/null +++ b/src/capi.h @@ -0,0 +1,29 @@ +#ifndef __CAPI_H__ +#define __CAPI_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#include "cache.h" +#include "web.hh" + +/* + * Function prototypes + */ +void a_Capi_init(void); +int a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData); +int a_Capi_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize); +int a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server, + int flags); +void a_Capi_stop_client(int Key, int force); +void a_Capi_conn_abort_by_url(const DilloUrl *url); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __CAPI_H__ */ + diff --git a/src/chain.c b/src/chain.c new file mode 100644 index 00000000..0324687d --- /dev/null +++ b/src/chain.c @@ -0,0 +1,128 @@ +/* + * File: chain.c + * Concomitant control chain (CCC) + * Theory and code by Jorge Arellano Cid + * + * Copyright 2001, 2002 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include "msg.h" +#include "chain.h" +#include "../dlib/dlib.h" + +#define VERBOSE 0 + +/* + * Create and initialize a new chain-link + */ +ChainLink *a_Chain_new(void) +{ + return dNew0(ChainLink, 1); +} + +/* + * Create a new link from module A to module B. + * 'Direction' tells whether to make a forward or backward link. + * => The link from 'A' to 'B' has 'Direction' direction. + * => The main flow of information names the FWD direction. + * => AtoB_branch: branch on which 'B' receives communications from 'A' + * => BtoA_branch: branch on which 'A' receives communications from 'B' + */ +ChainLink *a_Chain_link_new(ChainLink *AInfo, ChainFunction_t AFunc, + int Direction, ChainFunction_t BFunc, + int AtoB_branch, int BtoA_branch) +{ + ChainLink *NewLink = a_Chain_new(); + ChainLink *OldLink = AInfo; + + if (Direction == BCK) { + NewLink->Fcb = AFunc; + NewLink->FcbInfo = AInfo; + NewLink->FcbBranch = BtoA_branch; + OldLink->Bcb = BFunc; + OldLink->BcbInfo = NewLink; + OldLink->BcbBranch = AtoB_branch; + + } else { /* FWD */ + NewLink->Bcb = AFunc; + NewLink->BcbInfo = AInfo; + NewLink->BcbBranch = BtoA_branch; + OldLink->Fcb = BFunc; + OldLink->FcbInfo = NewLink; + OldLink->FcbBranch = AtoB_branch; + } + + return NewLink; +} + +/* + * Unlink a previously used link. + * 'Direction' tells whether to unlink the forward or backward link. + */ +void a_Chain_unlink(ChainLink *Info, int Direction) +{ + if (Direction == FWD) { + Info->Fcb = NULL; + Info->FcbInfo = NULL; + Info->FcbBranch = 0; + } else { /* BCK */ + Info->Bcb = NULL; + Info->BcbInfo = NULL; + Info->BcbBranch = 0; + } +} + +/* + * Issue the forward callback of the 'Info' link + */ +int a_Chain_fcb(int Op, ChainLink *Info, void *Data1, void *Data2) +{ + if (Info->Fcb) { + Info->Fcb(Op, Info->FcbBranch, FWD, Info->FcbInfo, Data1, Data2); + return 1; + } + return 0; +} + +/* + * Issue the backward callback of the 'Info' link + */ +int a_Chain_bcb(int Op, ChainLink *Info, void *Data1, void *Data2) +{ + if (Info->Bcb) { + Info->Bcb(Op, Info->BcbBranch, BCK, Info->BcbInfo, Data1, Data2); + return 1; + } + return 0; +} + + +/* + * Allocate and initialize a new DataBuf structure + */ +DataBuf *a_Chain_dbuf_new(void *buf, int size, int code) +{ + DataBuf *dbuf = dNew(DataBuf, 1); + dbuf->Buf = buf; + dbuf->Size = size; + dbuf->Code = code; + return dbuf; +} + +/* + * Show some debugging info + */ +void a_Chain_debug_msg(char *FuncStr, int Op, int Branch, int Dir) +{ +#if VERBOSE + const char *StrOps[] = {"", "OpStart", "OpSend", + "OpStop", "OpEnd", "OpAbort"}; + MSG("%-*s: %-*s [%d%s]\n", + 12, FuncStr, 7, StrOps[Op], Branch, (Dir == 1) ? "F" : "B"); +#endif +} diff --git a/src/chain.h b/src/chain.h new file mode 100644 index 00000000..2d5f0aae --- /dev/null +++ b/src/chain.h @@ -0,0 +1,69 @@ +#ifndef __CHAIN_H__ +#define __CHAIN_H__ + +/* + * Concomitant control chain (CCC) + * Theory and code by Jorge Arellano Cid <jcid@dillo.org> + */ + + +/* + * Supported CCC operations + */ +#define OpStart 1 +#define OpSend 2 +#define OpStop 3 +#define OpEnd 4 +#define OpAbort 5 + + +/* + * Linking direction + */ +#define FWD 1 +#define BCK 2 + + +typedef struct _ChainLink ChainLink; +typedef struct _DataBuf DataBuf; +typedef void (*ChainFunction_t)(int Op, int Branch, int Dir, ChainLink *Info, + void *Data1, void *Data2); + +/* This is the main data structure for CCC nodes */ +struct _ChainLink { + void *LocalKey; + + ChainLink *FcbInfo; + ChainFunction_t Fcb; + int FcbBranch; + + ChainLink *BcbInfo; + ChainFunction_t Bcb; + int BcbBranch; +}; + +/* A convenience data structure for passing data chunks between nodes */ +struct _DataBuf { + char *Buf; + int Size; + int Code; +}; + + + +/* + * Function prototypes + */ +ChainLink *a_Chain_new(void); +ChainLink *a_Chain_link_new(ChainLink *AInfo, ChainFunction_t AFunc, + int Direction, ChainFunction_t BFunc, + int AtoB_branch, int BtoA_branch); +void a_Chain_unlink(ChainLink *Info, int Direction); +int a_Chain_fcb(int Op, ChainLink *Info, void *Data1, void *Data2); +int a_Chain_bcb(int Op, ChainLink *Info, void *Data1, void *Data2); + +DataBuf *a_Chain_dbuf_new(void *buf, int size, int code); +void a_Chain_debug_msg(char *FuncStr, int Op, int Branch, int Dir); + + +#endif /* __CHAIN_H__ */ diff --git a/src/chg b/src/chg new file mode 100755 index 00000000..32d525f7 --- /dev/null +++ b/src/chg @@ -0,0 +1,28 @@ +#!/bin/sh +# +# Shell script for name changing source code +# + +if [ ! $# = 3 ]; then + echo "Usage: chg <source> <old_word> <new_word>" + echo " (this script changes <source> directly)" + exit 1 +fi + +if [ ! -r $1 ]; then + echo "source file ->$1<- doesn't exist..." + exit 1 +fi + +if [ ! -r $1.BAK ]; then + echo "creating backup file: $1.BAK" + cp $1 $1.BAK +fi + +sed "s/$2/$3/g" $1 > out +#sed s/$2/$3/ $1 > out +rm $1 +mv out $1 +echo "done!" + + diff --git a/src/colors.c b/src/colors.c new file mode 100644 index 00000000..86db3b4f --- /dev/null +++ b/src/colors.c @@ -0,0 +1,366 @@ +/* + * File: colors.c + * + * Copyright (C) 2000-2005 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include "colors.h" + +#define DEBUG_LEVEL 5 +#include "debug.h" +#include "msg.h" + +/* + * If EXTENDED_COLOR is defined, the extended set of named colors is supported. + * These colors're not standard but they're supported in most browsers. + * NOTE: The colors MUST be in alphabetical order and lower case because the + * code uses a binary search. + */ + +#define EXTENDED_COLOR + +static const struct key { + char *key; + int32_t val; +} color_keyword [] = { +#ifdef EXTENDED_COLOR + { "aliceblue", 0xf0f8ff}, + { "antiquewhite", 0xfaebd7}, +#endif + { "aqua", 0x00ffff}, +#ifdef EXTENDED_COLOR + { "aquamarine", 0x7fffd4}, + { "azure", 0xf0ffff}, + { "beige", 0xf5f5dc}, + { "bisque", 0xffe4c4}, +#endif + { "black", 0x000000}, +#ifdef EXTENDED_COLOR + { "blanchedalmond", 0xffebcd}, +#endif + {"blue", 0x0000ff}, +#ifdef EXTENDED_COLOR + { "blueviolet", 0x8a2be2}, + { "brown", 0xa52a2a}, + { "burlywood", 0xdeb887}, + { "cadetblue", 0x5f9ea0}, + { "chartreuse", 0x7fff00}, + { "chocolate", 0xd2691e}, + { "coral", 0xff7f50}, + { "cornflowerblue", 0x6495ed}, + { "cornsilk", 0xfff8dc}, + { "crimson", 0xdc1436}, + { "cyan", 0x00ffff}, + { "darkblue", 0x00008b}, + { "darkcyan", 0x008b8b}, + { "darkgoldenrod", 0xb8860b}, + { "darkgray", 0xa9a9a9}, + { "darkgreen", 0x006400}, + { "darkkhaki", 0xbdb76b}, + { "darkmagenta", 0x8b008b}, + { "darkolivegreen", 0x556b2f}, + { "darkorange", 0xff8c00}, + { "darkorchid", 0x9932cc}, + { "darkred", 0x8b0000}, + { "darksalmon", 0xe9967a}, + { "darkseagreen", 0x8fbc8f}, + { "darkslateblue", 0x483d8b}, + { "darkslategray", 0x2f4f4f}, + { "darkturquoise", 0x00ced1}, + { "darkviolet", 0x9400d3}, + { "deeppink", 0xff1493}, + { "deepskyblue", 0x00bfff}, + { "dimgray", 0x696969}, + { "dodgerblue", 0x1e90ff}, + { "firebrick", 0xb22222}, + { "floralwhite", 0xfffaf0}, + { "forestgreen", 0x228b22}, +#endif + { "fuchsia", 0xff00ff}, +#ifdef EXTENDED_COLOR + { "gainsboro", 0xdcdcdc}, + { "ghostwhite", 0xf8f8ff}, + { "gold", 0xffd700}, + { "goldenrod", 0xdaa520}, +#endif + { "gray", 0x808080}, + { "green", 0x008000}, +#ifdef EXTENDED_COLOR + { "greenyellow", 0xadff2f}, + { "honeydew", 0xf0fff0}, + { "hotpink", 0xff69b4}, + { "indianred", 0xcd5c5c}, + { "indigo", 0x4b0082}, + { "ivory", 0xfffff0}, + { "khaki", 0xf0e68c}, + { "lavender", 0xe6e6fa}, + { "lavenderblush", 0xfff0f5}, + { "lawngreen", 0x7cfc00}, + { "lemonchiffon", 0xfffacd}, + { "lightblue", 0xadd8e6}, + { "lightcoral", 0xf08080}, + { "lightcyan", 0xe0ffff}, + { "lightgoldenrodyellow", 0xfafad2}, + { "lightgreen", 0x90ee90}, + { "lightgrey", 0xd3d3d3}, + { "lightpink", 0xffb6c1}, + { "lightsalmon", 0xffa07a}, + { "lightseagreen", 0x20b2aa}, + { "lightskyblue", 0x87cefa}, + { "lightslategray", 0x778899}, + { "lightsteelblue", 0xb0c4de}, + { "lightyellow", 0xffffe0}, +#endif + { "lime", 0x00ff00}, +#ifdef EXTENDED_COLOR + { "limegreen", 0x32cd32}, + { "linen", 0xfaf0e6}, + { "magenta", 0xff00ff}, +#endif + { "maroon", 0x800000}, +#ifdef EXTENDED_COLOR + { "mediumaquamarine", 0x66cdaa}, + { "mediumblue", 0x0000cd}, + { "mediumorchid", 0xba55d3}, + { "mediumpurple", 0x9370db}, + { "mediumseagreen", 0x3cb371}, + { "mediumslateblue", 0x7b68ee}, + { "mediumspringgreen", 0x00fa9a}, + { "mediumturquoise", 0x48d1cc}, + { "mediumvioletred", 0xc71585}, + { "midnightblue", 0x191970}, + { "mintcream", 0xf5fffa}, + { "mistyrose", 0xffe4e1}, + { "moccasin", 0xffe4b5}, + { "navajowhite", 0xffdead}, +#endif + { "navy", 0x000080}, +#ifdef EXTENDED_COLOR + { "oldlace", 0xfdf5e6}, +#endif + { "olive", 0x808000}, +#ifdef EXTENDED_COLOR + { "olivedrab", 0x6b8e23}, + { "orange", 0xffa500}, + { "orangered", 0xff4500}, + { "orchid", 0xda70d6}, + { "palegoldenrod", 0xeee8aa}, + { "palegreen", 0x98fb98}, + { "paleturquoise", 0xafeeee}, + { "palevioletred", 0xdb7093}, + { "papayawhip", 0xffefd5}, + { "peachpuff", 0xffdab9}, + { "peru", 0xcd853f}, + { "pink", 0xffc0cb}, + { "plum", 0xdda0dd}, + { "powderblue", 0xb0e0e6}, +#endif + { "purple", 0x800080}, + { "red", 0xff0000}, +#ifdef EXTENDED_COLOR + { "rosybrown", 0xbc8f8f}, + { "royalblue", 0x4169e1}, + { "saddlebrown", 0x8b4513}, + { "salmon", 0xfa8072}, + { "sandybrown", 0xf4a460}, + { "seagreen", 0x2e8b57}, + { "seashell", 0xfff5ee}, + { "sienna", 0xa0522d}, +#endif + { "silver", 0xc0c0c0}, +#ifdef EXTENDED_COLOR + { "skyblue", 0x87ceeb}, + { "slateblue", 0x6a5acd}, + { "slategray", 0x708090}, + { "snow", 0xfffafa}, + { "springgreen", 0x00ff7f}, + { "steelblue", 0x4682b4}, + { "tan", 0xd2b48c}, +#endif + { "teal", 0x008080}, +#ifdef EXTENDED_COLOR + { "thistle", 0xd8bfd8}, + { "tomato", 0xff6347}, + { "turquoise", 0x40e0d0}, + { "violet", 0xee82ee}, + { "wheat", 0xf5deb3}, +#endif + { "white", 0xffffff}, +#ifdef EXTENDED_COLOR + { "whitesmoke", 0xf5f5f5}, +#endif + { "yellow", 0xffff00}, +#ifdef EXTENDED_COLOR + { "yellowgreen", 0x9acd32}, +#endif +}; + +#define NCOLORS (sizeof(color_keyword) / sizeof(struct key)) + +/* + * Parse a color in hex (RRGGBB) + * + * Return Value: + * parsed color if successful (err = 0), + * default_color on error (err = 1). + */ +static int32_t Color_parse_hex (const char *s, int32_t default_color, int *err) +{ + int32_t ret_color; + char *tail; + + *err = 1; + ret_color = strtol(s, &tail, 16); + if (tail - s == 6) + *err = 0; + else + ret_color = default_color; + + return ret_color; +} + +/* + * Parse the color info from a subtag. + * If subtag string begins with # or with 0x simply return the color number + * otherwise search one color from the set of named colors. + * + * Return Value: + * Parsed color if successful, + * default_color (+ error code) on error. + */ +int32_t a_Color_parse (const char *subtag, int32_t default_color, int *err) +{ + const char *cp; + int32_t ret_color; + int ret, low, mid, high, st = 1; + + /* skip leading spaces */ + for (cp = subtag; isspace(*cp); cp++); + + ret_color = default_color; + if (*cp == '#') { + ret_color = Color_parse_hex(cp + 1, default_color, &st); + + } else if (*cp == '0' && (cp[1] == 'x' || cp[1] == 'X') ) { + ret_color = Color_parse_hex(cp + 2, default_color, &st); + st = 2; + + } else { + /* Binary search */ + low = 0; + high = NCOLORS - 1; + while (low <= high) { + mid = (low + high) / 2; + if ((ret = dStrcasecmp(cp, color_keyword[mid].key)) < 0) + high = mid - 1; + else if (ret > 0) + low = mid + 1; + else { + ret_color = color_keyword[mid].val; + st = 0; + break; + } + } + + if (low > high) { + /* try for RRGGBB lacking the leading '#' */ + ret_color = Color_parse_hex(cp, default_color, &st); + st = 1; + } + } + + DEBUG_MSG(3, "subtag: %s\n", subtag); + DEBUG_MSG(3, "color : %X\n", ret_color); + + *err = st; + return ret_color; +} + +#if 0 +/* + * Return a "distance" measure (between [0, 10]) + */ +static int Color_distance(long c1, long c2) +{ + return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) + + labs(((c1 & 0x00ff00) - (c2 & 0x00ff00)) >> 8) + + labs(((c1 & 0xff0000) - (c2 & 0xff0000)) >> 16)) / 75; +} +#endif + +/* + * Return: [0-3] + */ +static int Color_distance2(long c1, long c2) +{ + return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) >= 0x000060) + + (labs((c1 & 0x00ff00) - (c2 & 0x00ff00)) >= 0x006000) + + (labs((c1 & 0xff0000) - (c2 & 0xff0000)) >= 0x600000); +} + +/* + * Return: [0-3] (requires less contrast than distance2) + */ +static int Color_distance3(long c1, long c2) +{ + return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) >= 0x000040) + + (labs((c1 & 0x00ff00) - (c2 & 0x00ff00)) >= 0x004000) + + (labs((c1 & 0xff0000) - (c2 & 0xff0000)) >= 0x400000); +} + +/* + * Return a suitable "visited link" color + * Return value: + * if candidate has good contrast with C_txt, C_lnk and C_bg -> candidate + * else another color (from the internal list) + */ +int32_t a_Color_vc(int32_t candidate, int32_t C_txt, int32_t C_lnk, int32_t C_bg) +{ + /* candidate purple darkcyan darkmagenta olive */ + static int32_t v[] = {0x000000, 0x800080, 0x008b8b, 0x8b008b, 0x808000, + /* darkred coral black */ + 0x8b0000, 0xff7f50, 0x000000}; + int v_size = sizeof(v) / sizeof(v[0]); + int i, max_i, score, max_score, d_bg, d_txt, d_lnk; + + + /* set candidate in the list */ + v[0] = candidate; + + /* Try to get good overall and individual contrast */ + max_i = max_score = 0; + for (i = 0; i < v_size; ++i) { + _MSG("a_Color_vc: [%d]%.6x: %d %d %d\n", i, v[i], + Color_distance2(C_txt, v[i]), + Color_distance2(C_lnk, v[i]), + Color_distance2(C_bg, v[i])); + + /* Tuned with: slashdot.org, paulgraham.com, newsforge.com, + * linuxjournal.com + */ + d_txt = Color_distance2(C_txt, v[i]); + d_lnk = Color_distance2(C_lnk, v[i]); + d_bg = Color_distance2(C_bg, v[i]); + score = (d_bg >= 2 ? 4 : 2 * d_bg) + + (d_txt + d_lnk >= 2 ? 2 : d_txt + d_lnk) + + (Color_distance3(C_lnk, v[i]) >= 1 ? 1 : 0); + if (score >= 7) { + /* enough distance, use this color */ + max_i = i; + break; + } else if (score > max_score) { + /* keep track of the best candidate so far */ + max_score = score; + max_i = i; + } + } + return v[max_i]; +} diff --git a/src/colors.h b/src/colors.h new file mode 100644 index 00000000..99d1bc0f --- /dev/null +++ b/src/colors.h @@ -0,0 +1,15 @@ +#ifndef __COLORS_H__ +#define __COLORS_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +int32_t a_Color_parse (const char *subtag, int32_t default_color, int *err); +int32_t a_Color_vc(int32_t candidate, int32_t c1, int32_t c2, int32_t c3); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __COLORS_H__ */ diff --git a/src/cookies.c b/src/cookies.c new file mode 100644 index 00000000..f6002842 --- /dev/null +++ b/src/cookies.c @@ -0,0 +1,332 @@ +/* + * File: cookies.c + * + * Copyright 2001 Lars Clausen <lrclause@cs.uiuc.edu> + * Jörgen Viksell <jorgen.viksell@telia.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* Handling of cookies takes place here. + * This implementation aims to follow RFC 2965: + * http://www.cis.ohio-state.edu/cs/Services/rfc/rfc-text/rfc2965.txt + */ + +#define DEBUG_LEVEL 10 +#include "debug.h" + + +#ifdef DISABLE_COOKIES + +/* + * Initialize the cookies module + */ +void a_Cookies_init(void) +{ + DEBUG_MSG(10, "Cookies: absolutely disabled at compilation time.\n"); +} + +#else + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> /* for time() and time_t */ +#include <ctype.h> + +#include "msg.h" +#include "IO/Url.h" +#include "list.h" +#include "cookies.h" +#include "capi.h" +#include "dpiapi.h" +#include "../dpip/dpip.h" + + +/* The maximum length of a line in the cookie file */ +#define LINE_MAXLEN 4096 + +typedef enum { + COOKIE_ACCEPT, + COOKIE_ACCEPT_SESSION, + COOKIE_DENY +} CookieControlAction; + +typedef struct { + CookieControlAction action; + char *domain; +} CookieControl; + +/* Variables for access control */ +static CookieControl *ccontrol = NULL; +static int num_ccontrol = 0; +static int num_ccontrol_max = 1; +static CookieControlAction default_action = COOKIE_DENY; + +static bool_t disabled; + +static FILE *Cookies_fopen(const char *file, char *init_str); +static CookieControlAction Cookies_control_check(const DilloUrl *url); +static CookieControlAction Cookies_control_check_domain(const char *domain); +static int Cookie_control_init(void); + +/* + * Return a file pointer. If the file doesn't exist, try to create it, + * with the optional 'init_str' as its content. + */ +static FILE *Cookies_fopen(const char *filename, char *init_str) +{ + FILE *F_in; + int fd; + + if ((F_in = fopen(filename, "r")) == NULL) { + /* Create the file */ + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd != -1) { + if (init_str) + write(fd, init_str, strlen(init_str)); + close(fd); + + DEBUG_MSG(10, "Cookies: Created file: %s\n", filename); + F_in = Cookies_fopen(filename, NULL); + } else { + DEBUG_MSG(10, "Cookies: Could not create file: %s!\n", filename); + } + } + + /* set close on exec */ + fcntl(fileno(F_in), F_SETFD, FD_CLOEXEC | fcntl(fileno(F_in), F_GETFD)); + + return F_in; +} + +/* + * Initialize the cookies module + * (The 'disabled' variable is writable only within a_Cookies_init) + */ +void a_Cookies_init(void) +{ + /* Default setting */ + disabled = TRUE; + + /* Read and parse the cookie control file (cookiesrc) */ + if (Cookie_control_init() != 0) { + DEBUG_MSG(10, "Disabling cookies.\n"); + return; + } + + DEBUG_MSG(10, "Enabling cookies as from cookiesrc...\n"); + disabled = FALSE; +} + +/* + * Flush cookies to disk and free all the memory allocated. + */ +void a_Cookies_freeall() +{ +} + +/* + * Set the value corresponding to the cookie string + */ +void a_Cookies_set(Dlist *cookie_strings, const DilloUrl *set_url) +{ + CookieControlAction action; + char *cmd, *cookie_string, numstr[16]; + const char *path; + int i; + + if (disabled) + return; + + action = Cookies_control_check(set_url); + if (action == COOKIE_DENY) { + DEBUG_MSG(5, "Cookies: denied SET for %s\n", URL_HOST_(set_url)); + return; + } + + for (i = 0; (cookie_string = dList_nth_data(cookie_strings, i)); ++i) { + path = URL_PATH_(set_url); + snprintf(numstr, 16, "%d", URL_PORT(set_url)); + cmd = a_Dpip_build_cmd("cmd=%s cookie=%s host=%s path=%s port=%s", + "set_cookie", cookie_string, URL_HOST_(set_url), + path ? path : "/", numstr); + + DEBUG_MSG(5, "Cookies.c: a_Cookies_set \n\t \"%s\" \n",cmd ); + a_Capi_dpi_send_cmd(NULL, NULL, cmd, "cookies", 1); + dFree(cmd); + } +} + +/* + * Return a string that contains all relevant cookies as headers. + */ +char *a_Cookies_get(const DilloUrl *request_url) +{ + char *cmd, *dpip_tag, *cookie, numstr[16]; + const char *path; + CookieControlAction action; + + cookie = dStrdup(""); + + if (disabled) + return cookie; + + action = Cookies_control_check(request_url); + if (action == COOKIE_DENY) { + DEBUG_MSG(5, "Cookies: denied GET for %s\n", URL_HOST_(request_url)); + return cookie; + } + path = URL_PATH_(request_url); + + snprintf(numstr, 16, "%d", URL_PORT(request_url)); + cmd = a_Dpip_build_cmd("cmd=%s scheme=%s host=%s path=%s port=%s", + "get_cookie", URL_SCHEME(request_url), + URL_HOST(request_url), path ? path : "/", numstr); + + /* Get the answer from cookies.dpi */ + dpip_tag = a_Dpi_send_blocking_cmd("cookies", cmd); + dFree(cmd); + + if (dpip_tag != NULL) { + cookie = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cookie"); + dFree(dpip_tag); + } + return cookie; +} + +/* ------------------------------------------------------------- + * Access control routines + * ------------------------------------------------------------- */ + + +/* + * Get the cookie control rules (from cookiesrc). + * Return value: + * 0 = Parsed OK, with cookies enabled + * 1 = Parsed OK, with cookies disabled + * 2 = Can't open the control file + */ +static int Cookie_control_init(void) +{ + CookieControl cc; + FILE *stream; + char *filename; + char line[LINE_MAXLEN]; + char domain[LINE_MAXLEN]; + char rule[LINE_MAXLEN]; + int i, j; + bool_t enabled = FALSE; + + /* Get a file pointer */ + filename = dStrconcat(dGethomedir(), "/.dillo/cookiesrc", NULL); + stream = Cookies_fopen(filename, "DEFAULT DENY\n"); + dFree(filename); + + if (!stream) + return 2; + + /* Get all lines in the file */ + while (!feof(stream)) { + line[0] = '\0'; + fgets(line, LINE_MAXLEN, stream); + + /* Remove leading and trailing whitespaces */ + dStrstrip(line); + + if (line[0] != '\0' && line[0] != '#') { + i = 0; + j = 0; + + /* Get the domain */ + while (!isspace(line[i])) + domain[j++] = line[i++]; + domain[j] = '\0'; + + /* Skip past whitespaces */ + i++; + while (isspace(line[i])) + i++; + + /* Get the rule */ + j = 0; + while (line[i] != '\0' && !isspace(line[i])) + rule[j++] = line[i++]; + rule[j] = '\0'; + + if (dStrcasecmp(rule, "ACCEPT") == 0) + cc.action = COOKIE_ACCEPT; + else if (dStrcasecmp(rule, "ACCEPT_SESSION") == 0) + cc.action = COOKIE_ACCEPT_SESSION; + else if (dStrcasecmp(rule, "DENY") == 0) + cc.action = COOKIE_DENY; + else { + MSG("Cookies: rule '%s' for domain '%s' is not recognised.\n", + rule, domain); + continue; + } + + cc.domain = dStrdup(domain); + if (dStrcasecmp(cc.domain, "DEFAULT") == 0) { + /* Set the default action */ + default_action = cc.action; + dFree(cc.domain); + } else { + a_List_add(ccontrol, num_ccontrol, num_ccontrol_max); + ccontrol[num_ccontrol++] = cc; + } + + if (cc.action != COOKIE_DENY) + enabled = TRUE; + } + } + + fclose(stream); + + return (enabled ? 0 : 1); +} + +/* + * Check the rules for an appropriate action for this domain + */ +static CookieControlAction Cookies_control_check_domain(const char *domain) +{ + int i, diff; + + for (i = 0; i < num_ccontrol; i++) { + if (ccontrol[i].domain[0] == '.') { + diff = strlen(domain) - strlen(ccontrol[i].domain); + if (diff >= 0) { + if (dStrcasecmp(domain + diff, ccontrol[i].domain) != 0) + continue; + } else { + continue; + } + } else { + if (dStrcasecmp(domain, ccontrol[i].domain) != 0) + continue; + } + + /* If we got here we have a match */ + return( ccontrol[i].action ); + } + + return default_action; +} + +/* + * Same as the above except it takes an URL + */ +static CookieControlAction Cookies_control_check(const DilloUrl *url) +{ + return Cookies_control_check_domain(URL_HOST(url)); +} + +#endif /* !DISABLE_COOKIES */ diff --git a/src/cookies.h b/src/cookies.h new file mode 100644 index 00000000..c792d633 --- /dev/null +++ b/src/cookies.h @@ -0,0 +1,24 @@ +#ifndef __COOKIES_H__ +#define __COOKIES_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#ifdef DISABLE_COOKIES +# define a_Cookies_get(url) dStrdup("") +# define a_Cookies_init() ; +# define a_Cookies_freeall() ; +#else + char *a_Cookies_get(const DilloUrl *request_url); + void a_Cookies_set(Dlist *cookie_string, const DilloUrl *set_url); + void a_Cookies_init( void ); + void a_Cookies_freeall( void ); +#endif + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* !__COOKIES_H__ */ diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 00000000..ff8baf36 --- /dev/null +++ b/src/debug.h @@ -0,0 +1,149 @@ +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +/* + * Simple debug messages. Add: + * + * #define DEBUG_LEVEL <n> + * #include "debug.h" + * + * to the file you are working on, or let DEBUG_LEVEL undefined to + * disable all messages. A higher level denotes a greater importance + * of the message. + */ + +#include <unistd.h> +#include <stdio.h> + + +# ifdef DEBUG_LEVEL +# define DEBUG_MSG(level, ...) \ + D_STMT_START { \ + if (DEBUG_LEVEL && (level) >= DEBUG_LEVEL) \ + printf(__VA_ARGS__); \ + } D_STMT_END +# else +# define DEBUG_MSG(level, ...) +# endif /* DEBUG_LEVEL */ + + + +/* + * Following is experimental, and will be explained soon. + */ + +#ifdef DBG_RTFL + + +#define DBG_MSG(obj, aspect, prio, msg) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:msg:%p:%s:%d:%s\n", \ + __FILE__, __LINE__, getpid(), obj, aspect, prio, msg); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_MSGF(obj, aspect, prio, fmt, ...) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:msg:%p:%s:%d:" fmt "\n", \ + __FILE__, __LINE__, getpid(), obj, aspect, prio, __VA_ARGS__); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_MSG_START(obj) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:msg-start:%p\n", \ + __FILE__, __LINE__, getpid(), obj); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_MSG_END(obj) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:msg-end:%p\n", \ + __FILE__, __LINE__, getpid(), obj); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_CREATE(obj, klass) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-create:%p:%s\n", \ + __FILE__, __LINE__, getpid(), obj, klass); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ASSOC(child, parent) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-assoc:%p:%p\n", \ + __FILE__, __LINE__, getpid(), child, parent); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_SET_NUM(obj, var, val) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-set:%p:%s:%d\n", \ + __FILE__, __LINE__, getpid(), obj, var, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_SET_STR(obj, var, val) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-set:%p:%s:%s\n", \ + __FILE__, __LINE__, getpid(), obj, var, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_SET_PTR(obj, var, val) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-set:%p:%s:%p\n", \ + __FILE__, __LINE__, getpid(), obj, var, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ARRSET_NUM(obj, var, ind, val) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-set:%p:" var ":%d\n", \ + __FILE__, __LINE__, getpid(), obj, ind, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ARRSET_STR(obj, var, ind, val) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-set:%p:" var ":%s\n", \ + __FILE__, __LINE__, getpid(), obj, ind, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_ARRSET_PTR(obj, var, ind, val) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-set:%p:" var ":%p\n", \ + __FILE__, __LINE__, getpid(), obj, ind, val); \ + fflush (stdout); \ + } D_STMT_END + +#define DBG_OBJ_COLOR(klass, color) \ + D_STMT_START { \ + printf ("[rtfl]%s:%d:%d:obj-color:%s:%s\n", \ + __FILE__, __LINE__, getpid(), klass, color); \ + fflush (stdout); \ + } D_STMT_END + +#else /* DBG_RTFL */ + +#define DBG_MSG(obj, aspect, prio, msg) +#define DBG_MSGF(obj, aspect, prio, fmt, ...) +#define DBG_MSG_START(obj) +#define DBG_MSG_END(obj) +#define DBG_OBJ_CREATE(obj, klass) +#define DBG_OBJ_ASSOC(child, parent) +#define DBG_OBJ_SET_NUM(obj, var, val) +#define DBG_OBJ_SET_STR(obj, var, val) +#define DBG_OBJ_SET_PTR(obj, var, val) +#define DBG_OBJ_ARRSET_NUM(obj, var, ind, val) +#define DBG_OBJ_ARRSET_STR(obj, var, ind, val) +#define DBG_OBJ_ARRSET_PTR(obj, var, ind, val) +#define DBG_OBJ_COLOR(klass, color) + +#endif /* DBG_RTFL */ + +#endif /* __DEBUG_H__ */ + + diff --git a/src/dialog.cc b/src/dialog.cc new file mode 100644 index 00000000..3b1badb2 --- /dev/null +++ b/src/dialog.cc @@ -0,0 +1,116 @@ +/* + * File: dialog.cc + * + * Copyright (C) 2005-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +// UI dialogs + +#include <fltk/Window.h> +#include <fltk/ask.h> +#include <fltk/file_chooser.h> +#include <fltk/TextBuffer.h> +#include <fltk/ReturnButton.h> +#include <fltk/TextDisplay.h> + +#include "dialog.hh" +#include "misc.h" + +using namespace fltk; + +/* + * Callback for the text window dialog. + */ +void text_window_cb(Widget *, void *vwin) +{ + Window *window = (Window*)vwin; + + window->destroy(); +} + +/* + * Display a message in a popup window. + */ +void a_Dialog_msg(const char *msg) +{ + message("%s", msg); +} + +/* + * Offer a three choice dialog. + * The option string that begins with "*" is the default. + * + * Return: 0, 1 or 2 (esc = 2, window close = 2) + */ +int a_Dialog_choice3(const char *msg, + const char *b0, const char *b1, const char *b2) +{ + return choice(msg, b0, b1, b2); +} + +/* + * Dialog for one line of Input with a message. + */ +const char *a_Dialog_input(const char *msg) +{ + return input("%s", "", msg); +} + +/* + * Show the save file dialog. + * + * Return: pointer to chosen filename, or NULL on Cancel. + */ +const char *a_Dialog_save_file(const char *msg, + const char *pattern, const char *fname) +{ + return file_chooser(msg, pattern, fname); +} + +//#include <fltk/FileIcon.h> +/* + * Show the open file dialog. + * + * Return: pointer to chosen filename, or NULL on Cancel. + */ +char *a_Dialog_open_file(const char *msg, + const char *pattern, const char *fname) +{ + const char *fc_name; +/* + static int icons_loaded = 0; + if (!icons_loaded) + FileIcon::load_system_icons(); +*/ + fc_name = file_chooser(msg, pattern, fname); + return (fc_name) ? a_Misc_escape_chars(fc_name, "% ") : NULL; +} + +/* + * Show a new window with the provided text + */ +void a_Dialog_text_window(const char *txt, const char *title) +{ + int wh = 500, ww = 480, bh = 30; + TextBuffer *text_buf = new TextBuffer(); + text_buf->text(txt); + + Window *window = new Window(ww, wh, title ? title : "Untitled"); + window->begin(); + + TextDisplay *td = new TextDisplay(0,0,ww, wh-bh); + td->buffer(text_buf); + + ReturnButton *b = new ReturnButton (0, wh-bh, ww, bh, "Close"); + b->callback(text_window_cb, window); + + window->resizable(window); + window->end(); + window->show(); +} + diff --git a/src/dialog.hh b/src/dialog.hh new file mode 100644 index 00000000..9b927832 --- /dev/null +++ b/src/dialog.hh @@ -0,0 +1,22 @@ +#ifndef __DIALOG_HH__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void a_Dialog_msg(const char *msg); +int a_Dialog_choice3(const char *msg, + const char *b0, const char *b1, const char *b2); +const char *a_Dialog_input(const char *msg); +const char *a_Dialog_save_file(const char *msg, + const char *pattern, const char *fname); +char *a_Dialog_open_file(const char *msg, + const char *pattern, const char *fname); +void a_Dialog_text_window(const char *txt, const char *title); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // __DIALOG_HH__ diff --git a/src/dicache.c b/src/dicache.c new file mode 100644 index 00000000..eac01ec2 --- /dev/null +++ b/src/dicache.c @@ -0,0 +1,451 @@ +/* + * File: dicache.c + * + * Copyright 2000-2005 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include <sys/time.h> /* for libc5 compatibility */ +#include <string.h> /* for memset */ +#include <stdio.h> +#include <stdlib.h> + +#include "image.hh" +#include "web.hh" +#include "dicache.h" +#include "cache.h" +#include "prefs.h" + +typedef struct _DICacheNode DICacheNode; + +struct _DICacheNode { + int valid; /* flag */ + DilloUrl *url; /* primary "Key" for this dicache entry */ + DICacheEntry *first; /* pointer to the first dicache entry in this list */ +}; + +/* + * List of DICacheNode. One node per URL. Each node may have several + * versions of the same image in a linked list. + */ +static Dlist *CachedIMGs = NULL; + +static int dicache_size_total; /* invariant: dicache_size_total is + * the sum of the image sizes (3*w*h) + * of all the images in the dicache. */ + +/* + * Compare two dicache nodes + */ +static int Dicache_node_cmp(const void *v1, const void *v2) +{ + const DICacheNode *n1 = v1, *n2 = v2; + + return a_Url_cmp(n1->url, n2->url); +} + +/* + * Compare function for searching a node by Url + */ +static int Dicache_node_by_url_cmp(const void *v1, const void *v2) +{ + const DICacheNode *node = v1; + const DilloUrl *url = v2; + + return a_Url_cmp(node->url, url); +} + +/* + * Initialize dicache data + */ +void a_Dicache_init(void) +{ + CachedIMGs = dList_new(256); + dicache_size_total = 0; +} + +/* + * Create, and initialize a new, empty, dicache entry + */ +static DICacheEntry *Dicache_entry_new(void) +{ + DICacheEntry *entry = dNew(DICacheEntry, 1); + + entry->width = 0; + entry->height = 0; + entry->type = DILLO_IMG_TYPE_NOTSET; + entry->cmap = NULL; + entry->linebuf = NULL; + entry->v_imgbuf = NULL; + entry->RefCount = 1; + entry->TotalSize = 0; + entry->Y = 0; + entry->BitVec = NULL; + entry->State = DIC_Empty; + entry->version = 0; + entry->next = NULL; + + return entry; +} + +/* + * Add a new entry in the dicache + * (a single node (URL) may have several entries) + */ +DICacheEntry *a_Dicache_add_entry(const DilloUrl *Url) +{ + DICacheEntry *entry; + DICacheNode *node; + + entry = Dicache_entry_new(); + + if ((node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp))) { + /* this URL is already in CachedIMGs, add entry at the END of the list */ + DICacheEntry *ptr = node->first; + + node->valid = 1; + for ( ; ptr->next; ptr = ptr->next); + ptr->next = entry; + entry->version = ptr->version+1; + entry->url = node->url; + + } else { /* no node yet, so create one */ + DICacheNode *node = dNew(DICacheNode, 1); + + node->url = a_Url_dup(Url); + entry->url = node->url; + node->first = entry; + node->valid = 1; + dList_insert_sorted(CachedIMGs, node, Dicache_node_cmp); + } + + return entry; +} + +/* + * Search an entry in the dicache (given the Url). + * Return value: a pointer to the entry of the _newest_ (i.e. highest) + * version if found; NULL otherwise. + */ +DICacheEntry *a_Dicache_get_entry(const DilloUrl *Url) +{ + DICacheNode *node; + DICacheEntry *entry; + + node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp); + + if (!node || !node->valid) + return NULL; + + for (entry = node->first; (entry && entry->next); entry = entry->next); + + return entry; +} + +/* + * Search a particular version of a URL in the Dicache. + * Return value: a pointer to the entry if found; NULL otherwise. + */ +static DICacheEntry *Dicache_get_entry_version(const DilloUrl *Url, + int version) +{ + DICacheNode *node; + DICacheEntry *entry; + + node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp); + entry = (node) ? node->first : NULL; + + while (entry && entry->version != version) + entry = entry->next; + + return entry; +} + +/* + * Actually free a dicache entry, given the URL and the version number. + */ +static void Dicache_remove(const DilloUrl *Url, int version) +{ + DICacheNode *node; + DICacheEntry *entry, *prev; + + node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp); + prev = entry = (node) ? node->first : NULL; + + while (entry && (entry->version != version) ) { + prev = entry; + entry = entry->next; + } + + if (entry) { + /* Eliminate this dicache entry */ + dFree(entry->cmap); + dFree(entry->linebuf); + a_Bitvec_free(entry->BitVec); + a_Image_imgbuf_unref(entry->v_imgbuf); + dicache_size_total -= entry->TotalSize; + + if (node->first == entry) { + if (!entry->next) { + /* last entry with this URL. Remove the node as well */ + dList_remove(CachedIMGs, node); + a_Url_free(node->url); + dFree(node); + } else + node->first = entry->next; + } else { + prev->next = entry->next; + } + dFree(entry); + } +} + +/* + * Unrefs the counter of a dicache entry, and _if_ no DwImage is acessing + * this buffer, then we call Dicache_free to do the dirty job. + */ +void a_Dicache_unref(const DilloUrl *Url, int version) +{ + DICacheEntry *entry; + + if ((entry = Dicache_get_entry_version(Url, version))) { + /*if (--entry->RefCount == 0 && (entry->next || !prefs.use_dicache)) {*/ + if (--entry->RefCount == 0) { + Dicache_remove(Url, version); + } + } +} + +/* + * Refs the counter of a dicache entry. + */ + +DICacheEntry* a_Dicache_ref(const DilloUrl *Url, int version) +{ + DICacheEntry *entry; + + if ((entry = Dicache_get_entry_version(Url, version))) { + ++entry->RefCount; + } + return entry; +} + +/* + * Invalidate this entry. This is used for the reloading mechanism. + * Can't erase current versions, but a_Dicache_get_entry must return NULL. + */ +void a_Dicache_invalidate_entry(const DilloUrl *Url) +{ + DICacheNode *node; + + node = dList_find_sorted(CachedIMGs, Url, Dicache_node_by_url_cmp); + if (node) + node->valid = 0; +} + + +/* ------------------------------------------------------------------------- */ + +/* + * This function is a cache client; (but feeds its clients from dicache) + */ +void a_Dicache_callback(int Op, CacheClient_t *Client) +{ + /* todo: Handle Op = CA_Abort (to show what was got) --Jcid */ + uint_t i; + DilloWeb *Web = Client->Web; + DilloImage *Image = Web->Image; + DICacheEntry *DicEntry = a_Dicache_get_entry(Web->url); + + dReturn_if_fail ( DicEntry != NULL ); + + /* when the data stream is not an image 'v_imgbuf' keeps NULL */ + if (Op == CA_Send && DicEntry->v_imgbuf) { + if (Image->height == 0 && DicEntry->State >= DIC_SetParms) { + /* Set parms */ + a_Image_set_parms( + Image, DicEntry->v_imgbuf, DicEntry->url, + DicEntry->version, DicEntry->width, DicEntry->height, + DicEntry->type); + } + if (DicEntry->State == DIC_Write) { + for (i = 0; i < DicEntry->height; ++i) + if (a_Bitvec_get_bit(DicEntry->BitVec, (int)i) && + !a_Bitvec_get_bit(Image->BitVec, (int)i) ) + a_Image_write(Image, DicEntry->v_imgbuf, + DicEntry->linebuf, i, FALSE); + } + } else if (Op == CA_Close || Op == CA_Abort) { + a_Image_close(Web->Image); + a_Bw_close_client(Web->bw, Client->Key); + } +} + +/* ------------------------------------------------------------------------- */ + +/* + * Set image's width, height & type + * (By now, we'll use the image information despite the html tags --Jcid) + */ +void a_Dicache_set_parms(DilloUrl *url, int version, DilloImage *Image, + uint_t width, uint_t height, DilloImgType type) +{ + DICacheEntry *DicEntry; + size_t Size = width * height * 3; + + dReturn_if_fail ( Image != NULL && width && height ); + /* Find the DicEntry for this Image */ + DicEntry = Dicache_get_entry_version(url, version); + dReturn_if_fail ( DicEntry != NULL ); + + /* Initialize the DicEntry */ + DicEntry->linebuf = dNew(uchar_t, width * 3); + dReturn_if_fail ( DicEntry->linebuf != NULL ); + + /* BUG: there's just one image-type now */ + #define I_RGB 0 + DicEntry->v_imgbuf = a_Image_imgbuf_new(Image->dw, I_RGB, width, height); + + /* This extra reference activates the dicache ALWAYS. + * Extra code is necessary in Imgbuf to be able to free it */ + //a_Image_imgbuf_ref(DicEntry->v_imgbuf); + + DicEntry->TotalSize = Size; + DicEntry->width = width; + DicEntry->height = height; + DicEntry->type = type; + DicEntry->BitVec = a_Bitvec_new((int)height); + DicEntry->State = DIC_SetParms; + + dicache_size_total += Size; + + /* Allocate and initialize this image */ + a_Image_set_parms(Image, DicEntry->v_imgbuf, url, version, + width, height, type); +} + +/* + * Implement the set_cmap method for the Image + */ +void a_Dicache_set_cmap(DilloUrl *url, int version, DilloImage *Image, + const uchar_t *cmap, uint_t num_colors, + int num_colors_max, int bg_index) +{ + DICacheEntry *DicEntry = Dicache_get_entry_version(url, version); + + dReturn_if_fail ( DicEntry != NULL ); + + dFree(DicEntry->cmap); + DicEntry->cmap = dNew0(uchar_t, 3 * num_colors_max); + memcpy(DicEntry->cmap, cmap, 3 * num_colors); + if (bg_index >= 0 && (uint_t)bg_index < num_colors) { + DicEntry->cmap[bg_index * 3] = (Image->bg_color >> 16) & 0xff; + DicEntry->cmap[bg_index * 3 + 1] = (Image->bg_color >> 8) & 0xff; + DicEntry->cmap[bg_index * 3 + 2] = (Image->bg_color) & 0xff; + } + + a_Image_set_cmap(Image, DicEntry->cmap); + DicEntry->State = DIC_SetCmap; +} + +/* + * Implement the write method + * (Write a scan line into the Dicache entry) + * buf: row buffer + * Y : row number + * x : horizontal offset? (always zero) + */ +void a_Dicache_write(DilloImage *Image, DilloUrl *url, int version, + const uchar_t *buf, int x, uint_t Y) +{ + DICacheEntry *DicEntry; + + dReturn_if_fail ( Image != NULL ); + DicEntry = Dicache_get_entry_version(url, version); + dReturn_if_fail ( DicEntry != NULL ); + dReturn_if_fail ( DicEntry->width > 0 && DicEntry->height > 0 ); + + a_Image_write(Image, DicEntry->v_imgbuf, buf, Y, TRUE); + DicEntry->Y = Y; + a_Bitvec_set_bit(DicEntry->BitVec, (int)Y); + DicEntry->State = DIC_Write; +} + +/* + * Implement the close method of the decoding process + */ +void a_Dicache_close(DilloUrl *url, int version, CacheClient_t *Client) +{ + DilloWeb *Web = Client->Web; + DICacheEntry *DicEntry = Dicache_get_entry_version(url, version); + + dReturn_if_fail ( DicEntry != NULL ); + + DicEntry->State = DIC_Close; + dFree(DicEntry->cmap); + DicEntry->cmap = NULL; + dFree(DicEntry->linebuf); + DicEntry->linebuf = NULL; + a_Image_close(Web->Image); + a_Bw_close_client(Web->bw, Client->Key); +} + +/* + * Free the imgbuf (RGB data) of unused entries. + */ +void a_Dicache_cleanup(void) +{ + int i; + DICacheNode *node; + DICacheEntry *entry; + + for (i = 0; i < dList_length(CachedIMGs); ++i) { + node = dList_nth_data(CachedIMGs, i); + /* iterate each entry of this node */ + for (entry = node->first; entry; entry = entry->next) { + if (entry->v_imgbuf && + a_Image_imgbuf_last_reference(entry->v_imgbuf)) { + /* free this unused entry */ + if (entry->next) { + Dicache_remove(node->url, entry->version); + } else { + Dicache_remove(node->url, entry->version); + --i; + break; + } + } + } + } +} + +/* ------------------------------------------------------------------------- */ + +/* + * Deallocate memory used by dicache module + * (Call this one at exit time) + */ +void a_Dicache_freeall(void) +{ + DICacheNode *node; + DICacheEntry *entry; + + /* Remove every dicache node and its entries */ + while ((node = dList_nth_data(CachedIMGs, 0))) { + while ((entry = node->first)) { + node->first = entry->next; + dFree(entry->cmap); + dFree(entry->linebuf); + a_Bitvec_free(entry->BitVec); + a_Image_imgbuf_unref(entry->v_imgbuf); + dicache_size_total -= entry->TotalSize; + } + dList_remove(CachedIMGs, node); + a_Url_free(node->url); + dFree(node); + } + dList_free(CachedIMGs); +} diff --git a/src/dicache.h b/src/dicache.h new file mode 100644 index 00000000..9afa5045 --- /dev/null +++ b/src/dicache.h @@ -0,0 +1,70 @@ +#ifndef __DICACHE_H__ +#define __DICACHE_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#include "bitvec.h" +#include "image.hh" +#include "cache.h" + +/* These will reflect the entry's "state" */ +typedef enum { + DIC_Empty, /* Just created the entry */ + DIC_SetParms, /* Parameters set */ + DIC_SetCmap, /* Color map set */ + DIC_Write, /* Feeding the entry */ + DIC_Close, /* Whole image got! */ + DIC_Abort /* Image transfer aborted */ +} DicEntryState; + +typedef struct _DICacheEntry DICacheEntry; + +struct _DICacheEntry { + DilloUrl *url; /* Image URL for this entry */ + uint_t width, height; /* As taken from image data */ + DilloImgType type; /* Image type */ + uchar_t *cmap; /* Color map */ + uchar_t *linebuf; /* Decompressed RGB buffer for one line */ + void *v_imgbuf; /* Void pointer to an Imgbuf object */ + size_t TotalSize; /* Amount of memory the image takes up */ + int Y; /* Current decoding row */ + bitvec_t *BitVec; /* Bit vector for decoded rows */ + DicEntryState State; /* Current status for this entry */ + int RefCount; /* Reference Counter */ + int version; /* Version number, used for different + versions of the same URL image */ + + DICacheEntry *next; /* Link to the next "newer" version */ +}; + + +void a_Dicache_init (void); + +DICacheEntry *a_Dicache_get_entry(const DilloUrl *Url); +DICacheEntry *a_Dicache_add_entry(const DilloUrl *Url); + +void a_Dicache_callback(int Op, CacheClient_t *Client); + +void a_Dicache_set_parms(DilloUrl *url, int version, DilloImage *Image, + uint_t width, uint_t height, DilloImgType type); +void a_Dicache_set_cmap(DilloUrl *url, int version, DilloImage *Image, + const uchar_t *cmap, uint_t num_colors, + int num_colors_max, int bg_index); +void a_Dicache_write(DilloImage *Image, DilloUrl *url, int version, + const uchar_t *buf, int x, uint_t Y); +void a_Dicache_close(DilloUrl *url, int version, CacheClient_t *Client); + +void a_Dicache_invalidate_entry(const DilloUrl *Url); +DICacheEntry* a_Dicache_ref(const DilloUrl *Url, int version); +void a_Dicache_unref(const DilloUrl *Url, int version); +void a_Dicache_cleanup(void); +void a_Dicache_freeall(void); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __DICACHE_H__ */ diff --git a/src/dillo.cc b/src/dillo.cc new file mode 100644 index 00000000..bd8ceeb8 --- /dev/null +++ b/src/dillo.cc @@ -0,0 +1,108 @@ +/* + * Dillo web browser + * + * Copyright 1999-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdio.h> +#include <unistd.h> + +#include <fltk/Window.h> +#include <fltk/run.h> + +#include "dir.h" +#include "uicmd.hh" + +#include "msg.h" +#include "bw.h" +#include "bookmark.h" +#include "misc.h" +#include "nav.h" + +#include "dns.h" +#include "web.hh" +#include "IO/Url.h" +#include "IO/mime.h" +#include "capi.h" +#include "dicache.h" +#include "cookies.h" + + +/* + * Given a command line argument, build a DilloUrl for it. + */ +static DilloUrl *Dillo_make_start_url(char *str) +{ + char *url_str, *p; + DilloUrl *start_url; + int is_file = FALSE; + + /* Relative path to a local file? */ + p = (*str == '/') ? dStrdup(str) : dStrconcat(a_Dir_get_owd(),"/",str,NULL); + + if (access(p, F_OK) == 0) { + /* absolute path may have non-URL characters */ + url_str = a_Misc_escape_chars(p, "% "); + is_file = TRUE; + } else { + /* Not a file, filter URL string */ + url_str = a_Url_string_strip_delimiters(str); + } + dFree(p); + + if (is_file) { + start_url = a_Url_new(url_str + 1, "file:/", 0, 0, 0); + } else { + start_url = a_Url_new(url_str, NULL, 0, 0, 0); + } + dFree(url_str); + + return start_url; +} + +/* + * MAIN + */ +int main(int argc, char **argv) +{ + // Initialize internal modules + a_Dir_init(); + a_Prefs_init(); + a_Dpi_init(); + a_Dns_init(); + a_Web_init(); + a_Http_init(); + a_Mime_init(); + a_Capi_init(); + a_Dicache_init(); + a_Bw_init(); + a_Cookies_init(); + + // Create a new UI/bw pair + BrowserWindow *bw = a_UIcmd_browser_window_new(0, 0); + + if (argc == 2) { + DilloUrl *url = Dillo_make_start_url(argv[1]); + a_UIcmd_open_urlstr(bw, URL_STR(url)); + a_Url_free(url); + } else { + /* Send startup screen */ + //a_Nav_push(bw, prefs.start_page); + } + + return fltk::run(); +} diff --git a/src/dir.c b/src/dir.c new file mode 100644 index 00000000..53497895 --- /dev/null +++ b/src/dir.c @@ -0,0 +1,48 @@ +/* + * File: dir.c + * + * Copyright 2006 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include <unistd.h> + +#include "../dlib/dlib.h" + + +/* + * Local data + */ +/* Dillo works from an unmounted directory (/tmp). */ +static char *OldWorkingDirectory = NULL; + +/* + * Change current working directory to "/tmp". + */ +void a_Dir_init(void) +{ + dFree(OldWorkingDirectory); + OldWorkingDirectory = dGetcwd(); + chdir("/tmp"); +} + +/* + * Return the initial current working directory in a string. + */ +char *a_Dir_get_owd(void) +{ + return OldWorkingDirectory; +} + +/* + * Free memory + */ +void a_Dir_free(void) +{ + dFree(OldWorkingDirectory); +} + diff --git a/src/dir.h b/src/dir.h new file mode 100644 index 00000000..580122f0 --- /dev/null +++ b/src/dir.h @@ -0,0 +1,19 @@ +#ifndef __DIR_H__ +#define __DIR_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +void a_Dir_init(void); +char *a_Dir_get_owd(void); +void a_Dir_free(void); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __DIR_H__ */ + diff --git a/src/dns.c b/src/dns.c new file mode 100644 index 00000000..2813f54f --- /dev/null +++ b/src/dns.c @@ -0,0 +1,535 @@ +/* + * File: dns.c + * + * Copyright (C) 1999-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * Non blocking pthread-handled Dns scheme + */ + +#include <pthread.h> + +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <string.h> + +#include "msg.h" +#include "dns.h" +#include "list.h" +#include "timeout.hh" + +#define DEBUG_LEVEL 5 +#include "debug.h" + + +/* + * Uncomment the following line for debugging or gprof profiling. + */ +/* #undef D_DNS_THREADED */ + +/* + * Uncomment the following line for libc5 optimization + */ +/* #define LIBC5 */ + + +/* Maximum dns resolving threads */ +#ifdef D_DNS_THREADED +# define D_DNS_MAX_SERVERS 4 +#else +# define D_DNS_MAX_SERVERS 1 +#endif + + +typedef struct { + int channel; /* Index of this channel [0 based] */ + bool_t in_use; /* boolean to tell if server is doing a lookup */ + bool_t ip_ready; /* boolean: is IP lookup done? */ + Dlist *addr_list; /* IP address */ + char *hostname; /* Adress to resolve */ + int status; /* errno code for resolving function */ +#ifdef D_DNS_THREADED + pthread_t th1; /* Thread id */ +#endif +} DnsServer; + +typedef struct { + char *hostname; /* host name for cache */ + Dlist *addr_list; /* addresses of host */ +} GDnsCache; + +typedef struct { + int channel; /* -2 if waiting, otherwise index to dns_server[] */ + char *hostname; /* The one we're resolving */ + DnsCallback_t cb_func; /* callback function */ + void *cb_data; /* extra data for the callback function */ +} GDnsQueue; + + +/* + * Forward declarations + */ +static void Dns_timeout_client(void *data); + +/* + * Local Data + */ +static DnsServer dns_server[D_DNS_MAX_SERVERS]; +static int num_servers; +static GDnsCache *dns_cache; +static int dns_cache_size, dns_cache_size_max; +static GDnsQueue *dns_queue; +static int dns_queue_size, dns_queue_size_max; +static bool_t ipv6_enabled; + + +/* ---------------------------------------------------------------------- + * Dns queue functions + */ +static void Dns_queue_add(int channel, const char *hostname, + DnsCallback_t cb_func, void *cb_data) +{ + a_List_add(dns_queue, dns_queue_size, dns_queue_size_max); + dns_queue[dns_queue_size].channel = channel; + dns_queue[dns_queue_size].hostname = dStrdup(hostname); + dns_queue[dns_queue_size].cb_func = cb_func; + dns_queue[dns_queue_size].cb_data = cb_data; + dns_queue_size++; +} + +/* + * Find hostname index in dns_queue + * (if found, returns queue index; -1 if not) + */ +static int Dns_queue_find(const char *hostname) +{ + int i; + + for (i = 0; i < dns_queue_size; i++) + if (!strcmp(hostname, dns_queue[i].hostname)) + return i; + + return -1; +} + +/* + * Given an index, remove an entry from the dns_queue + */ +static void Dns_queue_remove(int index) +{ + int i; + + DEBUG_MSG(2, "Dns_queue_remove: deleting client [%d] [queue_size=%d]\n", + index, dns_queue_size); + + if (index < dns_queue_size) { + dFree(dns_queue[index].hostname); + --dns_queue_size; /* you'll find out why ;-) */ + for (i = index; i < dns_queue_size; i++) + dns_queue[i] = dns_queue[i + 1]; + } +} + +/* + * Debug function + * +void Dns_queue_print() +{ + int i; + + MSG("Queue: ["); + for (i = 0; i < dns_queue_size; i++) + MSG("%d:%s ", dns_queue[i].channel, dns_queue[i].hostname); + MSG("]\n"); +} + */ + +/* + * Add an IP/hostname pair to Dns-cache + */ +static void Dns_cache_add(char *hostname, Dlist *addr_list) +{ + a_List_add(dns_cache, dns_cache_size, dns_cache_size_max); + dns_cache[dns_cache_size].hostname = dStrdup(hostname); + dns_cache[dns_cache_size].addr_list = addr_list; + ++dns_cache_size; + DEBUG_MSG(1, "Cache objects: %d\n", dns_cache_size); +} + + +/* + * Initializer function + */ +void a_Dns_init(void) +{ + int i; + +#ifdef D_DNS_THREADED + DEBUG_MSG(5, "dillo_dns_init: Here we go! (threaded)\n"); +#else + DEBUG_MSG(5, "dillo_dns_init: Here we go! (not threaded)\n"); +#endif + + dns_queue_size = 0; + dns_queue_size_max = 16; + dns_queue = dNew(GDnsQueue, dns_queue_size_max); + + dns_cache_size = 0; + dns_cache_size_max = 16; + dns_cache = dNew(GDnsCache, dns_cache_size_max); + + num_servers = D_DNS_MAX_SERVERS; + + /* Initialize servers data */ + for (i = 0; i < num_servers; ++i) { + dns_server[i].channel = i; + dns_server[i].in_use = FALSE; + dns_server[i].ip_ready = FALSE; + dns_server[i].addr_list = NULL; + dns_server[i].hostname = NULL; + dns_server[i].status = 0; +#ifdef D_DNS_THREADED + dns_server[i].th1 = (pthread_t) -1; +#endif + } + + /* IPv6 test */ + ipv6_enabled = FALSE; +#ifdef ENABLE_IPV6 + { + /* If the IPv6 address family is not available there is no point + wasting time trying to connect to v6 addresses. */ + int fd = socket(AF_INET6, SOCK_STREAM, 0); + if (fd >= 0) { + close(fd); + ipv6_enabled = TRUE; + } + } +#endif +} + +/* + * Allocate a host structure and add it to the list + */ +static void Dns_note_hosts(Dlist *list, int af, struct hostent *host) +{ + int i; + + if (host->h_length > DILLO_ADDR_MAX) + return; + for (i = 0; host->h_addr_list[i]; i++) { + DilloHost *dh = dNew0(DilloHost, 1); + dh->af = af; + dh->alen = host->h_length; + memcpy(&dh->data[0], host->h_addr_list[i], (size_t)host->h_length); + dList_append(list, dh); + } +} + +#ifdef D_DNS_THREADED +/* + * Server function (runs on its own thread) + */ +static void *Dns_server(void *data) +{ + struct hostent *host; + int channel = VOIDP2INT(data); +#ifdef LIBC5 + int h_err; + char buff[1024]; + struct hostent sh; +#endif + Dlist *hosts = dList_new(2); + + DEBUG_MSG(3, "Dns_server: starting...\n ch: %d host: %s\n", + channel, dns_server[channel].hostname); + +#ifdef ENABLE_IPV6 + if (ipv6_enabled) { + host = gethostbyname2(dns_server[channel].hostname, AF_INET6); + if (host) { + Dns_note_hosts(hosts, AF_INET6, host); + } + } +#endif + +#ifdef LIBC5 + host = gethostbyname_r(dns_server[channel].hostname, &sh, buff, + sizeof(buff), &h_err); +#else + host = gethostbyname(dns_server[channel].hostname); +#endif + + if (!host) { +#ifdef LIBC5 + dns_server[channel].status = h_err; +#else + dns_server[channel].status = h_errno; + if (h_errno == HOST_NOT_FOUND) + MSG("DNS error: HOST_NOT_FOUND\n"); + else if (h_errno == TRY_AGAIN) + MSG("DNS error: TRY_AGAIN\n"); + else if (h_errno == NO_RECOVERY) + MSG("DNS error: NO_RECOVERY\n"); + else if (h_errno == NO_ADDRESS) + MSG("DNS error: NO_ADDRESS\n"); +#endif + } else { + dns_server[channel].status = 0; + Dns_note_hosts(hosts, AF_INET, host); + } + if (dList_length(hosts) > 0) { + dns_server[channel].status = 0; + } else { + dList_free(hosts); + hosts = NULL; + } + + /* tell our findings */ + DEBUG_MSG(5, "Dns_server [%d]: %s is %p\n", channel, + dns_server[channel].hostname, hosts); + dns_server[channel].addr_list = hosts; + dns_server[channel].ip_ready = TRUE; + + return NULL; /* (avoids a compiler warning) */ +} +#endif + +#ifndef D_DNS_THREADED +/* + * Blocking server-function (it doesn't use threads) + */ +static void Dns_blocking_server(void) +{ + int channel = 0; + struct hostent *host = NULL; + dList *hosts = dList_new(2); +#ifdef LIBC5 + int h_err; +#endif + + DEBUG_MSG(3, "Dns_blocking_server: starting...\n"); + DEBUG_MSG(3, "Dns_blocking_server: dns_server[%d].hostname = %s\n", + channel, dns_server[channel].hostname); + +#ifdef ENABLE_IPV6 + if (ipv6_enabled) { + host = gethostbyname2(dns_server[channel].hostname, AF_INET6); + if (host) { + Dns_note_hosts(hosts, AF_INET6, host); + } + } +#endif + +#ifdef LIBC5 + host = gethostbyname_r(dns_server[channel].hostname, &sh, buff, + sizeof(buff), &h_err); +#else + host = gethostbyname(dns_server[channel].hostname); +#endif + + if (!host) { +#ifdef LIBC5 + dns_server[channel].status = h_err; +#else + dns_server[channel].status = h_errno; +#endif + } else { + Dns_note_hosts(hosts, AF_INET, host); + } + if (dList_length(hosts) > 0) { + /* at least one entry on the list is ok */ + dns_server[channel].status = 0; + } else { + dList_free(hosts); + hosts = NULL; + } + + /* write IP to server data channel */ + DEBUG_MSG(3, "Dns_blocking_server: IP of %s is %p\n", + dns_server[channel].hostname, hosts); + dns_server[channel].addr_list = hosts; + dns_server[channel].ip_ready = TRUE; + + DEBUG_MSG(3, "Dns_blocking_server: leaving...\n"); +} +#endif + +/* + * Request function (spawn a server and let it handle the request) + */ +static void Dns_server_req(int channel, const char *hostname) +{ +#ifdef D_DNS_THREADED + static pthread_attr_t thrATTR; + static int thrATTRInitialized = 0; +#endif + + dns_server[channel].in_use = TRUE; + dns_server[channel].ip_ready = FALSE; + + dFree(dns_server[channel].hostname); + dns_server[channel].hostname = dStrdup(hostname); + + /* Let's set a timeout client to poll the server channel (5 times/sec) */ + a_Timeout_add(0.2,Dns_timeout_client,(void*)(dns_server[channel].channel)); + +#ifdef D_DNS_THREADED + /* set the thread attribute to the detached state */ + if (!thrATTRInitialized) { + pthread_attr_init(&thrATTR); + pthread_attr_setdetachstate(&thrATTR, PTHREAD_CREATE_DETACHED); + thrATTRInitialized = 1; + } + /* Spawn thread */ + pthread_create(&dns_server[channel].th1, &thrATTR, Dns_server, + INT2VOIDP(dns_server[channel].channel)); +#else + Dns_blocking_server(); +#endif +} + +/* + * Return the IP for the given hostname using a callback. + * Side effect: a thread is spawned when hostname is not cached. + */ +void a_Dns_resolve(const char *hostname, DnsCallback_t cb_func, void *cb_data) +{ + int i, channel; + + if (!hostname) + return; + + /* check for cache hit. */ + for (i = 0; i < dns_cache_size; i++) + if (!strcmp(hostname, dns_cache[i].hostname)) + break; + + if (i < dns_cache_size) { + /* already resolved, call the Callback inmediately. */ + cb_func(0, dns_cache[i].addr_list, cb_data); + + } else if ((i = Dns_queue_find(hostname)) != -1) { + /* hit in queue, but answer hasn't come back yet. */ + Dns_queue_add(dns_queue[i].channel, hostname, cb_func, cb_data); + + } else { + /* Never requested before -- we must resolve it! */ + + /* Find a channel we can send the request to */ + for (channel = 0; channel < num_servers; channel++) + if (!dns_server[channel].in_use) + break; + if (channel < num_servers) { + /* Found a free channel! */ + Dns_queue_add(channel, hostname, cb_func, cb_data); + Dns_server_req(channel, hostname); + } else { + /* We'll have to wait for a thread to finish... */ + Dns_queue_add(-2, hostname, cb_func, cb_data); + } + } +} + +/* + * Give answer to all queued callbacks on this channel + */ +static void Dns_serve_channel(int channel) +{ + int i; + DnsServer *srv = &dns_server[channel]; + + for (i = 0; i < dns_queue_size; i++) { + if (dns_queue[i].channel == channel) { + dns_queue[i].cb_func(srv->status, srv->addr_list, + dns_queue[i].cb_data); + Dns_queue_remove(i); + --i; + } + } + /* set current channel free */ + srv->in_use = FALSE; +} + +/* + * Assign free channels to waiting clients (-2) + */ +static void Dns_assign_channels(void) +{ + int ch, i, j; + + for (ch = 0; ch < num_servers; ++ch) { + if (dns_server[ch].in_use == FALSE) { + /* Find the next query in the queue (we're a FIFO) */ + for (i = 0; i < dns_queue_size; i++) + if (dns_queue[i].channel == -2) + break; + + if (i < dns_queue_size) { + /* assign this channel to every queued request + * with the same hostname*/ + for (j = i; j < dns_queue_size; j++) + if (dns_queue[j].channel == -2 && + !strcmp(dns_queue[j].hostname, dns_queue[i].hostname)) + dns_queue[j].channel = ch; + Dns_server_req(ch, dns_queue[i].hostname); + } else + return; + } + } +} + +/* + * This is a timeout function that + * reads the DNS results and resumes the stopped jobs. + */ +static void Dns_timeout_client(void *data) +{ + int channel = (int)data; + DnsServer *srv = &dns_server[channel]; + + if (srv->ip_ready) { + if (srv->addr_list != NULL) { + /* DNS succeeded, let's cache it */ + Dns_cache_add(srv->hostname, srv->addr_list); + } + Dns_serve_channel(channel); + Dns_assign_channels(); + a_Timeout_remove(); /* Done! */ + + } else { + /* IP not already resolved, keep on trying... */ + a_Timeout_repeat(0.2, Dns_timeout_client, data); + } +} + + +/* + * Dns memory-deallocation + * (Call this one at exit time) + * The Dns_queue is deallocated at execution time (no need to do that here) + * 'dns_cache' is the only one that grows dinamically + */ +void a_Dns_freeall(void) +{ + int i; + + for ( i = 0; i < dns_cache_size; ++i ){ + dFree(dns_cache[i].hostname); + } + dFree(dns_cache); +} + diff --git a/src/dns.h b/src/dns.h new file mode 100644 index 00000000..13392eba --- /dev/null +++ b/src/dns.h @@ -0,0 +1,31 @@ +#ifndef __DNS_H__ +#define __DNS_H__ + +#include "chain.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +typedef void (*DnsCallback_t)(int Status, Dlist *addr_list, void *data); + +void a_Dns_init (void); +void a_Dns_freeall(void); +void a_Dns_resolve(const char *hostname, DnsCallback_t cb_func, void *cb_data); + +#define DILLO_ADDR_MAX 16 + +typedef struct _DilloHost +{ + int af; + int alen; + char data[DILLO_ADDR_MAX]; +} DilloHost; + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __DNS_H__ */ diff --git a/src/dpiapi.c b/src/dpiapi.c new file mode 100644 index 00000000..382cf122 --- /dev/null +++ b/src/dpiapi.c @@ -0,0 +1,82 @@ +/* + * File: dpiapi.c + * + * Copyright (C) 2004 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* Support for dpi/dpip from Dillo's side */ + +#include "msg.h" +#include "bw.h" +#include "capi.h" +#include "dpiapi.h" /* for prototypes */ +#include "../dpip/dpip.h" + + +//---------------------------------------------------------------------------- +// Dialog interface +// + +/* This variable can be eliminated as a parameter with a cleaner API. */ +static char *dialog_server = NULL; + + +/* + * Generic callback function for dpip dialogs. + */ +//static void Dpiapi_dialog_answer_cb(BrowserWindow *bw) +//{ +// DialogAnswer *answer = bw->question_dialog_answer; +// char *cmd, numstr[16]; +// +// /* make dpip tag with the answer */ +// snprintf(numstr, 16, "%d", answer->alt_num); +// cmd = a_Dpip_build_cmd("cmd=%s to_cmd=%s msg=%s", +// "answer", "dialog", numstr); +// +// /* Send answer */ +// a_Capi_dpi_send_cmd(NULL, bw, cmd, dialog_server, 0); +// +// /* cleanup */ +// bw->question_dialog_data = NULL; +// dFree(answer->tthis); +// bw->question_dialog_answer = NULL; +//} + +/* + * Process a dpip "dialog" command from any dpi. + */ +void a_Dpiapi_dialog(BrowserWindow *bw, char *server, char *dpip_tag) +{ + char *question, *alt1, *alt2, *alt3, *alt4, *alt5; + size_t dpip_tag_len; + + MSG("a_Dpiapi_dialog:\n"); + MSG(" dpip_tag: %s\n", dpip_tag); + + /* set the module scoped variable */ + dialog_server = server; + + /* other options can be parsed the same way */ + dpip_tag_len = strlen(dpip_tag); + question = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "msg"); + alt1 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt1"); + alt2 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt2"); + alt3 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt3"); + alt4 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt4"); + alt5 = a_Dpip_get_attr(dpip_tag, dpip_tag_len, "alt5"); + + //a_Dialog_question5( + // bw, question, TRUE, + // alt1, alt2, alt3, alt4, alt5, + // Dpiapi_dialog_answer_cb); + + dFree(alt1); dFree(alt2); dFree(alt3); dFree(alt4); dFree(alt5); + dFree(question); +} + diff --git a/src/dpiapi.h b/src/dpiapi.h new file mode 100644 index 00000000..26823026 --- /dev/null +++ b/src/dpiapi.h @@ -0,0 +1,3 @@ + +void a_Dpiapi_dialog(BrowserWindow *bw, char *server, char *dpip_tag); + diff --git a/src/form.cc b/src/form.cc new file mode 100644 index 00000000..f170cbef --- /dev/null +++ b/src/form.cc @@ -0,0 +1,98 @@ +#include "form.hh" +#include "html.hh" + +namespace form { + +using namespace dw::core::ui; + +Form::ResourceDecorator::ResourceDecorator (const char *name) +{ + this->name = strdup (name); +} + +Form::ResourceDecorator::~ResourceDecorator () +{ + delete name; +} + +Form::TextResourceDecorator::TextResourceDecorator (const char *name, + TextResource *resource): + Form::ResourceDecorator (name) +{ + this->resource = resource; +} + +const char *Form::TextResourceDecorator::getValue () +{ + return resource->getText (); +} + +Form::RadioButtonResourceDecorator::RadioButtonResourceDecorator + (const char *name, RadioButtonResource *resource, const char **values): + Form::ResourceDecorator (name) +{ + this->resource = resource; + + int n = 0; + while (values[n]) + n++; + this->values = new const char*[n]; + for(int i = 0; i < n; i++) + this->values[i] = strdup (values[i]); + values[n] = 0; +} + +Form::RadioButtonResourceDecorator::~RadioButtonResourceDecorator () +{ + for(int i = 0; values[i]; i++) + delete values[i]; + delete values; +} + +const char *Form::RadioButtonResourceDecorator::getValue () +{ + RadioButtonResource::GroupIterator *it; + int i; + for (it = resource->groupIterator (), i = 0; it->hasNext (); i++) { + RadioButtonResource *resource = it->getNext (); + if(resource->isActivated ()) { + it->unref (); + return values[i]; + } + } + + it->unref (); + return NULL; +} + + +Form::Form (void *p) +{ + ext_data = p; + resources = new container::typed::List <ResourceDecorator> (true); +} + +Form::~Form () +{ + delete resources; +} + +void Form::clicked (ButtonResource *resource, int buttonNo) +{ +/* + for (container::typed::Iterator <ResourceDecorator> it = + resources->iterator (); + it.hasNext (); ) { + ResourceDecorator *resource = it.getNext (); + const char *value = resource->getValue (); + if (value) + printf ("%s = %s\n", resource->getName (), value); + } +*/ + printf ("Form::clicked:: Button was clicked\n"); + + // Let html.cc handle the event + a_Html_form_event_handler(ext_data, this, (Resource*)resource); +} + +} // namespace form diff --git a/src/form.hh b/src/form.hh new file mode 100644 index 00000000..9ea47bb0 --- /dev/null +++ b/src/form.hh @@ -0,0 +1,87 @@ +#ifndef __FORM_HH__ +#define __FORM_HH__ + +#include "dw/core.hh" +#include "dw/ui.hh" + +namespace form { + +/** + * \brief Handles HTML form data. + * + * Add resources by calling the respective add...Resource method. Furtermore, + * this class impelements dw::core::ui::ButtonResource::ClickedReceiver, the + * form data is printed to stdout, when the "clicked" signal is received. + */ +class Form: public dw::core::ui::ButtonResource::ClickedReceiver +{ +private: + /** + * \brief Decorates instances of dw::core::ui::Resource. + * + * This is the abstract base class, sub classes have to be defined to + * decorate specific sub interfaces of dw::core::ui::Resource. + */ + class ResourceDecorator: public object::Object + { + private: + const char *name; + + protected: + ResourceDecorator (const char *name); + ~ResourceDecorator (); + + public: + inline const char *getName () { return name; } + virtual const char *getValue () = 0; + }; + + /** + * \brief Decorates instances of dw::core::ui::TextResource. + */ + class TextResourceDecorator: public ResourceDecorator + { + private: + dw::core::ui::TextResource *resource; + + public: + TextResourceDecorator (const char *name, + dw::core::ui::TextResource *resource); + const char *getValue (); + }; + + /** + * \brief Decorates instances of dw::core::ui::RadioButtonResource. + * + * This class has to be instanciated only once for a group of radio + * buttons. + */ + class RadioButtonResourceDecorator: public ResourceDecorator + { + private: + dw::core::ui::RadioButtonResource *resource; + const char **values; + + public: + RadioButtonResourceDecorator (const char *name, + dw::core::ui::RadioButtonResource + *resource, + const char **values); + ~RadioButtonResourceDecorator (); + const char *getValue (); + }; + + container::typed::List <ResourceDecorator> *resources; + + void *ext_data; // external data pointer + +public: + Form (void *p); + ~Form (); + void clicked (dw::core::ui::ButtonResource *resource, int buttonNo); + +}; + +} // namespace form + +#endif // __FORM_HH__ diff --git a/src/gif.c b/src/gif.c new file mode 100644 index 00000000..9806cbb1 --- /dev/null +++ b/src/gif.c @@ -0,0 +1,1054 @@ +/* + * File: gif.c + * + * Copyright (C) 1997 Raph Levien <raph@acm.org> + * Copyright (C) 2000-2002 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * The GIF decoder for dillo. It is responsible for decoding GIF data + * and transferring it to the dicache. + */ + + +/* Notes 13 Oct 1997 --RLL + * + * Today, just for the hell of it, I implemented a new decoder from + * scratch. It's oriented around pushing bytes, while the old decoder + * was based around reads which may suspend. There were basically + * three motivations. + * + * 1. To increase the speed. + * + * 2. To fix some bugs I had seen, most likely due to suspension. + * + * 3. To make sure that the code had no buffer overruns or the like. + * + * 4. So that the code could be released under a freer license. + * + * Let's see how we did on speed. I used a large image for testing + * (fvwm95-2.gif). + * + * The old decoder spent a total of about 1.04 seconds decoding the + * image. Another .58 seconds went into Image_line, almost + * entirely conversion from colormap to RGB. + * + * The new decoder spent a total of 0.46 seconds decoding the image. + * However, the time for Image_line went up to 1.01 seconds. + * Thus, even though the decoder seems to be about twice as fast, + * the net gain is pretty minimal. Could this be because of cache + * effects? + * + * Lessons learned: The first, which I keep learning over and over, is + * not to try to optimize too much. It doesn't work. Just keep things + * simple. + * + * Second, it seems that the colormap to RGB conversion is really a + * significant part of the overall time. It's possible that going + * directly to 16 bits would help, but that's optimization again :) + */ + + +/* todo: + * + Make sure to handle error cases gracefully (including aborting the + * connection, if necessary). + */ + +#include <config.h> +#ifdef ENABLE_GIF + +#include <stdio.h> /* for sprintf */ +#include <string.h> /* for memcpy and memmove */ + +#include "msg.h" +#include "image.hh" +#include "web.hh" +#include "cache.h" +#include "dicache.h" +#include "prefs.h" + +#define DEBUG_LEVEL 6 +#include "debug.h" + +#define INTERLACE 0x40 +#define LOCALCOLORMAP 0x80 + +#define LM_to_uint(a,b) ((((uchar_t)b)<<8)|((uchar_t)a)) + +#define MAXCOLORMAPSIZE 256 +#define MAX_LWZ_BITS 12 + + +typedef struct _DilloGif { + DilloImage *Image; + DilloUrl *url; + int version; + + int state; + size_t Start_Ofs; + uint_t Flags; + + uchar_t input_code_size; + uchar_t *linebuf; + int pass; + + uint_t y; + + /* state for lwz_read_byte */ + int code_size; + + /* The original GifScreen from giftopnm */ + uint_t Width; + uint_t Height; + size_t ColorMap_ofs; + uint_t ColorResolution; + uint_t NumColors; + int Background; + uint_t spill_line_index; +#if 0 + uint_t AspectRatio; /* AspectRatio (not used) */ +#endif + + /* Gif89 extensions */ + int transparent; +#if 0 + /* None are used: */ + int delayTime; + int inputFlag; + int disposal; +#endif + + /* state for the new push-oriented decoder */ + int packet_size; /* The amount of the data block left to process */ + uint_t window; + int bits_in_window; + uint_t last_code; /* Last "compressed" code in the look up table */ + uint_t line_index; + uchar_t **spill_lines; + int num_spill_lines_max; + int length[(1 << MAX_LWZ_BITS) + 1]; + int code_and_byte[(1 << MAX_LWZ_BITS) + 1]; +} DilloGif; + +/* Some invariants: + * + * last_code <= code_mask + * + * code_and_byte is stored packed: (code << 8) | byte + */ + + +/* + * Forward declarations + */ +static void Gif_write(DilloGif *gif, void *Buf, uint_t BufSize); +static void Gif_close(DilloGif *gif, CacheClient_t *Client); +static size_t Gif_process_bytes(DilloGif *gif, const uchar_t *buf, + int bufsize, void *Buf); +static DilloGif *Gif_new(DilloImage *Image, DilloUrl *url, int version); +static void Gif_callback(int Op, CacheClient_t *Client); + +/* exported function */ +void *a_Gif_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data); + + +/* + * MIME handler for "image/gif" type + * (Sets Gif_callback as cache-client) + */ +void *a_Gif_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data) +{ + DilloWeb *web = Ptr; + DICacheEntry *DicEntry; + + if (!web->Image) + web->Image = a_Image_new(0, 0, NULL, prefs.bg_color); + /* todo: get the backgound color from the parent widget -- Livio. */ + + /* Add an extra reference to the Image (for dicache usage) */ + a_Image_ref(web->Image); + + DicEntry = a_Dicache_get_entry(web->url); + if (!DicEntry) { + /* Let's create an entry for this image... */ + DicEntry = a_Dicache_add_entry(web->url); + + /* ... and let the decoder feed it! */ + *Data = Gif_new(web->Image, DicEntry->url, DicEntry->version); + *Call = (CA_Callback_t) Gif_callback; + } else { + /* Let's feed our client from the dicache */ + a_Dicache_ref(DicEntry->url, DicEntry->version); + *Data = web->Image; + *Call = (CA_Callback_t) a_Dicache_callback; + } + return (web->Image->dw); +} + +/* + * Create a new gif structure for decoding a gif into a RGB buffer + */ +static DilloGif *Gif_new(DilloImage *Image, DilloUrl *url, int version) +{ + DilloGif *gif = dMalloc(sizeof(DilloGif)); + + gif->Image = Image; + gif->url = url; + gif->version = version; + + gif->Flags = 0; + gif->state = 0; + gif->Start_Ofs = 0; + gif->linebuf = NULL; + gif->Background = -1; + gif->transparent = -1; + gif->num_spill_lines_max = 0; + gif->spill_lines = NULL; + gif->window = 0; + gif->packet_size = 0; + gif->ColorMap_ofs = 0; + + return gif; +} + +/* + * This function is a cache client, it receives data from the cache + * and dispatches it to the appropriate gif-processing functions + */ +static void Gif_callback(int Op, CacheClient_t *Client) +{ + if (Op) + Gif_close(Client->CbData, Client); + else + Gif_write(Client->CbData, Client->Buf, Client->BufSize); +} + +/* + * Receive and process new chunks of GIF image data + */ +static void Gif_write(DilloGif *gif, void *Buf, uint_t BufSize) +{ + uchar_t *buf; + int bufsize, bytes_consumed; + + /* Sanity checks */ + if (!Buf || !gif->Image || BufSize == 0) + return; + + buf = ((uchar_t *) Buf) + gif->Start_Ofs; + bufsize = BufSize - gif->Start_Ofs; + + DEBUG_MSG(5, "Gif_write: %u bytes\n", BufSize); + + /* Process the bytes in the input buffer. */ + bytes_consumed = Gif_process_bytes(gif, buf, bufsize, Buf); + + if (bytes_consumed < 1) + return; + gif->Start_Ofs += bytes_consumed; + + DEBUG_MSG(5, "exit Gif_write, bufsize=%ld\n", (long)bufsize); +} + +/* + * Finish the decoding process (and free the memory) + */ +static void Gif_close(DilloGif *gif, CacheClient_t *Client) +{ + int i; + + DEBUG_MSG(5, "destroy gif %p\n", gif); + + a_Dicache_close(gif->url, gif->version, Client); + + dFree(gif->linebuf); + + if (gif->spill_lines != NULL) { + for (i = 0; i < gif->num_spill_lines_max; i++) + dFree(gif->spill_lines[i]); + dFree(gif->spill_lines); + } + dFree(gif); +} + + +/* --- GIF Extensions ----------------------------------------------------- */ + +/* + * This reads a sequence of GIF data blocks.. and ignores them! + * Buf points to the first data block. + * + * Return Value + * 0 = There wasn't enough bytes read yet to read the whole datablock + * otherwise the size of the data blocks + */ +static inline size_t Gif_data_blocks(const uchar_t *Buf, size_t BSize) +{ + size_t Size = 0; + + if (BSize < 1) + return 0; + while (Buf[0]) { + if (BSize <= (size_t)(Buf[0] + 1)) + return 0; + Size += Buf[0] + 1; + BSize -= Buf[0] + 1; + Buf += Buf[0] + 1; + } + return Size + 1; +} + +/* + * This is a GIF extension. We ignore it with this routine. + * Buffer points to just after the extension label. + * + * Return Value + * 0 -- block not processed + * otherwise the size of the extension label. + */ +static inline size_t Gif_do_generic_ext(const uchar_t *Buf, size_t BSize) +{ + size_t Size = Buf[0] + 1, DSize; + + /* The Block size (the first byte) is supposed to be a specific size + * for each extension... we don't check. + */ + + if (Buf[0] > BSize) + return 0; + DSize = Gif_data_blocks(Buf + Size, BSize - Size); + if (!DSize) + return 0; + Size += DSize; + return Size <= BSize ? Size : 0; +} + +/* + * ? + */ +static inline size_t + Gif_do_gc_ext(DilloGif *gif, const uchar_t *Buf, size_t BSize) +{ + /* Graphic Control Extension */ + size_t Size = Buf[0] + 2; + uint_t Flags; + + if (Size > BSize) + return 0; + Buf++; + Flags = Buf[0]; + + /* The packed fields */ +#if 0 + gif->disposal = (Buf[0] >> 2) & 0x7; + gif->inputFlag = (Buf[0] >> 1) & 0x1; + + /* Delay time */ + gif->delayTime = LM_to_uint(Buf[1], Buf[2]); +#endif + + /* Transparent color index, may not be valid (unless flag is set) */ + if ((Flags & 0x1)) { + gif->transparent = Buf[3]; + } + return Size; +} + +#define App_Ext (0xff) +#define Cmt_Ext (0xfe) +#define GC_Ext (0xf9) +#define Txt_Ext (0x01) + +/* + * ? + * Return value: + * TRUE when the extension is over + */ +static size_t Gif_do_extension(DilloGif *gif, uint_t Label, + const uchar_t *buf, + size_t BSize) +{ + switch (Label) { + case GC_Ext: /* Graphics extension */ + return Gif_do_gc_ext(gif, buf, BSize); + + case Cmt_Ext: /* Comment extension */ + return Gif_data_blocks(buf, BSize); + + case Txt_Ext: /* Plain text Extension */ + /* This extension allows (rcm thinks) the image to be rendered as text. + */ + case App_Ext: /* Application Extension */ + default: + return Gif_do_generic_ext(buf, BSize); /*Ignore Extension */ + } +} + +/* --- General Image Decoder ----------------------------------------------- */ +/* Here begins the new push-oriented decoder. */ + +/* + * ? + */ +static void Gif_lwz_init(DilloGif *gif) +{ + gif->num_spill_lines_max = 1; + gif->spill_lines = dMalloc(sizeof(uchar_t *) * gif->num_spill_lines_max); + + gif->spill_lines[0] = dMalloc(gif->Width); + gif->bits_in_window = 0; + + /* First code in table = clear_code +1 + * Last code in table = first code in table + * clear_code = (1<< input code size) + */ + gif->last_code = (1 << gif->input_code_size) + 1; + memset(gif->code_and_byte, 0, + (1 + gif->last_code) * sizeof(gif->code_and_byte[0])); + gif->code_size = gif->input_code_size + 1; + gif->line_index = 0; +} + +/* + * Send the image line to the dicache, also handling the interlacing. + */ +static void Gif_emit_line(DilloGif *gif, const uchar_t *linebuf) +{ + a_Dicache_write(gif->Image, gif->url, gif->version, linebuf, 0, gif->y); + if (gif->Flags & INTERLACE) { + switch (gif->pass) { + case 0: + case 1: + gif->y += 8; + break; + case 2: + gif->y += 4; + break; + case 3: + gif->y += 2; + break; + } + if (gif->y >= gif->Height) { + gif->pass++; + switch (gif->pass) { + case 1: + gif->y = 4; + break; + case 2: + gif->y = 2; + break; + case 3: + gif->y = 1; + break; + default: + /* arriving here is an error in the input image. */ + gif->y = 0; + break; + } + } + } else { + if (gif->y < gif->Height) + gif->y++; + } +} + +/* + * I apologize for the large size of this routine and the goto error + * construct - I almost _never_ do that. I offer the excuse of + * optimizing for speed. + * + * RCM -- busted these down into smaller subroutines... still very hard to + * read. + */ + + +/* + * Decode the packetized lwz bytes + */ +static void Gif_literal(DilloGif *gif, uint_t code) +{ + gif->linebuf[gif->line_index++] = code; + if (gif->line_index >= gif->Width) { + Gif_emit_line(gif, gif->linebuf); + gif->line_index = 0; + } + gif->length[gif->last_code + 1] = 2; + gif->code_and_byte[gif->last_code + 1] = (code << 8); + gif->code_and_byte[gif->last_code] |= code; +} + +/* + * ? + */ +/* Profiling reveals over half the GIF time is spent here: */ +static void Gif_sequence(DilloGif *gif, uint_t code) +{ + uint_t o_index, o_size, orig_code; + uint_t sequence_length = gif->length[code]; + uint_t line_index = gif->line_index; + int num_spill_lines; + int spill_line_index = gif->spill_line_index; + uchar_t *last_byte_ptr, *obuf; + + gif->length[gif->last_code + 1] = sequence_length + 1; + gif->code_and_byte[gif->last_code + 1] = (code << 8); + + /* We're going to traverse the sequence backwards. Thus, + * we need to allocate spill lines if the sequence won't + * fit entirely within the present scan line. */ + if (line_index + sequence_length <= gif->Width) { + num_spill_lines = 0; + obuf = gif->linebuf; + o_index = line_index + sequence_length; + o_size = sequence_length - 1; + } else { + num_spill_lines = (line_index + sequence_length - 1) / + gif->Width; + o_index = (line_index + sequence_length - 1) % gif->Width + 1; + if (num_spill_lines > gif->num_spill_lines_max) { + /* Allocate more spill lines. */ + spill_line_index = gif->num_spill_lines_max; + gif->num_spill_lines_max = num_spill_lines << 1; + gif->spill_lines = dRealloc(gif->spill_lines, + gif->num_spill_lines_max * + sizeof(uchar_t *)); + + for (; spill_line_index < gif->num_spill_lines_max; + spill_line_index++) + gif->spill_lines[spill_line_index] = + dMalloc(gif->Width); + } + spill_line_index = num_spill_lines - 1; + obuf = gif->spill_lines[spill_line_index]; + o_size = o_index; + } + gif->line_index = o_index; /* for afterwards */ + + /* for fixing up later if last_code == code */ + orig_code = code; + last_byte_ptr = obuf + o_index - 1; + + /* spill lines are allocated, and we are clear to + * write. This loop does not write the first byte of + * the sequence, however (last byte traversed). */ + while (sequence_length > 1) { + sequence_length -= o_size; + /* Write o_size bytes to + * obuf[o_index - o_size..o_index). */ + for (; o_size > 0 && o_index > 0; o_size--) { + uint_t code_and_byte = gif->code_and_byte[code]; + + DEBUG_MSG(5, "%d ", gif->code_and_byte[code] & 255); + + obuf[--o_index] = code_and_byte & 255; + code = code_and_byte >> 8; + } + /* Prepare for writing to next line. */ + if (o_index == 0) { + if (spill_line_index > 0) { + spill_line_index--; + obuf = gif->spill_lines[spill_line_index]; + o_size = gif->Width; + } else { + obuf = gif->linebuf; + o_size = sequence_length - 1; + } + o_index = gif->Width; + } + } + /* Ok, now we write the first byte of the sequence. */ + /* We are sure that the code is literal. */ + DEBUG_MSG(5, "%d", code); + obuf[--o_index] = code; + gif->code_and_byte[gif->last_code] |= code; + + /* Fix up the output if the original code was last_code. */ + if (orig_code == gif->last_code) { + *last_byte_ptr = code; + DEBUG_MSG(5, " fixed (%d)!", code); + } + DEBUG_MSG(5, "\n"); + + /* Output any full lines. */ + if (gif->line_index >= gif->Width) { + Gif_emit_line(gif, gif->linebuf); + gif->line_index = 0; + } + if (num_spill_lines) { + if (gif->line_index) + Gif_emit_line(gif, gif->linebuf); + for (spill_line_index = 0; + spill_line_index < num_spill_lines - (gif->line_index ? 1 : 0); + spill_line_index++) + Gif_emit_line(gif, gif->spill_lines[spill_line_index]); + } + if (num_spill_lines) { + /* Swap the last spill line with the gif line, using + * linebuf as the swap temporary. */ + uchar_t *linebuf = gif->spill_lines[num_spill_lines - 1]; + + gif->spill_lines[num_spill_lines - 1] = gif->linebuf; + gif->linebuf = linebuf; + } + gif->spill_line_index = spill_line_index; +} + +/* + * ? + * + * Return Value: + * 2 -- quit + * 1 -- new last code needs to be done + * 0 -- okay, but reset the code table + * < 0 on error + * -1 if the decompression code was not in the lookup table + */ +static int Gif_process_code(DilloGif *gif, uint_t code, uint_t clear_code) +{ + + /* A short table describing what to do with the code: + * code < clear_code : This is uncompressed, raw data + * code== clear_code : Reset the decompression table + * code== clear_code+1: End of data stream + * code > clear_code+1: Compressed code; look up in table + */ + if (code < clear_code) { + /* a literal code. */ + DEBUG_MSG(5, "literal\n"); + Gif_literal(gif, code); + return 1; + } else if (code >= clear_code + 2) { + /* a sequence code. */ + if (code > gif->last_code) + return -1; + Gif_sequence(gif, code); + return 1; + } else if (code == clear_code) { + /* clear code. Resets the whole table */ + DEBUG_MSG(5, "clear\n"); + return 0; + } else { + /* end code. */ + DEBUG_MSG(5, "end\n"); + return 2; + } +} + +/* + * ? + */ +static int Gif_decode(DilloGif *gif, const uchar_t *buf, size_t bsize) +{ + /* + * Data block processing. The image stuff is a series of data blocks. + * Each data block is 1 to 256 bytes long. The first byte is the length + * of the data block. 0 == the last data block. + */ + size_t bufsize, packet_size; + uint_t clear_code; + uint_t window; + int bits_in_window; + uint_t code; + int code_size; + uint_t code_mask; + + bufsize = bsize; + + /* Want to get all inner loop state into local variables. */ + packet_size = gif->packet_size; + window = gif->window; + bits_in_window = gif->bits_in_window; + code_size = gif->code_size; + code_mask = (1 << code_size) - 1; + clear_code = 1 << gif->input_code_size; + + /* If packet size == 0, we are at the start of a data block. + * The first byte of the data block indicates how big it is (0 == last + * datablock) + * packet size is set to this size; it indicates how much of the data block + * we have left to process. + */ + while (bufsize > 0) { + /* lwz_bytes is the number of remaining lwz bytes in the packet. */ + int lwz_bytes = MIN(packet_size, bufsize); + + bufsize -= lwz_bytes; + packet_size -= lwz_bytes; + for (; lwz_bytes > 0; lwz_bytes--) { + /* printf ("%d ", *buf) would print the depacketized lwz stream. */ + + /* Push the byte onto the "end" of the window (MSB). The low order + * bits always come first in the LZW stream. */ + window = (window >> 8) | (*buf++ << 24); + bits_in_window += 8; + + while (bits_in_window >= code_size) { + /* Extract the code. The code is code_size (3 to 12) bits long, + * at the start of the window */ + code = (window >> (32 - bits_in_window)) & code_mask; + + DEBUG_MSG(5, "code = %d, ", code); + + bits_in_window -= code_size; + switch (Gif_process_code(gif, code, clear_code)) { + case 1: /* Increment last code */ + gif->last_code++; + /*gif->code_and_byte[gif->last_code+1]=0; */ + + if ((gif->last_code & code_mask) == 0) { + if (gif->last_code == (1 << MAX_LWZ_BITS)) + gif->last_code--; + else { + code_size++; + code_mask = (1 << code_size) - 1; + } + } + break; + + case 0: /* Reset codes size and mask */ + gif->last_code = clear_code + 1; + code_size = gif->input_code_size + 1; + code_mask = (1 << code_size) - 1; + break; + + case 2: /* End code... consume remaining data chunks..? */ + goto error; /* Could clean up better? */ + default: + printf("dillo_gif_decode: error!\n"); + goto error; + } + } + } + + /* We reach here if + * a) We have reached the end of the data block; + * b) we ran out of data before reaching the end of the data block + */ + if (bufsize <= 0) + break; /* We are out of data; */ + + /* Start of new data block */ + bufsize--; + if (!(packet_size = *buf++)) { + /* This is the "block terminator" -- the last data block */ + gif->state = 999; /* BUG: should Go back to getting GIF blocks. */ + break; + } + } + + gif->packet_size = packet_size; + gif->window = window; + gif->bits_in_window = bits_in_window; + gif->code_size = code_size; + return bsize - bufsize; + + error: + gif->state = 999; + return bsize - bufsize; +} + +/* + * ? + */ +static int Gif_check_sig(DilloGif *gif, const uchar_t *ibuf, int ibsize) +{ + /* at beginning of file - read magic number */ + if (ibsize < 6) + return 0; + if (memcmp(ibuf, "GIF", 3) != 0) { + gif->state = 999; + return 6; + } + if (memcmp(ibuf + 3, "87a", 3) != 0 && + memcmp(ibuf + 3, "89a", 3) != 0) { + gif->state = 999; + return 6; + } + gif->state = 1; + return 6; +} + +/* Read the color map + * + * Implements, from the spec: + * Global Color Table + * Local Color Table + */ +static inline size_t + Gif_do_color_table(DilloGif *gif, void *Buf, + const uchar_t *buf, size_t bsize, size_t CT_Size) +{ + size_t Size = 3 * (1 << (1 + CT_Size)); + + if (Size > bsize) + return 0; + + gif->ColorMap_ofs = (ulong_t) buf - (ulong_t) Buf; + gif->NumColors = (1 << (1 + CT_Size)); + return Size; +} + +/* + * This implements, from the spec: + * <Logical Screen> ::= Logical Screen Descriptor [Global Color Table] + */ +static size_t Gif_get_descriptor(DilloGif *gif, void *Buf, + const uchar_t *buf, int bsize) +{ + + /* screen descriptor */ + size_t Size = 7, /* Size of descriptor */ + mysize; /* Size of color table */ + uchar_t Flags; + + if (bsize < 7) + return 0; + Flags = buf[4]; + + if (Flags & LOCALCOLORMAP) { + mysize = Gif_do_color_table( + gif, Buf, buf + 7, (size_t)bsize - 7, Flags & (size_t)0x7); + if (!mysize) + return 0; + Size += mysize; /* Size of the color table that follows */ + gif->Background = buf[5]; + } + /* gif->Width = LM_to_uint(buf[0], buf[1]); + gif->Height = LM_to_uint(buf[2], buf[3]); */ + gif->ColorResolution = (((buf[4] & 0x70) >> 3) + 1); + /* gif->AspectRatio = buf[6]; */ + + return Size; +} + +/* + * This implements, from the spec: + * <Table-Based Image> ::= Image Descriptor [Local Color Table] Image Data + * + * ('Buf' points to just after the Image separator) + * we should probably just check that the local stuff is consistent + * with the stuff at the header. For now, we punt... + */ +static size_t Gif_do_img_desc(DilloGif *gif, void *Buf, + const uchar_t *buf, size_t bsize) +{ + uchar_t Flags; + size_t Size = 9 + 1; /* image descriptor size + first byte of image data */ + + if (bsize < 10) + return 0; + + gif->Width = LM_to_uint(buf[4], buf[5]); + gif->Height = LM_to_uint(buf[6], buf[7]); + gif->linebuf = dMalloc(gif->Width); + + a_Dicache_set_parms(gif->url, gif->version, gif->Image, + gif->Width, gif->Height, DILLO_IMG_TYPE_INDEXED); + + Flags = buf[8]; + + gif->Flags |= Flags & INTERLACE; + gif->pass = 0; + bsize -= 9; + buf += 9; + if (Flags & LOCALCOLORMAP) { + size_t LSize = Gif_do_color_table( + gif, Buf, buf, bsize, Flags & (size_t)0x7); + + if (!LSize) + return 0; + Size += LSize; + buf += LSize; + bsize -= LSize; + } + /* Finally, get the first byte of the LZW image data */ + if (bsize < 1) + return 0; + gif->input_code_size = *buf++; + if (gif->input_code_size > 8) { + gif->state = 999; + return Size; + } + gif->y = 0; + Gif_lwz_init(gif); + gif->spill_line_index = 0; + gif->state = 3; /*Process the lzw data next */ + if (gif->Image && gif->ColorMap_ofs) { + a_Dicache_set_cmap(gif->url, gif->version, gif->Image, + (uchar_t *) Buf + gif->ColorMap_ofs, + gif->NumColors, 256, gif->transparent); + } + return Size; +} + +/* --- Top level data block processors ------------------------------------ */ +#define Img_Desc (0x2c) +#define Trailer (0x3B) +#define Ext_Id (0x21) + +/* + * This identifies which kind of GIF blocks are next, and processes them. + * It returns if there isn't enough data to process the next blocks, or if + * the next block is the lzw data (which is streamed differently) + * + * This implements, from the spec, <Data>* Trailer + * <Data> ::= <Graphic Block> | <Special-Purpose Block> + * <Special-Purpose Block> ::= Application Extension | Comment Extension + * <Graphic Block> ::= [Graphic Control Extension] <Graphic-Rendering Block> + * <Graphic-Rendering Block> ::= <Table-Based Image> | Plain Text Extension + * + * <Data>* --> GIF_Block + * <Data> --> while (...) + * <Special-Purpose Block> --> Gif_do_extension + * Graphic Control Extension --> Gif_do_extension + * Plain Text Extension --> Gif_do_extension + * <Table-Based Image> --> Gif_do_img_desc + * + * Return Value + * 0 if not enough data is present, otherwise the number of bytes + * "consumed" + */ +static size_t GIF_Block(DilloGif * gif, void *Buf, + const uchar_t *buf, size_t bsize) +{ + size_t Size = 0, mysize; + uchar_t C; + + if (bsize < 1) + return 0; + while (gif->state == 2) { + if (bsize < 1) + return Size; + bsize--; + switch (*buf++) { + case Ext_Id: + /* get the extension type */ + if (bsize < 2) + return Size; + + /* Have the extension block intepreted. */ + C = *buf++; + bsize--; + mysize = Gif_do_extension(gif, C, buf, bsize); + + if (!mysize) + /* Not all of the extension is there.. quit until more data + * arrives */ + return Size; + + bsize -= mysize; + buf += mysize; + + /* Increment the amount consumed by the extension introducer + * and id, and extension block size */ + Size += mysize + 2; + /* Do more GIF Blocks */ + continue; + + case Img_Desc: /* Image descriptor */ + mysize = Gif_do_img_desc(gif, Buf, buf, bsize); + if (!mysize) + return Size; + + /* Increment the amount consumed by the Image Separator and the + * Resultant blocks */ + Size += 1 + mysize; + return Size; + + case Trailer: + gif->state = 999; /* BUG: should close the rest of the file */ + return Size + 1; + break; /* GIF terminator */ + + default: /* Unknown */ + /* gripe and complain */ + MSG ("gif.c::GIF_Block: Error, 0x%x found\n", *(buf-1)); + gif->state = 999; + return Size + 1; + } + } + return Size; +} + + +/* + * Process some bytes from the input gif stream. It's a state machine. + * + * From the GIF spec: + * <GIF Data Stream> ::= Header <Logical Screen> <Data>* Trailer + * + * <GIF Data Stream> --> Gif_process_bytes + * Header --> State 0 + * <Logical Screen> --> State 1 + * <Data>* --> State 2 + * Trailer --> State > 3 + * + * State == 3 is special... this is inside of <Data> but all of the stuff in + * there has been gotten and set up. So we stream it outside. + */ +static size_t Gif_process_bytes(DilloGif *gif, const uchar_t *ibuf, + int bufsize, void *Buf) +{ + int tmp_bufsize = bufsize; + size_t mysize; + + switch (gif->state) { + case 0: + mysize = Gif_check_sig(gif, ibuf, tmp_bufsize); + if (!mysize) + break; + tmp_bufsize -= mysize; + ibuf += mysize; + if (gif->state != 1) + break; + + case 1: + mysize = Gif_get_descriptor(gif, Buf, ibuf, tmp_bufsize); + if (!mysize) + break; + tmp_bufsize -= mysize; + ibuf += mysize; + gif->state = 2; + + case 2: + /* Ok, this loop construction looks weird. It implements the <Data>* of + * the GIF grammar. All sorts of stuff is allocated to set up for the + * decode part (state ==2) and then there is the actual decode part (3) + */ + mysize = GIF_Block(gif, Buf, ibuf, (size_t)tmp_bufsize); + if (!mysize) + break; + tmp_bufsize -= mysize; + ibuf += mysize; + if (gif->state != 3) + break; + + case 3: + /* get an image byte */ + /* The users sees all of this stuff */ + mysize = Gif_decode(gif, ibuf, (size_t)tmp_bufsize); + if (mysize == 0) + break; + ibuf += mysize; + tmp_bufsize -= mysize; + + default: + /* error - just consume all input */ + tmp_bufsize = 0; + break; + } + + DEBUG_MSG(5, "Gif_process_bytes: final state %d, %ld bytes consumed\n", + gif->state, (long)(bufsize - tmp_bufsize)); + + return bufsize - tmp_bufsize; +} + +#endif /* ENABLE_GIF */ diff --git a/src/history.c b/src/history.c new file mode 100644 index 00000000..7819e58e --- /dev/null +++ b/src/history.c @@ -0,0 +1,125 @@ +/* + * File: history.c + * + * Copyright (C) 2001, 2002 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * Linear history (it also provides indexes for the navigation stack) + */ + +#include "list.h" +#include "history.h" + + +typedef struct { + DilloUrl *url; + char *title; +} H_Item; + + +/* Global history list */ +static H_Item *history = NULL; +static int history_size = 0; /* [1 based] */ +static int history_size_max = 16; + + +/* + * Add a new H_Item at the end of the history list + * (taking care of not making a duplicate entry) + */ +int a_History_add_url(DilloUrl *url) +{ + int i, idx; + + for (i = 0; i < history_size; ++i) + if (a_Url_cmp(history[i].url, url) == 0) + return i; + + idx = history_size; + a_List_add(history, history_size, history_size_max); + history[idx].url = a_Url_dup(url); + history[idx].title = NULL; + ++history_size; + return idx; +} + +/* + * Set the page-title for a given URL (by idx) + * (this is known when the first chunks of HTML data arrive) + */ +int a_History_set_title(int idx, const char *title) +{ + dReturn_val_if_fail(idx >= 0 && idx < history_size, 0); + + dFree(history[idx].title); + history[idx].title = dStrdup(title); + return 1; +} + +/* + * Return the DilloUrl camp (by index) + */ +DilloUrl *a_History_get_url(int idx) +{ + dReturn_val_if_fail(idx >= 0 && idx < history_size, NULL); + + return history[idx].url; +} + +/* + * Return the title camp (by index) + * ('force' returns URL_STR when there's no title) + */ +const char *a_History_get_title(int idx, int force) +{ + dReturn_val_if_fail(idx >= 0 && idx < history_size, NULL); + + if (history[idx].title) + return history[idx].title; + else if (force) + return URL_STR(history[idx].url); + else + return NULL; +} + +/* + * Return the title camp (by url) + * ('force' returns URL_STR when there's no title) + */ +const char *a_History_get_title_by_url(DilloUrl *url, int force) +{ + int i; + + dReturn_val_if_fail(url != NULL, NULL); + + for (i = 0; i < history_size; ++i) + if (a_Url_cmp(url, history[i].url) == 0) + break; + + if (i < history_size && history[i].title) + return history[i].title; + else if (force) + return URL_STR_(url); + return NULL; +} + + +/* + * Free all the memory used by this module + */ +void a_History_free() +{ + int i; + + for (i = 0; i < history_size; ++i) { + a_Url_free(history[i].url); + dFree(history[i].title); + } + dFree(history); +} diff --git a/src/history.h b/src/history.h new file mode 100644 index 00000000..a6f9f13f --- /dev/null +++ b/src/history.h @@ -0,0 +1,24 @@ + +#ifndef __DILLO_HISTORY_H__ +#define __DILLO_HISTORY_H__ + +#include "url.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +int a_History_add_url(DilloUrl *url); +int a_History_set_title(int idx, const char *title); +DilloUrl *a_History_get_url(int idx); +const char *a_History_get_title(int idx, int force); +const char *a_History_get_title_by_url(DilloUrl *url, int force); +void a_History_free(void); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __DILLO_HISTORY_H__ */ diff --git a/src/html.cc b/src/html.cc new file mode 100644 index 00000000..4661525a --- /dev/null +++ b/src/html.cc @@ -0,0 +1,5123 @@ +/* + * File: html.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * Dillo HTML parsing routines + */ + +/* Undefine if you want to unroll tables. For instance for PDAs */ +#define USE_TABLES + +/* Define to 1 to ignore white space immediately after an open tag, + * and immediately before a close tag. */ +#define SGML_SPCDEL 0 + + +#include <ctype.h> /* for isspace and tolower */ +#include <string.h> /* for memcpy and memmove */ +#include <stdlib.h> +#include <stdio.h> /* for sprintf */ +#include <math.h> /* for rint */ +#include <errno.h> + +#include <fltk/utf.h> /* for utf8encode */ + +#define DEBUG_LEVEL 10 +#include "debug.h" + +#include "msg.h" +#include "binaryconst.h" +#include "colors.h" + +#include "uicmd.hh" + +#define dillo_dbg_rendering 0 + +#include "history.h" +#include "nav.h" +#include "menu.hh" +#include "prefs.h" +#include "misc.h" +#include "capi.h" + +#include "html.hh" +#include "dw/textblock.hh" +#include "dw/bullet.hh" +#include "dw/table.hh" +#include "dw/tablecell.hh" +#include "dw/listitem.hh" +#include "dw/image.hh" +#include "dw/ruler.hh" + + +using namespace dw; +using namespace dw::core; +using namespace dw::core::ui; +using namespace dw::core::style; + +typedef void (*TagOpenFunct) (DilloHtml *Html, char *Tag, int Tagsize); +typedef void (*TagCloseFunct) (DilloHtml *Html, int TagIdx); + +#define TAB_SIZE 8 + +// Dw to Textblock +#define DW2TB(dw) ((Textblock*)dw) +// "html struct" to "Layout" +#define HT2LT(html) ((Layout*)html->bw->render_layout) +// "Image" to "Dw Widget" +#define IM2DW(Image) ((Widget*)Image->dw) +// Top of the parsing stack +#define S_TOP(html) (html->stack->getRef(html->stack->size()-1)) + +/* + * Exported function with C linkage. + */ +extern "C" { +void *a_Html_text(const char *type, void *P, CA_Callback_t *Call,void **Data); +} + +/* + * Forward declarations + */ +static const char *Html_get_attr(DilloHtml *html, + const char *tag, + int tagsize, + const char *attrname); +static const char *Html_get_attr2(DilloHtml *html, + const char *tag, + int tagsize, + const char *attrname, + int tag_parsing_flags); +static char *Html_get_attr_wdef(DilloHtml *html, + const char *tag, + int tagsize, + const char *attrname, + const char *def); +static void Html_add_widget(DilloHtml *html, Widget *widget, + char *width_str, char *height_str, + StyleAttrs *style_attrs); +static int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof); +static void Html_write(DilloHtml *html, char *Buf, int BufSize, int Eof); +static void Html_close(DilloHtml *html, int ClientKey); +static void Html_callback(int Op, CacheClient_t *Client); +static DilloHtml *Html_new(BrowserWindow *bw, const DilloUrl *url); +static void Html_tag_open_input(DilloHtml *html, char *tag, int tagsize); +static void Html_add_input(DilloHtmlForm *form, + DilloHtmlInputType type, + Widget *widget, + Embed *embed, + const char *name, + const char *init_str, + DilloHtmlSelect *select, + bool_t init_val); +//static void Html_reset_form(GtkWidget *reset, DilloHtmlLB *html_lb); +static int Html_tag_index(const char *tag); + + +/* + * Local Data + */ + +/* The following array of font sizes has to be _strictly_ crescent */ +static const int FontSizes[] = {8, 10, 12, 14, 18, 24}; +static const int FontSizesNum = 6; +static const int FontSizesBase = 2; + +/* Parsing table structure */ +typedef struct { + char *name; /* element name */ + unsigned char Flags; /* flags (explained near the table data) */ + char EndTag; /* Is it Required, Optional or Forbidden */ + uchar_t TagLevel; /* Used to heuristically parse bad HTML */ + TagOpenFunct open; /* Open function */ + TagCloseFunct close; /* Close function */ +} TagInfo; +extern const TagInfo Tags[]; + +/* + * Return the line number of the tag being processed by the parser. + */ +static int Html_get_line_number(DilloHtml *html) +{ + int i, ofs, line; + const char *p = html->Start_Buf; + + dReturn_val_if_fail(p != NULL, -1); + + ofs = html->CurrTagOfs; + line = html->OldTagLine; + for (i = html->OldTagOfs; i < ofs; ++i) + if (p[i] == '\n') + ++line; + html->OldTagOfs = html->CurrTagOfs; + html->OldTagLine = line; + return line; +} + +/* + * Collect HTML error strings inside the linkblock. + */ +static void Html_msg(DilloHtml *html, const char *format, ... ) +{ + va_list argp; + + dStr_sprintfa(html->bw->page_bugs, + "HTML warning: line %d, ", + Html_get_line_number(html)); + va_start(argp, format); + dStr_vsprintfa(html->bw->page_bugs, format, argp); + va_end(argp); + a_UIcmd_set_bug_prog(html->bw, ++html->bw->num_page_bugs); +} + +/* + * Wrapper for a_Url_new that adds an error detection message. + * (if use_base_url is TRUE, html->linkblock->base_url is used) + */ +static DilloUrl *Html_url_new(DilloHtml *html, + const char *url_str, const char *base_url, + int flags, int32_t posx, int32_t posy, + int use_base_url) +{ + DilloUrl *url; + int n_ic, n_ic_spc; + + url = a_Url_new( + url_str, + (use_base_url) ? base_url : URL_STR_(html->linkblock->base_url), + flags, posx, posy); + if ((n_ic = URL_ILLEGAL_CHARS(url)) != 0) { + const char *suffix = (n_ic) > 1 ? "s" : ""; + n_ic_spc = URL_ILLEGAL_CHARS_SPC(url); + if (n_ic == n_ic_spc) { + MSG_HTML("URL has %d illegal character%s [%d space%s]\n", + n_ic, suffix, n_ic_spc, suffix); + } else if (n_ic_spc == 0) { + MSG_HTML("URL has %d illegal character%s [%d in (00-1F or 7F)]\n", + n_ic, suffix, n_ic); + } else { + MSG_HTML("URL has %d illegal character%s " + "[%d space%s and %d in (00-1F or 7F)]\n", + n_ic, suffix, n_ic_spc, n_ic_spc ? "s" : "", n_ic-n_ic_spc); + } + } + return url; +} + +/* + * Set callback function and callback data for "html/text" MIME type. + */ +void *a_Html_text(const char *Type, void *P, CA_Callback_t *Call, void **Data) +{ + DilloWeb *web = (DilloWeb*)P; + DilloHtml *html = Html_new(web->bw, web->url); + + *Data = (void *) html; + *Call = (CA_Callback_t) Html_callback; + + return (void*) html->dw; +} + +bool DilloHtmlLB::HtmlLinkReceiver::enter (Widget *widget, int link, + int x, int y) +{ + BrowserWindow *bw = this->lb->bw; + + MSG(" ** "); + if (link == -1 && x == -1 && y == -1) { + _MSG(" Link LEAVE notify...\n"); + a_UIcmd_set_msg(bw, ""); + } else { + _MSG(" Link ENTER notify...\n"); + a_UIcmd_set_msg(bw, "%s", URL_STR(this->lb->links->get(link))); + } + return true; +} + +bool DilloHtmlLB::HtmlLinkReceiver::press (Widget *widget, int link, + int x, int y, EventButton *event) +{ + int ret = false; + + _MSG("pressed button %d\n", event->button); + if (event->button == 3) { + a_UIcmd_page_popup(lb->bw, lb->links->get(link), + lb->bw->num_page_bugs ? lb->bw->page_bugs->str:NULL); + //a_UIcmd_link_popup(lb->bw, lb->links->get(link)); + //a_UIcmd_bugmeter_popup(lb->bw); + ret = true; + } + return ret; +} + +bool DilloHtmlLB::HtmlLinkReceiver::click (Widget *widget, int link, + int x, int y, EventButton *event) +{ + DilloUrl *url = lb->links->get(link); + _MSG("clicked on URL %d: %s\n", link, a_Url_str (url)); + + + if (x != -1) { + char data[64]; + snprintf(data, 64, "?%d,%d", x, y); + a_Url_set_ismap_coords(url, data); + } + + if (event->button == 1) { + a_Nav_push(lb->bw, url); + } else if (event->button == 2) { + a_Nav_push_nw(lb->bw, url); + } else { + return false; + } + + /* Change the link color to "visited" as visual feedback */ + for (Widget *w = widget; w; w = w->getParent()) { + _MSG(" ->%s\n", w->getClassName()); + if (w->instanceOf(dw::Textblock::CLASS_ID)) { + ((Textblock*)w)->changeLinkColor (link, lb->visited_color); + break; + } + } + + return true; +} + + +/* + * We'll make the linkblock first to get it out of the way. + */ +static DilloHtmlLB *Html_lb_new(BrowserWindow *bw, const DilloUrl *url) +{ + DilloHtmlLB *html_lb = dNew(DilloHtmlLB, 1); + + html_lb->bw = bw; + html_lb->base_url = a_Url_dup(url); + html_lb->linkReceiver = new DilloHtmlLB::HtmlLinkReceiver (html_lb); + + html_lb->forms = new misc::SimpleVector <DilloHtmlForm> (1); + + html_lb->links = new misc::SimpleVector <DilloUrl*> (64); + + //a_Dw_image_map_list_init(&html_lb->maps); + + html_lb->link_color = prefs.link_color; + html_lb->visited_color = prefs.visited_color; + + return html_lb; +} + +/* + * Free the memory used by the linkblock + */ +static void Html_lb_free(void *lb) +{ + int i, j, k; + DilloHtmlForm *form; + DilloHtmlInput *input_j; + DilloHtmlLB *html_lb = (DilloHtmlLB*)lb; + + DEBUG_MSG(3, "Html_lb_free\n"); + + delete html_lb->linkReceiver; + a_Url_free(html_lb->base_url); + + for (i = 0; i < html_lb->forms->size(); i++) { + form = html_lb->forms->getRef(i); + a_Url_free(form->action); + for (j = 0; j < form->inputs->size(); j++) { + input_j = form->inputs->getRef(j); + dFree(input_j->name); + dFree(input_j->init_str); + + if (input_j->type == DILLO_HTML_INPUT_SELECT || + input_j->type == DILLO_HTML_INPUT_SEL_LIST) { + for (k = 0; k < input_j->select->num_options; k++) { + dFree(input_j->select->options[k].value); + } + dFree(input_j->select->options); + dFree(input_j->select); + } + } + delete(form->inputs); + delete(form->form_receiver); + } + delete(html_lb->forms); + + for (i = 0; i < html_lb->links->size(); i++) + if (html_lb->links->get(i)) + a_Url_free(html_lb->links->get(i)); + delete (html_lb->links); + + //a_Dw_image_map_list_free(&html_lb->maps); + + dFree(html_lb); +} + + +/* + * Set the URL data for image maps. + */ +//static void Html_set_link_coordinates(DilloHtmlLB *lb, +// int link, int x, int y) +//{ +// char data[64]; +// +// if (x != -1) { +// snprintf(data, 64, "?%d,%d", x, y); +// a_Url_set_ismap_coords(lb->links->get(link), data); +// } +//} + +///* +// * Handle the status function generated by the dw scroller, +// * and show the url in the browser status-bar. +// */ +//static void Html_handle_status(Widget *widget, int link, int x, int y, +// DilloHtmlLB *lb) +//{ +// DilloUrl *url; +// +// url = (link == -1) ? NULL : lb->links->get(link); +// if (url) { +// Html_set_link_coordinates(lb, link, x, y); +// a_UIcmd_set_msg(lb->bw, "%s", +// URL_ALT_(url) ? URL_ALT_(url) : URL_STR_(url)); +// lb->bw->status_is_link = 1; +// +// } else { +// if (lb->bw->status_is_link) +// a_UIcmd_set_msg(lb->bw, ""); +// } +//} + +///* +// * Activate a link ("link_clicked" callback of the page) +// */ +//static bool_t Html_link_clicked(Widget *widget, int link, int x, int y, +// EventButton *event, DilloHtmlLB *lb) +//{ +// Html_set_link_coordinates(lb, link, x, y); +// if (event->button == 1) +// a_Nav_push(lb->bw, lb->links->get(link)); +// else if (event->button == 2) { +// a_Nav_push_nw(lb->bw, lb->links->get(link)); +// } else { +// return FALSE; +// } +// +// if (widget->instanceOf (Textblock::CLASS_ID)) +// ((Textblock*)widget)->changeLinkColor (link, lb->visited_color); +// +// return TRUE; +//} + +/* + * Popup the image menu ("button_press_event" callback of image) + */ +static bool_t Html_image_menu(Widget *widget, + EventButton *event, + BrowserWindow *bw) +{ +// Image *image = (Image*)widget; +//:AL +// if (event->button == 3 && image->url) { +// a_Menu_popup_set_url(bw, image->url); +// a_Menu_popup_clear_url2(bw->menu_popup.over_image); +// +// gtk_menu_popup(GTK_MENU(bw->menu_popup.over_image), NULL, NULL, +// NULL, NULL, event->button, ((DwMouseEvent*)event)->time); +// return TRUE; +// } + + return FALSE; +} + +/* + * Connect all signals of a textblock or an image. + */ +static void Html_connect_signals(DilloHtml *html, Widget *widget) +{ + widget->connectLink (html->linkblock->linkReceiver); +} + + +/* + * Create a new link in the linkblock, set it as the url's parent + * and return the index. + */ +static int Html_set_new_link(DilloHtml *html, DilloUrl **url) +{ + int nl = html->linkblock->links->size(); + html->linkblock->links->increase(); + html->linkblock->links->set(nl, (*url) ? *url : NULL); + return nl; +} + + +/* + * Allocate and insert form information into the Html linkblock + */ +static int Html_form_new(DilloHtmlLB *html_lb, + DilloHtmlMethod method, + const DilloUrl *action, + DilloHtmlEnc enc) +{ + DilloHtmlForm *form; + + html_lb->forms->increase(); + form = html_lb->forms->getRef (html_lb->forms->size() - 1); + form->method = method; + form->action = a_Url_dup(action); + form->enc = enc; + form->inputs = new misc::SimpleVector <DilloHtmlInput> (4); + form->num_entry_fields = 0; + form->num_submit_buttons = 0; + form->form_receiver = new form::Form(html_lb); + + _MSG("Html_form_new: action=%s nform=%d\n", action, nf); + return html_lb->forms->size(); +} + + +/* + * Change one toplevel attribute. var should be an identifier. val is + * only evaluated once, so you can safely use a function call for it. + */ +#define HTML_SET_TOP_ATTR(html, var, val) \ + do { \ + StyleAttrs style_attrs; \ + Style *old_style; \ + \ + old_style = S_TOP(html)->style; \ + style_attrs = *old_style; \ + style_attrs.var = (val); \ + S_TOP(html)->style = \ + Style::create (HT2LT(html), &style_attrs); \ + old_style->unref (); \ + } while (FALSE) + + + +/* + * Set the font at the top of the stack. BImask specifies which + * attributes in BI should be changed. + */ +static void Html_set_top_font(DilloHtml *html, char *name, int size, + int BI, int BImask) +{ + FontAttrs font_attrs; + + font_attrs = *S_TOP(html)->style->font; + if (name) + font_attrs.name = name; + if (size) + font_attrs.size = size; + if (BImask & 1) + font_attrs.weight = (BI & 1) ? 700 : 400; + if (BImask & 2) + font_attrs.style = (BI & 2) ? + (prefs.use_oblique ? + FONT_STYLE_OBLIQUE : FONT_STYLE_ITALIC) : + FONT_STYLE_NORMAL; + + HTML_SET_TOP_ATTR (html, font, + Font::create (HT2LT(html), &font_attrs)); +} + +/* + * Evaluates the ALIGN attribute (left|center|right|justify) and + * sets the style at the top of the stack. + */ +static void Html_tag_set_align_attr(DilloHtml *html, char *tag, int tagsize) +{ + const char *align, *charattr; + + if ((align = Html_get_attr(html, tag, tagsize, "align"))) { + if (dStrcasecmp (align, "left") == 0) + HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_LEFT); + else if (dStrcasecmp (align, "right") == 0) + HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_RIGHT); + else if (dStrcasecmp (align, "center") == 0) + HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_CENTER); + else if (dStrcasecmp (align, "justify") == 0) + HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_JUSTIFY); + else if (dStrcasecmp (align, "char") == 0) { + /* todo: Actually not supported for <p> etc. */ + HTML_SET_TOP_ATTR (html, textAlign, TEXT_ALIGN_STRING); + if ((charattr = Html_get_attr(html, tag, tagsize, "char"))) { + if (charattr[0] == 0) + /* todo: ALIGN=" ", and even ALIGN="&32;" will reult in + * an empty string (don't know whether the latter is + * correct, has to be clarified with the specs), so + * that for empty strings, " " is assumed. */ + HTML_SET_TOP_ATTR (html, textAlignChar, ' '); + else + HTML_SET_TOP_ATTR (html, textAlignChar, charattr[0]); + } else + /* todo: Examine LANG attr of <html>. */ + HTML_SET_TOP_ATTR (html, textAlignChar, '.'); + } + } +} + +/* + * Evaluates the VALIGN attribute (top|bottom|middle|baseline) and + * sets the style in style_attrs. Returns TRUE when set. + */ +static bool_t Html_tag_set_valign_attr(DilloHtml *html, char *tag, + int tagsize, StyleAttrs *style_attrs) +{ + const char *attr; + + if ((attr = Html_get_attr(html, tag, tagsize, "valign"))) { + if (dStrcasecmp (attr, "top") == 0) + style_attrs->valign = VALIGN_TOP; + else if (dStrcasecmp (attr, "bottom") == 0) + style_attrs->valign = VALIGN_BOTTOM; + else if (dStrcasecmp (attr, "baseline") == 0) + style_attrs->valign = VALIGN_BASELINE; + else + style_attrs->valign = VALIGN_MIDDLE; + return TRUE; + } else + return FALSE; +} + + +/* + * Add a new DwPage into the current DwPage, for indentation. + * left and right are the horizontal indentation amounts, space is the + * vertical space around the block. + */ +static void Html_add_indented_widget(DilloHtml *html, Widget *textblock, + int left, int right, int space) +{ + StyleAttrs style_attrs; + Style *style; + + style_attrs = *S_TOP(html)->style; + + style_attrs.margin.setVal (0); + style_attrs.borderWidth.setVal (0); + style_attrs.padding.setVal(0); + + /* Activate this for debugging */ +#if 0 + style_attrs.borderWidth.setVal (1); + style_attrs.setBorderColor ( + Color::createShaded (HT2LT(html), style_attrs.color->getColor()); + style_attrs.setBorderStyle (BORDER_DASHED); +#endif + + style_attrs.margin.left = left; + style_attrs.margin.right = right; + style = Style::create (HT2LT(html), &style_attrs); + + DW2TB(html->dw)->addParbreak (space, style); + DW2TB(html->dw)->addWidget (textblock, style); + DW2TB(html->dw)->addParbreak (space, style); + S_TOP(html)->textblock = html->dw = textblock; + S_TOP(html)->hand_over_break = TRUE; + style->unref (); + + /* Handle it when the user clicks on a link */ + Html_connect_signals(html, textblock); +} + +/* + * Create and add a new indented DwPage to the current DwPage + */ +static void Html_add_indented(DilloHtml *html, int left, int right, int space) +{ + Textblock *textblock = new Textblock (false); + Html_add_indented_widget (html, textblock, left, right, space); +} + +/* + * Given a font_size, this will return the correct 'level'. + * (or the closest, if the exact level isn't found). + */ +static int Html_fontsize_to_level(int fontsize) +{ + int i, level; + double normalized_size = fontsize / prefs.font_factor, + approximation = FontSizes[FontSizesNum-1] + 1; + + for (i = level = 0; i < FontSizesNum; i++) + if (approximation >= fabs(normalized_size - FontSizes[i])) { + approximation = fabs(normalized_size - FontSizes[i]); + level = i; + } else { + break; + } + + return level; +} + +/* + * Given a level of a font, this will return the correct 'size'. + */ +static int Html_level_to_fontsize(int level) +{ + level = MAX(0, level); + level = MIN(FontSizesNum - 1, level); + + return (int)rint(FontSizes[level]*prefs.font_factor); +} + +/* + * Miscelaneous initializations for a DwPage + */ +static void Html_set_dwpage(DilloHtml *html) +{ + Widget *widget; + Textblock *textblock; + StyleAttrs style_attrs; + FontAttrs font_attrs; + + dReturn_if_fail (html->dw == NULL); + + widget = textblock = new Textblock (false); + html->dw = html->stack->getRef(0)->textblock = widget; + + /* Create a dummy font, attribute, and tag for the bottom of the stack. */ + font_attrs.name = prefs.vw_fontname; /* Helvetica */ + font_attrs.size = Html_level_to_fontsize(FontSizesBase); + font_attrs.weight = 400; + font_attrs.style = FONT_STYLE_NORMAL; + + style_attrs.initValues (); + style_attrs.font = Font::create (HT2LT(html), &font_attrs); + style_attrs.color = Color::createSimple (HT2LT(html), prefs.text_color); + html->stack->getRef(0)->style = Style::create (HT2LT(html), &style_attrs); + + html->stack->getRef(0)->table_cell_style = NULL; + + /* Handle it when the user clicks on a link */ + Html_connect_signals(html, widget); + + html->bw->num_page_bugs = 0; + dStr_truncate(html->bw->page_bugs, 0); + +// gtk_signal_connect_while_alive ( +// GTK_OBJECT(GTK_BIN(html->bw->render_main_scroll)->child), +// "button_press_event", GTK_SIGNAL_FUNC(Html_page_menu), +// html->bw, GTK_OBJECT (page)); +// +// /* Destroy the linkblock when the DwPage is destroyed */ +// gtk_signal_connect_object(GTK_OBJECT(page), "destroy", +// GTK_SIGNAL_FUNC(Html_lb_free), +// html->linkblock); +} + +/* + * Create and initialize a new DilloHtml structure + */ +static DilloHtml *Html_new(BrowserWindow *bw, const DilloUrl *url) +{ + DilloHtml *html; + + html = dNew(DilloHtml, 1); + + html->Start_Buf = NULL; + html->Start_Ofs = 0; + html->CurrTagOfs = 0; + html->OldTagOfs = 0; + html->OldTagLine = 1; + + html->DocType = DT_NONE; /* assume Tag Soup 0.0! :-) */ + html->DocTypeVersion = 0.0f; + + html->dw = NULL; + html->bw = bw; + html->linkblock = Html_lb_new(bw, url); + + html->stack = new misc::SimpleVector <DilloHtmlState> (16); + html->stack->increase(); + html->stack->getRef(0)->tag_name = dStrdup("none"); + html->stack->getRef(0)->style = NULL; + html->stack->getRef(0)->table_cell_style = NULL; + html->stack->getRef(0)->parse_mode = DILLO_HTML_PARSE_MODE_INIT; + html->stack->getRef(0)->table_mode = DILLO_HTML_TABLE_MODE_NONE; + html->stack->getRef(0)->cell_text_align_set = FALSE; + html->stack->getRef(0)->list_type = HTML_LIST_NONE; + html->stack->getRef(0)->list_number = 0; + html->stack->getRef(0)->tag_idx = -1; /* MUST not be used */ + html->stack->getRef(0)->textblock = NULL; + html->stack->getRef(0)->table = NULL; + html->stack->getRef(0)->ref_list_item = NULL; + html->stack->getRef(0)->current_bg_color = prefs.bg_color; + html->stack->getRef(0)->hand_over_break = FALSE; + + html->Stash = dStr_new(""); + html->StashSpace = FALSE; + + html->SPCBuf = NULL; + + html->pre_column = 0; + html->PreFirstChar = FALSE; + html->PrevWasCR = FALSE; + html->PrevWasOpenTag = FALSE; + html->SPCPending = FALSE; + html->InVisitedLink = FALSE; + html->ReqTagClose = FALSE; + html->CloseOneTag = FALSE; + html->TagSoup = TRUE; + html->NameVal = NULL; + + html->Num_HTML = html->Num_HEAD = html->Num_BODY = html->Num_TITLE = 0; + + html->InFlags = IN_NONE; + + html->attr_data = dStr_sized_new(1024); + + Html_set_dwpage(html); + + return html; +} + +/* + * Initialize the stash buffer + */ +static void Html_stash_init(DilloHtml *html) +{ + S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_STASH; + html->StashSpace = FALSE; + dStr_truncate(html->Stash, 0); +} + +/* Entities list from the HTML 4.01 DTD */ +typedef struct { + char *entity; + int isocode; +} Ent_t; + +#define NumEnt 252 +static const Ent_t Entities[NumEnt] = { + {"AElig",0306}, {"Aacute",0301}, {"Acirc",0302}, {"Agrave",0300}, + {"Alpha",01621},{"Aring",0305}, {"Atilde",0303}, {"Auml",0304}, + {"Beta",01622}, {"Ccedil",0307}, {"Chi",01647}, {"Dagger",020041}, + {"Delta",01624},{"ETH",0320}, {"Eacute",0311}, {"Ecirc",0312}, + {"Egrave",0310},{"Epsilon",01625},{"Eta",01627}, {"Euml",0313}, + {"Gamma",01623},{"Iacute",0315}, {"Icirc",0316}, {"Igrave",0314}, + {"Iota",01631}, {"Iuml",0317}, {"Kappa",01632}, {"Lambda",01633}, + {"Mu",01634}, {"Ntilde",0321}, {"Nu",01635}, {"OElig",0522}, + {"Oacute",0323},{"Ocirc",0324}, {"Ograve",0322}, {"Omega",01651}, + {"Omicron",01637},{"Oslash",0330},{"Otilde",0325},{"Ouml",0326}, + {"Phi",01646}, {"Pi",01640}, {"Prime",020063},{"Psi",01650}, + {"Rho",01641}, {"Scaron",0540}, {"Sigma",01643}, {"THORN",0336}, + {"Tau",01644}, {"Theta",01630}, {"Uacute",0332}, {"Ucirc",0333}, + {"Ugrave",0331},{"Upsilon",01645},{"Uuml",0334}, {"Xi",01636}, + {"Yacute",0335},{"Yuml",0570}, {"Zeta",01626}, {"aacute",0341}, + {"acirc",0342}, {"acute",0264}, {"aelig",0346}, {"agrave",0340}, + {"alefsym",020465},{"alpha",01661},{"amp",38}, {"and",021047}, + {"ang",021040}, {"aring",0345}, {"asymp",021110},{"atilde",0343}, + {"auml",0344}, {"bdquo",020036},{"beta",01662}, {"brvbar",0246}, + {"bull",020042},{"cap",021051}, {"ccedil",0347}, {"cedil",0270}, + {"cent",0242}, {"chi",01707}, {"circ",01306}, {"clubs",023143}, + {"cong",021105},{"copy",0251}, {"crarr",020665},{"cup",021052}, + {"curren",0244},{"dArr",020723}, {"dagger",020040},{"darr",020623}, + {"deg",0260}, {"delta",01664}, {"diams",023146},{"divide",0367}, + {"eacute",0351},{"ecirc",0352}, {"egrave",0350}, {"empty",021005}, + {"emsp",020003},{"ensp",020002}, {"epsilon",01665},{"equiv",021141}, + {"eta",01667}, {"eth",0360}, {"euml",0353}, {"euro",020254}, + {"exist",021003},{"fnof",0622}, {"forall",021000},{"frac12",0275}, + {"frac14",0274},{"frac34",0276}, {"frasl",020104},{"gamma",01663}, + {"ge",021145}, {"gt",62}, {"hArr",020724}, {"harr",020624}, + {"hearts",023145},{"hellip",020046},{"iacute",0355},{"icirc",0356}, + {"iexcl",0241}, {"igrave",0354}, {"image",020421},{"infin",021036}, + {"int",021053}, {"iota",01671}, {"iquest",0277}, {"isin",021010}, + {"iuml",0357}, {"kappa",01672}, {"lArr",020720}, {"lambda",01673}, + {"lang",021451},{"laquo",0253}, {"larr",020620}, {"lceil",021410}, + {"ldquo",020034},{"le",021144}, {"lfloor",021412},{"lowast",021027}, + {"loz",022712}, {"lrm",020016}, {"lsaquo",020071},{"lsquo",020030}, + {"lt",60}, {"macr",0257}, {"mdash",020024},{"micro",0265}, + {"middot",0267},{"minus",021022},{"mu",01674}, {"nabla",021007}, + {"nbsp",32}, {"ndash",020023},{"ne",021140}, {"ni",021013}, + {"not",0254}, {"notin",021011},{"nsub",021204}, {"ntilde",0361}, + {"nu",01675}, {"oacute",0363}, {"ocirc",0364}, {"oelig",0523}, + {"ograve",0362},{"oline",020076},{"omega",01711}, {"omicron",01677}, + {"oplus",021225},{"or",021050}, {"ordf",0252}, {"ordm",0272}, + {"oslash",0370},{"otilde",0365}, {"otimes",021227},{"ouml",0366}, + {"para",0266}, {"part",021002}, {"permil",020060},{"perp",021245}, + {"phi",01706}, {"pi",01700}, {"piv",01726}, {"plusmn",0261}, + {"pound",0243}, {"prime",020062},{"prod",021017}, {"prop",021035}, + {"psi",01710}, {"quot",34}, {"rArr",020722}, {"radic",021032}, + {"rang",021452},{"raquo",0273}, {"rarr",020622}, {"rceil",021411}, + {"rdquo",020035},{"real",020434},{"reg",0256}, {"rfloor",021413}, + {"rho",01701}, {"rlm",020017}, {"rsaquo",020072},{"rsquo",020031}, + {"sbquo",020032},{"scaron",0541},{"sdot",021305}, {"sect",0247}, + {"shy",0255}, {"sigma",01703}, {"sigmaf",01702},{"sim",021074}, + {"spades",023140},{"sub",021202},{"sube",021206}, {"sum",021021}, + {"sup",021203}, {"sup1",0271}, {"sup2",0262}, {"sup3",0263}, + {"supe",021207},{"szlig",0337}, {"tau",01704}, {"there4",021064}, + {"theta",01670},{"thetasym",01721},{"thinsp",020011},{"thorn",0376}, + {"tilde",01334},{"times",0327}, {"trade",020442},{"uArr",020721}, + {"uacute",0372},{"uarr",020621}, {"ucirc",0373}, {"ugrave",0371}, + {"uml",0250}, {"upsih",01722}, {"upsilon",01705},{"uuml",0374}, + {"weierp",020430},{"xi",01676}, {"yacute",0375}, {"yen",0245}, + {"yuml",0377}, {"zeta",01666}, {"zwj",020015}, {"zwnj",020014} +}; + + +/* + * Comparison function for binary search + */ +static int Html_entity_comp(const void *a, const void *b) +{ + return strcmp(((Ent_t *)a)->entity, ((Ent_t *)b)->entity); +} + +/* + * Binary search of 'key' in entity list + */ +static int Html_entity_search(char *key) +{ + Ent_t *res, EntKey; + + EntKey.entity = key; + res = (Ent_t*) bsearch(&EntKey, Entities, NumEnt, + sizeof(Ent_t), Html_entity_comp); + if (res) + return (res - Entities); + return -1; +} + +/* + * This is M$ non-standard "smart quotes" (w1252). Now even deprecated by them! + * + * SGML for HTML4.01 defines c >= 128 and c <= 159 as UNUSED. + * TODO: Probably I should remove this hack, and add a HTML warning. --Jcid + */ +static int Html_ms_stupid_quotes_2ucs(int isocode) +{ + int ret; + switch (isocode) { + case 145: + case 146: ret = '\''; break; + case 147: + case 148: ret = '"'; break; + case 149: ret = 176; break; + case 150: + case 151: ret = '-'; break; + default: ret = isocode; break; + } + return ret; +} + +/* + * Given an entity, return the UCS character code. + * Returns a negative value (error code) if not a valid entity. + * + * The first character *token is assumed to be == '&' + * + * For valid entities, *entsize is set to the length of the parsed entity. + */ +static int Html_parse_entity(DilloHtml *html, const char *token, + int toksize, int *entsize) +{ + int isocode, i; + char *tok, *s, c; + + token++; + tok = s = toksize ? dStrndup(token, (uint_t)toksize) : dStrdup(token); + + isocode = -1; + + if (*s == '#') { + /* numeric character reference */ + errno = 0; + if (*++s == 'x' || *s == 'X') { + if (isxdigit(*++s)) { + /* strtol with base 16 accepts leading "0x" - we don't */ + if (*s == '0' && s[1] == 'x') { + s++; + isocode = 0; + } else { + isocode = strtol(s, &s, 16); + } + } + } else if (isdigit(*s)) { + isocode = strtol(s, &s, 10); + } + + if (!isocode || errno || isocode > 0xffff) { + /* this catches null bytes, errors and codes >= 0xFFFF */ + MSG_HTML("numeric character reference out of range\n"); + isocode = -2; + } + + if (isocode != -1) { + if (*s == ';') + s++; + else if (prefs.show_extra_warnings) + MSG_HTML("numeric character reference without trailing ';'\n"); + } + + } else if (isalpha(*s)) { + /* character entity reference */ + while (isalnum(*++s) || strchr(":_.-", *s)); + c = *s; + *s = 0; + + if ((i = Html_entity_search(tok)) == -1) { + if ((html->DocType == DT_HTML && html->DocTypeVersion == 4.01f) || + html->DocType == DT_XHTML) + MSG_HTML("undefined character entity '%s'\n", tok); + isocode = -3; + } else + isocode = Entities[i].isocode; + + if (c == ';') + s++; + else if (prefs.show_extra_warnings) + MSG_HTML("character entity reference without trailing ';'\n"); + } + + *entsize = s-tok+1; + dFree(tok); + + if (isocode >= 145 && isocode <= 151) { + /* TODO: remove this hack. */ + isocode = Html_ms_stupid_quotes_2ucs(isocode); + } else if (isocode == -1 && prefs.show_extra_warnings) + MSG_HTML("literal '&'\n"); + + return isocode; +} + +/* + * Convert all the entities in a token to utf8 encoding. Takes + * a token and its length, and returns a newly allocated string. + */ +static char * + Html_parse_entities(DilloHtml *html, char *token, int toksize) +{ + char *esc_set = "&\xE2\xC2"; + char *new_str, buf[4]; + int i, j, k, n, isocode, entsize; + + new_str = dStrndup(token, toksize); + if (new_str[strcspn(new_str, esc_set)] == 0) + return new_str; + + for (i = j = 0; i < toksize; i++) { + if (token[i] == '&' && + (isocode = Html_parse_entity(html, token+i, + toksize-i, &entsize)) >= 0) { + if (isocode >= 128) { + /* multibyte encoding */ + n = utf8encode(isocode, buf); + for (k = 0; k < n; ++k) + new_str[j++] = buf[k]; + } else { + new_str[j++] = (char) isocode; + } + i += entsize-1; + } else { + new_str[j++] = token[i]; + } + } + new_str[j] = '\0'; + return new_str; +} + +/* + * Parse spaces + */ +static void Html_process_space(DilloHtml *html, char *space, int spacesize) +{ + int i, offset; + DilloHtmlParseMode parse_mode = S_TOP(html)->parse_mode; + + if (parse_mode == DILLO_HTML_PARSE_MODE_STASH) { + html->StashSpace = (html->Stash->len > 0); + html->SPCPending = FALSE; + + } else if (parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) { + char *Pword = dStrndup(space, spacesize); + dStr_append(html->Stash, Pword); + dFree(Pword); + html->SPCPending = FALSE; + + } else if (parse_mode == DILLO_HTML_PARSE_MODE_PRE) { + /* re-scan the string for characters that cause line breaks */ + for (i = 0; i < spacesize; i++) { + /* Support for "\r", "\n" and "\r\n" line breaks (skips the first) */ + if (!html->PreFirstChar && + (space[i] == '\r' || (space[i] == '\n' && !html->PrevWasCR))) { + DW2TB(html->dw)->addLinebreak (S_TOP(html)->style); + html->pre_column = 0; + } + html->PreFirstChar = FALSE; + + /* cr and lf should not be rendered -- they appear as a break */ + switch (space[i]) { + case '\r': + case '\n': + break; + case '\t': + if (prefs.show_extra_warnings) + MSG_HTML("TAB character inside <PRE>\n"); + offset = TAB_SIZE - html->pre_column % TAB_SIZE; + DW2TB(html->dw)->addText (dStrnfill(offset, ' '), + S_TOP(html)->style); + html->pre_column += offset; + break; + default: + DW2TB(html->dw)->addText (dStrndup(space + i, 1), + S_TOP(html)->style); + html->pre_column++; + break; + } + + html->PrevWasCR = (space[i] == '\r'); + } + html->SPCPending = FALSE; + + } else { + if (SGML_SPCDEL && html->PrevWasOpenTag) { + /* SGML_SPCDEL ignores white space inmediately after an open tag */ + html->SPCPending = FALSE; + } else { + dFree(html->SPCBuf); + html->SPCBuf = dStrndup(space, spacesize); + html->SPCPending = TRUE; + } + + if (parse_mode == DILLO_HTML_PARSE_MODE_STASH_AND_BODY) + html->StashSpace = (html->Stash->len > 0); + } +} + +/* + * Handles putting the word into its proper place + * > STASH and VERBATIM --> html->Stash + * > otherwise it goes through addText() + * + * Entities are parsed (or not) according to parse_mode. + */ +static void Html_process_word(DilloHtml *html, char *word, int size) +{ + int i, j, start; + char *Pword; + DilloHtmlParseMode parse_mode = S_TOP(html)->parse_mode; + + if (parse_mode == DILLO_HTML_PARSE_MODE_STASH || + parse_mode == DILLO_HTML_PARSE_MODE_STASH_AND_BODY) { + if (html->StashSpace) { + dStr_append_c(html->Stash, ' '); + html->StashSpace = FALSE; + } + Pword = Html_parse_entities(html, word, size); + dStr_append(html->Stash, Pword); + dFree(Pword); + + } else if (parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) { + /* word goes in untouched, it is not processed here. */ + Pword = dStrndup(word, size); + dStr_append(html->Stash, Pword); + dFree(Pword); + } + + if (parse_mode == DILLO_HTML_PARSE_MODE_STASH || + parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) { + /* skip until the closing instructions */ + + } else if (parse_mode == DILLO_HTML_PARSE_MODE_PRE) { + /* all this overhead is to catch white-space entities */ + Pword = Html_parse_entities(html, word, size); + for (start = i = 0; Pword[i]; start = i) + if (isspace(Pword[i])) { + while (Pword[++i] && isspace(Pword[i])); + Html_process_space(html, Pword + start, i - start); + } else { + while (Pword[++i] && !isspace(Pword[i])); + DW2TB(html->dw)->addText( + dStrndup(Pword + start, i - start), + S_TOP(html)->style); + html->pre_column += i - start; + html->PreFirstChar = FALSE; + } + dFree(Pword); + + } else { + /* add pending space if present */ + if (html->SPCPending && (!SGML_SPCDEL || !html->PrevWasOpenTag)) + /* SGML_SPCDEL ignores space after an open tag */ + DW2TB(html->dw)->addSpace (S_TOP(html)->style); + + /* Collapse white-space entities inside the word (except ) */ + Pword = Html_parse_entities(html, word, size); + for (i = 0; Pword[i]; ++i) + if (strchr("\t\f\n\r", Pword[i])) + for (j = i; (Pword[j] = Pword[j+1]); ++j); + + DW2TB(html->dw)->addText(Pword, S_TOP(html)->style); + } + + html->PrevWasOpenTag = FALSE; + html->SPCPending = FALSE; +} + +/* + * Does the tag in tagstr (e.g. "p") match the tag in the tag, tagsize + * structure, with the initial < skipped over (e.g. "P align=center>") + */ +static bool_t Html_match_tag(const char *tagstr, char *tag, int tagsize) +{ + int i; + + for (i = 0; i < tagsize && tagstr[i] != '\0'; i++) { + if (tolower(tagstr[i]) != tolower(tag[i])) + return FALSE; + } + /* The test for '/' is for xml compatibility: "empty/>" will be matched. */ + if (i < tagsize && (isspace(tag[i]) || tag[i] == '>' || tag[i] == '/')) + return TRUE; + return FALSE; +} + +/* + * This function is called after popping the stack, to + * handle nested DwPage widgets. + */ +static void Html_eventually_pop_dw(DilloHtml *html, bool_t hand_over_break) +{ + if (html->dw != S_TOP(html)->textblock) { + if (hand_over_break) + DW2TB(html->dw)->handOverBreak (S_TOP(html)->style); + DW2TB(html->dw)->flush (); + html->dw = S_TOP(html)->textblock; + } +} + +/* + * Push the tag (copying attributes from the top of the stack) + */ +static void Html_push_tag(DilloHtml *html, int tag_idx) +{ + char *tagstr; + int n_items; + + /* Save the element's name (no parameters) into tagstr. */ + tagstr = dStrdup(Tags[tag_idx].name); + + n_items = html->stack->size (); + html->stack->increase (); + /* We'll copy the former stack item and just change the tag and its index + * instead of copying all fields except for tag. --Jcid */ + *html->stack->getRef(n_items) = *html->stack->getRef(n_items - 1); + html->stack->getRef(n_items)->tag_name = tagstr; + html->stack->getRef(n_items)->tag_idx = tag_idx; + /* proper memory management, may be unref'd later */ + (S_TOP(html)->style)->ref (); + if (S_TOP(html)->table_cell_style) + (S_TOP(html)->table_cell_style)->ref (); + html->dw = S_TOP(html)->textblock; +} + +/* + * Push the tag (used to force en element with optional open into the stack) + * Note: now it's the same as Html_push_tag(), but things may change... + */ +static void Html_force_push_tag(DilloHtml *html, int tag_idx) +{ + Html_push_tag(html, tag_idx); +} + +/* + * Pop the top tag in the stack + */ +static void Html_real_pop_tag(DilloHtml *html) +{ + bool_t hand_over_break; + + (S_TOP(html)->style)->unref (); + if (S_TOP(html)->table_cell_style) + (S_TOP(html)->table_cell_style)->unref (); + dFree(S_TOP(html)->tag_name); + hand_over_break = S_TOP(html)->hand_over_break; + html->stack->setSize (html->stack->size() - 1); + Html_eventually_pop_dw(html, hand_over_break); +} + +/* + * Default close function for tags. + * (conditional cleanup of the stack) + * There're several ways of doing it. Considering the HTML 4.01 spec + * which defines optional close tags, and the will to deliver useful diagnose + * messages for bad-formed HTML, it'll go as follows: + * 1.- Search the stack for the first tag that requires a close tag. + * 2.- If it matches, clean all the optional-close tags in between. + * 3.- Cleanup the matching tag. (on error, give a warning message) + * + * If 'w3c_mode' is NOT enabled: + * 1.- Search the stack for a matching tag based on tag level. + * 2.- If it exists, clean all the tags in between. + * 3.- Cleanup the matching tag. (on error, give a warning message) + */ +static void Html_tag_cleanup_at_close(DilloHtml *html, int TagIdx) +{ + int w3c_mode = !prefs.w3c_plus_heuristics; + int stack_idx, cmp = 1; + int new_idx = TagIdx; + + if (html->CloseOneTag) { + Html_real_pop_tag(html); + html->CloseOneTag = FALSE; + return; + } + + /* Look for the candidate tag to close */ + stack_idx = html->stack->size() - 1; + while (stack_idx && + (cmp = (new_idx != html->stack->getRef(stack_idx)->tag_idx)) && + ((w3c_mode && + Tags[html->stack->getRef(stack_idx)->tag_idx].EndTag == 'O') || + (!w3c_mode && + Tags[html->stack->getRef(stack_idx)->tag_idx].TagLevel < + Tags[new_idx].TagLevel))) { + --stack_idx; + } + + /* clean, up to the matching tag */ + if (cmp == 0 && stack_idx > 0) { + /* There's a valid matching tag in the stack */ + while (html->stack->size() > stack_idx) { + int toptag_idx = S_TOP(html)->tag_idx; + /* Warn when we decide to close an open tag (for !w3c_mode) */ + if (html->stack->size() > stack_idx + 1 && + Tags[toptag_idx].EndTag != 'O') + MSG_HTML(" - forcing close of open tag: <%s>\n", + Tags[toptag_idx].name); + + /* Close this and only this tag */ + html->CloseOneTag = TRUE; + Tags[toptag_idx].close (html, toptag_idx); + } + + } else { + MSG_HTML("unexpected closing tag: </%s>. -- expected </%s>\n", + Tags[new_idx].name, html->stack->getRef(stack_idx)->tag_name); + } +} + +/* + * Cleanup (conditional), and Pop the tag (if it matches) + */ +static void Html_pop_tag(DilloHtml *html, int TagIdx) +{ + Html_tag_cleanup_at_close(html, TagIdx); +} + +/* + * Some parsing routines. + */ + +/* + * Used by Html_parse_length + */ +static Length Html_parse_length_or_multi_length (const char *attr, + char **endptr) +{ + Length l; + double v; + char *end; + + v = strtod (attr, &end); + switch (*end) { + case '%': + end++; + l = createPerLength (v / 100); + break; + + case '*': + end++; + l = createRelLength (v); + break; +/* + The "px" suffix seems not allowed by HTML4.01 SPEC. + case 'p': + if (end[1] == 'x') + end += 2; +*/ + default: + l = createAbsLength ((int)v); + break; + } + + if (endptr) + *endptr = end; + return l; +} + + +/* + * Returns a length or a percentage, or UNDEF_LENGTH in case + * of an error, or if attr is NULL. + */ +static Length Html_parse_length (DilloHtml *html, const char *attr) +{ + Length l; + char *end; + + l = Html_parse_length_or_multi_length (attr, &end); + if (isRelLength (l)) + /* not allowed as &Length; */ + return LENGTH_AUTO; + else { + /* allow only whitespaces */ + if (*end && !isspace (*end)) { + MSG_HTML("Garbage after length: %s\n", attr); + return LENGTH_AUTO; + } + } + + _MSG("Html_parse_length: \"%s\" %d\n", attr, absLengthVal(l)); + return l; +} + +/* + * Parse a color attribute. + * Return value: parsed color, or default_color (+ error msg) on error. + */ +static int32_t + Html_color_parse(DilloHtml *html, const char *subtag, int32_t default_color) +{ + int err = 1; + int32_t color = a_Color_parse(subtag, default_color, &err); + + if (err) { + MSG_HTML("color is not in \"#RRGGBB\" format\n"); + } + return color; +} + +/* + * Check that 'val' is composed of characters inside [A-Za-z0-9:_.-] + * Note: ID can't have entities, but this check is enough (no '&'). + * Return value: 1 if OK, 0 otherwise. + */ +static int + Html_check_name_val(DilloHtml *html, const char *val, const char *attrname) +{ + int i; + + for (i = 0; val[i]; ++i) + if (!(isalnum(val[i]) || strchr(":_.-", val[i]))) + break; + + if (val[i] || !isalpha(val[0])) + MSG_HTML("'%s' value is not of the form " + "[A-Za-z][A-Za-z0-9:_.-]*\n", attrname); + + return !(val[i]); +} + +/* + * Handle DOCTYPE declaration + * + * Follows the convention that HTML 4.01 + * doctypes which include a full w3c DTD url are treated as + * standards-compliant, but 4.01 without the url and HTML 4.0 and + * earlier are not. XHTML doctypes are always standards-compliant + * whether or not an url is present. + * + * Note: I'm not sure about this convention. The W3C validator + * recognizes the "HTML Level" with or without the URL. The convention + * comes from mozilla (see URLs below), but Dillo doesn't have the same + * rendering modes, so it may be better to chose another behaviour. --Jcid + * + * http://www.mozilla.org/docs/web-developer/quirks/doctypes.html + * http://lists.auriga.wearlab.de/pipermail/dillo-dev/2004-October/002300.html + * + * This is not a full DOCTYPE parser, just enough for what Dillo uses. + */ +static void Html_parse_doctype(DilloHtml *html, char *tag, int tagsize) +{ + char *HTML_sig = "<!DOCTYPE HTML PUBLIC "; + char *HTML20 = "-//IETF//DTD HTML//EN"; + char *HTML32 = "-//W3C//DTD HTML 3.2"; + char *HTML40 = "-//W3C//DTD HTML 4.0"; + char *HTML401 = "-//W3C//DTD HTML 4.01"; + char *HTML401_url = "http://www.w3.org/TR/html4/"; + char *XHTML1 = "-//W3C//DTD XHTML 1.0"; + char *XHTML1_url = "http://www.w3.org/TR/xhtml1/DTD/"; + char *XHTML11 = "-//W3C//DTD XHTML 1.1"; + char *XHTML11_url = "http://www.w3.org/TR/xhtml11/DTD/"; + + int i, quote; + char *p, *ntag = dStrndup(tag, tagsize); + + /* Tag sanitization: Collapse whitespace between tokens + * and replace '\n' and '\r' with ' ' inside quoted strings. */ + for (i = 0, p = ntag; *p; ++p) { + if (isspace(*p)) { + for (ntag[i++] = ' '; isspace(p[1]); ++p); + } else if ((quote = *p) == '"' || *p == '\'') { + for (ntag[i++] = *p++; (ntag[i++] = *p) && *p != quote; ++p) { + if (*p == '\n' || *p == '\r') + ntag[i - 1] = ' '; + p += (p[0] == '\r' && p[1] == '\n') ? 1 : 0; + } + } else { + ntag[i++] = *p; + } + if (!*p) + break; + } + ntag[i] = 0; + + _MSG("New: {%s}\n", ntag); + + /* The default DT_NONE type is TagSoup */ + if (!dStrncasecmp(ntag, HTML_sig, strlen(HTML_sig))) { + p = ntag + strlen(HTML_sig) + 1; + if (!strncmp(p, HTML401, strlen(HTML401)) && + dStristr(p + strlen(HTML401), HTML401_url)) { + html->DocType = DT_HTML; + html->DocTypeVersion = 4.01f; + } else if (!strncmp(p, XHTML1, strlen(XHTML1)) && + dStristr(p + strlen(XHTML1), XHTML1_url)) { + html->DocType = DT_XHTML; + html->DocTypeVersion = 1.0f; + } else if (!strncmp(p, XHTML11, strlen(XHTML11)) && + dStristr(p + strlen(XHTML11), XHTML11_url)) { + html->DocType = DT_XHTML; + html->DocTypeVersion = 1.1f; + } else if (!strncmp(p, HTML40, strlen(HTML40))) { + html->DocType = DT_HTML; + html->DocTypeVersion = 4.0f; + } else if (!strncmp(p, HTML32, strlen(HTML32))) { + html->DocType = DT_HTML; + html->DocTypeVersion = 3.2f; + } else if (!strncmp(p, HTML20, strlen(HTML20))) { + html->DocType = DT_HTML; + html->DocTypeVersion = 2.0f; + } + } + + dFree(ntag); +} + +/* + * Handle open HTML element + */ +static void Html_tag_open_html(DilloHtml *html, char *tag, int tagsize) +{ + if (!(html->InFlags & IN_HTML)) + html->InFlags |= IN_HTML; + ++html->Num_HTML; + + if (html->Num_HTML > 1) { + MSG_HTML("HTML element was already open\n"); + } +} + +/* + * Handle close HTML element + */ +static void Html_tag_close_html(DilloHtml *html, int TagIdx) +{ + /* todo: may add some checks here */ + if (html->Num_HTML == 1) { + /* beware of pages with multiple HTML close tags... :-P */ + html->InFlags &= ~IN_HTML; + } + Html_pop_tag(html, TagIdx); +} + +/* + * Handle open HEAD element + */ +static void Html_tag_open_head(DilloHtml *html, char *tag, int tagsize) +{ + if (html->InFlags & IN_BODY) { + MSG_HTML("HEAD element must go before the BODY section\n"); + html->ReqTagClose = TRUE; + return; + } + + if (!(html->InFlags & IN_HEAD)) + html->InFlags |= IN_HEAD; + ++html->Num_HEAD; + + if (html->Num_HEAD > 1) { + MSG_HTML("HEAD element was already open\n"); + } +} + +/* + * Handle close HEAD element + * Note: as a side effect of Html_test_section() this function is called + * twice when the head element is closed implicitly. + */ +static void Html_tag_close_head(DilloHtml *html, int TagIdx) +{ + if (html->InFlags & IN_HEAD) { + if (html->Num_TITLE == 0) + MSG_HTML("HEAD section lacks the TITLE element\n"); + + html->InFlags &= ~IN_HEAD; + } + Html_pop_tag(html, TagIdx); +} + +/* + * Handle open TITLE + * calls stash init, where the title string will be stored + */ +static void Html_tag_open_title(DilloHtml *html, char *tag, int tagsize) +{ + ++html->Num_TITLE; + Html_stash_init(html); +} + +/* + * Handle close TITLE + * set page-title in the browser window and in the history. + */ +static void Html_tag_close_title(DilloHtml *html, int TagIdx) +{ + if (html->InFlags & IN_HEAD) { + /* title is only valid inside HEAD */ + a_UIcmd_set_page_title(html->linkblock->bw, html->Stash->str); + a_History_set_title(NAV_TOP(html->linkblock->bw), html->Stash->str); + } else { + MSG_HTML("the TITLE element must be inside the HEAD section\n"); + } + Html_pop_tag(html, TagIdx); +} + +/* + * Handle open SCRIPT + * initializes stash, where the embedded code will be stored. + * MODE_VERBATIM is used because MODE_STASH catches entities. + */ +static void Html_tag_open_script(DilloHtml *html, char *tag, int tagsize) +{ + Html_stash_init(html); + S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM; +} + +/* + * Handle close SCRIPT + */ +static void Html_tag_close_script(DilloHtml *html, int TagIdx) +{ + /* eventually the stash will be sent to an interpreter for parsing */ + Html_pop_tag(html, TagIdx); +} + +/* + * Handle open STYLE + * store the contents to the stash where (in the future) the style + * sheet interpreter can get it. + */ +static void Html_tag_open_style(DilloHtml *html, char *tag, int tagsize) +{ + Html_stash_init(html); + S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM; +} + +/* + * Handle close STYLE + */ +static void Html_tag_close_style(DilloHtml *html, int TagIdx) +{ + /* eventually the stash will be sent to an interpreter for parsing */ + Html_pop_tag(html, TagIdx); +} + +/* + * <BODY> + */ +static void Html_tag_open_body(DilloHtml *html, char *tag, int tagsize) +{ + const char *attrbuf; + Textblock *textblock; + StyleAttrs style_attrs; + Style *style; + int32_t color; + + if (!(html->InFlags & IN_BODY)) + html->InFlags |= IN_BODY; + ++html->Num_BODY; + + if (html->Num_BODY > 1) { + MSG_HTML("BODY element was already open\n"); + return; + } + if (html->InFlags & IN_HEAD) { + /* if we're here, it's bad XHTML, no need to recover */ + MSG_HTML("unclosed HEAD element\n"); + } + + textblock = DW2TB(html->dw); + + if (!prefs.force_my_colors) { + if ((attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) { + color = Html_color_parse(html, attrbuf, prefs.bg_color); + if ((color == 0xffffff && !prefs.allow_white_bg) || + prefs.force_my_colors) + color = prefs.bg_color; + + style_attrs = *html->dw->getStyle (); + style_attrs.backgroundColor = + Color::createSimple (HT2LT(html), color); + style = Style::create (HT2LT(html), &style_attrs); + html->dw->setStyle (style); + style->unref (); + S_TOP(html)->current_bg_color = color; + } + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "text"))) { + color = Html_color_parse(html, attrbuf, prefs.text_color); + HTML_SET_TOP_ATTR (html, color, + Color::createSimple (HT2LT(html),color)); + } + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "link"))) + html->linkblock->link_color = Html_color_parse(html, attrbuf, + prefs.link_color); + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "vlink"))) + html->linkblock->visited_color = + Html_color_parse(html, attrbuf, prefs.visited_color); + + if (prefs.contrast_visited_color) { + /* get a color that has a "safe distance" from text, link and bg */ + html->linkblock->visited_color = + a_Color_vc(html->linkblock->visited_color, + S_TOP(html)->style->color->getColor(), + html->linkblock->link_color, + S_TOP(html)->current_bg_color); + } + } + + S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_BODY; +} + +/* + * BODY + */ +static void Html_tag_close_body(DilloHtml *html, int TagIdx) +{ + if (html->Num_BODY == 1) { + /* some tag soup pages use multiple BODY tags... */ + html->InFlags &= ~IN_BODY; + } + Html_pop_tag(html, TagIdx); +} + +/* + * <P> + * todo: what's the point between adding the parbreak before and + * after the push? + */ +static void Html_tag_open_p(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_tag_set_align_attr (html, tag, tagsize); +} + +/* + * <TABLE> + */ +static void Html_tag_open_table(DilloHtml *html, char *tag, int tagsize) +{ +#ifdef USE_TABLES + Widget *table; + StyleAttrs style_attrs; + Style *tstyle, *old_style; + const char *attrbuf; + int32_t border = 0, cellspacing = 1, cellpadding = 2, bgcolor; +#endif + + DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style); + +#ifdef USE_TABLES + if ((attrbuf = Html_get_attr(html, tag, tagsize, "border"))) + border = isdigit(attrbuf[0]) ? strtol (attrbuf, NULL, 10) : 1; + if ((attrbuf = Html_get_attr(html, tag, tagsize, "cellspacing"))) + cellspacing = strtol (attrbuf, NULL, 10); + if ((attrbuf = Html_get_attr(html, tag, tagsize, "cellpadding"))) + cellpadding = strtol (attrbuf, NULL, 10); + + /* The style for the table */ + style_attrs = *S_TOP(html)->style; + + /* When dillo was started with the --debug-rendering option, there + * is always a border around the table. */ + if (dillo_dbg_rendering) + style_attrs.borderWidth.setVal (MIN (border, 1)); + else + style_attrs.borderWidth.setVal (border); + + style_attrs.setBorderColor ( + Color::createShaded (HT2LT(html), + S_TOP(html)->current_bg_color)); + style_attrs.setBorderStyle (BORDER_OUTSET); + style_attrs.hBorderSpacing = cellspacing; + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "width"))) + style_attrs.width = Html_parse_length (html, attrbuf); + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "align"))) { + if (dStrcasecmp (attrbuf, "left") == 0) + style_attrs.textAlign = TEXT_ALIGN_LEFT; + else if (dStrcasecmp (attrbuf, "right") == 0) + style_attrs.textAlign = TEXT_ALIGN_RIGHT; + else if (dStrcasecmp (attrbuf, "center") == 0) + style_attrs.textAlign = TEXT_ALIGN_CENTER; + } + + if (!prefs.force_my_colors && + (attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) { + bgcolor = Html_color_parse(html, attrbuf, -1); + if (bgcolor != -1) { + if (bgcolor == 0xffffff && !prefs.allow_white_bg) + bgcolor = prefs.bg_color; + S_TOP(html)->current_bg_color = bgcolor; + style_attrs.backgroundColor = + Color::createSimple (HT2LT(html), bgcolor); + } + } + + tstyle = Style::create (HT2LT(html), &style_attrs); + + /* The style for the cells */ + style_attrs = *S_TOP(html)->style; + /* When dillo was started with the --debug-rendering option, there + * is always a border around the cells. */ + if (dillo_dbg_rendering) + style_attrs.borderWidth.setVal (1); + else + style_attrs.borderWidth.setVal (border ? 1 : 0); + + style_attrs.padding.setVal(cellpadding); + style_attrs.setBorderColor (tstyle->borderColor.top); + style_attrs.setBorderStyle (BORDER_INSET); + + old_style = S_TOP(html)->table_cell_style; + S_TOP(html)->table_cell_style = + Style::create (HT2LT(html), &style_attrs); + if (old_style) + old_style->unref (); + + table = new Table(false); + DW2TB(html->dw)->addWidget (table, tstyle); + tstyle->unref (); + + S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TOP; + S_TOP(html)->cell_text_align_set = FALSE; + S_TOP(html)->table = table; +#endif +} + + +/* + * used by <TD> and <TH> + */ +static void Html_tag_open_table_cell(DilloHtml *html, char *tag, int tagsize, + TextAlignType text_align) +{ +#ifdef USE_TABLES + Widget *col_tb; + int colspan = 1, rowspan = 1; + const char *attrbuf; + StyleAttrs style_attrs; + Style *style, *old_style; + int32_t bgcolor; + bool_t new_style; + + switch (S_TOP(html)->table_mode) { + case DILLO_HTML_TABLE_MODE_NONE: + MSG_HTML("<td> or <th> outside <table>\n"); + return; + + case DILLO_HTML_TABLE_MODE_TOP: + MSG_HTML("<td> or <th> outside <tr>\n"); + /* a_Dw_table_add_cell takes care that dillo does not crash. */ + /* continues */ + case DILLO_HTML_TABLE_MODE_TR: + case DILLO_HTML_TABLE_MODE_TD: + /* todo: check errors? */ + if ((attrbuf = Html_get_attr(html, tag, tagsize, "colspan"))) + colspan = strtol (attrbuf, NULL, 10); + if ((attrbuf = Html_get_attr(html, tag, tagsize, "rowspan"))) + rowspan = strtol (attrbuf, NULL, 10); + + /* text style */ + old_style = S_TOP(html)->style; + style_attrs = *old_style; + if (!S_TOP(html)->cell_text_align_set) + style_attrs.textAlign = text_align; + if (Html_get_attr(html, tag, tagsize, "nowrap")) + style_attrs.whiteSpace = WHITE_SPACE_NOWRAP; + else + style_attrs.whiteSpace = WHITE_SPACE_NORMAL; + + S_TOP(html)->style = + Style::create (HT2LT(html), &style_attrs); + old_style->unref (); + Html_tag_set_align_attr (html, tag, tagsize); + + /* cell style */ + style_attrs = *S_TOP(html)->table_cell_style; + new_style = FALSE; + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "width"))) { + style_attrs.width = Html_parse_length (html, attrbuf); + new_style = TRUE; + } + + if (Html_tag_set_valign_attr (html, tag, tagsize, &style_attrs)) + new_style = TRUE; + + if (!prefs.force_my_colors && + (attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) { + bgcolor = Html_color_parse(html, attrbuf, -1); + if (bgcolor != -1) { + if (bgcolor == 0xffffff && !prefs.allow_white_bg) + bgcolor = prefs.bg_color; + + new_style = TRUE; + style_attrs.backgroundColor = + Color::createSimple (HT2LT(html), bgcolor); + S_TOP(html)->current_bg_color = bgcolor; + } + } + + if (S_TOP(html)->style->textAlign + == TEXT_ALIGN_STRING) + col_tb = new TableCell ( + ((Table*)S_TOP(html)->table)->getCellRef (), + false); + else + col_tb = new Textblock (false); + + if (new_style) { + style = Style::create (HT2LT(html), &style_attrs); + col_tb->setStyle (style); + style->unref (); + } else + col_tb->setStyle (S_TOP(html)->table_cell_style); + + ((Table*)S_TOP(html)->table)->addCell ( + col_tb, colspan, rowspan); + S_TOP(html)->textblock = html->dw = col_tb; + + /* Handle it when the user clicks on a link */ + Html_connect_signals(html, col_tb); + break; + + default: + /* compiler happiness */ + break; + } + + S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TD; +#endif +} + + +/* + * <TD> + */ +static void Html_tag_open_td(DilloHtml *html, char *tag, int tagsize) +{ + Html_tag_open_table_cell (html, tag, tagsize, TEXT_ALIGN_LEFT); +} + + +/* + * <TH> + */ +static void Html_tag_open_th(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 1, 1); + Html_tag_open_table_cell (html, tag, tagsize, TEXT_ALIGN_CENTER); +} + + +/* + * <TR> + */ +static void Html_tag_open_tr(DilloHtml *html, char *tag, int tagsize) +{ + const char *attrbuf; + StyleAttrs style_attrs; + Style *style, *old_style; + int32_t bgcolor; + +#ifdef USE_TABLES + switch (S_TOP(html)->table_mode) { + case DILLO_HTML_TABLE_MODE_NONE: + _MSG("Invalid HTML syntax: <tr> outside <table>\n"); + return; + + case DILLO_HTML_TABLE_MODE_TOP: + case DILLO_HTML_TABLE_MODE_TR: + case DILLO_HTML_TABLE_MODE_TD: + style = NULL; + + if (!prefs.force_my_colors && + (attrbuf = Html_get_attr(html, tag, tagsize, "bgcolor"))) { + bgcolor = Html_color_parse(html, attrbuf, -1); + if (bgcolor != -1) { + if (bgcolor == 0xffffff && !prefs.allow_white_bg) + bgcolor = prefs.bg_color; + + style_attrs = *S_TOP(html)->style; + style_attrs.backgroundColor = + Color::createSimple (HT2LT(html), bgcolor); + style = Style::create (HT2LT(html), &style_attrs); + S_TOP(html)->current_bg_color = bgcolor; + } + } + + ((Table*)S_TOP(html)->table)->addRow (style); + if (style) + style->unref (); + + if (Html_get_attr (html, tag, tagsize, "align")) { + S_TOP(html)->cell_text_align_set = TRUE; + Html_tag_set_align_attr (html, tag, tagsize); + } + + style_attrs = *S_TOP(html)->table_cell_style; + if (Html_tag_set_valign_attr (html, tag, tagsize, &style_attrs)) { + old_style = S_TOP(html)->table_cell_style; + S_TOP(html)->table_cell_style = + Style::create (HT2LT(html), &style_attrs); + old_style->unref (); + } else + + break; + + default: + break; + } + + S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TR; +#else + DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style); +#endif +} + +/* + * <FRAME>, <IFRAME> + * todo: This is just a temporary fix while real frame support + * isn't finished. Imitates lynx/w3m's frames. + */ +static void Html_tag_open_frame (DilloHtml *html, char *tag, int tagsize) +{ + const char *attrbuf; + char *src, *buf; + DilloUrl *url; + Textblock *textblock; + StyleAttrs style_attrs; + Style *link_style; + Widget *bullet; + int buf_size; + + textblock = DW2TB(html->dw); + + if (!(attrbuf = Html_get_attr(html, tag, tagsize, "src"))) + return; + + if (!(url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0))) + return; + + src = dStrdup(attrbuf); + + style_attrs = *(S_TOP(html)->style); + + if (a_Capi_get_buf(url, &buf, &buf_size)) /* visited frame */ + style_attrs.color = + Color::createSimple (HT2LT(html), + html->linkblock->visited_color); + else /* unvisited frame */ + style_attrs.color = Color::createSimple (HT2LT(html), + html->linkblock->link_color); + + style_attrs.textDecoration |= TEXT_DECORATION_UNDERLINE; + style_attrs.x_link = Html_set_new_link(html, &url); + link_style = Style::create (HT2LT(html), &style_attrs); + + textblock->addParbreak (5, S_TOP(html)->style); + + /* The bullet will be assigned the current list style, which should + * be "disc" by default, but may in very weird pages be different. + * Anyway, there should be no harm. */ + bullet = new Bullet(); + textblock->addWidget(bullet, S_TOP(html)->style); + textblock->addSpace(S_TOP(html)->style); + + if (tolower(tag[1]) == 'i') { + /* IFRAME usually comes with very long advertising/spying URLS, + * to not break rendering we will force name="IFRAME" */ + textblock->addText (dStrdup("IFRAME"), link_style); + + } else { + /* FRAME: + * If 'name' tag is present use it, if not use 'src' value */ + if (!(attrbuf = Html_get_attr(html, tag, tagsize, "name"))) { + textblock->addText (dStrdup(src), link_style); + } else { + textblock->addText (dStrdup(attrbuf), link_style); + } + } + + textblock->addParbreak (5, S_TOP(html)->style); + + link_style->unref (); + dFree(src); +} + +/* + * <FRAMESET> + * todo: This is just a temporary fix while real frame support + * isn't finished. Imitates lynx/w3m's frames. + */ +static void Html_tag_open_frameset (DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + DW2TB(html->dw)->addText(dStrdup("--FRAME--"), + S_TOP(html)->style); + Html_add_indented(html, 40, 0, 5); +} + +/* + * <H1> | <H2> | <H3> | <H4> | <H5> | <H6> + */ +static void Html_tag_open_h(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + + /* todo: combining these two would be slightly faster */ + Html_set_top_font(html, prefs.vw_fontname, + Html_level_to_fontsize(FontSizesNum - (tag[2] - '0')), + 1, 3); + Html_tag_set_align_attr (html, tag, tagsize); + + /* First finalize unclosed H tags (we test if already named anyway) */ + a_Menu_pagemarks_set_text(html->bw, html->Stash->str); + a_Menu_pagemarks_add(html->bw, DW2TB(html->dw), + S_TOP(html)->style, (tag[2] - '0')); + Html_stash_init(html); + S_TOP(html)->parse_mode = + DILLO_HTML_PARSE_MODE_STASH_AND_BODY; +} + +/* + * Handle close: <H1> | <H2> | <H3> | <H4> | <H5> | <H6> + */ +static void Html_tag_close_h(DilloHtml *html, int TagIdx) +{ + a_Menu_pagemarks_set_text(html->bw, html->Stash->str); + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_pop_tag(html, TagIdx); +} + +/* + * <BIG> | <SMALL> + */ +static void Html_tag_open_big_small(DilloHtml *html, char *tag, int tagsize) +{ + int level; + + level = + Html_fontsize_to_level(S_TOP(html)->style->font->size) + + ((dStrncasecmp(tag+1, "big", 3)) ? -1 : 1); + Html_set_top_font(html, NULL, Html_level_to_fontsize(level), 0, 0); +} + +/* + * <BR> + */ +static void Html_tag_open_br(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addLinebreak (S_TOP(html)->style); +} + +/* + * <BUTTON> + */ +static void Html_tag_open_button(DilloHtml *html, char *tag, int tagsize) +{ +// // AL +// /* +// * Buttons are rendered on one line, this is (at several levels) a +// * bit simpler. May be changed in the future. +// */ +// StyleAttrs style_attrs; +// Style *style; +// Widget *button, *page; +// DilloHtmlForm *form; +// DilloHtmlLB *html_lb; +// DilloHtmlInputType inp_type; +// char *name, *value, *type; +// +// /* Render the button */ +// style_attrs = *S_TOP(html)->style; +// +// style_attrs.margin.setVal (0); +// style_attrs.borderWidth.setVal (0); +// style_attrs.padding.setVal(0); +// style = Style::create (HT2LT(html), &style_attrs); +// button = a_Dw_button_new (DW_USES_HINTS, TRUE); +// +// /* The new button is not set button-insensitive, since nested buttons +// * (if they are anyway allowed, todo: search in spec) should all be +// * activatable. */ +// a_Dw_widget_set_button_sensitive (button, TRUE); +// +// DW2TB(html->dw)->addParbreak (5, style); +// DW2TB(html->dw)->addWidget (button, style); +// DW2TB(html->dw)->addParbreak (5, style); +// style->unref (); +// +// style_attrs.margin.setVal (5); +// style = Style::create (HT2LT(html), &style_attrs); +// page = new Textblock (false); +// page->setStyle (style); +// style->unref (); +// a_Dw_container_add (DW_CONTAINER (button), page); +// a_Dw_widget_set_button_sensitive (DW_WIDGET (page), FALSE); +// style_attrs.margin.setVal (0); +// +// a_Dw_button_set_sensitive (DW_BUTTON (button), FALSE); +// +// S_TOP(html)->textblock = html->dw = page; +// +// /* Handle it when the user clicks on a link */ +// Html_connect_signals(html, GTK_OBJECT(page)); +// +// /* Connect it to the form */ +// html_lb = html->linkblock; +// form = html_lb->forms->getRef (html_lb->forms->size() - 1); +// +// type = Html_get_attr_wdef(html, tag, tagsize, "type", ""); +// +// if (strcmp(type, "submit") == 0) { +// inp_type = DILLO_HTML_INPUT_BUTTON_SUBMIT; +// gtk_signal_connect(GTK_OBJECT(button), "clicked", +// GTK_SIGNAL_FUNC(Html_submit_form), html_lb); +// } else if (strcmp(type, "reset") == 0) { +// inp_type = DILLO_HTML_INPUT_BUTTON_RESET; +// gtk_signal_connect(GTK_OBJECT(button), "clicked", +// GTK_SIGNAL_FUNC(Html_reset_form), html_lb); +// } else +// return; +// +// value = Html_get_attr_wdef(html, tag, tagsize, "value", NULL); +// name = Html_get_attr_wdef(html, tag, tagsize, "name", NULL); +// +// Html_add_input(form, inp_type, (GtkWidget*)button, name, value, +// NULL, FALSE); +// +// dFree(type); +// dFree(name); +// dFree(value); +} + + +/* + * <FONT> + */ +static void Html_tag_open_font(DilloHtml *html, char *tag, int tagsize) +{ +#if 1 + StyleAttrs style_attrs; + Style *old_style; + /*Font font;*/ + const char *attrbuf; + int32_t color; + + if (!prefs.force_my_colors) { + old_style = S_TOP(html)->style; + style_attrs = *old_style; + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "color"))) { + if (prefs.contrast_visited_color && html->InVisitedLink) { + color = html->linkblock->visited_color; + } else { + /* use the tag-specified color */ + color = Html_color_parse( + html, attrbuf, style_attrs.color->getColor()); + style_attrs.color = Color::createSimple (HT2LT(html), color); + } + } + +#if 0 + //if ((attrbuf = Html_get_attr(html, tag, tagsize, "face"))) { + // font = *( style_attrs.font ); + // font.name = attrbuf; + // style_attrs.font = a_Dw_style_font_new_from_list (&font); + //} +#endif + + S_TOP(html)->style = + Style::create (HT2LT(html), &style_attrs); + old_style->unref (); + } + +#endif +} + +/* + * <ABBR> + */ +static void Html_tag_open_abbr(DilloHtml *html, char *tag, int tagsize) +{ +// DwTooltip *tooltip; +// const char *attrbuf; +// +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "title"))) { +// tooltip = a_Dw_tooltip_new_no_ref(attrbuf); +// HTML_SET_TOP_ATTR(html, x_tooltip, tooltip); +// } +} + +/* + * <B> + */ +static void Html_tag_open_b(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 1, 1); +} + +/* + * <STRONG> + */ +static void Html_tag_open_strong(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 1, 1); +} + +/* + * <I> + */ +static void Html_tag_open_i(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 2, 2); +} + +/* + * <EM> + */ +static void Html_tag_open_em(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 2, 2); +} + +/* + * <CITE> + */ +static void Html_tag_open_cite(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 2, 2); +} + +/* + * <CENTER> + */ +static void Html_tag_open_center(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style); + HTML_SET_TOP_ATTR(html, textAlign, TEXT_ALIGN_CENTER); +} + +/* + * <ADDRESS> + */ +static void Html_tag_open_address(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_set_top_font(html, NULL, 0, 2, 2); +} + +/* + * <TT> + */ +static void Html_tag_open_tt(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); +} + +/* + * Read image-associated tag attributes, + * create new image and add it to the html page (if add is TRUE). + */ +static DilloImage *Html_add_new_image(DilloHtml *html, char *tag, + int tagsize, StyleAttrs *style_attrs, + bool_t add) +{ + const int MAX_W = 6000, MAX_H = 6000; + + DilloImage *Image; + char *width_ptr, *height_ptr, *alt_ptr; + const char *attrbuf; + Length l_w, l_h; + int space, w = 0, h = 0; + +// if (prefs.show_tooltip && +// (attrbuf = Html_get_attr(html, tag, tagsize, "title"))) +// style_attrs->x_tooltip = a_Dw_tooltip_new_no_ref(attrbuf); + + alt_ptr = Html_get_attr_wdef(html, tag, tagsize, "alt", NULL); + width_ptr = Html_get_attr_wdef(html, tag, tagsize, "width", NULL); + height_ptr = Html_get_attr_wdef(html, tag, tagsize, "height", NULL); + // Check for malicious values + // TODO: the same for percentage and relative lengths. + if (width_ptr) { + l_w = Html_parse_length (html, width_ptr); + w = isAbsLength(l_w) ? absLengthVal(l_w) : 0; + } + if (height_ptr) { + l_h = Html_parse_length (html, height_ptr); + h = isAbsLength(l_h) ? absLengthVal(l_h) : 0; + } + if (w < 0 || h < 0 || abs(w*h) > MAX_W * MAX_H) { + dFree(width_ptr); + dFree(height_ptr); + width_ptr = height_ptr = NULL; + MSG("Html_add_new_image: suspicious image size request %dx%d\n", w, h); + } + + /* todo: we should scale the image respecting its ratio. + * As the image size is not known at this time, maybe a flag + * can be set to scale it later. + if ((width_ptr && !height_ptr) || (height_ptr && !width_ptr)) + [...] + */ + + /* Spacing to the left and right */ + if ((attrbuf = Html_get_attr(html, tag, tagsize, "hspace"))) { + space = strtol(attrbuf, NULL, 10); + + if (space > 0) + style_attrs->margin.left = style_attrs->margin.right = space; + } + + /* Spacing at the top and bottom */ + if ((attrbuf = Html_get_attr(html, tag, tagsize, "vspace"))) { + space = strtol(attrbuf, NULL, 10); + + if (space > 0) + style_attrs->margin.top = style_attrs->margin.bottom = space; + } + + /* Add a new image widget to this page */ + Image = a_Image_new(0, 0, alt_ptr, S_TOP(html)->current_bg_color); + if (add) { + Html_add_widget(html, (Widget*)Image->dw, + width_ptr, height_ptr, style_attrs); + } + + dFree(width_ptr); + dFree(height_ptr); + dFree(alt_ptr); + return Image; +} + +/* + * Tell cache to retrieve image + */ +static void Html_load_image(DilloHtml *html, DilloUrl *url, DilloImage *Image) +{ + DilloWeb *Web; + int ClientKey; + /* Fill a Web structure for the cache query */ + Web = a_Web_new(url); + Web->bw = html->bw; + Web->Image = Image; + Web->flags |= WEB_Image; + /* Request image data from the cache */ + if ((ClientKey = a_Capi_open_url(Web, NULL, NULL)) != 0) { + a_Bw_add_client(html->bw, ClientKey, 0); + a_Bw_add_url(html->bw, url); + } +} + +/* + * Create a new Image struct and request the image-url to the cache + * (If it either hits or misses, is not relevant here; that's up to the + * cache functions) + */ +static void Html_tag_open_img(DilloHtml *html, char *tag, int tagsize) +{ + DilloImage *Image; + DilloUrl *url, *usemap_url; + Textblock *textblock; + StyleAttrs style_attrs; + const char *attrbuf; + int border; + + /* This avoids loading images. Useful for viewing suspicious HTML email. */ + if (URL_FLAGS(html->linkblock->base_url) & URL_SpamSafe) + return; + + if (!(attrbuf = Html_get_attr(html, tag, tagsize, "src")) || + !(url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0))) + return; + + textblock = DW2TB(html->dw); + + + usemap_url = NULL; + if ((attrbuf = Html_get_attr(html, tag, tagsize, "usemap"))) + /* todo: usemap URLs outside of the document are not used. */ + usemap_url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0); + + style_attrs = *S_TOP(html)->style; + + if (S_TOP(html)->style->x_link != -1 || + usemap_url != NULL) { + /* Images within links */ + border = 1; + if ((attrbuf = Html_get_attr(html, tag, tagsize, "border"))) + border = strtol (attrbuf, NULL, 10); + + if (S_TOP(html)->style->x_link != -1) { + /* In this case we can use the text color */ + style_attrs.setBorderColor ( + Color::createShaded (HT2LT(html), + style_attrs.color->getColor())); + } else { + style_attrs.setBorderColor ( + Color::createShaded (HT2LT(html), + html->linkblock->link_color)); + } + style_attrs.setBorderStyle (BORDER_SOLID); + style_attrs.borderWidth.setVal (border); + } + + Image = Html_add_new_image(html, tag, tagsize, &style_attrs, TRUE); + // Html_connect_signals(html, GTK_OBJECT(Image->dw)); +// gtk_signal_connect_after(GTK_OBJECT(Image->dw), "button_press_event", +// GTK_SIGNAL_FUNC(Html_image_menu), html->bw); + +#if 0 + /* Image maps */ + if (Html_get_attr(html, tag, tagsize, "ismap")) { + /* BUG: if several ISMAP images follow each other without + * being separated with a word, only the first one is ISMAPed + */ +// a_Dw_image_set_ismap (Image->dw); + _MSG(" Html_tag_open_img: server-side map (ISMAP)\n"); + } else if (S_TOP(html)->style->x_link != -1 && + usemap_url == NULL) + /* For simple links, we have to suppress the "image_pressed" signal. + * This is overridden for USEMAP images. */ +// a_Dw_widget_set_button_sensitive (IM2DW(Image->dw), FALSE); + + if (usemap_url) { +// a_Dw_image_set_usemap (Image->dw, &html->linkblock->maps, usemap_url); + a_Url_free (usemap_url); + } +#endif + + Html_load_image(html, url, Image); + a_Url_free(url); +} + +/* + * <map> + */ +static void Html_tag_open_map(DilloHtml *html, char *tag, int tagsize) +{ + char *hash_name; + const char *attrbuf; + DilloUrl *url; + + if (html->InFlags & IN_MAP) { + MSG_HTML("nested <map>\n"); + } else { + if ((attrbuf = Html_get_attr(html, tag, tagsize, "name"))) { + hash_name = dStrconcat("#", attrbuf, NULL); + url = Html_url_new(html, hash_name, NULL, 0, 0, 0, 0); + //a_Dw_image_map_list_add_map (&html->linkblock->maps, url); + a_Url_free (url); + dFree(hash_name); + } + html->InFlags |= IN_MAP; + } +} + +/* + * Handle close <MAP> + */ +static void Html_tag_close_map(DilloHtml *html, int TagIdx) +{ + html->InFlags &= ~IN_MAP; + Html_pop_tag(html, TagIdx); +} + +/* + * <AREA> + */ +static void Html_tag_open_area(DilloHtml *html, char *tag, int tagsize) +{ +// // AL +// /* todo: point must be a dynamic array */ +// GdkPoint point[1024]; +// DilloUrl* url; +// const char *attrbuf; +// int type = DW_IMAGE_MAP_SHAPE_RECT; +// int nbpoints, link = -1; +// +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "shape"))) { +// if (dStrcasecmp(attrbuf, "rect") == 0) +// type = DW_IMAGE_MAP_SHAPE_RECT; +// else if (dStrcasecmp(attrbuf, "circle") == 0) +// type = DW_IMAGE_MAP_SHAPE_CIRCLE; +// else if (dStrncasecmp(attrbuf, "poly", 4) == 0) +// type = DW_IMAGE_MAP_SHAPE_POLY; +// else +// type = DW_IMAGE_MAP_SHAPE_RECT; +// } +// /* todo: add support for coords in % */ +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "coords"))) { +// /* Is this a valid poly ? +// * rect = x0,y0,x1,y1 => 2 +// * circle = x,y,r => 2 +// * poly = x0,y0,x1,y1,x2,y2 minimum => 3 */ +// nbpoints = Html_read_coords(html, attrbuf, point); +// } else +// return; +// +// if (Html_get_attr(html, tag, tagsize, "nohref")) { +// link = -1; +// _MSG("nohref"); +// } +// +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "href"))) { +// url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0); +// dReturn_if_fail ( url != NULL ); +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "alt"))) +// a_Url_set_alt(url, attrbuf); +// +// link = Html_set_new_link(html, &url); +// } +// +// a_Dw_image_map_list_add_shape(&html->linkblock->maps, type, link, +// point, nbpoints); +} + + +/* + * Test and extract the link from a javascript instruction. + */ +static const char* Html_get_javascript_link(DilloHtml *html) +{ + size_t i; + char ch, *p1, *p2; + Dstr *Buf = html->attr_data; + + if (dStrncasecmp("javascript", Buf->str, 10) == 0) { + i = strcspn(Buf->str, "'\""); + ch = Buf->str[i]; + if ((ch == '"' || ch == '\'') && + (p2 = strchr(Buf->str + i + 1 , ch))) { + p1 = Buf->str + i; + MSG_HTML("link depends on javascript()\n"); + dStr_truncate(Buf, p2 - Buf->str); + dStr_erase(Buf, 0, p1 - Buf->str + 1); + } + } + return Buf->str; +} + +/* + * Register an anchor for this page. + */ +static void Html_add_anchor(DilloHtml *html, const char *name) +{ + _MSG("Registering ANCHOR: %s\n", name); + if (!DW2TB(html->dw)->addAnchor (name, S_TOP(html)->style)) + MSG_HTML("Anchor names must be unique within the document\n"); + /* + * According to Sec. 12.2.1 of the HTML 4.01 spec, "anchor names that + * differ only in case may not appear in the same document", but + * "comparisons between fragment identifiers and anchor names must be + * done by exact (case-sensitive) match." We ignore the case issue and + * always test for exact matches. Moreover, what does uppercase mean + * for Unicode characters outside the ASCII range? + */ +} + +/* + * <A> + */ +static void Html_tag_open_a(DilloHtml *html, char *tag, int tagsize) +{ + StyleAttrs style_attrs; + Style *old_style; + DilloUrl *url; + const char *attrbuf; + char *buf; + int buf_size; + + /* todo: add support for MAP with A HREF */ + Html_tag_open_area(html, tag, tagsize); + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "href"))) { + /* if it's a javascript link, extract the reference. */ + if (tolower(attrbuf[0]) == 'j') + attrbuf = Html_get_javascript_link(html); + + url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0); + dReturn_if_fail ( url != NULL ); + + old_style = S_TOP(html)->style; + style_attrs = *old_style; + + if (a_Capi_get_buf(url, &buf, &buf_size)) { + html->InVisitedLink = TRUE; + style_attrs.color = Color::createSimple ( + HT2LT(html), + html->linkblock->visited_color +/* + a_Color_vc(html->linkblock->visited_color, + S_TOP(html)->style->color->getColor(), + html->linkblock->link_color, + S_TOP(html)->current_bg_color), +*/ + ); + } else { + style_attrs.color = Color::createSimple(HT2LT(html), + html->linkblock->link_color); + } + +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "title"))) +// style_attrs.x_tooltip = a_Dw_tooltip_new_no_ref(attrbuf); + + style_attrs.textDecoration |= TEXT_DECORATION_UNDERLINE; + style_attrs.x_link = Html_set_new_link(html, &url); + style_attrs.cursor = CURSOR_POINTER; + + S_TOP(html)->style = + Style::create (HT2LT(html), &style_attrs); + old_style->unref (); + } + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "name"))) { + if (prefs.show_extra_warnings) + Html_check_name_val(html, attrbuf, "name"); + /* html->NameVal is freed in Html_process_tag */ + html->NameVal = a_Url_decode_hex_str(attrbuf); + Html_add_anchor(html, html->NameVal); + } +} + +/* + * <A> close function + */ +static void Html_tag_close_a(DilloHtml *html, int TagIdx) +{ + html->InVisitedLink = FALSE; + Html_pop_tag(html, TagIdx); +} + +/* + * Insert underlined text in the page. + */ +static void Html_tag_open_u(DilloHtml *html, char *tag, int tagsize) +{ + Style *style; + StyleAttrs style_attrs; + + style = S_TOP(html)->style; + style_attrs = *style; + style_attrs.textDecoration |= TEXT_DECORATION_UNDERLINE; + S_TOP(html)->style = + Style::create (HT2LT(html), &style_attrs); + style->unref (); +} + +/* + * Insert strike-through text. Used by <S>, <STRIKE> and <DEL>. + */ +static void Html_tag_open_strike(DilloHtml *html, char *tag, int tagsize) +{ + Style *style; + StyleAttrs style_attrs; + + style = S_TOP(html)->style; + style_attrs = *style; + style_attrs.textDecoration |= TEXT_DECORATION_LINE_THROUGH; + S_TOP(html)->style = + Style::create (HT2LT(html), &style_attrs); + style->unref (); +} + +/* + * <BLOCKQUOTE> + */ +static void Html_tag_open_blockquote(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_add_indented(html, 40, 40, 9); +} + +/* + * Handle the <UL> tag. + */ +static void Html_tag_open_ul(DilloHtml *html, char *tag, int tagsize) +{ + const char *attrbuf; + ListStyleType list_style_type; + + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_add_indented(html, 40, 0, 9); + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "type"))) { + /* list_style_type explicitly defined */ + if (dStrncasecmp(attrbuf, "disc", 4) == 0) + list_style_type = LIST_STYLE_TYPE_DISC; + else if (dStrncasecmp(attrbuf, "circle", 6) == 0) + list_style_type = LIST_STYLE_TYPE_CIRCLE; + else if (dStrncasecmp(attrbuf, "square", 6) == 0) + list_style_type = LIST_STYLE_TYPE_SQUARE; + else + /* invalid value */ + list_style_type = LIST_STYLE_TYPE_DISC; + } else { + if (S_TOP(html)->list_type == HTML_LIST_UNORDERED) { + /* Nested <UL>'s. */ + /* --EG :: I changed the behavior here : types are cycling instead of + * being forced to square. It's easier for mixed lists level counting. + */ + switch (S_TOP(html)->style->listStyleType) { + case LIST_STYLE_TYPE_DISC: + list_style_type = LIST_STYLE_TYPE_CIRCLE; + break; + case LIST_STYLE_TYPE_CIRCLE: + list_style_type = LIST_STYLE_TYPE_SQUARE; + break; + case LIST_STYLE_TYPE_SQUARE: + default: /* this is actually a bug */ + list_style_type = LIST_STYLE_TYPE_DISC; + break; + } + } else { + /* Either first <UL>, or a <OL> before. */ + list_style_type = LIST_STYLE_TYPE_DISC; + } + } + + HTML_SET_TOP_ATTR(html, listStyleType, list_style_type); + S_TOP(html)->list_type = HTML_LIST_UNORDERED; + + S_TOP(html)->list_number = 0; + S_TOP(html)->ref_list_item = NULL; +} + +/* + * Handle the <MENU> tag. + * (Deprecated and almost the same as <UL>) + */ +static void Html_tag_open_menu(DilloHtml *html, char *tag, int tagsize) +{ + ListStyleType list_style_type = LIST_STYLE_TYPE_DISC; + + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_add_indented(html, 40, 0, 9); + HTML_SET_TOP_ATTR(html, listStyleType, list_style_type); + S_TOP(html)->list_type = HTML_LIST_UNORDERED; + S_TOP(html)->list_number = 0; + S_TOP(html)->ref_list_item = NULL; + + if (prefs.show_extra_warnings) + MSG_HTML("it is strongly recommended using <UL> instead of <MENU>\n"); +} + +/* + * Handle the <OL> tag. + */ +static void Html_tag_open_ol(DilloHtml *html, char *tag, int tagsize) +{ + const char *attrbuf; + ListStyleType list_style_type; + int n = 1; + + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_add_indented(html, 40, 0, 9); + + list_style_type = LIST_STYLE_TYPE_DECIMAL; + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "type"))) { + if (*attrbuf == '1') + list_style_type = LIST_STYLE_TYPE_DECIMAL; + else if (*attrbuf == 'a') + list_style_type = LIST_STYLE_TYPE_LOWER_ALPHA; + else if (*attrbuf == 'A') + list_style_type = LIST_STYLE_TYPE_UPPER_ALPHA; + else if (*attrbuf == 'i') + list_style_type = LIST_STYLE_TYPE_LOWER_ROMAN; + else if (*attrbuf == 'I') + list_style_type = LIST_STYLE_TYPE_UPPER_ROMAN; + } + + HTML_SET_TOP_ATTR(html, listStyleType, list_style_type); + S_TOP(html)->list_type = HTML_LIST_ORDERED; + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "start")) && + (n = (int) strtol(attrbuf, NULL, 10)) < 0) { + MSG_HTML( "illegal '-' character in START attribute; Starting from 0\n"); + n = 0; + } + S_TOP(html)->list_number = n; + S_TOP(html)->ref_list_item = NULL; +} + +/* + * Handle the <LI> tag. + */ +static void Html_tag_open_li(DilloHtml *html, char *tag, int tagsize) +{ + StyleAttrs style_attrs; + Style *item_style, *word_style; + Widget **ref_list_item; + ListItem *list_item; + int *list_number; + const char *attrbuf; + char buf[16]; + + /* Get our parent tag's variables (used as state storage) */ + list_number = &html->stack->getRef(html->stack->size()-2)->list_number; + ref_list_item = &html->stack->getRef(html->stack->size()-2)->ref_list_item; + + /* set the item style */ + word_style = S_TOP(html)->style; + style_attrs = *word_style; + //style_attrs.backgroundColor = Color::createSimple (HT2LT(html), 0xffff40); + //style_attrs.setBorderColor (Color::createSimple (HT2LT(html), 0x000000)); + //style_attrs.setBorderStyle (BORDER_SOLID); + //style_attrs.borderWidth.setVal (1); + item_style = Style::create (HT2LT(html), &style_attrs); + + DW2TB(html->dw)->addParbreak (2, word_style); + + list_item = new ListItem ((ListItem *)*ref_list_item, false); + DW2TB(html->dw)->addWidget (list_item, item_style); + DW2TB(html->dw)->addParbreak (2, word_style); + *ref_list_item = list_item; + S_TOP(html)->textblock = html->dw = list_item; + item_style->unref(); + + switch (S_TOP(html)->list_type) { + case HTML_LIST_ORDERED: + if ((attrbuf = Html_get_attr(html, tag, tagsize, "value")) && + (*list_number = strtol(attrbuf, NULL, 10)) < 0) { + MSG_HTML("illegal negative LIST VALUE attribute; Starting from 0\n"); + *list_number = 0; + } + numtostr((*list_number)++, buf, 16, S_TOP(html)->style->listStyleType); + list_item->initWithText (dStrdup(buf), word_style); + list_item->addSpace (word_style); + break; + case HTML_LIST_NONE: + MSG_HTML("<li> outside <ul> or <ol>\n"); + default: + list_item->initWithWidget (new Bullet(), word_style); + list_item->addSpace (word_style); + break; + } + + // list_item->flush (); Looks like there's no need to flush. + // If it MUST be done, it could be inside Html_tag_close_li(). +} + +/* + * Close <LI> + */ +static void Html_tag_close_li(DilloHtml *html, int TagIdx) +{ + ((ListItem *)html->dw)->flush (); + Html_pop_tag(html, TagIdx); +} + +/* + * <HR> + */ +static void Html_tag_open_hr(DilloHtml *html, char *tag, int tagsize) +{ + Widget *hruler; + StyleAttrs style_attrs; + Style *style; + char *width_ptr; + const char *attrbuf; + int32_t size = 0; + + width_ptr = Html_get_attr_wdef(html, tag, tagsize, "width", "100%"); + + style_attrs = *S_TOP(html)->style; + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "size"))) + size = strtol(attrbuf, NULL, 10); + + if ((attrbuf = Html_get_attr(html, tag, tagsize, "align"))) { + if (dStrcasecmp (attrbuf, "left") == 0) + style_attrs.textAlign = TEXT_ALIGN_LEFT; + else if (dStrcasecmp (attrbuf, "right") == 0) + style_attrs.textAlign = TEXT_ALIGN_RIGHT; + else if (dStrcasecmp (attrbuf, "center") == 0) + style_attrs.textAlign = TEXT_ALIGN_CENTER; + } + + /* todo: evaluate attribute */ + if (Html_get_attr(html, tag, tagsize, "noshade")) { + style_attrs.setBorderStyle (BORDER_SOLID); + style_attrs.setBorderColor ( + Color::createShaded (HT2LT(html), style_attrs.color->getColor())); + if (size < 1) + size = 1; + } else { + style_attrs.setBorderStyle (BORDER_INSET); + style_attrs.setBorderColor ( + Color::createShaded (HT2LT(html), + (S_TOP(html)->current_bg_color))); + if (size < 2) + size = 2; + } + + style_attrs.borderWidth.top = + style_attrs.borderWidth.left = (size + 1) / 2; + style_attrs.borderWidth.bottom = + style_attrs.borderWidth.right = size / 2; + style = Style::create (HT2LT(html), &style_attrs); + + DW2TB(html->dw)->addParbreak (5, S_TOP(html)->style); + hruler = new Ruler(); + hruler->setStyle (style); + DW2TB(html->dw)->addWidget (hruler, style); + style->unref (); + + //DW2TB(html->dw)->addWidget (hruler, S_TOP(html)->style); + DW2TB(html->dw)->addParbreak (5, S_TOP(html)->style); + dFree(width_ptr); +} + +/* + * <DL> + */ +static void Html_tag_open_dl(DilloHtml *html, char *tag, int tagsize) +{ + /* may want to actually do some stuff here. */ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); +} + +/* + * <DT> + */ +static void Html_tag_open_dt(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_set_top_font(html, NULL, 0, 1, 1); +} + +/* + * <DD> + */ +static void Html_tag_open_dd(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_add_indented(html, 40, 40, 9); +} + +/* + * <PRE> + */ +static void Html_tag_open_pre(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); + + /* Is the placement of this statement right? */ + S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_PRE; + HTML_SET_TOP_ATTR (html, whiteSpace, WHITE_SPACE_PRE); + html->pre_column = 0; + html->PreFirstChar = TRUE; + html->InFlags |= IN_PRE; +} + +/* + * Custom close for <PRE> + */ +static void Html_tag_close_pre(DilloHtml *html, int TagIdx) +{ + html->InFlags &= ~IN_PRE; + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_pop_tag(html, TagIdx); +} + +/* + * Check whether a tag is in the "excluding" element set for PRE + * Excl. Set = {IMG, OBJECT, APPLET, BIG, SMALL, SUB, SUP, FONT, BASEFONT} + */ +static int Html_tag_pre_excludes(int tag_idx) +{ + char *es_set[] = {"img", "object", "applet", "big", "small", "sub", "sup", + "font", "basefont", NULL}; + static int ei_set[10], i; + + /* initialize array */ + if (!ei_set[0]) + for (i = 0; es_set[i]; ++i) + ei_set[i] = Html_tag_index(es_set[i]); + + for (i = 0; ei_set[i]; ++i) + if (tag_idx == ei_set[i]) + return 1; + return 0; +} + +/* + * Handle <FORM> tag + */ +static void Html_tag_open_form(DilloHtml *html, char *tag, int tagsize) +{ + DilloUrl *action; + DilloHtmlMethod method; + DilloHtmlEnc enc; + const char *attrbuf; + + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + + if (html->InFlags & IN_FORM) { + MSG_HTML("nested forms\n"); + return; + } + html->InFlags |= IN_FORM; + + method = DILLO_HTML_METHOD_GET; + if ((attrbuf = Html_get_attr(html, tag, tagsize, "method"))) { + if (!dStrcasecmp(attrbuf, "post")) + method = DILLO_HTML_METHOD_POST; + /* todo: maybe deal with unknown methods? */ + } + if ((attrbuf = Html_get_attr(html, tag, tagsize, "action"))) + action = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0); + else + action = a_Url_dup(html->linkblock->base_url); + enc = DILLO_HTML_ENC_URLENCODING; + if ((attrbuf = Html_get_attr(html, tag, tagsize, "encoding"))) { + /* todo: maybe deal with unknown encodings? */ + } + Html_form_new(html->linkblock, method, action, enc); + a_Url_free(action); +} + +static void Html_tag_close_form(DilloHtml *html, int TagIdx) +{ + static char *SubmitTag = + "<input type='submit' value='?Submit?' alt='dillo-generated-button'>"; + DilloHtmlForm *form; +// int i; + + if (html->InFlags & IN_FORM) { + form = html->linkblock->forms->getRef ( + html->linkblock->forms->size() - 1); + /* If we don't have a submit button and the user desires one, + let's add a custom one */ + if (form->num_submit_buttons == 0) { + if (prefs.show_extra_warnings || form->num_entry_fields != 1) + MSG_HTML("FORM lacks a Submit button\n"); + if (prefs.generate_submit) { + MSG_HTML(" (added a submit button internally)\n"); + Html_tag_open_input(html, SubmitTag, strlen(SubmitTag)); + form->num_submit_buttons = 0; + } + } + +// /* Make buttons sensitive again */ +// for (i = 0; i < form->inputs->size(); i++) { +// input_i = form->inputs->getRef(i); +// /* Check for tricky HTML (e.g. <input type=image>) */ +// if (!input_i->widget) +// continue; +// if (input_i->type == DILLO_HTML_INPUT_SUBMIT || +// input_i->type == DILLO_HTML_INPUT_RESET) { +// gtk_widget_set_sensitive(input_i->widget, TRUE); +// } else if (input_i->type == DILLO_HTML_INPUT_IMAGE || +// input_i->type == DILLO_HTML_INPUT_BUTTON_SUBMIT || +// input_i->type == DILLO_HTML_INPUT_BUTTON_RESET) { +// a_Dw_button_set_sensitive(DW_BUTTON(input_i->widget), TRUE); +// } +// } + } + html->InFlags &= ~IN_FORM; + html->InFlags &= ~IN_SELECT; + html->InFlags &= ~IN_TEXTAREA; + Html_pop_tag(html, TagIdx); +} + +/* + * Handle <META> + * We do not support http-equiv=refresh because it's non standard, + * (the HTML 4.01 SPEC recommends explicitily to avoid it), and it + * can be easily abused! + * + * More info at: + * http://lists.w3.org/Archives/Public/www-html/2000Feb/thread.html#232 + * + * todo: Note that we're sending custom HTML while still IN_HEAD. This + * is a hackish way to put the message. A much cleaner approach is to + * build a custom widget for it. + */ +static void Html_tag_open_meta(DilloHtml *html, char *tag, int tagsize) +{ + const char *meta_template = +"<table width='100%%'><tr><td bgcolor='#ee0000'>Warning:</td>\n" +" <td bgcolor='#8899aa' width='100%%'>\n" +" This page uses the NON-STANDARD meta refresh tag.<br> The HTML 4.01 SPEC\n" +" (sec 7.4.4) recommends explicitly to avoid it.</td></tr>\n" +" <tr><td bgcolor='#a0a0a0' colspan='2'>The author wanted you to go\n" +" <a href='%s'>here</a>%s</td></tr></table><br>\n"; + + const char *equiv, *content; + char delay_str[64]; + Dstr *ds_msg; + int delay; + + /* only valid inside HEAD */ + if (!(html->InFlags & IN_HEAD)) { + MSG_HTML("META elements must be inside the HEAD section\n"); + return; + } + + if ((equiv = Html_get_attr(html, tag, tagsize, "http-equiv")) && + !dStrcasecmp(equiv, "refresh") && + (content = Html_get_attr(html, tag, tagsize, "content"))) { + + /* Get delay, if present, and make a message with it */ + if ((delay = strtol(content, NULL, 0))) + snprintf(delay_str, 64, " after %d second%s.", + delay, (delay > 1) ? "s" : ""); + else + sprintf(delay_str, "."); + + /* Skip to anything after "URL=" */ + while (*content && *(content++) != '='); + + /* Send a custom HTML message + * todo: this is a hairy hack, It'd be much better to build a widget. */ + ds_msg = dStr_sized_new(256); + dStr_sprintf(ds_msg, meta_template, content, delay_str); + { + int SaveFlags = html->InFlags; + html->InFlags = IN_BODY; + html->TagSoup = FALSE; + Html_write_raw(html, ds_msg->str, ds_msg->len, 0); + html->TagSoup = TRUE; + html->InFlags = SaveFlags; + } + dStr_free(ds_msg, 1); + } +} + +/* + * Set the history of the menu to be consistent with the active menuitem. + */ +//static void Html_select_set_history(DilloHtmlInput *input) +//{ +// int i; +// +// for (i = 0; i < input->select->num_options; i++) { +// if (GTK_CHECK_MENU_ITEM(input->select->options[i].menuitem)->active) { +// gtk_option_menu_set_history(GTK_OPTION_MENU(input->widget), i); +// break; +// } +// } +//} + +/* + * Reset the input widget to the initial value. + */ +static void Html_reset_input(DilloHtmlInput *input) +{ + int i; + + switch (input->type) { + case DILLO_HTML_INPUT_TEXT: + case DILLO_HTML_INPUT_PASSWORD: + EntryResource *entryres; + entryres = (EntryResource*)((Embed*)input->widget)->getResource(); + entryres->setText(input->init_str ? input->init_str : ""); + break; + case DILLO_HTML_INPUT_CHECKBOX: + case DILLO_HTML_INPUT_RADIO: + ToggleButtonResource *tb_r; + tb_r = (ToggleButtonResource*)((Embed*)input->widget)->getResource(); + tb_r->setActivated(input->init_val); + break; + case DILLO_HTML_INPUT_SELECT: + if (input->select != NULL) { + /* this is in reverse order so that, in case more than one was + * selected, we get the last one, which is consistent with handling + * of multiple selected options in the layout code. */ + for (i = input->select->num_options - 1; i >= 0; i--) { + if (input->select->options[i].init_val) { +// gtk_menu_item_activate(GTK_MENU_ITEM +// (input->select->options[i].menuitem)); +// Html_select_set_history(input); + break; + } + } + } + break; + case DILLO_HTML_INPUT_SEL_LIST: + if (!input->select) + break; + for (i = 0; i < input->select->num_options; i++) { +// if (input->select->options[i].init_val) { +// if (input->select->options[i].menuitem->state == GTK_STATE_NORMAL) +// gtk_list_select_child(GTK_LIST(input->select->menu), +// input->select->options[i].menuitem); +// } else { +// if (input->select->options[i].menuitem->state==GTK_STATE_SELECTED) +// gtk_list_unselect_child(GTK_LIST(input->select->menu), +// input->select->options[i].menuitem); +// } + } + break; + case DILLO_HTML_INPUT_TEXTAREA: + if (input->init_str != NULL) { +// int pos = 0; +// gtk_editable_delete_text(GTK_EDITABLE(input->widget), 0, -1); +// gtk_editable_insert_text(GTK_EDITABLE(input->widget), input->init_str, +// strlen(input->init_str), &pos); + } + break; + default: + break; + } +} + + +/* + * Add a new input to the form data structure, setting the initial + * values. + */ +static void Html_add_input(DilloHtmlForm *form, + DilloHtmlInputType type, + Widget *widget, + Embed *embed, + const char *name, + const char *init_str, + DilloHtmlSelect *select, + bool_t init_val) +{ + DilloHtmlInput *input; + + _MSG("name=[%s] init_str=[%s] init_val=[%d]\n", + name, init_str, init_val); + form->inputs->increase(); + input = form->inputs->getRef (form->inputs->size() - 1); + input->type = type; + input->widget = widget; + input->embed = embed; + input->name = (name) ? dStrdup(name) : NULL; + input->init_str = (init_str) ? dStrdup(init_str) : NULL; + input->select = select; + input->init_val = init_val; + Html_reset_input(input); + + /* some stats */ + if (type == DILLO_HTML_INPUT_PASSWORD || + type == DILLO_HTML_INPUT_TEXT || + type == DILLO_HTML_INPUT_TEXTAREA) { + form->num_entry_fields++; + } else if (type == DILLO_HTML_INPUT_SUBMIT || + type == DILLO_HTML_INPUT_BUTTON_SUBMIT || + type == DILLO_HTML_INPUT_IMAGE) { + form->num_submit_buttons++; + } +} + + +/* + * Given a GtkWidget, find the form that contains it. + * Return value: form_index if successful, -1 otherwise. + */ +//static int Html_find_form(GtkWidget *reset, DilloHtmlLB *html_lb) +//{ +// int form_index; +// int input_idx; +// DilloHtmlForm *form; +// +// for (form_index = 0; form_index < html_lb->forms->size(); form_index++) { +// form = html_lb->forms->getRef (form_index); +// for (input_idx = 0; input_idx < form->inputs->size(); input_idx++) { +// if (form->inputs->get(input_idx).widget == reset) { +// return form_index; +// } +// } +// } +// return -1; +//} + +/* + * Reset all inputs in the form containing reset to their initial values. + * In general, reset is the reset button for the form. + */ +//static void Html_reset_form(GtkWidget *reset, DilloHtmlLB *html_lb) +//{ +// int i, j; +// DilloHtmlForm *form; +// +// if ((i = Html_find_form(reset, html_lb)) != -1){ +// form = html_lb->forms->getRef (i); +// for ( j = 0; j < form->inputs->size(); j++) +// Html_reset_input(&(form->inputs[j])); +// } +//} + +/* + * Urlencode 'val' and append it to 'str' + */ +static void Html_urlencode_append(Dstr *str, const char *val) +{ + char *enc_val = a_Url_encode_hex_str(val); + dStr_append(str, enc_val); + dFree(enc_val); +} + +/* + * Append a name-value pair to an existing url. + * (name and value are urlencoded before appending them) + */ +static void + Html_append_input(Dstr *url, const char *name, const char *value) +{ + if (name != NULL) { + Html_urlencode_append(url, name); + dStr_append_c(url, '='); + Html_urlencode_append(url, value); + dStr_append_c(url, '&'); + } +} + +/* + * Append a image button click position to an existing url. + */ +//static void Html_append_clickpos(Dstr *url, const char *name, int x, int y) +//{ +// if (name) { +// Html_urlencode_append(url, name); +// dStr_sprintfa(url, ".x=%d&", x); +// Html_urlencode_append(url, name); +// dStr_sprintfa(url, ".y=%d&", y); +// } else +// dStr_sprintfa(url, "x=%d&y=%d&", x, y); +//} + +/* + * Submit the form containing the submit input by making a new query URL + * and sending it with a_Nav_push. + * (Called by GTK+) + * click_x and click_y are used only by input images and are set only when + * called by Html_image_clicked. GTK+ does NOT give these arguments. + */ +static void Html_submit_form2(DilloHtmlLB *html_lb, DilloHtmlForm *form, + int e_input_idx) +{ + int input_idx; + DilloHtmlInput *input; + DilloUrl *new_url; + char *url_str, *action_str, *p; + + if ((form->method == DILLO_HTML_METHOD_GET) || + (form->method == DILLO_HTML_METHOD_POST)) { + Dstr *DataStr = dStr_sized_new(4096); + + _MSG("Html_submit_form2: form->action=%s\n",URL_STR_(form->action)); + + for (input_idx = 0; input_idx < form->inputs->size(); input_idx++) { + input = form->inputs->getRef (input_idx); + switch (input->type) { + case DILLO_HTML_INPUT_TEXT: + case DILLO_HTML_INPUT_PASSWORD: + EntryResource *entryres; + entryres = (EntryResource*)((Embed*)input->widget)->getResource(); + Html_append_input(DataStr, input->name, entryres->getText()); + break; + case DILLO_HTML_INPUT_CHECKBOX: + case DILLO_HTML_INPUT_RADIO: + ToggleButtonResource *cb_r; + cb_r=(ToggleButtonResource*)((Embed*)input->widget)->getResource(); + if (input->name && input->init_str && cb_r->isActivated()) { + Html_append_input(DataStr, input->name, input->init_str); + } + break; + case DILLO_HTML_INPUT_HIDDEN: + Html_append_input(DataStr, input->name, input->init_str); + break; +// case DILLO_HTML_INPUT_SELECT: +// for (i = 0; i < input->select->num_options; i++) { +// if (GTK_CHECK_MENU_ITEM(input->select->options[i].menuitem)-> +// active) { +// Html_append_input(DataStr, input->name, +// input->select->options[i].value); +// break; +// } +// } +// break; +// case DILLO_HTML_INPUT_SEL_LIST: +// for (i = 0; i < input->select->num_options; i++) { +// if (input->select->options[i].menuitem->state == +// GTK_STATE_SELECTED) { +// Html_append_input(DataStr, input->name, +// input->select->options[i].value); +// } +// } +// break; +// case DILLO_HTML_INPUT_TEXTAREA: +// text = gtk_editable_get_chars(GTK_EDITABLE (input->widget),0,-1); +// Html_append_input(DataStr, input->name, text); +// dFree(text); +// break; +// case DILLO_HTML_INPUT_INDEX: +// Html_urlencode_append(DataStr, +// gtk_entry_get_text(GTK_ENTRY(input->widget))); +// break; +// case DILLO_HTML_INPUT_IMAGE: +// if (input->widget == submit) { +// Html_append_input(DataStr, input->name, input->init_str); +// Html_append_clickpos(DataStr, input->name, click_x, click_y); +// } +// break; + case DILLO_HTML_INPUT_SUBMIT: + case DILLO_HTML_INPUT_BUTTON_SUBMIT: + if (input_idx == e_input_idx && form->num_submit_buttons > 0) + Html_append_input(DataStr, input->name, input->init_str); + break; + default: + break; + } /* switch */ + } /* for (inputs) */ + + if (DataStr->str[DataStr->len - 1] == '&') + dStr_truncate(DataStr, DataStr->len - 1); + + /* form->action was previously resolved against base URL */ + action_str = dStrdup(URL_STR(form->action)); + + if (form->method == DILLO_HTML_METHOD_POST) { + new_url = a_Url_new(action_str, NULL, 0, 0, 0); + a_Url_set_data(new_url, DataStr->str); + a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_Post); + } else { + /* remove <fragment> and <query> sections if present */ + if ((p = strchr(action_str, '#'))) + *p = 0; + if ((p = strchr(action_str, '?'))) + *p = 0; + + url_str = dStrconcat(action_str, "?", DataStr->str, NULL); + new_url = a_Url_new(url_str, NULL, 0, 0, 0); + a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_Get); + dFree(url_str); + } + + a_Nav_push(html_lb->bw, new_url); + dFree(action_str); + dStr_free(DataStr, TRUE); + a_Url_free(new_url); + } else { + MSG("Html_submit_form2: Method unknown\n"); + } + +// /* now, make the rendered area have its focus back */ +// gtk_widget_grab_focus(GTK_BIN(html_lb->bw->render_main_scroll)->child); +} + +/* + * Handler for events related to forms. + * + * TODO: Currently there's "clicked" for buttons, we surely need "enter" for + * textentries, and maybe the "mouseover, ...." set for Javascript. + */ +void a_Html_form_event_handler(void *data, + form::Form *form_receiver, + void *v_resource) +{ + int form_index, input_idx = -1, idx; + DilloHtmlForm *form = NULL; + DilloHtmlLB *html_lb = (DilloHtmlLB*)data; + + printf("Html_form_event_handler %p %p\n", html_lb, form_receiver); + + /* Search the form that generated the submit event */ + for (form_index = 0; form_index < html_lb->forms->size(); form_index++) { + form = html_lb->forms->getRef (form_index); + if (form->form_receiver == form_receiver) { + /* form found, let's get the input index for this event */ + for (idx = 0; idx < form->inputs->size(); idx++) { + DilloHtmlInput *input = form->inputs->getRef(idx); + if (input->embed && + v_resource == (void*)((Embed*)input->widget)->getResource()) { + input_idx = idx; + break; + } + } + break; + } + } + if (form_index == html_lb->forms->size()) { + MSG("a_Html_form_event_handler: ERROR, form not found!\n"); + } else { + Html_submit_form2(html_lb, form, input_idx); + } +} + +/* + * Submit form if it has no submit button. + * (Called by GTK+ when the user presses enter in a text entry within a form) + */ +//static void Html_enter_submit_form(GtkWidget *submit, DilloHtmlLB *html_lb) +//{ +// int i; +// +// /* Search the form that generated the submit event */ +// if ((i = Html_find_form(submit, html_lb)) == -1) +// return; +// +// /* Submit on enterpress when there's a single text-entry only, +// * or if the user set enter to always submit */ +// if ((html_lb->forms->getRef(i))->.num_entry_fields == 1) || +// prefs.enterpress_forces_submit) +// Html_submit_form(submit, html_lb, 1, 1); +//} + +/* + * Call submit form, when input image has been clicked + */ +//static void Html_image_clicked(Widget *widget, int x, int y, +// DilloHtmlLB *lb) +//{ +// _MSG("Hallo! (%d, %d, %p)\n", x, y, lb); +// Html_submit_form((GtkWidget*) widget, lb, x, y); +//} + +/* + * Create input image for the form + */ +static Widget *Html_input_image(DilloHtml *html, char *tag, int tagsize, + DilloHtmlLB *html_lb, DilloUrl *action) +{ +// // AL +// DilloImage *Image; +// Widget *button; +// DilloUrl *url = NULL; +// Style style_attrs; +// const char *attrbuf; +// +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "src")) && +// (url = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0))) { +// button = a_Dw_button_new (0, FALSE); +// DW2TB(html->dw)->addWidget (button, +// S_TOP(html)->style); +// gtk_signal_connect(GTK_OBJECT(button), "clicked_at", +// GTK_SIGNAL_FUNC(Html_image_clicked), html_lb); +// a_Dw_button_set_sensitive(DW_BUTTON(button), FALSE); +// +// /* create new image and add it to the button */ +// if ((Image = Html_add_new_image(html, tag, tagsize, &style_attrs, +// FALSE))) { +// /* By suppressing the "image_pressed" signal, the events are sent +// * to the parent DwButton */ +// a_Dw_widget_set_button_sensitive (IM2DW(Image->dw), FALSE); +// IM2DW(Image->dw)->setStyle (S_TOP(html)->style); +// a_Dw_container_add(DW_CONTAINER(button), IM2DW(Image->dw)); +// IM2DW(Image->dw)->setCursor (CURSOR_HAND); +// Html_load_image(html, url, Image); +// a_Url_free(url); +// return button; +// } +// } +// +// DEBUG_MSG(10, "Html_input_image: unable to create image submit.\n"); +// a_Url_free(url); + return NULL; +} + +/* + * Add a new input to current form + */ +static void Html_tag_open_input(DilloHtml *html, char *tag, int tagsize) +{ + DilloHtmlForm *form; + DilloHtmlInputType inp_type; + DilloHtmlLB *html_lb; + Widget *widget = NULL; + Embed *embed = NULL; + char *value, *name, *type, *init_str; +// const char *attrbuf, *label; + bool_t init_val = FALSE; + int input_idx; + + if (!(html->InFlags & IN_FORM)) { + MSG_HTML("input camp outside <form>\n"); + return; + } + + html_lb = html->linkblock; + form = html_lb->forms->getRef (html_lb->forms->size() - 1); + + /* Get 'value', 'name' and 'type' */ + value = Html_get_attr_wdef(html, tag, tagsize, "value", NULL); + name = Html_get_attr_wdef(html, tag, tagsize, "name", NULL); + type = Html_get_attr_wdef(html, tag, tagsize, "type", ""); + + init_str = NULL; + inp_type = DILLO_HTML_INPUT_UNKNOWN; + if (!dStrcasecmp(type, "password")) { + inp_type = DILLO_HTML_INPUT_PASSWORD; + EntryResource *entryResource = + HT2LT(html)->getResourceFactory()->createEntryResource (15, true); + embed = new Embed (entryResource); + widget = embed; + init_str = (value) ? value : NULL; + } else if (!dStrcasecmp(type, "checkbox")) { + inp_type = DILLO_HTML_INPUT_CHECKBOX; + CheckButtonResource *check_b_r = HT2LT(html)->getResourceFactory() + ->createCheckButtonResource(false); + embed = new Embed (check_b_r); + widget = embed; + init_val = (Html_get_attr(html, tag, tagsize, "checked") != NULL); + init_str = (value) ? value : dStrdup("on"); + } else if (!dStrcasecmp(type, "radio")) { + inp_type = DILLO_HTML_INPUT_RADIO; + RadioButtonResource *rb_r = NULL; + for (input_idx = 0; input_idx < form->inputs->size(); input_idx++) { + DilloHtmlInput *input = form->inputs->getRef(input_idx); + if (input->type == DILLO_HTML_INPUT_RADIO && + (input->name && !dStrcasecmp(input->name, name)) ) { + rb_r =(RadioButtonResource*)((Embed*)input->widget)->getResource(); + break; + } + } + rb_r = HT2LT(html)->getResourceFactory() + ->createRadioButtonResource(rb_r, false); + embed = new Embed (rb_r); + widget = embed; + + init_val = (Html_get_attr(html, tag, tagsize, "checked") != NULL); + init_str = (value) ? value : NULL; + } else if (!dStrcasecmp(type, "hidden")) { + inp_type = DILLO_HTML_INPUT_HIDDEN; + if (value) + init_str = dStrdup(Html_get_attr(html, tag, tagsize, "value")); + } else if (!dStrcasecmp(type, "submit")) { + inp_type = DILLO_HTML_INPUT_SUBMIT; + init_str = (value) ? value : dStrdup("submit"); + LabelButtonResource *label_b_r = HT2LT(html)->getResourceFactory() + ->createLabelButtonResource(init_str); + widget = embed = new Embed (label_b_r); +// gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */ + label_b_r->connectClicked (form->form_receiver); + } else if (!dStrcasecmp(type, "reset")) { + inp_type = DILLO_HTML_INPUT_RESET; + init_str = (value) ? value : dStrdup("Reset"); + LabelButtonResource *label_b_r = HT2LT(html)->getResourceFactory() + ->createLabelButtonResource(init_str); + widget = embed = new Embed (label_b_r); +// gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */ +// gtk_signal_connect(GTK_OBJECT(widget), "clicked", +// GTK_SIGNAL_FUNC(Html_reset_form), html_lb); +// } else if (!dStrcasecmp(type, "image")) { +// if (URL_FLAGS(html->linkblock->base_url) & URL_SpamSafe) { +// /* Don't request the image, make a text submit button instead */ +// inp_type = DILLO_HTML_INPUT_SUBMIT; +// attrbuf = Html_get_attr(html, tag, tagsize, "alt"); +// label = attrbuf ? attrbuf : value ? value : name ? name : "Submit"; +// init_str = dStrdup(label); +// widget = gtk_button_new_with_label(init_str); +// gtk_widget_set_sensitive(widget, FALSE); /* Until end of FORM! */ +// gtk_signal_connect(GTK_OBJECT(widget), "clicked", +// GTK_SIGNAL_FUNC(Html_submit_form), html_lb); +// } else { +// inp_type = DILLO_HTML_INPUT_IMAGE; +// /* use a dw_image widget */ +// widget = (GtkWidget*) Html_input_image(html, tag, tagsize, +// html_lb, form->action); +// init_str = value; +// } +// } else if (!dStrcasecmp(type, "file")) { +// /* todo: implement it! */ +// inp_type = DILLO_HTML_INPUT_FILE; +// init_str = (value) ? value : NULL; +// MSG("An input of the type \"file\" wasn't rendered!\n"); + } else if (!dStrcasecmp(type, "button")) { + inp_type = DILLO_HTML_INPUT_BUTTON; + if (value) { + init_str = value; + LabelButtonResource *label_b_r = HT2LT(html)->getResourceFactory() + ->createLabelButtonResource(init_str); + widget = embed = new Embed (label_b_r); + } + } else if (!dStrcasecmp(type, "text") || !*type) { + /* Text input, which also is the default */ + inp_type = DILLO_HTML_INPUT_TEXT; + EntryResource *entryResource = + //HT2LT(html)->getResourceFactory()->createEntryResource (15, false); + HT2LT(html)->getResourceFactory()->createEntryResource (10, false); + widget = embed = new Embed (entryResource); + init_str = (value) ? value : NULL; + } else { + /* Unknown input type */ + MSG_HTML("Unknown input type: \"%s\"\n", type); + } + + if (inp_type != DILLO_HTML_INPUT_UNKNOWN) { + Html_add_input(form, inp_type, widget, embed, name, + (init_str) ? init_str : "", NULL, init_val); + } + + if (widget != NULL && inp_type != DILLO_HTML_INPUT_IMAGE && + inp_type != DILLO_HTML_INPUT_UNKNOWN) { + if (inp_type == DILLO_HTML_INPUT_TEXT || + inp_type == DILLO_HTML_INPUT_PASSWORD) { + EntryResource *entryres = (EntryResource*)embed->getResource(); + /* Readonly or not? */ + if (Html_get_attr(html, tag, tagsize, "readonly")) + entryres->setEditable(false); + +// /* Set width of the entry */ +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "size"))) +// gtk_widget_set_usize(widget, (strtol(attrbuf, NULL, 10) + 1) * +// gdk_char_width(widget->style->font, '0'), 0); +// +// /* Maximum length of the text in the entry */ +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "maxlength"))) +// gtk_entry_set_max_length(GTK_ENTRY(widget), +// strtol(attrbuf, NULL, 10)); + } + + DW2TB(html->dw)->addWidget (embed, S_TOP(html)->style); + } + + dFree(type); + dFree(name); + if (init_str != value) + dFree(init_str); + dFree(value); +} + +/* + * The ISINDEX tag is just a deprecated form of <INPUT type=text> with + * implied FORM, afaics. + */ +static void Html_tag_open_isindex(DilloHtml *html, char *tag, int tagsize) +{ +// // AL +// DilloHtmlForm *form; +// DilloHtmlLB *html_lb; +// DilloUrl *action; +// GtkWidget *widget; +// Widget *embed_gtk; +// const char *attrbuf; +// +// html_lb = html->linkblock; +// +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "action"))) +// action = Html_url_new(html, attrbuf, NULL, 0, 0, 0, 0); +// else +// action = a_Url_dup(html->linkblock->base_url); +// +// Html_form_new(html->linkblock, DILLO_HTML_METHOD_GET, action, +// DILLO_HTML_ENC_URLENCODING); +// +// form = html_lb->forms->getRef (html_lb->forms->size() - 1); +// +// DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); +// +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "prompt"))) +// DW2TB(html->dw)->addText(dStrdup(attrbuf), +// S_TOP(html)->style); +// +// widget = gtk_entry_new(); +// Html_add_input(form, DILLO_HTML_INPUT_INDEX, +// widget, NULL, NULL, NULL, FALSE); +// gtk_signal_connect(GTK_OBJECT(widget), "activate", +// GTK_SIGNAL_FUNC(Html_enter_submit_form), +// html_lb); +// gtk_widget_show(widget); +// /* compare <input type=text> */ +// gtk_signal_connect_after(GTK_OBJECT(widget), "button_press_event", +// GTK_SIGNAL_FUNC(gtk_true), +// NULL); +// +// embed_gtk = a_Dw_embed_gtk_new(); +// a_Dw_embed_gtk_add_gtk(DW_EMBED_GTK(embed_gtk), widget); +// DW2TB(html->dw)->addWidget (embed_gtk, +// S_TOP(html)->style); +// +// a_Url_free(action); +} + +/* + * Close textarea + * (TEXTAREA is parsed in VERBATIM mode, and entities are handled here) + */ +static void Html_tag_close_textarea(DilloHtml *html, int TagIdx) +{ + DilloHtmlLB *html_lb = html->linkblock; + char *str; + DilloHtmlForm *form; + int i; + + if (html->InFlags & IN_FORM && html->InFlags & IN_TEXTAREA) { + /* Remove the line ending that follows the opening tag */ + if (html->Stash->str[0] == '\r') + dStr_erase(html->Stash, 0, 1); + if (html->Stash->str[0] == '\n') + dStr_erase(html->Stash, 0, 1); + + /* As the spec recommends to canonicalize line endings, it is safe + * to replace '\r' with '\n'. It will be canonicalized anyway! */ + for (i = 0; i < html->Stash->len; ++i) { + if (html->Stash->str[i] == '\r') { + if (html->Stash->str[i + 1] == '\n') + dStr_erase(html->Stash, i, 1); + else + html->Stash->str[i] = '\n'; + } + } + + /* The HTML3.2 spec says it can have "text and character entities". */ + str = Html_parse_entities(html, html->Stash->str, html->Stash->len); + + form = html_lb->forms->getRef (html_lb->forms->size() - 1); + form->inputs->get(form->inputs->size() - 1).init_str = str; +// gtk_text_insert(GTK_TEXT(form->inputs[form->inputs->size() - 1].widget), +// NULL, NULL, NULL, str, -1); + + html->InFlags &= ~IN_TEXTAREA; + } + Html_pop_tag(html, TagIdx); +} + +/* + * The textarea tag + * (todo: It doesn't support wrapping). + */ +static void Html_tag_open_textarea(DilloHtml *html, char *tag, int tagsize) +{ +// // AL +// DilloHtmlLB *html_lb; +// DilloHtmlForm *form; +// GtkWidget *widget; +// GtkWidget *scroll; +// Widget *embed_gtk; +// char *name; +// const char *attrbuf; +// int cols, rows; +// +// /* We can't push a new <FORM> because the 'action' URL is unknown */ +// if (!(html->InFlags & IN_FORM)) { +// MSG_HTML("<textarea> outside <form>\n"); +// html->ReqTagClose = TRUE; +// return; +// } +// if (html->InFlags & IN_TEXTAREA) { +// MSG_HTML("nested <textarea>\n"); +// html->ReqTagClose = TRUE; +// return; +// } +// +// html->InFlags |= IN_TEXTAREA; +// html_lb = html->linkblock; +// form = html_lb->forms->getRef (html_lb->forms->size() - 1); +// Html_stash_init(html); +// S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM; +// +// cols = 20; +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "cols"))) +// cols = strtol(attrbuf, NULL, 10); +// rows = 10; +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "rows"))) +// rows = strtol(attrbuf, NULL, 10); +// name = NULL; +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "name"))) +// name = dStrdup(attrbuf); +// +// widget = gtk_text_new(NULL, NULL); +// /* compare <input type=text> */ +// gtk_signal_connect_after(GTK_OBJECT(widget), "button_press_event", +// GTK_SIGNAL_FUNC(gtk_true), +// NULL); +// +// /* Calculate the width and height based on the cols and rows +// * todo: Get it right... Get the metrics from the font that will be used. +// */ +// gtk_widget_set_usize(widget, 6 * cols, 16 * rows); +// +// /* If the attribute readonly isn't specified we make the textarea +// * editable. If readonly is set we don't have to do anything. +// */ +// if (!Html_get_attr(html, tag, tagsize, "readonly")) +// gtk_text_set_editable(GTK_TEXT(widget), TRUE); +// +// scroll = gtk_scrolled_window_new(NULL, NULL); +// gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), +// GTK_POLICY_AUTOMATIC, +// GTK_POLICY_AUTOMATIC); +// gtk_container_add(GTK_CONTAINER(scroll), widget); +// gtk_widget_show(widget); +// gtk_widget_show(scroll); +// +// Html_add_input(form, DILLO_HTML_INPUT_TEXTAREA, +// widget, name, NULL, NULL, FALSE); +// dFree(name); +// +// embed_gtk = a_Dw_embed_gtk_new (); +// a_Dw_embed_gtk_add_gtk (DW_EMBED_GTK (embed_gtk), scroll); +// DW2TB(html->dw)->addWidget (embed_gtk, +// S_TOP(html)->style); +} + +/* + * <SELECT> + */ +/* The select tag is quite tricky, because of gorpy html syntax. */ +static void Html_tag_open_select(DilloHtml *html, char *tag, int tagsize) +{ +// DilloHtmlForm *form; +// DilloHtmlSelect *Select; +// DilloHtmlLB *html_lb; +// GtkWidget *widget, *menu; +// char *name; +// const char *attrbuf; +// int size, type, multi; +// +// if (!(html->InFlags & IN_FORM)) { +// MSG_HTML("<select> outside <form>\n"); +// return; +// } +// if (html->InFlags & IN_SELECT) { +// MSG_HTML("nested <select>\n"); +// return; +// } +// html->InFlags |= IN_SELECT; +// +// html_lb = html->linkblock; +// +// form = html_lb->forms->getRef (html_lb->forms->size() - 1); +// +// name = Html_get_attr_wdef(html, tag, tagsize, "name", NULL); +// +// size = 0; +// if ((attrbuf = Html_get_attr(html, tag, tagsize, "size"))) +// size = strtol(attrbuf, NULL, 10); +// +// multi = (Html_get_attr(html, tag, tagsize, "multiple")) ? 1 : 0; +// if (size < 1) +// size = multi ? 10 : 1; +// +// if (size == 1) { +// menu = gtk_menu_new(); +// widget = gtk_option_menu_new(); +// type = DILLO_HTML_INPUT_SELECT; +// } else { +// menu = gtk_list_new(); +// widget = menu; +// if (multi) +// gtk_list_set_selection_mode(GTK_LIST(menu), GTK_SELECTION_MULTIPLE); +// type = DILLO_HTML_INPUT_SEL_LIST; +// } +// +// Select = dNew(DilloHtmlSelect, 1); +// Select->menu = menu; +// Select->size = size; +// Select->num_options = 0; +// Select->num_options_max = 8; +// Select->options = dNew(DilloHtmlOption, Select->num_options_max); +// +// Html_add_input(form, type, widget, name, NULL, Select, FALSE); +// Html_stash_init(html); +// dFree(name); +} + +/* + * ? + */ +static void Html_option_finish(DilloHtml *html) +{ +// DilloHtmlForm *form; +// DilloHtmlInput *input; +// GtkWidget *menuitem; +// GSList *group; +// DilloHtmlSelect *select; +// +// if (!(html->InFlags & IN_FORM)) +// return; +// +// form = html->linkblock->forms->getRef (html->linkblock->forms->size() - 1); +// input = form->inputs->getRef (form->inputs->size() - 1); +// if (input->select->num_options <= 0) +// return; +// +// select = input->select; +// if (input->type == DILLO_HTML_INPUT_SELECT ) { +// if (select->num_options == 1) +// group = NULL; +// else +// group = gtk_radio_menu_item_group(GTK_RADIO_MENU_ITEM +// (select->options[0].menuitem)); +// menuitem = gtk_radio_menu_item_new_with_label(group, html->Stash->str); +// select->options[select->num_options - 1].menuitem = menuitem; +// if (select->options[select->num_options - 1].value == NULL) +// select->options[select->num_options - 1].value = +// dStrdup(html->Stash->str); +// gtk_menu_append(GTK_MENU(select->menu), menuitem); +// if (select->options[select->num_options - 1].init_val) +// gtk_menu_item_activate(GTK_MENU_ITEM(menuitem)); +// gtk_widget_show(menuitem); +// } else if (input->type == DILLO_HTML_INPUT_SEL_LIST) { +// menuitem = gtk_list_item_new_with_label(html->Stash->str); +// select->options[select->num_options - 1].menuitem = menuitem; +// if (select->options[select->num_options - 1].value == NULL) +// select->options[select->num_options - 1].value = +// dStrdup(html->Stash->str); +// gtk_container_add(GTK_CONTAINER(select->menu), menuitem); +// if (select->options[select->num_options - 1].init_val) +// gtk_list_select_child(GTK_LIST(select->menu), menuitem); +// gtk_widget_show(menuitem); +// } +} + +/* + * <OPTION> + */ +static void Html_tag_open_option(DilloHtml *html, char *tag, int tagsize) +{ +// DilloHtmlForm *form; +// DilloHtmlInput *input; +// DilloHtmlLB *html_lb; +// int no; +// +// if (!(html->InFlags & IN_SELECT)) +// return; +// +// html_lb = html->linkblock; +// +// form = html_lb->forms->getRef (html_lb->forms->size() - 1); +// input = form->inputs->getRef (form->inputs->size() - 1); +// if (input->type == DILLO_HTML_INPUT_SELECT || +// input->type == DILLO_HTML_INPUT_SEL_LIST) { +// Html_option_finish(html); +// no = input->select->num_options; +// a_List_add(input->select->options, no, input->select->num_options_max); +// input->select->options[no].menuitem = NULL; +// input->select->options[no].value = Html_get_attr_wdef(html, tag, tagsize, +// "value", NULL); +// input->select->options[no].init_val = +// (Html_get_attr(html, tag, tagsize, "selected") != NULL); +// input->select->num_options++; +// } +// Html_stash_init(html); +} + +/* + * ? + */ +static void Html_tag_close_select(DilloHtml *html, int TagIdx) +{ +// // AL +// DilloHtmlForm *form; +// DilloHtmlInput *input; +// GtkWidget *scrolledwindow; +// DilloHtmlLB *html_lb; +// Widget *embed_gtk; +// GtkRequisition req; +// int height; +// +// if (html->InFlags & IN_SELECT) { +// html->InFlags &= ~IN_SELECT; +// +// html_lb = html->linkblock; +// +// form = html_lb->forms->getRef (html_lb->forms->size() - 1); +// input = form->inputs->getRef (form->inputs->size() - 1); +// if (input->type == DILLO_HTML_INPUT_SELECT) { +// Html_option_finish(html); +// +// gtk_option_menu_set_menu(GTK_OPTION_MENU(input->widget), +// input->select->menu); +// Html_select_set_history(input); +// +// // gtk_option_menu_set_history(GTK_OPTION_MENU(input->widget), 1); +// +// gtk_widget_show(input->widget); +// +// embed_gtk = a_Dw_embed_gtk_new (); +// a_Dw_embed_gtk_add_gtk (DW_EMBED_GTK (embed_gtk), input->widget); +// DW2TB(html->dw)->addWidget (embed_gtk, +// S_TOP(html)->style); +// } else if (input->type == DILLO_HTML_INPUT_SEL_LIST) { +// Html_option_finish(html); +// +// if (input->select->size < input->select->num_options) { +// scrolledwindow = gtk_scrolled_window_new(NULL, NULL); +// gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow), +// GTK_POLICY_NEVER, +// GTK_POLICY_AUTOMATIC); +// gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW +// (scrolledwindow), +// input->widget); +// +// gtk_container_set_focus_vadjustment +// (GTK_CONTAINER (input->widget), +// gtk_scrolled_window_get_vadjustment +// (GTK_SCROLLED_WINDOW(scrolledwindow))); +// +// /* Calculate the height of the scrolled window */ +// gtk_widget_size_request(input->select->options[0].menuitem, &req); +// height = input->select->size * req.height + +// 2 * scrolledwindow->style->klass->ythickness; +// gtk_widget_set_usize(scrolledwindow, -1, height); +// +// gtk_widget_show(input->widget); +// input->widget = scrolledwindow; +// } +// gtk_widget_show(input->widget); +// +// /* note: In this next call, scrolledwindows get a warning from +// * gdkwindow.c:422. I'm not really going to sweat it now - the +// * embedded widget stuff is going to get massively redone anyway. */ +// embed_gtk = a_Dw_embed_gtk_new (); +// a_Dw_embed_gtk_add_gtk (DW_EMBED_GTK (embed_gtk), input->widget); +// DW2TB(html->dw)->addWidget (embed_gtk, +// S_TOP(html)->style); +// } +// } + Html_pop_tag(html, TagIdx); +} + +/* + * Set the Document Base URI + */ +static void Html_tag_open_base(DilloHtml *html, char *tag, int tagsize) +{ + const char *attrbuf; + DilloUrl *BaseUrl; + + if (html->InFlags & IN_HEAD) { + if ((attrbuf = Html_get_attr(html, tag, tagsize, "href"))) { + BaseUrl = Html_url_new(html, attrbuf, "", 0, 0, 0, 1); + if (URL_SCHEME_(BaseUrl)) { + /* Pass the URL_SpamSafe flag to the new base url */ + a_Url_set_flags( + BaseUrl, URL_FLAGS(html->linkblock->base_url) & URL_SpamSafe); + a_Url_free(html->linkblock->base_url); + html->linkblock->base_url = BaseUrl; + } else { + MSG_HTML("base URI is relative (it MUST be absolute)\n"); + a_Url_free(BaseUrl); + } + } + } else { + MSG_HTML("the BASE element must appear in the HEAD section\n"); + } +} + +/* + * <CODE> + */ +static void Html_tag_open_code(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); +} + +/* + * <DFN> + */ +static void Html_tag_open_dfn(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 2, 3); +} + +/* + * <KBD> + */ +static void Html_tag_open_kbd(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); +} + +/* + * <SAMP> + */ +static void Html_tag_open_samp(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, prefs.fw_fontname, 0, 0, 0); +} + +/* + * <VAR> + */ +static void Html_tag_open_var(DilloHtml *html, char *tag, int tagsize) +{ + Html_set_top_font(html, NULL, 0, 2, 2); +} + +/* + * <SUB> + */ +static void Html_tag_open_sub(DilloHtml *html, char *tag, int tagsize) +{ + HTML_SET_TOP_ATTR (html, valign, VALIGN_SUB); +} + +/* + * <SUP> + */ +static void Html_tag_open_sup(DilloHtml *html, char *tag, int tagsize) +{ + HTML_SET_TOP_ATTR (html, valign, VALIGN_SUPER); +} + +/* + * <DIV> (todo: make a complete implementation) + */ +static void Html_tag_open_div(DilloHtml *html, char *tag, int tagsize) +{ + DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style); + Html_tag_set_align_attr (html, tag, tagsize); +} + +/* + * </DIV>, also used for </TABLE> and </CENTER> + */ +static void Html_tag_close_div(DilloHtml *html, int TagIdx) +{ + DW2TB(html->dw)->addParbreak (0, S_TOP(html)->style); + Html_pop_tag(html, TagIdx); +} + +/* + * Default close for most tags - just pop the stack. + */ +static void Html_tag_close_default(DilloHtml *html, int TagIdx) +{ + Html_pop_tag(html, TagIdx); +} + +/* + * Default close for paragraph tags - pop the stack and break. + */ +static void Html_tag_close_par(DilloHtml *html, int TagIdx) +{ + DW2TB(html->dw)->addParbreak (9, S_TOP(html)->style); + Html_pop_tag(html, TagIdx); +} + + +/* + * Function index for the open and close functions for each tag + * (Alphabetically sorted for a binary search) + * + * Explanation for the 'Flags' camp: + * + * {"address", B8(010110), ...} + * |||||`- inline element + * ||||`-- block element + * |||`--- inline container + * ||`---- block container + * |`----- body element + * `------ head element + * + * Notes: + * - The upper two bits are not used yet. + * - Empty elements have both inline and block container clear. + * (flow have both set) + */ +struct _TagInfo{ + char *name; + unsigned char Flags; + char EndTag; + uchar_t TagLevel; + TagOpenFunct open; + TagCloseFunct close; +}; + + +const TagInfo Tags[] = { + {"a", B8(010101),'R',2, Html_tag_open_a, Html_tag_close_a}, + {"abbr", B8(010101),'R',2, Html_tag_open_abbr, Html_tag_close_default}, + /* acronym 010101 */ + {"address", B8(010110),'R',2, Html_tag_open_address, Html_tag_close_par}, + {"area", B8(010001),'F',0, Html_tag_open_area, Html_tag_close_default}, + {"b", B8(010101),'R',2, Html_tag_open_b, Html_tag_close_default}, + {"base", B8(100001),'F',0, Html_tag_open_base, Html_tag_close_default}, + /* basefont 010001 */ + /* bdo 010101 */ + {"big", B8(010101),'R',2, Html_tag_open_big_small, Html_tag_close_default}, + {"blockquote", B8(011110),'R',2,Html_tag_open_blockquote,Html_tag_close_par}, + {"body", B8(011110),'O',1, Html_tag_open_body, Html_tag_close_body}, + {"br", B8(010001),'F',0, Html_tag_open_br, Html_tag_close_default}, + {"button", B8(011101),'R',2, Html_tag_open_button, Html_tag_close_default}, + /* caption */ + {"center", B8(011110),'R',2, Html_tag_open_center, Html_tag_close_div}, + {"cite", B8(010101),'R',2, Html_tag_open_cite, Html_tag_close_default}, + {"code", B8(010101),'R',2, Html_tag_open_code, Html_tag_close_default}, + /* col 010010 'F' */ + /* colgroup */ + {"dd", B8(011110),'O',1, Html_tag_open_dd, Html_tag_close_par}, + {"del", B8(011101),'R',2, Html_tag_open_strike, Html_tag_close_default}, + {"dfn", B8(010101),'R',2, Html_tag_open_dfn, Html_tag_close_default}, + /* dir 011010 */ + /* todo: complete <div> support! */ + {"div", B8(011110),'R',2, Html_tag_open_div, Html_tag_close_div}, + {"dl", B8(011010),'R',2, Html_tag_open_dl, Html_tag_close_par}, + {"dt", B8(010110),'O',1, Html_tag_open_dt, Html_tag_close_par}, + {"em", B8(010101),'R',2, Html_tag_open_em, Html_tag_close_default}, + /* fieldset */ + {"font", B8(010101),'R',2, Html_tag_open_font, Html_tag_close_default}, + {"form", B8(011110),'R',2, Html_tag_open_form, Html_tag_close_form}, + {"frame", B8(010010),'F',0, Html_tag_open_frame, Html_tag_close_default}, + {"frameset", B8(011110),'R',2,Html_tag_open_frameset, Html_tag_close_default}, + {"h1", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h}, + {"h2", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h}, + {"h3", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h}, + {"h4", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h}, + {"h5", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h}, + {"h6", B8(010110),'R',2, Html_tag_open_h, Html_tag_close_h}, + {"head", B8(101101),'O',1, Html_tag_open_head, Html_tag_close_head}, + {"hr", B8(010010),'F',0, Html_tag_open_hr, Html_tag_close_default}, + {"html", B8(001110),'O',1, Html_tag_open_html, Html_tag_close_html}, + {"i", B8(010101),'R',2, Html_tag_open_i, Html_tag_close_default}, + {"iframe", B8(011110),'R',2, Html_tag_open_frame, Html_tag_close_default}, + {"img", B8(010001),'F',0, Html_tag_open_img, Html_tag_close_default}, + {"input", B8(010001),'F',0, Html_tag_open_input, Html_tag_close_default}, + /* ins */ + {"isindex", B8(110001),'F',0, Html_tag_open_isindex, Html_tag_close_default}, + {"kbd", B8(010101),'R',2, Html_tag_open_kbd, Html_tag_close_default}, + /* label 010101 */ + /* legend 01?? */ + {"li", B8(011110),'O',1, Html_tag_open_li, Html_tag_close_li}, + /* link 100000 'F' */ + {"map", B8(011001),'R',2, Html_tag_open_map, Html_tag_close_map}, + /* menu 1010 -- todo: not exactly 1010, it can contain LI and inline */ + {"menu", B8(011010),'R',2, Html_tag_open_menu, Html_tag_close_par}, + {"meta", B8(100001),'F',0, Html_tag_open_meta, Html_tag_close_default}, + /* noframes 1011 */ + /* noscript 1011 */ + /* object 11xxxx */ + {"ol", B8(011010),'R',2, Html_tag_open_ol, Html_tag_close_par}, + /* optgroup */ + {"option", B8(010001),'O',1, Html_tag_open_option, Html_tag_close_default}, + {"p", B8(010110),'O',1, Html_tag_open_p, Html_tag_close_par}, + /* param 010001 'F' */ + {"pre", B8(010110),'R',2, Html_tag_open_pre, Html_tag_close_pre}, + /* q 010101 */ + {"s", B8(010101),'R',2, Html_tag_open_strike, Html_tag_close_default}, + {"samp", B8(010101),'R',2, Html_tag_open_samp, Html_tag_close_default}, + {"script", B8(111001),'R',2, Html_tag_open_script, Html_tag_close_script}, + {"select", B8(011001),'R',2, Html_tag_open_select, Html_tag_close_select}, + {"small", B8(010101),'R',2, Html_tag_open_big_small, Html_tag_close_default}, + /* span 0101 */ + {"strike", B8(010101),'R',2, Html_tag_open_strike, Html_tag_close_default}, + {"strong", B8(010101),'R',2, Html_tag_open_strong, Html_tag_close_default}, + {"style", B8(100101),'R',2, Html_tag_open_style, Html_tag_close_style}, + {"sub", B8(010101),'R',2, Html_tag_open_sub, Html_tag_close_default}, + {"sup", B8(010101),'R',2, Html_tag_open_sup, Html_tag_close_default}, + {"table", B8(011010),'R',5, Html_tag_open_table, Html_tag_close_div}, + /* tbody */ + {"td", B8(011110),'O',3, Html_tag_open_td, Html_tag_close_default}, + {"textarea", B8(010101),'R',2,Html_tag_open_textarea,Html_tag_close_textarea}, + /* tfoot */ + {"th", B8(011110),'O',1, Html_tag_open_th, Html_tag_close_default}, + /* thead */ + {"title", B8(100101),'R',2, Html_tag_open_title, Html_tag_close_title}, + {"tr", B8(011010),'O',4, Html_tag_open_tr, Html_tag_close_default}, + {"tt", B8(010101),'R',2, Html_tag_open_tt, Html_tag_close_default}, + {"u", B8(010101),'R',2, Html_tag_open_u, Html_tag_close_default}, + {"ul", B8(011010),'R',2, Html_tag_open_ul, Html_tag_close_par}, + {"var", B8(010101),'R',2, Html_tag_open_var, Html_tag_close_default} + +}; +#define NTAGS (sizeof(Tags)/sizeof(Tags[0])) + + +/* + * Compares tag from buffer ('/' or '>' or space-ended string) [p1] + * with tag from taglist (lowercase, zero ended string) [p2] + * Return value: as strcmp() + */ +static int Html_tag_compare(const char *p1, const char *p2) +{ + while ( *p2 ) { + if (tolower(*p1) != *p2) + return(tolower(*p1) - *p2); + ++p1; + ++p2; + } + return !strchr(" >/\n\r\t", *p1); +} + +/* + * Get 'tag' index + * return -1 if tag is not handled yet + */ +static int Html_tag_index(const char *tag) +{ + int low, high, mid, cond; + + /* Binary search */ + low = 0; + high = NTAGS - 1; /* Last tag index */ + while (low <= high) { + mid = (low + high) / 2; + if ((cond = Html_tag_compare(tag, Tags[mid].name)) < 0 ) + high = mid - 1; + else if (cond > 0) + low = mid + 1; + else + return mid; + } + return -1; +} + +/* + * For elements with optional close, check whether is time to close. + * Return value: (1: Close, 0: Don't close) + * --tuned for speed. + */ +static int Html_needs_optional_close(int old_idx, int cur_idx) +{ + static int i_P = -1, i_LI, i_TD, i_TR, i_TH, i_DD, i_DT, i_OPTION; + // i_THEAD, i_TFOOT, i_COLGROUP; + + if (i_P == -1) { + /* initialize the indexes of elements with optional close */ + i_P = Html_tag_index("p"), + i_LI = Html_tag_index("li"), + i_TD = Html_tag_index("td"), + i_TR = Html_tag_index("tr"), + i_TH = Html_tag_index("th"), + i_DD = Html_tag_index("dd"), + i_DT = Html_tag_index("dt"), + i_OPTION = Html_tag_index("option"); + // i_THEAD = Html_tag_index("thead"); + // i_TFOOT = Html_tag_index("tfoot"); + // i_COLGROUP = Html_tag_index("colgroup"); + } + + if (old_idx == i_P || old_idx == i_DT) { + /* P and DT are closed by block elements */ + return (Tags[cur_idx].Flags & 2); + } else if (old_idx == i_LI) { + /* LI closes LI */ + return (cur_idx == i_LI); + } else if (old_idx == i_TD || old_idx == i_TH) { + /* TD and TH are closed by TD, TH and TR */ + return (cur_idx == i_TD || cur_idx == i_TH || cur_idx == i_TR); + } else if (old_idx == i_TR) { + /* TR closes TR */ + return (cur_idx == i_TR); + } else if (old_idx == i_DD) { + /* DD is closed by DD and DT */ + return (cur_idx == i_DD || cur_idx == i_DT); + } else if (old_idx == i_OPTION) { + return 1; // OPTION always needs close + } + + /* HTML, HEAD, BODY are handled by Html_test_section(), not here. */ + /* todo: TBODY is pending */ + return 0; +} + + +/* + * Conditional cleanup of the stack (at open time). + * - This helps catching block elements inside inline containers (a BUG). + * - It also closes elements with "optional" close tag. + * + * This function is called when opening a block element or <OPTION>. + * + * It searches the stack closing open inline containers, and closing + * elements with optional close tag when necessary. + * + * Note: OPTION is the only non-block element with an optional close. + */ +static void Html_stack_cleanup_at_open(DilloHtml *html, int new_idx) +{ + /* We know that the element we're about to push is a block element. + * (except for OPTION, which is an empty inline, so is closed anyway) + * Notes: + * Its 'tag' is not yet pushed into the stack, + * 'new_idx' is its index inside Tags[]. + */ + + if (!html->TagSoup) + return; + + while (html->stack->size() > 1) { + int oldtag_idx = S_TOP(html)->tag_idx; + + if (Tags[oldtag_idx].EndTag == 'O') { // Element with optional close + if (!Html_needs_optional_close(oldtag_idx, new_idx)) + break; + } else if (Tags[oldtag_idx].Flags & 8) { // Block container + break; + } + + /* we have an inline (or empty) container... */ + if (Tags[oldtag_idx].EndTag == 'R') { + MSG_HTML("<%s> is not allowed to contain <%s>. -- closing <%s>\n", + Tags[oldtag_idx].name, Tags[new_idx].name, + Tags[oldtag_idx].name); + } + + /* Workaround for Apache and its bad HTML directory listings... */ + if ((html->InFlags & IN_PRE) && + strcmp(Tags[new_idx].name, "hr") == 0) + break; + + /* This call closes the top tag only. */ + Html_tag_cleanup_at_close(html, oldtag_idx); + } +} + +/* + * HTML, HEAD and BODY elements have optional open and close tags. + * Handle this "magic" here. + */ +static void Html_test_section(DilloHtml *html, int new_idx, int IsCloseTag) +{ + char *tag; + int tag_idx; + + if (!(html->InFlags & IN_HTML) && html->DocType == DT_NONE) + MSG_HTML("the required DOCTYPE declaration is missing (or invalid)\n"); + + if (!(html->InFlags & IN_HTML)) { + tag = "<html>"; + tag_idx = Html_tag_index(tag + 1); + if (tag_idx != new_idx || IsCloseTag) { + /* implicit open */ + Html_force_push_tag(html, tag_idx); + Tags[tag_idx].open (html, tag, strlen(tag)); + } + } + + if (Tags[new_idx].Flags & 32) { + /* head element */ + if (!(html->InFlags & IN_HEAD)) { + tag = "<head>"; + tag_idx = Html_tag_index(tag + 1); + if (tag_idx != new_idx || IsCloseTag) { + /* implicit open of the head element */ + Html_force_push_tag(html, tag_idx); + Tags[tag_idx].open (html, tag, strlen(tag)); + } + } + + } else if (Tags[new_idx].Flags & 16) { + /* body element */ + if (html->InFlags & IN_HEAD) { + tag = "</head>"; + tag_idx = Html_tag_index(tag + 2); + Tags[tag_idx].close (html, tag_idx); + } + tag = "<body>"; + tag_idx = Html_tag_index(tag + 1); + if (tag_idx != new_idx || IsCloseTag) { + /* implicit open */ + Html_force_push_tag(html, tag_idx); + Tags[tag_idx].open (html, tag, strlen(tag)); + } + } +} + +/* + * Process a tag, given as 'tag' and 'tagsize'. -- tagsize is [1 based] + * ('tag' must include the enclosing angle brackets) + * This function calls the right open or close function for the tag. + */ +static void Html_process_tag(DilloHtml *html, char *tag, int tagsize) +{ + int ci, ni; /* current and new tag indexes */ + const char *attrbuf; + char *start = tag + 1; /* discard the '<' */ + int IsCloseTag = (*start == '/'); + + ni = Html_tag_index(start + IsCloseTag); + + /* todo: doctype parsing is a bit fuzzy, but enough for the time being */ + if (ni == -1 && !(html->InFlags & IN_HTML)) { + if (tagsize > 9 && !dStrncasecmp(tag, "<!doctype", 9)) + Html_parse_doctype(html, tag, tagsize); + } + + if (!(html->InFlags & IN_HTML)) { + _MSG("\nDoctype: %f\n\n", html->DocTypeVersion); + } + + /* Handle HTML, HEAD and BODY. Elements with optional open and close */ + if (ni != -1 && !(html->InFlags & IN_BODY) /* && parsing HTML */) + Html_test_section(html, ni, IsCloseTag); + + /* White space handling */ + if (html->SPCPending && (!SGML_SPCDEL || !IsCloseTag)) + /* SGML_SPCDEL requires space pending and open tag */ + DW2TB(html->dw)->addSpace(S_TOP(html)->style); + html->SPCPending = FALSE; + + /* Tag processing */ + ci = S_TOP(html)->tag_idx; + if (ni != -1) { + + if (!IsCloseTag) { + /* Open function */ + + /* Cleanup when opening a block element, or + * when openning over an element with optional close */ + if (Tags[ni].Flags & 2 || (ci != -1 && Tags[ci].EndTag == 'O')) + Html_stack_cleanup_at_open(html, ni); + + /* todo: this is only raising a warning, take some defined action. + * Note: apache uses IMG inside PRE (we could use its "alt"). */ + if ((html->InFlags & IN_PRE) && Html_tag_pre_excludes(ni)) + MSG_HTML("<pre> is not allowed to contain <%s>\n", Tags[ni].name); + + /* Push the tag into the stack */ + Html_push_tag(html, ni); + + /* Call the open function for this tag */ + Tags[ni].open (html, tag, tagsize); + + /* Now parse attributes that can appear on any tag */ + if (tagsize >= 8 && /* length of "<t id=i>" */ + (attrbuf = Html_get_attr2(html, tag, tagsize, "id", + HTML_LeftTrim | HTML_RightTrim))) { + /* According to the SGML declaration of HTML 4, all NAME values + * occuring outside entities must be converted to uppercase + * (this is what "NAMECASE GENERAL YES" says). But the HTML 4 + * spec states in Sec. 7.5.2 that anchor ids are case-sensitive. + * So we don't do it and hope for better specs in the future ... + */ + Html_check_name_val(html, attrbuf, "id"); + /* We compare the "id" value with the url-decoded "name" value */ + if (!html->NameVal || strcmp(html->NameVal, attrbuf)) { + if (html->NameVal) + MSG_HTML("'id' and 'name' attribute of <a> tag differ\n"); + Html_add_anchor(html, attrbuf); + } + } + + /* Reset NameVal */ + if (html->NameVal) { + dFree(html->NameVal); + html->NameVal = NULL; + } + + /* let the parser know this was an open tag */ + html->PrevWasOpenTag = TRUE; + + /* Request inmediate close for elements with forbidden close tag. */ + /* todo: XHTML always requires close tags. A simple implementation + * of the commented clause below will make it work. */ + if (/* parsing HTML && */ Tags[ni].EndTag == 'F') + html->ReqTagClose = TRUE; + } + + /* Close function: test for </x>, ReqTagClose, <x /> and <x/> */ + if (*start == '/' || /* </x> */ + html->ReqTagClose || /* request */ + (tag[tagsize - 2] == '/' && /* XML: */ + (isspace(tag[tagsize - 3]) || /* <x /> */ + (size_t)tagsize == strlen(Tags[ni].name) + 3))) { /* <x/> */ + + Tags[ni].close (html, ni); + /* This was a close tag */ + html->PrevWasOpenTag = FALSE; + html->ReqTagClose = FALSE; + } + + } else { + /* tag not working - just ignore it */ + } +} + +/* + * Get attribute value for 'attrname' and return it. + * Tags start with '<' and end with a '>' (Ex: "<P align=center>") + * tagsize = strlen(tag) from '<' to '>', inclusive. + * + * Returns one of the following: + * * The value of the attribute. + * * An empty string if the attribute exists but has no value. + * * NULL if the attribute doesn't exist. + */ +static const char *Html_get_attr2(DilloHtml *html, + const char *tag, + int tagsize, + const char *attrname, + int tag_parsing_flags) +{ + int i, isocode, entsize, Found = 0, delimiter = 0, attr_pos = 0; + Dstr *Buf = html->attr_data; + DilloHtmlTagParsingState state = SEEK_ATTR_START; + + dReturn_val_if_fail(*attrname, NULL); + + dStr_truncate(Buf, 0); + + for (i = 1; i < tagsize; ++i) { + switch (state) { + case SEEK_ATTR_START: + if (isspace(tag[i])) + state = SEEK_TOKEN_START; + else if (tag[i] == '=') + state = SEEK_VALUE_START; + break; + + case MATCH_ATTR_NAME: + if ((Found = (!(attrname[attr_pos]) && + (tag[i] == '=' || isspace(tag[i]) || tag[i] == '>')))) { + state = SEEK_TOKEN_START; + --i; + } else if (tolower(tag[i]) != tolower(attrname[attr_pos++])) + state = SEEK_ATTR_START; + break; + + case SEEK_TOKEN_START: + if (tag[i] == '=') { + state = SEEK_VALUE_START; + } else if (!isspace(tag[i])) { + attr_pos = 0; + state = (Found) ? FINISHED : MATCH_ATTR_NAME; + --i; + } + break; + case SEEK_VALUE_START: + if (!isspace(tag[i])) { + delimiter = (tag[i] == '"' || tag[i] == '\'') ? tag[i] : ' '; + i -= (delimiter == ' '); + state = (Found) ? GET_VALUE : SKIP_VALUE; + } + break; + + case SKIP_VALUE: + if ((delimiter == ' ' && isspace(tag[i])) || tag[i] == delimiter) + state = SEEK_TOKEN_START; + break; + case GET_VALUE: + if ((delimiter == ' ' && (isspace(tag[i]) || tag[i] == '>')) || + tag[i] == delimiter) { + state = FINISHED; + } else if (tag[i] == '&' && + (tag_parsing_flags & HTML_ParseEntities)) { + if ((isocode = Html_parse_entity(html, tag+i, + tagsize-i, &entsize)) >= 0) { + if (isocode >= 128) { + char buf[4]; + int k, n = utf8encode(isocode, buf); + for (k = 0; k < n; ++k) + dStr_append_c(Buf, buf[k]); + } else { + dStr_append_c(Buf, (char) isocode); + } + i += entsize-1; + } else { + dStr_append_c(Buf, tag[i]); + } + } else if (tag[i] == '\r' || tag[i] == '\t') { + dStr_append_c(Buf, ' '); + } else if (tag[i] == '\n') { + /* ignore */ + } else { + dStr_append_c(Buf, tag[i]); + } + break; + + case FINISHED: + i = tagsize; + break; + } + } + + if (tag_parsing_flags & HTML_LeftTrim) + while (isspace(Buf->str[0])) + dStr_erase(Buf, 0, 1); + if (tag_parsing_flags & HTML_RightTrim) + while (Buf->len && isspace(Buf->str[Buf->len - 1])) + dStr_truncate(Buf, Buf->len - 1); + + return (Found) ? Buf->str : NULL; +} + +/* + * Call Html_get_attr2 telling it to parse entities and strip the result + */ +static const char *Html_get_attr(DilloHtml *html, + const char *tag, + int tagsize, + const char *attrname) +{ + return Html_get_attr2(html, tag, tagsize, attrname, + HTML_LeftTrim | HTML_RightTrim | HTML_ParseEntities); +} + +/* + * "Html_get_attr with default" + * Call Html_get_attr() and strdup() the returned string. + * If the attribute isn't found a copy of 'def' is returned. + */ +static char *Html_get_attr_wdef(DilloHtml *html, + const char *tag, + int tagsize, + const char *attrname, + const char *def) +{ + const char *attrbuf = Html_get_attr(html, tag, tagsize, attrname); + + return attrbuf ? dStrdup(attrbuf) : dStrdup(def); +} + +/* + * Add a widget to the page. + */ +static void Html_add_widget(DilloHtml *html, + Widget *widget, + char *width_str, + char *height_str, + StyleAttrs *style_attrs) +{ + StyleAttrs new_style_attrs; + Style *style; + + new_style_attrs = *style_attrs; + new_style_attrs.width = width_str ? + Html_parse_length (html, width_str) : LENGTH_AUTO; + new_style_attrs.height = height_str ? + Html_parse_length (html, height_str) : LENGTH_AUTO; + style = Style::create (HT2LT(html), &new_style_attrs); + DW2TB(html->dw)->addWidget (widget, style); + style->unref (); +} + + +/* + * Dispatch the apropriate function for 'Op' + * This function is a Cache client and gets called whenever new data arrives + * Op : operation to perform. + * CbData : a pointer to a DilloHtml structure + * Buf : a pointer to new data + * BufSize : new data size (in bytes) + */ +static void Html_callback(int Op, CacheClient_t *Client) +{ + if (Op) { + Html_write((DilloHtml*)Client->CbData, (char*)Client->Buf, + Client->BufSize, 1); + Html_close((DilloHtml*)Client->CbData, Client->Key); + } else { + Html_write((DilloHtml*)Client->CbData, (char*)Client->Buf, + Client->BufSize, 0); + } +} + +/* + * Here's where we parse the html and put it into the Textblock structure. + * Return value: number of bytes parsed + */ +static int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof) +{ + char ch = 0, *p, *text; + Textblock *textblock; + int token_start, buf_index; + + dReturn_val_if_fail ((textblock = DW2TB(html->dw)) != NULL, 0); + + /* Now, 'buf' and 'bufsize' define a buffer aligned to start at a token + * boundary. Iterate through tokens until end of buffer is reached. */ + buf_index = 0; + token_start = buf_index; + while (buf_index < bufsize) { + /* invariant: buf_index == bufsize || token_start == buf_index */ + + if (S_TOP(html)->parse_mode == + DILLO_HTML_PARSE_MODE_VERBATIM) { + /* Non HTML code here, let's skip until closing tag */ + do { + char *tag = S_TOP(html)->tag_name; + buf_index += strcspn(buf + buf_index, "<"); + if (buf_index + (int)strlen(tag) + 3 > bufsize) { + buf_index = bufsize; + } else if (strncmp(buf + buf_index, "</", 2) == 0 && + Html_match_tag(tag, buf+buf_index+2, strlen(tag)+1)) { + /* copy VERBATIM text into the stash buffer */ + text = dStrndup(buf + token_start, buf_index - token_start); + dStr_append(html->Stash, text); + dFree(text); + token_start = buf_index; + break; + } else + ++buf_index; + } while (buf_index < bufsize); + } + + if (isspace(buf[buf_index])) { + /* whitespace: group all available whitespace */ + while (++buf_index < bufsize && isspace(buf[buf_index])); + Html_process_space(html, buf + token_start, buf_index - token_start); + token_start = buf_index; + + } else if (buf[buf_index] == '<' && (ch = buf[buf_index + 1]) && + (isalpha(ch) || strchr("/!?", ch)) ) { + /* Tag */ + if (buf_index + 3 < bufsize && !strncmp(buf + buf_index, "<!--", 4)) { + /* Comment: search for close of comment, skipping over + * everything except a matching "-->" tag. */ + while ( (p = (char*) memchr(buf + buf_index, '>', + bufsize - buf_index)) ){ + buf_index = p - buf + 1; + if (p[-1] == '-' && p[-2] == '-') break; + } + if (p) { + /* Got the whole comment. Let's throw it away! :) */ + token_start = buf_index; + } else + buf_index = bufsize; + } else { + /* Tag: search end of tag (skipping over quoted strings) */ + html->CurrTagOfs = html->Start_Ofs + token_start; + + while ( buf_index < bufsize ) { + buf_index++; + buf_index += strcspn(buf + buf_index, ">\"'<"); + if ((ch = buf[buf_index]) == '>') { + break; + } else if (ch == '"' || ch == '\'') { + /* Skip over quoted string */ + buf_index++; + buf_index += strcspn(buf + buf_index, + (ch == '"') ? "\">" : "'>"); + if (buf[buf_index] == '>') { + /* Unterminated string value? Let's look ahead and test: + * (<: unterminated, closing-quote: terminated) */ + int offset = buf_index + 1; + offset += strcspn(buf + offset, + (ch == '"') ? "\"<" : "'<"); + if (buf[offset] == ch || !buf[offset]) { + buf_index = offset; + } else { + MSG_HTML("attribute lacks closing quote\n"); + break; + } + } + } else if (ch == '<') { + /* unterminated tag detected */ + p = dStrndup(buf+token_start+1, + strcspn(buf+token_start+1, " <")); + MSG_HTML("<%s> element lacks its closing '>'\n", p); + dFree(p); + --buf_index; + break; + } + } + if (buf_index < bufsize) { + buf_index++; + Html_process_tag(html, buf + token_start, + buf_index - token_start); + token_start = buf_index; + } + } + } else { + /* A Word: search for whitespace or tag open */ + while (++buf_index < bufsize) { + buf_index += strcspn(buf + buf_index, " <\n\r\t\f\v"); + if (buf[buf_index] == '<' && (ch = buf[buf_index + 1]) && + !isalpha(ch) && !strchr("/!?", ch)) + continue; + break; + } + if (buf_index < bufsize || Eof) { + /* successfully found end of token */ + Html_process_word(html, buf + token_start, + buf_index - token_start); + token_start = buf_index; + } + } + }/*while*/ + + textblock->flush (); + + return token_start; +} + +/* + * Process the newly arrived html and put it into the page structure. + * (This function is called by Html_callback whenever there's new data) + */ +static void Html_write(DilloHtml *html, char *Buf, int BufSize, int Eof) +{ + int token_start; + char *buf = Buf + html->Start_Ofs; + int bufsize = BufSize - html->Start_Ofs; + + dReturn_if_fail (DW2TB(html->dw) != NULL); + + html->Start_Buf = Buf; + token_start = Html_write_raw(html, buf, bufsize, Eof); + html->Start_Ofs += token_start; + + if (html->bw) + a_UIcmd_set_page_prog(html->bw, html->Start_Ofs, 1); +} + +/* + * Finish parsing a HTML page + * (Free html struct, close the client and update the page progress bar). + */ +static void Html_close(DilloHtml *html, int ClientKey) +{ + int si; + + //#if defined (DEBUG_LEVEL) && DEBUG_LEVEL >= 1 + //a_Dw_widget_print_tree (GTK_DW_VIEWPORT(html->dw->viewport)->child); + //#endif + + /* force the close of elements left open (todo: not for XHTML) */ + while ((si = html->stack->size() - 1)) { + if (html->stack->getRef(si)->tag_idx != -1) { + Html_tag_cleanup_at_close(html, html->stack->getRef(si)->tag_idx); + } + } + dFree(html->stack->getRef(0)->tag_name); /* "none" */ + (html->stack->getRef(0)->style)->unref (); /* template style */ + + delete (html->stack); + + dStr_free(html->Stash, TRUE); + dFree(html->SPCBuf); + dStr_free(html->attr_data, TRUE); + + /* Remove this client from our active list */ + a_Bw_close_client(html->bw, ClientKey); + + /* Set progress bar insensitive */ + a_UIcmd_set_page_prog(html->bw, 0, 0); + dFree(html); +} + + diff --git a/src/html.hh b/src/html.hh new file mode 100644 index 00000000..2d9202e6 --- /dev/null +++ b/src/html.hh @@ -0,0 +1,279 @@ +#ifndef __HTML_HH__ +#define __HTML_HH__ + +#include "d_size.h" // for uchar_t +#include "bw.h" // for BrowserWindow + +#include "dw/core.hh" +#include "lout/misc.hh" // For SimpleVector + +//#include "dw_image.h" // for DwImageMapList + +#include "form.hh" // For receiving the "clicked" signal + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * First, the html linkblock. For now, this mostly has forms, although + * pointers to actual links will go here soon, if for no other reason + * than to implement history-sensitive link colors. Also, it seems + * likely that imagemaps will go here. + */ + +typedef struct _DilloHtmlLB DilloHtmlLB; + +typedef struct _DilloHtml DilloHtml; +typedef struct _DilloHtmlClass DilloHtmlClass; +typedef struct _DilloHtmlState DilloHtmlState; +typedef struct _DilloHtmlForm DilloHtmlForm; +typedef struct _DilloHtmlOption DilloHtmlOption; +typedef struct _DilloHtmlSelect DilloHtmlSelect; +typedef struct _DilloHtmlInput DilloHtmlInput; + + +struct _DilloHtmlLB { + class HtmlLinkReceiver: public dw::core::Widget::LinkReceiver + { + private: + DilloHtmlLB *lb; + + public: + inline HtmlLinkReceiver (DilloHtmlLB *lb) { this->lb = lb; } + + bool enter (dw::core::Widget *widget, int link, int x, int y); + bool press (dw::core::Widget *widget, int link, int x, int y, + dw::core::EventButton *event); + bool click (dw::core::Widget *widget, int link, int x, int y, + dw::core::EventButton *event); + }; + + // Since DilloHtmlLB is a struct, not a class, a simple + // "HtmlLinkReceiver linkReceiver" (see signal documentation) would not + // work, therefore the pointer. + HtmlLinkReceiver *linkReceiver; + + BrowserWindow *bw; + DilloUrl *base_url; + + misc::SimpleVector<DilloHtmlForm> *forms; + + misc::SimpleVector<DilloUrl*> *links; + + //DwImageMapList maps; + + int32_t link_color; + int32_t visited_color; +}; + + +typedef enum { + DT_NONE, + DT_HTML, + DT_XHTML +} DilloHtmlDocumentType; + +typedef enum { + DILLO_HTML_PARSE_MODE_INIT = 0, + DILLO_HTML_PARSE_MODE_STASH, + DILLO_HTML_PARSE_MODE_STASH_AND_BODY, + DILLO_HTML_PARSE_MODE_VERBATIM, + DILLO_HTML_PARSE_MODE_BODY, + DILLO_HTML_PARSE_MODE_PRE +} DilloHtmlParseMode; + +typedef enum { + SEEK_ATTR_START, + MATCH_ATTR_NAME, + SEEK_TOKEN_START, + SEEK_VALUE_START, + SKIP_VALUE, + GET_VALUE, + FINISHED +} DilloHtmlTagParsingState; + +typedef enum { + HTML_LeftTrim = 1 << 0, + HTML_RightTrim = 1 << 1, + HTML_ParseEntities = 1 << 2 +} DilloHtmlTagParsingFlags; + +typedef enum { + DILLO_HTML_TABLE_MODE_NONE, /* no table at all */ + DILLO_HTML_TABLE_MODE_TOP, /* outside of <tr> */ + DILLO_HTML_TABLE_MODE_TR, /* inside of <tr>, outside of <td> */ + DILLO_HTML_TABLE_MODE_TD /* inside of <td> */ +} DilloHtmlTableMode; + +typedef enum { + HTML_LIST_NONE, + HTML_LIST_UNORDERED, + HTML_LIST_ORDERED +} DilloHtmlListMode; + +enum DilloHtmlProcessingState { + IN_NONE = 0, + IN_HTML = 1 << 0, + IN_HEAD = 1 << 1, + IN_BODY = 1 << 2, + IN_FORM = 1 << 3, + IN_SELECT = 1 << 4, + IN_TEXTAREA = 1 << 5, + IN_MAP = 1 << 6, + IN_PRE = 1 << 7, + IN_BUTTON = 1 << 8 +}; + + +struct _DilloHtmlState { + char *tag_name; + //DwStyle *style, *table_cell_style; + dw::core::style::Style *style, *table_cell_style; + DilloHtmlParseMode parse_mode; + DilloHtmlTableMode table_mode; + bool_t cell_text_align_set; + DilloHtmlListMode list_type; + int list_number; + + /* TagInfo index for the tag that's being processed */ + int tag_idx; + + dw::core::Widget *textblock, *table; + + /* This is used to align list items (especially in enumerated lists) */ + dw::core::Widget *ref_list_item; + + /* This makes image processing faster than a function + a_Dw_widget_get_background_color. */ + int32_t current_bg_color; + + /* This is used for list items etc; if it is set to TRUE, breaks + have to be "handed over" (see Html_add_indented and + Html_eventually_pop_dw). */ + bool_t hand_over_break; +}; + +typedef enum { + DILLO_HTML_METHOD_UNKNOWN, + DILLO_HTML_METHOD_GET, + DILLO_HTML_METHOD_POST +} DilloHtmlMethod; + +typedef enum { + DILLO_HTML_ENC_URLENCODING +} DilloHtmlEnc; + +struct _DilloHtmlForm { + DilloHtmlMethod method; + DilloUrl *action; + DilloHtmlEnc enc; + + misc::SimpleVector<DilloHtmlInput> *inputs; + + int num_entry_fields; + int num_submit_buttons; + + form::Form *form_receiver; +}; + +struct _DilloHtmlOption { + //GtkWidget *menuitem; + char *value; + bool_t init_val; +}; + +struct _DilloHtmlSelect { + //GtkWidget *menu; + int size; + + DilloHtmlOption *options; + int num_options; + int num_options_max; +}; + +typedef enum { + DILLO_HTML_INPUT_UNKNOWN, + DILLO_HTML_INPUT_TEXT, + DILLO_HTML_INPUT_PASSWORD, + DILLO_HTML_INPUT_CHECKBOX, + DILLO_HTML_INPUT_RADIO, + DILLO_HTML_INPUT_IMAGE, + DILLO_HTML_INPUT_FILE, + DILLO_HTML_INPUT_BUTTON, + DILLO_HTML_INPUT_HIDDEN, + DILLO_HTML_INPUT_SUBMIT, + DILLO_HTML_INPUT_RESET, + DILLO_HTML_INPUT_BUTTON_SUBMIT, + DILLO_HTML_INPUT_BUTTON_RESET, + DILLO_HTML_INPUT_SELECT, + DILLO_HTML_INPUT_SEL_LIST, + DILLO_HTML_INPUT_TEXTAREA, + DILLO_HTML_INPUT_INDEX +} DilloHtmlInputType; + +struct _DilloHtmlInput { + DilloHtmlInputType type; + void *widget; /* May be a FLTKWidget or a Dw Widget. */ + void *embed; /* May be NULL */ + char *name; + char *init_str; /* note: some overloading - for buttons, init_str + is simply the value of the button; for text + entries, it is the initial value */ + DilloHtmlSelect *select; + bool_t init_val; /* only meaningful for buttons */ +}; + +struct _DilloHtml { + dw::core::Widget *dw; /* this is duplicated in the stack (page) */ + + DilloHtmlLB *linkblock; + char *Start_Buf; + size_t Start_Ofs; + size_t CurrTagOfs; + size_t OldTagOfs, OldTagLine; + + DilloHtmlDocumentType DocType; /* as given by DOCTYPE tag */ + float DocTypeVersion; /* HTML or XHTML version number */ + + misc::SimpleVector<DilloHtmlState> *stack; + + int InFlags; /* tracks which tags we are in */ + + Dstr *Stash; + bool_t StashSpace; + + char *SPCBuf; /* Buffer for white space */ + + int pre_column; /* current column, used in PRE tags with tabs */ + bool_t PreFirstChar; /* used to skip the first CR or CRLF in PRE tags */ + bool_t PrevWasCR; /* Flag to help parsing of "\r\n" in PRE tags */ + bool_t PrevWasOpenTag; /* Flag to help deferred parsing of white space */ + bool_t SPCPending; /* Flag to help deferred parsing of white space */ + bool_t InVisitedLink; /* used to 'contrast_visited_colors' */ + bool_t ReqTagClose; /* Flag to help handling bad-formed HTML */ + bool_t CloseOneTag; /* Flag to help Html_tag_cleanup_at_close() */ + bool_t TagSoup; /* Flag to enable the parser's cleanup functions */ + char *NameVal; /* used for validation of "NAME" and "ID" in <A> */ + + /* element counters: used for validation purposes */ + uchar_t Num_HTML, Num_HEAD, Num_BODY, Num_TITLE; + + Dstr *attr_data; + + BrowserWindow *bw; +}; + +/* + * Exported functions + */ +void a_Html_form_event_handler(void *data, + form::Form *form_receiver, + void *v_resource); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __HTML_HH__ */ diff --git a/src/image.cc b/src/image.cc new file mode 100644 index 00000000..f815d575 --- /dev/null +++ b/src/image.cc @@ -0,0 +1,226 @@ +/* + * File: image.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org>, + * Sebastian Geerken <sgeerken@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * This file implements image data transfer methods. It handles the transfer + * of data from an Image to a DwImage widget. + */ + +#include <stdio.h> +#include <string.h> + +#include "msg.h" + +#include "image.hh" +#include "dw/core.hh" +#include "dw/image.hh" + +using namespace dw::core; + +// Image to Object-Image macro +#define OI(Image) ((dw::Image*)(Image->dw)) + + +/* + * Local data + */ +static size_t linebuf_size = 0; +static uchar_t *linebuf = NULL; + + +/* + * Create and initialize a new image structure. + */ +DilloImage *a_Image_new(int width, + int height, + const char *alt_text, + int32_t bg_color) +{ + DilloImage *Image; + + Image = dNew(DilloImage, 1); + Image->dw = (void*) new dw::Image(alt_text); + Image->width = 0; + Image->height = 0; + Image->cmap = NULL; + Image->in_type = DILLO_IMG_TYPE_NOTSET; + Image->bg_color = bg_color; + Image->ProcessedBytes = 0; + Image->BitVec = NULL; + Image->State = IMG_Empty; + + Image->RefCount = 1; + + return Image; +} + +/* + * Deallocate an Image structure + */ +static void Image_free(DilloImage *Image) +{ + a_Bitvec_free(Image->BitVec); + dFree(Image); +} + +/* + * Unref and free if necessary + */ +void a_Image_unref(DilloImage *Image) +{ + _MSG(" %d ", Image->RefCount); + if (Image && --Image->RefCount == 0) + Image_free(Image); +} + +/* + * Add a reference to an Image struct + */ +void a_Image_ref(DilloImage *Image) +{ + if (Image) + ++Image->RefCount; +} + +/* + * Decode 'buf' (an image line) into RGB format. + */ +static uchar_t * + Image_line(DilloImage *Image, const uchar_t *buf, const uchar_t *cmap, int y) +{ + uint_t x; + + switch (Image->in_type) { + case DILLO_IMG_TYPE_INDEXED: + if (cmap) { + for (x = 0; x < Image->width; x++) + memcpy(linebuf + x * 3, cmap + buf[x] * 3, 3); + } else { + MSG("Gif:: WARNING, image lacks a color map\n"); + } + break; + case DILLO_IMG_TYPE_GRAY: + for (x = 0; x < Image->width; x++) + memset(linebuf + x * 3, buf[x], 3); + break; + case DILLO_IMG_TYPE_RGB: + /* avoid a memcpy here! --Jcid */ + return (uchar_t *)buf; + case DILLO_IMG_TYPE_NOTSET: + MSG_ERR("Image_line: type not set...\n"); + break; + } + return linebuf; +} + +/* + * Set initial parameters of the image + */ +void a_Image_set_parms(DilloImage *Image, void *v_imgbuf, DilloUrl *url, + int version, uint_t width, uint_t height, + DilloImgType type) +{ + _MSG("a_Image_set_parms: width=%d height=%d\n", width, height); + + OI(Image)->setBuffer((Imgbuf*)v_imgbuf); + + if (!Image->BitVec) + Image->BitVec = a_Bitvec_new(height); + Image->in_type = type; + Image->width = width; + Image->height = height; + if (3 * width > linebuf_size) { + linebuf_size = 3 * width; + linebuf = (uchar_t*) dRealloc(linebuf, linebuf_size); + } + Image->State = IMG_SetParms; +} + +/* + * Reference the dicache entry color map + */ +void a_Image_set_cmap(DilloImage *Image, const uchar_t *cmap) +{ + Image->cmap = cmap; + Image->State = IMG_SetCmap; +} + +/* + * Implement the write method + */ +void a_Image_write(DilloImage *Image, void *v_imgbuf, + const uchar_t *buf, uint_t y, int decode) +{ + uchar_t *newbuf; + + dReturn_if_fail ( y < Image->height ); + + if (decode) { + /* Decode 'buf' and copy it into the DicEntry buffer */ + newbuf = Image_line(Image, buf, Image->cmap, y); + ((Imgbuf*)v_imgbuf)->copyRow(y, (byte *)newbuf); + } + a_Bitvec_set_bit(Image->BitVec, y); + Image->State = IMG_Write; + + /* Update the row in DwImage */ + OI(Image)->drawRow(y); +} + +/* + * Implement the close method + */ +void a_Image_close(DilloImage *Image) +{ + a_Image_unref(Image); +} + + +// Wrappers for Imgbuf ------------------------------------------------------- + +/* + * Increment reference count for an Imgbuf + */ +void a_Image_imgbuf_ref(void *v_imgbuf) +{ + ((Imgbuf*)v_imgbuf)->ref(); +} + +/* + * Decrement reference count for an Imgbuf + */ +void a_Image_imgbuf_unref(void *v_imgbuf) +{ + ((Imgbuf*)v_imgbuf)->unref(); +} + +/* + * Create a new Imgbuf + */ +void *a_Image_imgbuf_new(void *v_dw, int img_type, int width, int height) +{ + Layout *layout = ((Widget*)v_dw)->getLayout(); + if (!layout) { + MSG_ERR("a_Image_imgbuf_new: layout is NULL.\n"); + exit(1); + } + return (void*)layout->createImgbuf(Imgbuf::RGB, width, height); +} + +/* + * Last reference for this Imgbuf? + */ +int a_Image_imgbuf_last_reference(void *v_imgbuf) +{ + return ((Imgbuf*)v_imgbuf)->lastReference () ? 1 : 0; +} + diff --git a/src/image.hh b/src/image.hh new file mode 100644 index 00000000..fde51838 --- /dev/null +++ b/src/image.hh @@ -0,0 +1,79 @@ +#ifndef __IMAGE_HH__ +#define __IMAGE_HH__ + +// The DilloImage data-structure and methods + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#include "bitvec.h" +#include "url.h" + +typedef struct _DilloImage DilloImage; + +typedef enum { + DILLO_IMG_TYPE_INDEXED, + DILLO_IMG_TYPE_RGB, + DILLO_IMG_TYPE_GRAY, + DILLO_IMG_TYPE_NOTSET /* Initial value */ +} DilloImgType; + +/* These will reflect the Image's "state" */ +typedef enum { + IMG_Empty, /* Just created the entry */ + IMG_SetParms, /* Parameters set */ + IMG_SetCmap, /* Color map set */ + IMG_Write, /* Feeding the entry */ + IMG_Close, /* Whole image got! */ + IMG_Abort /* Image transfer aborted */ +} ImageState; + +struct _DilloImage { + void *dw; + + /* Parameters as told by image data */ + uint_t width; + uint_t height; + + const uchar_t *cmap; /* Color map (only for indexed) */ + DilloImgType in_type; /* Image Type */ + int32_t bg_color; /* Background color */ + + int ProcessedBytes; /* Amount of bytes already decoded */ + bitvec_t *BitVec; /* Bit vector for decoded rows */ + ImageState State; + + int RefCount; /* Reference counter */ +}; + + +/* + * Function prototypes + */ +DilloImage *a_Image_new(int width, int height, + const char *alt_text, int32_t bg_color); +void a_Image_ref(DilloImage *Image); +void a_Image_unref(DilloImage *Image); + +void a_Image_set_parms(DilloImage *Image, void *v_imgbuf, DilloUrl *url, + int version, uint_t width, uint_t height, + DilloImgType type); +void a_Image_set_cmap(DilloImage *Image, const uchar_t *cmap); +void a_Image_write(DilloImage *Image, void *v_imgbuf, + const uchar_t *buf, uint_t y, int decode); +void a_Image_close(DilloImage *Image); + +void a_Image_imgbuf_ref(void *v_imgbuf); +void a_Image_imgbuf_unref(void *v_imgbuf); +void *a_Image_imgbuf_new(void *v_dw, int img_type, int width, int height) ; +int a_Image_imgbuf_last_reference(void *v_imgbuf); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __IMAGE_HH__ */ + diff --git a/src/jpeg.c b/src/jpeg.c new file mode 100644 index 00000000..fbf3eeee --- /dev/null +++ b/src/jpeg.c @@ -0,0 +1,334 @@ +/* + * File: jpeg.c + * + * Copyright (C) 1997 Raph Levien <raph@acm.org> + * Copyright (C) 2000-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * The jpeg decoder for dillo. It is responsible for decoding JPEG data + * and transferring it to the dicache. It uses libjpeg to do the actual + * decoding. + */ + +#include <config.h> +#ifdef ENABLE_JPEG + +#include <stdio.h> +#include <setjmp.h> + +/* avoid a redefinition of HAVE_STDLIB_H with old jpeglib.h */ +#ifdef HAVE_STDLIB_H +# undef HAVE_STDLIB_H +#endif +#include <jpeglib.h> +#ifdef HAVE_STDLIB_H +# undef HAVE_STDLIB_H +#endif + +#include "image.hh" +#include "web.hh" +#include "cache.h" +#include "dicache.h" + +#define DEBUG_LEVEL 6 +#include "debug.h" + +typedef enum { + DILLO_JPEG_INIT, + DILLO_JPEG_STARTING, + DILLO_JPEG_READING, + DILLO_JPEG_DONE, + DILLO_JPEG_ERROR +} DilloJpegState; + +/* An implementation of a suspending source manager */ + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + struct DilloJpeg *jpeg; /* a pointer back to the jpeg object */ +} my_source_mgr; + +struct my_error_mgr { + struct jpeg_error_mgr pub; /* "public" fields */ + jmp_buf setjmp_buffer; /* for return to caller */ +}; +typedef struct my_error_mgr * my_error_ptr; + +typedef struct DilloJpeg { + DilloImage *Image; + DilloUrl *url; + int version; + + my_source_mgr Src; + + DilloJpegState state; + size_t Start_Ofs, Skip, NewStart; + char *Data; + + uint_t y; + + struct jpeg_decompress_struct cinfo; + struct my_error_mgr jerr; +} DilloJpeg; + +/* + * Forward declarations + */ +static DilloJpeg *Jpeg_new(DilloImage *Image, DilloUrl *url, int version); +static void Jpeg_callback(int Op, CacheClient_t *Client); +static void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize); +static void Jpeg_close(DilloJpeg *jpeg, CacheClient_t *Client); +METHODDEF(void) Jpeg_errorexit (j_common_ptr cinfo); + +/* exported function */ +void *a_Jpeg_image(const char *Type, void *P, CA_Callback_t *Call, + void **Data); + + +/* this is the routine called by libjpeg when it detects an error. */ +METHODDEF(void) Jpeg_errorexit (j_common_ptr cinfo) +{ + /* display message and return to setjmp buffer */ + my_error_ptr myerr = (my_error_ptr) cinfo->err; + (*cinfo->err->output_message) (cinfo); + longjmp(myerr->setjmp_buffer, 1); +} + +/* + * MIME handler for "image/jpeg" type + * (Sets Jpeg_callback or a_Dicache_callback as the cache-client) + */ +void *a_Jpeg_image(const char *Type, void *P, CA_Callback_t *Call, + void **Data) +{ + DilloWeb *web = P; + DICacheEntry *DicEntry; + + if (!web->Image) + web->Image = a_Image_new(0, 0, NULL, 0); + + /* Add an extra reference to the Image (for dicache usage) */ + a_Image_ref(web->Image); + + DicEntry = a_Dicache_get_entry(web->url); + if (!DicEntry) { + /* Let's create an entry for this image... */ + DicEntry = a_Dicache_add_entry(web->url); + + /* ... and let the decoder feed it! */ + *Data = Jpeg_new(web->Image, DicEntry->url, DicEntry->version); + *Call = (CA_Callback_t) Jpeg_callback; + } else { + /* Let's feed our client from the dicache */ + a_Dicache_ref(DicEntry->url, DicEntry->version); + *Data = web->Image; + *Call = (CA_Callback_t) a_Dicache_callback; + } + return (web->Image->dw); +} + +/* + * Finish the decoding process + */ +static void Jpeg_close(DilloJpeg *jpeg, CacheClient_t *Client) +{ + a_Dicache_close(jpeg->url, jpeg->version, Client); + + if (jpeg->state != DILLO_JPEG_DONE) { + jpeg_destroy_decompress(&(jpeg->cinfo)); + } + dFree(jpeg); +} + +static void init_source(j_decompress_ptr cinfo) +{ +} + +static boolean fill_input_buffer(j_decompress_ptr cinfo) +{ + DilloJpeg *jpeg = ((my_source_mgr *) cinfo->src)->jpeg; + + DEBUG_MSG(5, "fill_input_buffer\n"); +#if 0 + if (!cinfo->src->bytes_in_buffer) { + DEBUG_MSG(5, "fill_input_buffer: %ld bytes in buffer\n", + (long)cinfo->src->bytes_in_buffer); + + jpeg->Start_Ofs = (ulong_t) jpeg->cinfo.src->next_input_byte - + (ulong_t) jpeg->Data; +#endif + if (jpeg->Skip) { + jpeg->Start_Ofs = jpeg->NewStart + jpeg->Skip - 1; + jpeg->Skip = 0; + } else { + jpeg->Start_Ofs = (ulong_t) jpeg->cinfo.src->next_input_byte - + (ulong_t) jpeg->Data; + } + return FALSE; +#if 0 + } + return TRUE; +#endif +} + +static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) +{ + DilloJpeg *jpeg; + + if (num_bytes < 1) + return; + jpeg = ((my_source_mgr *) cinfo->src)->jpeg; + + DEBUG_MSG(5, "skip_input_data: Start_Ofs = %lu, num_bytes = %ld," + " %ld bytes in buffer\n", + (ulong_t)jpeg->Start_Ofs, num_bytes, + (long)cinfo->src->bytes_in_buffer); + + cinfo->src->next_input_byte += num_bytes; + if (num_bytes < (long)cinfo->src->bytes_in_buffer) { + cinfo->src->bytes_in_buffer -= num_bytes; + } else { + jpeg->Skip += num_bytes - cinfo->src->bytes_in_buffer + 1; + cinfo->src->bytes_in_buffer = 0; + } +} + +static void term_source(j_decompress_ptr cinfo) +{ +} + +static DilloJpeg *Jpeg_new(DilloImage *Image, DilloUrl *url, int version) +{ + my_source_mgr *src; + DilloJpeg *jpeg = dMalloc(sizeof(*jpeg)); + + jpeg->Image = Image; + jpeg->url = url; + jpeg->version = version; + + jpeg->state = DILLO_JPEG_INIT; + jpeg->Start_Ofs = 0; + jpeg->Skip = 0; + + /* decompression step 1 (see libjpeg.doc) */ + jpeg->cinfo.err = jpeg_std_error(&(jpeg->jerr.pub)); + jpeg->jerr.pub.error_exit = Jpeg_errorexit; + + jpeg_create_decompress(&(jpeg->cinfo)); + + /* decompression step 2 (see libjpeg.doc) */ + jpeg->cinfo.src = &jpeg->Src.pub; + src = &jpeg->Src; + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart;/* use default method */ + src->pub.term_source = term_source; + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL;/* until buffer loaded */ + + src->jpeg = jpeg; + + /* decompression steps continue in write method */ + return jpeg; +} + +static void Jpeg_callback(int Op, CacheClient_t *Client) +{ + if (Op) + Jpeg_close(Client->CbData, Client); + else + Jpeg_write(Client->CbData, Client->Buf, Client->BufSize); +} + +/* + * Receive and process new chunks of JPEG image data + */ +static void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize) +{ + DilloImgType type; + uchar_t *linebuf; + JSAMPLE *array[1]; + int num_read; + + DEBUG_MSG(5, "Jpeg_write: (%p) Bytes in buff: %ld Ofs: %lu\n", jpeg, + (long) BufSize, (ulong_t)jpeg->Start_Ofs); + + /* See if we are supposed to skip ahead. */ + if (BufSize <= jpeg->Start_Ofs) + return; + + /* Concatenate with the partial input, if any. */ + jpeg->cinfo.src->next_input_byte = (uchar_t *)Buf + jpeg->Start_Ofs; + jpeg->cinfo.src->bytes_in_buffer = BufSize - jpeg->Start_Ofs; + jpeg->NewStart = BufSize; + jpeg->Data = Buf; + + if (setjmp(jpeg->jerr.setjmp_buffer)) { + /* If we get here, the JPEG code has signaled an error. */ + jpeg->state = DILLO_JPEG_ERROR; + } + + /* Process the bytes in the input buffer. */ + if (jpeg->state == DILLO_JPEG_INIT) { + + /* decompression step 3 (see libjpeg.doc) */ + if (jpeg_read_header(&(jpeg->cinfo), TRUE) != JPEG_SUSPENDED) { + type = DILLO_IMG_TYPE_GRAY; + if (jpeg->cinfo.num_components == 1) + type = DILLO_IMG_TYPE_GRAY; + else if (jpeg->cinfo.num_components == 3) + type = DILLO_IMG_TYPE_RGB; + else + DEBUG_MSG(5, "jpeg: can't handle %d component images\n", + jpeg->cinfo.num_components); + a_Dicache_set_parms(jpeg->url, jpeg->version, jpeg->Image, + (uint_t)jpeg->cinfo.image_width, + (uint_t)jpeg->cinfo.image_height, + type); + + /* decompression step 4 (see libjpeg.doc) */ + jpeg->state = DILLO_JPEG_STARTING; + } + } + if (jpeg->state == DILLO_JPEG_STARTING) { + /* decompression step 5 (see libjpeg.doc) */ + if (jpeg_start_decompress(&(jpeg->cinfo))) { + jpeg->y = 0; + jpeg->state = DILLO_JPEG_READING; + } + } + if (jpeg->state == DILLO_JPEG_READING) { + linebuf = dMalloc(jpeg->cinfo.image_width * + jpeg->cinfo.num_components); + array[0] = linebuf; + while (jpeg->y < jpeg->cinfo.image_height) { + num_read = jpeg_read_scanlines(&(jpeg->cinfo), array, 1); + if (num_read == 0) + break; + a_Dicache_write(jpeg->Image, jpeg->url, jpeg->version, + linebuf, 0, jpeg->y); + + jpeg->y++; + } + if (jpeg->y == jpeg->cinfo.image_height) { + DEBUG_MSG(5, "height achieved\n"); + + jpeg_destroy_decompress(&(jpeg->cinfo)); + jpeg->state = DILLO_JPEG_DONE; + } + dFree(linebuf); + } + if (jpeg->state == DILLO_JPEG_ERROR) { + jpeg_destroy_decompress(&(jpeg->cinfo)); + jpeg->state = DILLO_JPEG_DONE; + } +} + +#endif /* ENABLE_JPEG */ diff --git a/src/klist.c b/src/klist.c new file mode 100644 index 00000000..113472b6 --- /dev/null +++ b/src/klist.c @@ -0,0 +1,118 @@ +/* + * File: klist.c + * + * Copyright 2001 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * A simple ADT for Key-Data pairs + * + * NOTE: this ADT is not perfect. The possibility of holding a Key, after + * its data has been removed, long enough for the key-counter to reset and + * reuse the same key is very low, but EXISTS. So, the responsibility + * remains with the caller. + */ + +#include "klist.h" + + +/* + * Compare function for searching data by its key + */ +static int Klist_node_by_key_cmp(const void *Node, const void *key) +{ + return ((KlistNode_t *)Node)->Key - VOIDP2INT(key); +} + +/* + * Return the data pointer for a given Key (or NULL if not found) + */ +void *a_Klist_get_data(Klist_t *Klist, int Key) +{ + void *aux; + + if (!Klist) + return NULL; + aux = dList_find_sorted(Klist->List, INT2VOIDP(Key), Klist_node_by_key_cmp); + return (aux) ? ((KlistNode_t*)aux)->Data : NULL; +} + +/* + * Insert a data pointer and return a key for it. + */ +int a_Klist_insert(Klist_t **Klist, void *Data) +{ + KlistNode_t *Node; + + if (!*Klist) { + (*Klist) = dNew(Klist_t, 1); + (*Klist)->List = dList_new(32); + (*Klist)->Clean = 1; + (*Klist)->Counter = 0; + } + + /* This avoids repeated keys at the same time */ + do { + if (++((*Klist)->Counter) == 0) { + (*Klist)->Counter = 1; + (*Klist)->Clean = 0; + } + } while (!((*Klist)->Clean) && + a_Klist_get_data((*Klist), (*Klist)->Counter)); + + Node = dNew(KlistNode_t, 1); + Node->Key = (*Klist)->Counter; + Node->Data = Data; + dList_insert_sorted((*Klist)->List, Node, Klist_node_by_key_cmp); + return (*Klist)->Counter; +} + +/* + * Remove data by Key + */ +void a_Klist_remove(Klist_t *Klist, int Key) +{ + void *data; + + data = dList_find_sorted(Klist->List, INT2VOIDP(Key),Klist_node_by_key_cmp); + if (data) { + dList_remove(Klist->List, data); + dFree(data); + } + if (dList_length(Klist->List) == 0) + Klist->Clean = 1; +} + +/* + * Return the number of elements in the Klist + */ +int a_Klist_length(Klist_t *Klist) +{ + return dList_length(Klist->List); +} + +/* + * Free a Klist + */ +void a_Klist_free(Klist_t **KlistPtr) +{ + void *node; + Klist_t *Klist = *KlistPtr; + + if (!Klist) + return; + + while (dList_length(Klist->List) > 0) { + node = dList_nth_data(Klist->List, 0); + dList_remove_fast(Klist->List, 0); + dFree(node); + } + dFree(Klist); + *KlistPtr = NULL; +} + diff --git a/src/klist.h b/src/klist.h new file mode 100644 index 00000000..53b2bc31 --- /dev/null +++ b/src/klist.h @@ -0,0 +1,40 @@ +#ifndef __KLIST_H__ +#define __KLIST_H__ + +#include "../dlib/dlib.h" + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct _KlistNode KlistNode_t; +typedef struct _Klist Klist_t; + +struct _KlistNode { + int Key; /* primary key */ + void *Data; /* data reference */ +}; + +struct _Klist { + Dlist *List; + int Clean; /* check flag */ + int Counter; /* counter (for making keys) */ +}; + + +/* + * Function prototypes + */ +void* a_Klist_get_data(Klist_t *Klist, int Key); +int a_Klist_insert(Klist_t **Klist, void *Data); +void a_Klist_remove(Klist_t *Klist, int Key); +int a_Klist_length(Klist_t *Klist); +void a_Klist_free(Klist_t **Klist); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __KLIST_H__ */ diff --git a/src/list.h b/src/list.h new file mode 100644 index 00000000..8623bf09 --- /dev/null +++ b/src/list.h @@ -0,0 +1,49 @@ +/* + * Fast list methods + * Feb 2000 --Jcid + * + */ + +#ifndef __LIST_H__ +#define __LIST_H__ + +/* + * a_List_resize() + * + * Make sure there's space for 'num_items' items within the list + * (First, allocate an 'alloc_step' sized chunk, after that, double the + * list size --to make it faster) + */ +#define a_List_resize(list,num_items,alloc_step) \ + if (!list) { \ + list = dMalloc(alloc_step * sizeof((*list))); \ + } \ + if (num_items >= alloc_step){ \ + while ( num_items >= alloc_step ) \ + alloc_step <<= 1; \ + list = dRealloc(list, alloc_step * sizeof((*list))); \ + } + + +/* + * a_List_add() + * + * Make sure there's space for one more item within the list. + */ +#define a_List_add(list,num_items,alloc_step) \ + a_List_resize(list,num_items,alloc_step) + + +/* + * a_List_remove() + * + * Quickly remove an item from the list + * ==> We preserve relative position, but not the element index <== + */ +#define a_List_remove(list, item, num_items) \ + if (list && item < num_items) { \ + list[item] = list[--num_items]; \ + } + + +#endif /* __LIST_H__ */ diff --git a/src/menu.cc b/src/menu.cc new file mode 100644 index 00000000..1de42acf --- /dev/null +++ b/src/menu.cc @@ -0,0 +1,358 @@ +/* + * File: menu.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +// Functions/Methods for menus + +#include <stdio.h> +#include <stdarg.h> +#include <fltk/events.h> +#include <fltk/PopupMenu.h> +#include <fltk/Item.h> +#include <fltk/Divider.h> + +#include "menu.hh" +#include "uicmd.hh" +#include "history.h" + +using namespace fltk; + +/* + * Local data + */ + +// (This data can be encapsulated inside a class for each popup, but +// as popups are modal, there's no need). +// Weak reference to the popup's URL +static DilloUrl *popup_url = NULL; +// Weak reference to the popup's bw +static BrowserWindow *popup_bw = NULL; +// Weak reference to the page's HTML bugs +static const char *popup_bugs = NULL; +// History popup direction (-1 = back, 1 = forward). +static int history_direction = -1; +// History popup, list of URL-indexes. +static int *history_list = NULL; + +/* + * Local sub class + * (Used to add the hint for history popup menus) + */ + +class NewItem : public Item { +public: + NewItem (const char* label) : Item(label) {}; + void draw(); +}; + +/* + * This adds a call to a_UIcmd_set_msg() to show the URL in the status bar + * TODO: erase the URL on popup close. + */ +void NewItem::draw() { + if (flags() & SELECTED) { + DilloUrl *url = a_History_get_url(history_list[((int)user_data())-1]); + a_UIcmd_set_msg(popup_bw, "%s", URL_STR(url)); + } + Item::draw(); +} + + +//-------------------------------------------------------------------------- +static void Menu_link_cb(Widget* , void *v_url) +{ + printf("Menu_link_cb: click! :-)\n"); +} + +/* + * Open URL in new window + */ +static void Menu_open_url_nw_cb(Widget* ) +{ + printf("Open URL in new window cb: click! :-)\n"); + a_UIcmd_open_url_nw(popup_bw, popup_url); +} + +/* + * Add bookmark + */ +static void Menu_add_bookmark_cb(Widget* ) +{ + a_UIcmd_add_bookmark(popup_bw, popup_url); +} + +/* + * Find text + */ +static void Menu_find_text_cb(Widget* ) +{ + a_UIcmd_fullscreen_toggle(popup_bw); +} + +/* + * Save link + */ +static void Menu_save_link_cb(Widget* ) +{ + a_UIcmd_save_link(popup_bw, popup_url); +} + +/* + * Save current page + */ +static void Menu_save_page_cb(Widget* ) +{ + a_UIcmd_save(popup_bw); +} + +/* + * Save current page + */ +static void Menu_view_page_source_cb(Widget* ) +{ + a_UIcmd_view_page_source(popup_url); +} + +/* + * View current page's bugs + */ +static void Menu_view_page_bugs_cb(Widget* ) +{ + a_UIcmd_view_page_bugs(popup_bw); +} + +/* + * Validate URL with the W3C + */ +static void Menu_bugmeter_validate_w3c_cb(Widget* ) +{ + Dstr *dstr = dStr_sized_new(128); + + dStr_sprintf(dstr, "http://validator.w3.org/check?uri=%s", + URL_STR(popup_url)); + a_UIcmd_open_urlstr(popup_bw, dstr->str); + dStr_free(dstr, 1); +} + +/* + * Validate URL with the WDG + */ +static void Menu_bugmeter_validate_wdg_cb(Widget* ) +{ + Dstr *dstr = dStr_sized_new(128); + + dStr_sprintf(dstr, + "http://www.htmlhelp.org/cgi-bin/validate.cgi?url=%s&warnings=yes", + URL_STR(popup_url)); + a_UIcmd_open_urlstr(popup_bw, dstr->str); + dStr_free(dstr, 1); +} + +/* + * Show info page for the bug meter + */ +static void Menu_bugmeter_about_cb(Widget* ) +{ + a_UIcmd_open_urlstr(popup_bw, "http://www.dillo.org/help/bug_meter.html"); +} + +/* + * Navigation History callback. + * Go to selected URL. + */ +static void Menu_history_cb(Widget *wid, void *data) +{ + int k = event_button(); + int offset = history_direction * (int)data; + + if (k == 2) { + /* middle button, open in a new window */ + a_UIcmd_nav_jump(popup_bw, offset, 1); + } else { + a_UIcmd_nav_jump(popup_bw, offset, 0); + } +} + +/* + * Page popup menu (construction & popup) + */ +void a_Menu_page_popup(BrowserWindow *bw, DilloUrl *url, const char *bugs_txt) +{ + // One menu for every browser window + static PopupMenu *pm = 0; + // Active/inactive control. + static Item *view_page_bugs_item = 0; + + popup_bw = bw; + popup_url = url; + popup_bugs = bugs_txt; + if (!pm) { + Item *i; + pm = new PopupMenu(0,0,0,0,"&PAGE OPTIONS"); + pm->begin(); + i = new Item("View page Source"); + i->callback(Menu_view_page_source_cb); + //i->shortcut(CTRL+'n'); + i = view_page_bugs_item = new Item("View page Bugs"); + i->callback(Menu_view_page_bugs_cb); + i = new Item("Bookmark this page"); + i->callback(Menu_add_bookmark_cb); + new Divider(); + i = new Item("&Find Text"); + i->callback(Menu_find_text_cb); + i->shortcut(CTRL+'f'); + i = new Item("Jump to..."); + i->deactivate(); + new Divider(); + i = new Item("Save page As..."); + i->callback(Menu_save_page_cb); + + pm->type(PopupMenu::POPUP123); + pm->end(); + } + + if (bugs_txt == NULL) + view_page_bugs_item->deactivate(); + else + view_page_bugs_item->activate(); + + // Make the popup a child of the calling UI object + ((Group *)bw->ui)->add(pm); + + pm->popup(); +} + +/* + * Link popup menu (construction & popup) + */ +void a_Menu_link_popup(BrowserWindow *bw, DilloUrl *url) +{ + // One menu for every browser window + static PopupMenu *pm = 0; + + popup_bw = bw; + popup_url = url; + if (!pm) { + Item *i; + pm = new PopupMenu(0,0,0,0,"&LINK OPTIONS"); + //pm->callback(Menu_link_cb, url); + pm->begin(); + i = new Item("Open Link in New Window"); + i->callback(Menu_open_url_nw_cb); + i = new Item("Bookmark this Link"); + i->callback(Menu_add_bookmark_cb); + i = new Item("Copy Link location"); + i->callback(Menu_link_cb, url); + i->deactivate(); + new Divider(); + i = new Item("Save Link As..."); + i->callback(Menu_save_link_cb); + + pm->type(PopupMenu::POPUP123); + pm->end(); + } + + // Make the popup a child of the calling UI object + ((Group *)bw->ui)->add(pm); + + pm->popup(); +} + +/* + * Bugmeter popup menu (construction & popup) + */ +void a_Menu_bugmeter_popup(BrowserWindow *bw, DilloUrl *url) +{ + // One menu for every browser window + static PopupMenu *pm = 0; + + popup_bw = bw; + popup_url = url; + if (!pm) { + Item *i; + pm = new PopupMenu(0,0,0,0,"&BUG METER OPTIONS"); + pm->begin(); + i = new Item("Validate URL with W3C"); + i->callback(Menu_bugmeter_validate_w3c_cb); + i = new Item("Validate URL with WDG"); + i->callback(Menu_bugmeter_validate_wdg_cb); + new Divider(); + i = new Item("About Bug Meter..."); + i->callback(Menu_bugmeter_about_cb); + + pm->type(PopupMenu::POPUP123); + pm->end(); + } + + // Make the popup a child of the calling UI object + ((Group *)bw->ui)->add(pm); + + pm->popup(); +} + +/* + * Navigation History popup menu (construction & popup) + * + * direction: {backward = -1, forward = 1} + */ +void a_Menu_history_popup(BrowserWindow *bw, int direction) +{ + // One menu for every browser window + static PopupMenu *pm = 0; + Item *it; + int i; + + popup_bw = bw; + history_direction = direction; + + // TODO: hook popdown event with delete or similar. + if (pm) + delete(pm); + if (history_list) + dFree(history_list); + + if (direction == -1) { + pm = new PopupMenu(0,0,0,0, "&PREVIOUS PAGES"); + } else { + pm = new PopupMenu(0,0,0,0, "&FOLLOWING PAGES"); + } + + // Get a list of URLs for this popup + history_list = a_UIcmd_get_history(bw, direction); + + pm->begin(); + for (i = 0; history_list[i] != -1; i += 1) { + // TODO: restrict title size + it = new NewItem(a_History_get_title(history_list[i], 1)); + it->callback(Menu_history_cb, (void*)(i+1)); + } + pm->type(PopupMenu::POPUP123); + pm->end(); + + // Make the popup a child of the calling UI object + // I don't know whether this is necessary... + ((Group *)bw->ui)->add(pm); + + pm->popup(); +} + + +void a_Menu_popup_set_url(BrowserWindow *bw, const DilloUrl *url) { } +void a_Menu_popup_set_url2(BrowserWindow *bw, const DilloUrl *url) { } +void a_Menu_popup_clear_url2(void *menu_popup) { } + +DilloUrl *a_Menu_popup_get_url(BrowserWindow *bw) { return NULL; } + +void a_Menu_pagemarks_new (BrowserWindow *bw) { } +void a_Menu_pagemarks_destroy (BrowserWindow *bw) { } +void a_Menu_pagemarks_add(BrowserWindow *bw, void *page, void *style, + int level) { } +void a_Menu_pagemarks_set_text(BrowserWindow *bw, const char *str) { } + diff --git a/src/menu.hh b/src/menu.hh new file mode 100644 index 00000000..b9a2eb99 --- /dev/null +++ b/src/menu.hh @@ -0,0 +1,34 @@ +#ifndef __MENU_HH__ +#define __MENU_HH__ + +#include "bw.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void a_Menu_page_popup(BrowserWindow *bw, DilloUrl *url, const char *bugs_txt); +void a_Menu_link_popup(BrowserWindow *bw, DilloUrl *url); +void a_Menu_bugmeter_popup(BrowserWindow *bw, DilloUrl *url); +void a_Menu_history_popup(BrowserWindow *bw, int direction); + +//--------------------- +void a_Menu_popup_set_url(BrowserWindow *bw, const DilloUrl *url); +void a_Menu_popup_set_url2(BrowserWindow *bw, const DilloUrl *url); +void a_Menu_popup_clear_url2(void *menu_popup); + +DilloUrl *a_Menu_popup_get_url(BrowserWindow *bw); + +void a_Menu_pagemarks_new (BrowserWindow *bw); +void a_Menu_pagemarks_destroy (BrowserWindow *bw); +void a_Menu_pagemarks_add(BrowserWindow *bw, void *page, void *style, + int level); +void a_Menu_pagemarks_set_text(BrowserWindow *bw, const char *str); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* MENU_HH */ + diff --git a/src/misc.c b/src/misc.c new file mode 100644 index 00000000..f7ab00d3 --- /dev/null +++ b/src/misc.c @@ -0,0 +1,271 @@ +/* + * File: misc.c + * + * Copyright (C) 2000 Jorge Arellano Cid <jcid@dillo.org>, + * Jörgen Viksell <vsksga@hotmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> + +#include "msg.h" +#include "misc.h" + + +/* + * Escape characters as %XX sequences. + * Return value: New string. + */ +char *a_Misc_escape_chars(const char *str, char *esc_set) +{ + static const char *hex = "0123456789ABCDEF"; + char *p = NULL; + Dstr *dstr; + int i; + + dstr = dStr_sized_new(64); + for (i = 0; str[i]; ++i) { + if (str[i] <= 0x1F || str[i] == 0x7F || strchr(esc_set, str[i])) { + dStr_append_c(dstr, '%'); + dStr_append_c(dstr, hex[(str[i] >> 4) & 15]); + dStr_append_c(dstr, hex[str[i] & 15]); + } else { + dStr_append_c(dstr, str[i]); + } + } + p = dstr->str; + dStr_free(dstr, FALSE); + + return p; +} + + +#define TAB_SIZE 8 +/* + * Takes a string and converts any tabs to spaces. + */ +char *a_Misc_expand_tabs(const char *str) +{ + Dstr *New = dStr_new(""); + int len, i, j, pos, old_pos; + char *val; + + if ((len = strlen(str))) { + for (pos = 0, i = 0; i < len; i++) { + if (str[i] == '\t') { + /* Fill with whitespaces until the next tab. */ + old_pos = pos; + pos += TAB_SIZE - (pos % TAB_SIZE); + for (j = old_pos; j < pos; j++) + dStr_append_c(New, ' '); + } else { + dStr_append_c(New, str[i]); + pos++; + } + } + } + val = New->str; + dStr_free(New, FALSE); + return val; +} + +/* TODO: could use dStr ADT! */ +typedef struct ContentType_ { + const char *str; + int len; +} ContentType_t; + +static const ContentType_t MimeTypes[] = { + { "application/octet-stream", 24 }, + { "text/html", 9 }, + { "text/plain", 10 }, + { "image/gif", 9 }, + { "image/png", 9 }, + { "image/jpeg", 10 }, + { NULL, 0 } +}; + +/* + * Detects 'Content-Type' from a data stream sample. + * + * It uses the magic(5) logic from file(1). Currently, it + * only checks the few mime types that Dillo supports. + * + * 'Data' is a pointer to the first bytes of the raw data. + * + * Return value: (0 on success, 1 on doubt, 2 on lack of data). + */ +int a_Misc_get_content_type_from_data(void *Data, size_t Size, const char **PT) +{ + int st = 1; /* default to "doubt' */ + int Type = 0; /* default to "application/octet-stream" */ + char *p = Data; + size_t i, non_ascci; + + /* HTML try */ + for (i = 0; i < Size && isspace(p[i]); ++i); + if ((Size - i >= 5 && !dStrncasecmp(p+i, "<html", 5)) || + (Size - i >= 5 && !dStrncasecmp(p+i, "<head", 5)) || + (Size - i >= 6 && !dStrncasecmp(p+i, "<title", 6)) || + (Size - i >= 14 && !dStrncasecmp(p+i, "<!doctype html", 14)) || + /* this line is workaround for FTP through the Squid proxy */ + (Size - i >= 17 && !dStrncasecmp(p+i, "<!-- HTML listing", 17))) { + + Type = 1; + st = 0; + /* Images */ + } else if (Size >= 4 && !dStrncasecmp(p, "GIF8", 4)) { + Type = 3; + st = 0; + } else if (Size >= 4 && !dStrncasecmp(p, "\x89PNG", 4)) { + Type = 4; + st = 0; + } else if (Size >= 2 && !dStrncasecmp(p, "\xff\xd8", 2)) { + /* JPEG has the first 2 bytes set to 0xffd8 in BigEndian - looking + * at the character representation should be machine independent. */ + Type = 5; + st = 0; + + /* Text */ + } else { + /* We'll assume "text/plain" if the set of chars above 127 is <= 10 + * in a 256-bytes sample. Better heuristics are welcomed! :-) */ + non_ascci = 0; + Size = MIN (Size, 256); + for (i = 0; i < Size; i++) + if ((uchar_t) p[i] > 127) + ++non_ascci; + if (Size == 256) { + Type = (non_ascci > 10) ? 0 : 2; + st = 0; + } else { + Type = (non_ascci > 0) ? 0 : 2; + } + } + + *PT = MimeTypes[Type].str; + return st; +} + +/* + * Check the server-supplied 'Content-Type' against our detected type. + * (some servers seem to default to "text/plain"). + * + * Return value: + * 0, if they match + * -1, if a mismatch is detected + * + * There're many MIME types Dillo doesn't know, they're handled + * as "application/octet-stream" (as the SPEC says). + * + * A mismatch happens when receiving a binary stream as + * "text/plain" or "text/html", or an image that's not an image of its kind. + * + * Note: this is a basic security procedure. + * + */ +int a_Misc_content_type_check(const char *EntryType, const char *DetectedType) +{ + int i; + int st = -1; + + _MSG("Type check: [Srv: %s Det: %s]\n", EntryType, DetectedType); + + if (!EntryType) + return 0; /* there's no mismatch without server type */ + + for (i = 1; MimeTypes[i].str; ++i) + if (dStrncasecmp(EntryType, MimeTypes[i].str, MimeTypes[i].len) == 0) + break; + + if (!MimeTypes[i].str) { + /* type not found, no mismatch */ + st = 0; + } else if (dStrncasecmp(EntryType, "image/", 6) == 0 && + !dStrncasecmp(DetectedType,MimeTypes[i].str,MimeTypes[i].len)){ + /* An image, and there's an exact match */ + st = 0; + } else if (dStrncasecmp(EntryType, "text/", 5) || + dStrncasecmp(DetectedType, "application/", 12)) { + /* Not an application sent as text */ + st = 0; + } + + return st; +} + +/* + * Parse a geometry string. + */ +int a_Misc_parse_geometry(char *str, int *x, int *y, int *w, int *h) +{ + char *p, *t1, *t2; + int n1, n2; + int ret = 0; + + if ((p = strchr(str, 'x')) || (p = strchr(str, 'X'))) { + n1 = strtol(str, &t1, 10); + n2 = strtol(++p, &t2, 10); + if (t1 != str && t2 != p) { + *w = n1; + *h = n2; + ret = 1; + /* parse x,y now */ + p = t2; + n1 = strtol(p, &t1, 10); + n2 = strtol(t1, &t2, 10); + if (t1 != p && t2 != t1) { + *x = n1; + *y = n2; + } + } + } + _MSG("geom: w,h,x,y = (%d,%d,%d,%d)\n", *w, *h, *x, *y); + return ret; +} + +/* + * Encodes string using base64 encoding. + * Return value: new string or NULL if input string is empty. + */ +char *a_Misc_encode_base64(const char *in) +{ + static const char *base64_hex = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + char *out = NULL; + int len, i = 0; + + if (in == NULL) return NULL; + len = strlen(in); + + out = (char *)dMalloc((len + 2) / 3 * 4 + 1); + + for (; len >= 3; len -= 3) { + out[i++] = base64_hex[in[0] >> 2]; + out[i++] = base64_hex[((in[0]<<4) & 0x30) | (in[1]>>4)]; + out[i++] = base64_hex[((in[1]<<2) & 0x3c) | (in[2]>>6)]; + out[i++] = base64_hex[in[2] & 0x3f]; + in += 3; + } + + if (len > 0) { + unsigned char fragment; + out[i++] = base64_hex[in[0] >> 2]; + fragment = (in[0] << 4) & 0x30; + if (len > 1) fragment |= in[1] >> 4; + out[i++] = base64_hex[fragment]; + out[i++] = (len < 2) ? '=' : base64_hex[(in[1] << 2) & 0x3c]; + out[i++] = '='; + } + out[i] = '\0'; + return out; +} diff --git a/src/misc.h b/src/misc.h new file mode 100644 index 00000000..71a00b68 --- /dev/null +++ b/src/misc.h @@ -0,0 +1,25 @@ +#ifndef __DILLO_MISC_H__ +#define __DILLO_MISC_H__ + +#include <stddef.h> /* for size_t */ + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +char *a_Misc_escape_chars(const char *str, char *esc_set); +char *a_Misc_expand_tabs(const char *str); +int a_Misc_get_content_type_from_data(void *Data, size_t Size,const char **PT); +int a_Misc_content_type_check(const char *EntryType, const char *DetectedType); +int a_Misc_parse_geometry(char *geom, int *x, int *y, int *w, int *h); +char *a_Misc_encode_base64(const char *in); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __DILLO_MISC_H__ */ + diff --git a/src/msg.h b/src/msg.h new file mode 100644 index 00000000..cf5b8fed --- /dev/null +++ b/src/msg.h @@ -0,0 +1,42 @@ +#ifndef __MSG_H__ +#define __MSG_H__ + +#include <stdio.h> +#include "prefs.h" + +/* + * You can disable any MSG* macro by adding the '_' prefix. + */ +#define _MSG(...) +#define _MSG_WARN(...) +#define _MSG_HTML(...) +#define _MSG_HTTP(...) + + +#define MSG(...) \ + D_STMT_START { \ + if (prefs.show_msg) \ + printf(__VA_ARGS__); \ + } D_STMT_END + +#define MSG_WARN(...) \ + D_STMT_START { \ + if (prefs.show_msg) \ + printf("** WARNING **: " __VA_ARGS__); \ + } D_STMT_END + +#define MSG_ERR(...) \ + D_STMT_START { \ + if (prefs.show_msg) \ + printf("** ERROR **: " __VA_ARGS__); \ + } D_STMT_END + +#define MSG_HTML(...) \ + D_STMT_START { \ + Html_msg(html, __VA_ARGS__); \ + } D_STMT_END + +#define MSG_HTTP(...) \ + printf("HTTP warning: " __VA_ARGS__) + +#endif /* __MSG_H__ */ diff --git a/src/nav.c b/src/nav.c new file mode 100644 index 00000000..b2d1d679 --- /dev/null +++ b/src/nav.c @@ -0,0 +1,427 @@ +/* + * File: nav.c + * + * Copyright (C) 2000-2006 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* Support for a navigation stack */ + +#include <stdio.h> +#include <sys/stat.h> +#include "msg.h" +#include "list.h" +#include "nav.h" +#include "history.h" +#include "web.hh" +#include "uicmd.hh" +#include "dialog.hh" +#include "prefs.h" +#include "capi.h" + +//#define DEBUG_LEVEL 3 +#include "debug.h" + +/* + * Forward declarations + */ +static void Nav_reload(BrowserWindow *bw); + + +/* + * Free memory used by this module + */ +void a_Nav_free(BrowserWindow *bw) +{ + a_Nav_cancel_expect(bw); + dFree(bw->nav_stack); +} + + +/* Navigation stack methods ------------------------------------------------ */ + +/* + * Return current nav_stack pointer [0 based; -1 = empty] + */ +int a_Nav_stack_ptr(BrowserWindow *bw) +{ + return bw->nav_stack_ptr; +} + +/* + * Move the nav_stack pointer + */ +static void Nav_stack_move_ptr(BrowserWindow *bw, int offset) +{ + int nptr; + + dReturn_if_fail (bw != NULL); + if (offset != 0) { + nptr = bw->nav_stack_ptr + offset; + dReturn_if_fail (nptr >= 0 && nptr < bw->nav_stack_size); + bw->nav_stack_ptr = nptr; + } +} + +/* + * Return size of nav_stack [1 based] + */ +int a_Nav_stack_size(BrowserWindow *bw) +{ + return bw->nav_stack_size; +} + +/* + * Add an URL-index in the navigation stack. + */ +static void Nav_stack_add(BrowserWindow *bw, int idx) +{ + dReturn_if_fail (bw != NULL); + + ++bw->nav_stack_ptr; + if (bw->nav_stack_ptr == bw->nav_stack_size) { + a_List_add(bw->nav_stack, bw->nav_stack_size, bw->nav_stack_size_max); + ++bw->nav_stack_size; + } else { + bw->nav_stack_size = bw->nav_stack_ptr + 1; + } + bw->nav_stack[bw->nav_stack_ptr] = idx; +} + +/* + * Remove an URL-index from the navigation stack. + */ +static void Nav_stack_remove(BrowserWindow *bw, int idx) +{ + int sz = a_Nav_stack_size(bw); + + dReturn_if_fail (bw != NULL && idx >=0 && idx < sz); + + for ( ; idx < sz - 1; ++idx) + bw->nav_stack[idx] = bw->nav_stack[idx + 1]; + if (bw->nav_stack_ptr == --bw->nav_stack_size) + --bw->nav_stack_ptr; +} + +/* + * Remove equal adyacent URLs at the top of the stack. + * (It may happen with redirections) + */ +static void Nav_stack_clean(BrowserWindow *bw) +{ + int i; + + dReturn_if_fail (bw != NULL); + + if ((i = a_Nav_stack_size(bw)) >= 2 && + bw->nav_stack[i-2] == bw->nav_stack[i-1]) + Nav_stack_remove(bw, i - 1); +} + + +/* General methods --------------------------------------------------------- */ + +/* + * Create a DilloWeb structure for 'url' and ask the cache to send it back. + * - Also set a few things related to the browser window. + * This function requests the page's root-URL; images and related stuff + * are fetched directly by the HTML module. + */ +static void Nav_open_url(BrowserWindow *bw, const DilloUrl *url, int offset) +{ + DilloUrl *old_url = NULL; + bool_t MustLoad; + int ClientKey; + DilloWeb *Web; + char *loc_text; + bool_t ForceReload = (URL_FLAGS(url) & URL_E2EReload); + + MSG("Nav_open_url: Url=>%s<\n", URL_STR_(url)); + + /* Get the url of the current page */ + if (a_Nav_stack_ptr(bw) != -1) + old_url = a_History_get_url(NAV_TOP(bw)); + + /* Record current scrolling position + * (the strcmp check is necessary because of redirections) */ + loc_text = a_UIcmd_get_location_text(bw); + if (old_url && + !strcmp(URL_STR(old_url), loc_text)) { + + //old_url->scrolling_position_x = + // a_Dw_gtk_scrolled_window_get_scrolling_position_x( + // GTK_DW_SCROLLED_WINDOW(bw->docwin)); + //old_url->scrolling_position_y = + // a_Dw_gtk_scrolled_window_get_scrolling_position_y( + // GTK_DW_SCROLLED_WINDOW(bw->docwin)); + + a_Url_set_pos(old_url, 0, 0); + } + + /* Update navigation-stack-pointer (offset may be zero) */ + Nav_stack_move_ptr(bw, offset); + + /* Page must be reloaded, if old and new url (without anchor) differ */ + MustLoad = ForceReload || !old_url; + if (old_url){ + MustLoad |= (a_Url_cmp(old_url, url) != 0); + MustLoad |= strcmp(URL_STR(old_url), a_UIcmd_get_location_text(bw)); + } + dFree(loc_text); + + if (MustLoad) { + a_Bw_stop_clients(bw, BW_Root + BW_Img); + a_Bw_cleanup(bw); + + // a_Menu_pagemarks_new(bw); + + Web = a_Web_new(url); + Web->bw = bw; + Web->flags |= WEB_RootUrl; + if ((ClientKey = a_Capi_open_url(Web, NULL, NULL)) != 0) { + a_Bw_add_client(bw, ClientKey, 1); + a_Bw_add_url(bw, url); + } + } + + /* Jump to #anchor position */ + if (URL_FRAGMENT_(url)) { + /* todo: push on stack */ + char *pf = a_Url_decode_hex_str(URL_FRAGMENT_(url)); + //a_Dw_render_layout_set_anchor(bw->render_layout, pf); + dFree(pf); + } +} + +/* + * Cancel the last expected url if present. The responsibility + * for actually aborting the data stream remains with the caller. + */ +void a_Nav_cancel_expect(BrowserWindow *bw) +{ + if (bw->nav_expecting) { + if (bw->nav_expect_url) { + a_Url_free(bw->nav_expect_url); + bw->nav_expect_url = NULL; + } + bw->nav_expecting = FALSE; + } +} + +/* + * We have an answer! Set things accordingly. + */ +void a_Nav_expect_done(BrowserWindow *bw) +{ + int idx; + DilloUrl *url; + + dReturn_if_fail(bw != NULL); + + if (bw->nav_expecting) { + url = bw->nav_expect_url; + /* unset E2EReload before adding this url to history */ + a_Url_set_flags(url, URL_FLAGS(url) & ~URL_E2EReload); + idx = a_History_add_url(url); + Nav_stack_add(bw, idx); + + a_Url_free(url); + bw->nav_expect_url = NULL; + bw->nav_expecting = FALSE; + } + Nav_stack_clean(bw); + a_UIcmd_set_buttons_sens(bw); +} + +/* + * Make 'url' the current browsed page (upon data arrival) + * - Set bw to expect the URL data + * - Ask the cache to feed back the requested URL (via Nav_open_url) + */ +void a_Nav_push(BrowserWindow *bw, const DilloUrl *url) +{ + dReturn_if_fail (bw != NULL); + + if (bw->nav_expecting && !a_Url_cmp(bw->nav_expect_url, url) && + !strcmp(URL_FRAGMENT(bw->nav_expect_url),URL_FRAGMENT(url))) { + /* we're already expecting that url (most probably a double-click) */ + return; + } + a_Nav_cancel_expect(bw); + bw->nav_expect_url = a_Url_dup(url); + bw->nav_expecting = TRUE; + Nav_open_url(bw, url, 0); +} + +/* + * Same as a_Nav_push() but in a new window. + */ +void a_Nav_push_nw(BrowserWindow *bw, const DilloUrl *url) +{ + int w, h; + BrowserWindow *newbw; + + a_UIcmd_get_wh(bw, &w, &h); + newbw = a_UIcmd_browser_window_new(w, h); + a_Nav_push(newbw, url); +} + +/* + * Wraps a_Nav_push to match 'DwPage->link' function type + */ +void a_Nav_vpush(void *vbw, const DilloUrl *url) +{ + a_Nav_push(vbw, url); +} + +/* + * Send the browser back to previous page + */ +void a_Nav_back(BrowserWindow *bw) +{ + int idx = a_Nav_stack_ptr(bw); + + a_Nav_cancel_expect(bw); + if (--idx >= 0){ + a_UIcmd_set_msg(bw, ""); + Nav_open_url(bw, a_History_get_url(NAV_IDX(bw,idx)), -1); + } +} + +/* + * Send the browser to next page in the history list + */ +void a_Nav_forw(BrowserWindow *bw) +{ + int idx = a_Nav_stack_ptr(bw); + + a_Nav_cancel_expect(bw); + if (++idx < a_Nav_stack_size(bw)) { + a_UIcmd_set_msg(bw, ""); + Nav_open_url(bw, a_History_get_url(NAV_IDX(bw,idx)), +1); + } +} + +/* + * Redirect the browser to the HOME page! + */ +void a_Nav_home(BrowserWindow *bw) +{ + a_Nav_push(bw, prefs.home); +} + +/* + * This one does a_Nav_reload's job! + */ +static void Nav_reload(BrowserWindow *bw) +{ + DilloUrl *url, *ReqURL; + + a_Nav_cancel_expect(bw); + if (a_Nav_stack_size(bw)) { + url = a_History_get_url(NAV_TOP(bw)); + ReqURL = a_Url_dup(a_History_get_url(NAV_TOP(bw))); + /* Let's make reload be end-to-end */ + a_Url_set_flags(ReqURL, URL_FLAGS(ReqURL) | URL_E2EReload); + /* This is an explicit reload, so clear the SpamSafe flag */ + a_Url_set_flags(ReqURL, URL_FLAGS(ReqURL) & ~URL_SpamSafe); + Nav_open_url(bw, ReqURL, 0); + a_Url_free(ReqURL); + } +} + +/* + * Implement the RELOAD button functionality. + * (Currently it only reloads the page, not its images) + */ +void a_Nav_reload(BrowserWindow *bw) +{ + DilloUrl *url; + int choice; + + a_Nav_cancel_expect(bw); + if (a_Nav_stack_size(bw)) { + url = a_History_get_url(NAV_TOP(bw)); + if (URL_FLAGS(url) & URL_Post) { + /* Attempt to repost data, let's confirm... */ + choice = a_Dialog_choice3("Repost form data?", + "Yes", "*No", "Cancel"); + if (choice == 0) { /* "Yes" */ + DEBUG_MSG(3, "Nav_reload_confirmed\n"); + Nav_reload(bw); + } + + } else { + Nav_reload(bw); + } + } +} + +/* + * Jump to a URL in the Navigation stack. + */ +void a_Nav_jump(BrowserWindow *bw, int offset, int new_bw) +{ + int idx = a_Nav_stack_ptr(bw) + offset; + + if (new_bw) { + a_Nav_push_nw(bw, a_History_get_url(NAV_IDX(bw,idx))); + } else { + a_Nav_cancel_expect(bw); + Nav_open_url(bw, a_History_get_url(NAV_IDX(bw,idx)), offset); + a_UIcmd_set_buttons_sens(bw); + } +} + + +/* Specific methods -------------------------------------------------------- */ + +/* + * Receive data from the cache and save it to a local file + */ +static void Nav_save_cb(int Op, CacheClient_t *Client) +{ + DilloWeb *Web = Client->Web; + int Bytes; + + if (Op){ + struct stat st; + + fflush(Web->stream); + fstat(fileno(Web->stream), &st); + fclose(Web->stream); + a_UIcmd_set_msg(Web->bw, "File saved (%d Bytes)", st.st_size); + } else { + if ((Bytes = Client->BufSize - Web->SavedBytes) > 0) { + Bytes = fwrite(Client->Buf + Web->SavedBytes, 1, Bytes, Web->stream); + Web->SavedBytes += Bytes; + } + } +} + +/* + * Save a URL (from cache or from the net). + */ +void a_Nav_save_url(BrowserWindow *bw, + const DilloUrl *url, const char *filename) +{ + DilloWeb *Web = a_Web_new(url); + Web->bw = bw; + Web->filename = dStrdup(filename); + Web->flags |= WEB_Download; + /* todo: keep track of this client */ + a_Capi_open_url(Web, Nav_save_cb, Web); +} + +/* + * Wrapper for a_Capi_get_buf. + */ +int a_Nav_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize) +{ + return a_Capi_get_buf(Url, PBuf, BufSize); +} + diff --git a/src/nav.h b/src/nav.h new file mode 100644 index 00000000..7b6999ac --- /dev/null +++ b/src/nav.h @@ -0,0 +1,40 @@ +#ifndef __NAV_H__ +#define __NAV_H__ + +#include "bw.h" + + +/* useful macros for the navigation stack */ +#define NAV_IDX(bw, i) (bw)->nav_stack[i] +#define NAV_TOP(bw) (bw)->nav_stack[(bw)->nav_stack_ptr] + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +void a_Nav_push(BrowserWindow *bw, const DilloUrl *url); +void a_Nav_push_nw(BrowserWindow *bw, const DilloUrl *url); +void a_Nav_vpush(void *vbw, const DilloUrl *url); +void a_Nav_back(BrowserWindow *bw); +void a_Nav_forw(BrowserWindow *bw); +void a_Nav_home(BrowserWindow *bw); +void a_Nav_reload(BrowserWindow *bw); +void a_Nav_jump(BrowserWindow *bw, int offset, int new_bw); +void a_Nav_free(BrowserWindow *bw); +void a_Nav_cancel_expect (BrowserWindow *bw); +void a_Nav_expect_done(BrowserWindow *bw); +int a_Nav_stack_ptr(BrowserWindow *bw); +int a_Nav_stack_size(BrowserWindow *bw); + +void a_Nav_save_url(BrowserWindow *bw, + const DilloUrl *url, const char *filename); +int a_Nav_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __NAV_H__ */ + + diff --git a/src/pixmaps.h b/src/pixmaps.h new file mode 100644 index 00000000..57b05f97 --- /dev/null +++ b/src/pixmaps.h @@ -0,0 +1,1652 @@ +/* + * File: pixmaps.h + * + * Copyright (C) 2000, 2001 Jorge Arellano Cid <jcid@dillo.org> + * Design by John Grantham, Dipl.-Designer; http://www.grantham.de/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __PIXMAPS_H__ +#define __PIXMAPS_H__ + +/* XPM +static char * history_xpm[] = { +"11 20 6 1", +" c None", +". c #000000000000", +"X c #9658A289BEFB", +"O c #FFFF9A690000", +"+ c #FFFFFFFF0000", +"@ c #FFFFFFFFFFFF", +" ", +" . ", +" .X. ", +" .XXX. ", +" .XXXXX. ", +" .XXXXXXX. ", +" ...XXX... ", +" .XXX. ", +" .XXX. ", +" .XXX. ", +" OOOOO ", +" .XXX. ", +" .XXX. ", +" ...XXX... ", +" .XXXXXXX. ", +" .XXXXX. ", +" .XXX. ", +" .X. ", +" . ", +" "}; +*/ +/* XPM */ +static char * left_xpm[] = { +"22 22 46 1", +" c None", +". c #000000", +"+ c #FF7856", +"@ c #FF8361", +"# c #FF5325", +"$ c #FF8C68", +"% c #FF6636", +"& c #FF6333", +"* c #FF936B", +"= c #FF6E39", +"- c #FF5111", +"; c #FF8C61", +"> c #FFA685", +", c #FF8659", +"' c #FF996E", +") c #FF773D", +"! c #FF5C16", +"~ c #FF570F", +"{ c #EC510E", +"] c #FF894C", +"^ c #FF7E3B", +"/ c #FF681A", +"( c #FF6414", +"_ c #EC5D13", +": c #C56932", +"< c #CB5A15", +"[ c #F86E19", +"} c #FF711A", +"| c #EC6918", +"1 c #9F5F31", +"2 c #C56118", +"3 c #F87A1E", +"4 c #FF7D1F", +"5 c #EC741D", +"6 c #9F6532", +"7 c #C56A1C", +"8 c #F88523", +"9 c #8C4B14", +"0 c #582F0C", +"a c #894A13", +"b c #9F6B35", +"c c #C57320", +"d c #B96C1E", +"e c #A47339", +"f c #C77C23", +"g c #93601C", +" ", +" ", +" ", +" ", +" .. ", +" .+. ", +" .@#. ", +" .$%&...... .... ", +" .*=-=;>>>,. .,,. ", +" .')!~~~~~~{. .{{. ", +" .]^/(((((((_. .__. ", +" .:<[}}}}}}}|. .||. ", +" .1234444445. .55. ", +" .67879000a. .aa. ", +" .bcd...... .... ", +" .ef. ", +" .g. ", +" .. ", +" .....", +" .>. ", +" . ", +" "}; +/* XPM */ +static char * right_xpm[] = { +"22 22 46 1", +" c None", +". c #000000", +"+ c #569A59", +"@ c #228126", +"# c #60A463", +"$ c #2E9132", +"% c #319335", +"& c #65AE68", +"* c #52AC55", +"= c #80C282", +"- c #5BB15E", +"; c #319C35", +"> c #07880C", +", c #65B568", +"' c #008905", +") c #009405", +"! c #07970C", +"~ c #31A935", +"{ c #65BF68", +"] c #009705", +"^ c #00A305", +"/ c #07A60C", +"( c #2AB22E", +"_ c #3CB83F", +": c #00A605", +"< c #00B305", +"[ c #00AE05", +"} c #008E04", +"| c #219424", +"1 c #00B405", +"2 c #00C205", +"3 c #00BD05", +"4 c #009604", +"5 c #218124", +"6 c #007003", +"7 c #004802", +"8 c #007303", +"9 c #00A104", +"0 c #00CB05", +"a c #218924", +"b c #00A304", +"c c #00AD04", +"d c #219024", +"e c #00B804", +"f c #219B24", +"g c #008E03", +" ", +" ", +" ", +" ", +" .. ", +" .+. ", +" .@#. ", +" .... ......$%&. ", +" .**. .*===-;>;,. ", +" .''. .'))))))!~{. ", +" .]]. .]^^^^^^^/(_. ", +" .::. .:<<<<<<<[}|. ", +" .11. .1222222345. ", +" .66. .67778909a. ", +" .... ......bcd. ", +" .ef. ", +" .g. ", +" .. ", +" .....", +" .=. ", +" . ", +" "}; +/* XPM */ +static char * reload_xpm[] = { +"22 22 147 2", +" c None", +". c #000000", +"+ c #BABABA", +"@ c #CCCCCC", +"# c #CBCBCB", +"$ c #5D5D5D", +"% c #909090", +"& c #9C9C9C", +"* c #A0A0A0", +"= c #EBEBEB", +"- c #4C4C4C", +"; c #949494", +"> c #8D8D8D", +", c #FFFFFF", +"' c #E4E4E4", +") c #424242", +"! c #989898", +"~ c #A4A4A4", +"{ c #878787", +"] c #FBFBFB", +"^ c #E1E1E1", +"/ c #393939", +"( c #9B9B9B", +"_ c #A8A8A8", +": c #898989", +"< c #ECECEC", +"[ c #DEDEDE", +"} c #313131", +"| c #0B0B0B", +"1 c #0C0C0C", +"2 c #2B2B2B", +"3 c #575757", +"4 c #8B8B8B", +"5 c #AEAEAE", +"6 c #8E8E8E", +"7 c #DDDDDD", +"8 c #2C2C2C", +"9 c #282828", +"0 c #666666", +"a c #A3A3A3", +"b c #C0C0C0", +"c c #7D7D7D", +"d c #3F3F3F", +"e c #1D1D1D", +"f c #6B6B6B", +"g c #929292", +"h c #585858", +"i c #BEBEBE", +"j c #4F4F4F", +"k c #A5A5A5", +"l c #BDBDBD", +"m c #B1B1B1", +"n c #A9A9A9", +"o c #AAAAAA", +"p c #696969", +"q c #0A0A0A", +"r c #5F5F5F", +"s c #515151", +"t c #545454", +"u c #686868", +"v c #868686", +"w c #B5B5B5", +"x c #B4B4B4", +"y c #7F907F", +"z c #436B43", +"A c #144F14", +"B c #004800", +"C c #1E1E1E", +"D c #838383", +"E c #9F9F9F", +"F c #262626", +"G c #9D9D9D", +"H c #B8B8B8", +"I c #839583", +"J c #577A57", +"K c #7E917E", +"L c #A7AAA7", +"M c #426D42", +"N c #004600", +"O c #A1A1A1", +"P c #404040", +"Q c #636363", +"R c #C6C6C6", +"S c #B7B7B7", +"T c #728C72", +"U c #B0B2B0", +"V c #777777", +"W c #323232", +"X c #C2C2C2", +"Y c #BCBCBC", +"Z c #8EA18E", +"` c #004B00", +" . c #979797", +".. c #0F0F0F", +"+. c #D2D2D2", +"@. c #AFAFAF", +"#. c #C4C4C4", +"$. c #B0B0B0", +"%. c #D9D9D9", +"&. c #C9C9C9", +"*. c #7A5200", +"=. c #B8AE9A", +"-. c #101010", +";. c #DFDFDF", +">. c #CECECE", +",. c #BFBFBF", +"'. c #D4D4D4", +"). c #7F5500", +"!. c #D2D1CE", +"~. c #B4A585", +"{. c #E5E5E5", +"]. c #845800", +"^. c #A58A53", +"/. c #C3B79F", +"(. c #D9D7D3", +"_. c #B09A6E", +":. c #C6BBA5", +"<. c #747474", +"[. c #3A3A3A", +"}. c #EAEAEA", +"|. c #222222", +"1. c #865A00", +"2. c #CCC1AA", +"3. c #AC915B", +"4. c #936C1C", +"5. c #E0E0E0", +"6. c #414141", +"7. c #787878", +"8. c #F0F0F0", +"9. c #8C8C8C", +"0. c #C3C3C3", +"a. c #E2E2E2", +"b. c #F4F4F4", +"c. c #646464", +"d. c #9E9E9E", +"e. c #DCDCDC", +"f. c #6A6A6A", +"g. c #555555", +"h. c #0E0E0E", +"i. c #F8F8F8", +"j. c #E6E6E6", +"k. c #191919", +"l. c #2F2F2F", +"m. c #464646", +"n. c #444444", +"o. c #242424", +"p. c #343434", +" . . . . . . . . . . . ", +" . + @ @ @ @ @ @ @ # $ . ", +" . % & & & & & & & * = - . ", +" . ; * * * * * * * > , ' ) . ", +" . ! ~ ~ ~ ~ ~ ~ ~ { ] , ^ / . ", +" . ( _ _ _ _ _ _ _ : < , , [ } . ", +" . | 1 1 1 2 3 4 5 6 < , , , 7 8 . ", +" . 9 0 a b b a c d e f g : h h f * i . ", +" . j k l m n n m l o p q r f s s t u v . ", +" . - w x y z A A z y B w u C D & & & E * . ", +" F G H I J K L K M N N H O P Q R R R R S . ", +". $ i S T U w w w B B B S i V W @ @ @ @ l . ", +". 6 X Y Z Y Y Y ` ` ` ` Y X ...+.+.+.+.X . ", +". @.R #.#.#.#.#.#.#.#.#.#.R $...%.%.%.%.&.. ", +". n #.@ *.*.*.*.@ @ @ =.@ #.o -.;.;.;.;.>.. ", +". v ,.'.).).).'.'.'.!.~.'.,.% -.{.{.{.{.'.. ", +". 3 w '.].].^./.(./._.:.'.w <.[.}.}.}.}.%.. ", +" |.{ X 1.2.3.4.4.3.2.5.X > 6.7.8.8.8.8.[ . ", +" . [.9.0.a.}.}.}.}.a.0.> $ 9 X b.b.b.b.a.. ", +" . W c.d.R e.e.R d.f.g.h.; i.i.i.i.i.j.. ", +" . k.l.m.j j n.[.o.h.p.3 3 3 3 3 3 { . ", +" . . . . . . . . . . . . . . . . . "}; +/* XPM */ +static char * home_xpm[] = { +"22 22 106 2", +" c None", +". c #190E0B", +"+ c #180D09", +"@ c #D8947D", +"# c #000000", +"$ c #190D09", +"% c #CC6746", +"& c #A7401F", +"* c #C65834", +"= c #1A0D09", +"- c #D16746", +"; c #A63F1F", +"> c #160D09", +", c #C13206", +"' c #D86746", +") c #AD3F1F", +"! c #1C0F0A", +"~ c #9B9376", +"{ c #D2502A", +"] c #1B0D09", +"^ c #DF6746", +"/ c #B43F1F", +"( c #999174", +"_ c #C1B588", +": c #D34721", +"< c #D8461E", +"[ c #1C0D09", +"} c #E76746", +"| c #BB3F1F", +"1 c #1D0F0A", +"2 c #C5B98B", +"3 c #C4B889", +"4 c #93371F", +"5 c #E14821", +"6 c #1B0803", +"7 c #1D0D09", +"8 c #EE6746", +"9 c #C33F1F", +"0 c #1E0F0A", +"a c #9E9678", +"b c #CABD8F", +"c c #CCBF8E", +"d c #CFC290", +"e c #1E0D09", +"f c #F56746", +"g c #CA3F1F", +"h c #21110A", +"i c #A09879", +"j c #CDC091", +"k c #D2C593", +"l c #D8CA97", +"m c #DCCE9A", +"n c #1D0500", +"o c #C63F21", +"p c #9E361F", +"q c #130300", +"r c #988E6A", +"s c #D1C492", +"t c #D5C895", +"u c #DDCF9A", +"v c #E4D69F", +"w c #E9DAA3", +"x c #C9BC8C", +"y c #DACC98", +"z c #E0D29C", +"A c #E8D9A2", +"B c #EFE0A7", +"C c #F2E3A9", +"D c #5E5842", +"E c #676047", +"F c #F9E9AE", +"G c #655F46", +"H c #DBCD99", +"I c #C16C4E", +"J c #D3927C", +"K c #C86F51", +"L c #FCECB0", +"M c #D6D6D6", +"N c #D2D2D2", +"O c #E3D49F", +"P c #A82900", +"Q c #B82C00", +"R c #AB2A00", +"S c #FEEEB1", +"T c #6F6D6D", +"U c #6D6B6B", +"V c #E7D9A2", +"W c #B52A00", +"X c #C42D00", +"Y c #B62A00", +"Z c #FFEFB2", +"` c #696249", +" . c #676048", +".. c #EADBA4", +"+. c #C12A00", +"@. c #D22D00", +"#. c #C22A00", +"$. c #ECDDA5", +"%. c #CE2A00", +"&. c #DF2D00", +"*. c #DA2A00", +"=. c #EC2D00", +"-. c #898060", +";. c #851800", +">. c #551000", +",. c #58523D", +" ", +" . . ", +" + @ @ + # # # ", +" $ % & & % $ # * # ", +" = - ; > > ; - = # , # ", +" = ' ) ! ~ ~ ! ) ' = { # ", +" ] ^ / ! ( _ _ ( ! / : < # ", +" [ } | 1 ~ 2 3 3 2 ~ 1 4 5 6 ", +" 7 8 9 0 a b c d d c b a 0 9 8 7 ", +" e f g h i j k l m m l k j i h g f e ", +" n o p q r s t u v w w v u t s r q p o n ", +" # # # # x y z A B C C B A z y x # # # # ", +" # s D # # # E F G # # D s # ", +" # H # I J K # L # M N # H # ", +" # O # P Q R # S # T U # O # ", +" # V # W X Y # Z ` # # .V # ", +" # ..# +.@.#.# Z Z Z S S ..# ", +" # $.# %.&.%.# Z Z Z Z Z $.# ", +" # $.# *.=.*.# Z Z Z Z Z $.# ", +" # -.# ;.>.;.# ,.,.,.,.,.-.# ", +" # # # # # # # # # # # # # # ", +" "}; +/* XPM */ +static char * save_xpm[] = { +"22 22 59 1", +" c None", +". c #000000", +"+ c #638163", +"@ c #4D5A4D", +"# c #A4BDA4", +"$ c #184318", +"% c #0E280E", +"& c #4B7E4B", +"* c #194419", +"= c #0F290F", +"- c #508350", +"; c #1B481B", +"> c #102B10", +", c #568956", +"' c #1D4C1D", +") c #112D11", +"! c #5D905D", +"~ c #205020", +"{ c #133013", +"] c #639663", +"^ c #235223", +"/ c #153115", +"( c #6B9E6B", +"_ c #275627", +": c #173317", +"< c #73A673", +"[ c #2B5A2B", +"} c #2E612E", +"| c #193619", +"1 c #2E5D2E", +"2 c #152A15", +"3 c #326132", +"4 c #838383", +"5 c #9D9D9D", +"6 c #B3B3B3", +"7 c #979797", +"8 c #366536", +"9 c #585858", +"0 c #565656", +"a c #727272", +"b c #6A6A6A", +"c c #386838", +"d c #848484", +"e c #7A7A7A", +"f c #3C6B3C", +"g c #999999", +"h c #8E8E8E", +"i c #366036", +"j c #3F6E3F", +"k c #BDBDBD", +"l c #BABABA", +"m c #ADADAD", +"n c #A0A0A0", +"o c #162616", +"p c #264126", +"q c #949494", +"r c #696969", +"s c #424242", +"t c #676767", +" ", +" ", +" ................ ", +" .+@############@+. ", +" .$%&&&&&&&&&&&&%$. ", +" .*=------------=*. ", +" .;>,,,,,,,,,,,,>;. ", +" .')!!!!!!!!!!!!)'. ", +" .~{]]]]]]]]]]]]{~. ", +" .^/((((((((((((/^. ", +" ._:<<<<<<<<<<<<:_. ", +" .[}||||||||||||}[. ", +" .12............21. ", +" .3.444566666667.3. ", +" .8.9..0aaaaaaab.8. ", +" .c.d..eddddddde.c. ", +" .f.g..hgggggggh.i. ", +" .j.k..lmmmmmmmn.o. ", +" .p.qqqrssssssst.. ", +" ............... ", +" ", +" "}; + +/* XPM */ +static char * stop_xpm[] = { +"22 22 77 1", +" c None", +". c #000000", +"+ c #703434", +"@ c #E57F7F", +"# c #E68080", +"$ c #6F2929", +"% c #DB4343", +"& c #D31A1A", +"* c #CE0000", +"= c #DC4343", +"- c #D51A1A", +"; c #D00000", +"> c #702929", +", c #DF4343", +"' c #D71A1A", +") c #D30000", +"! c #6F1B1B", +"~ c #E14343", +"{ c #DA1A1A", +"] c #D60000", +"^ c #A66666", +"/ c #841717", +"( c #DA0404", +"_ c #DD1A1A", +": c #D90000", +"< c #D56B6B", +"[ c #FFFFFF", +"} c #E7D5D5", +"| c #861717", +"1 c #CC0000", +"2 c #DC0000", +"3 c #E64C4C", +"4 c #FADEDE", +"5 c #EAD5D5", +"6 c #722C2C", +"7 c #CF0000", +"8 c #E00000", +"9 c #EB5C5C", +"0 c #FBDFDF", +"a c #FDF8F8", +"b c #E40000", +"c c #DE3535", +"d c #FEF8F8", +"e c #E70000", +"f c #971C1C", +"g c #EED5D5", +"h c #FEFAFA", +"i c #EB0000", +"j c #AA1C1C", +"k c #EFD5D5", +"l c #FDE3E3", +"m c #F69595", +"n c #C50000", +"o c #D50000", +"p c #EF0000", +"q c #F77777", +"r c #FDE4E4", +"s c #F56565", +"t c #4D0000", +"u c #9E0000", +"v c #D70000", +"w c #F20000", +"x c #FAA7A7", +"y c #F76969", +"z c #420000", +"A c #A00000", +"B c #DA0000", +"C c #F50000", +"D c #A20000", +"E c #DD0000", +"F c #F80000", +"G c #430000", +"H c #A40000", +"I c #DF0000", +"J c #FB0000", +"K c #380000", +"L c #570000", +" ", +" ", +" ......... ", +" .+@#####@+. ", +" .$%&*****&%$. ", +" .$=-;;;;;;;-=$. ", +" .>,')))))))))',>. ", +" .!~{]]^/]]]/^]]{~!. ", +" .(_::<[}|:|}[<::_(. ", +" .122234[565[432221. ", +" .7888890[a[0988887. ", +" .)bbbbbcd[dcbbbbb). ", +" .]eeeefg[h[gfeeee]. ", +" .:iiijk[lml[kjiii:. ", +" .noppq[rspsr[qppon. ", +" .tuvwwxywwwyxwwvut. ", +" .zABCCCCCCCCCBAz. ", +" .zDEFFFFFFFEDz. ", +" .GHIJJJJJIHG. ", +" .KLLLLLLLK. ", +" ......... ", +" "}; +/* XPM */ +static char * bm_xpm[] = { +"22 22 86 1", +" c None", +". c #000000", +"+ c #150B0A", +"@ c #A46C6A", +"# c #B15652", +"$ c #C68380", +"% c #840600", +"& c #8F0600", +"* c #979797", +"= c #B3B3B3", +"- c #880600", +"; c #930600", +"> c #616161", +", c #696969", +"' c #595959", +") c #8D0600", +"! c #980600", +"~ c #666666", +"{ c #6E6E6E", +"] c #5E5E5E", +"^ c #920600", +"/ c #9E0700", +"( c #6B6B6B", +"_ c #747474", +": c #636363", +"< c #970600", +"[ c #A30700", +"} c #727272", +"| c #7B7B7B", +"1 c #9D0600", +"2 c #AA0700", +"3 c #777777", +"4 c #818181", +"5 c #A40600", +"6 c #B10700", +"7 c #7F7F7F", +"8 c #898989", +"9 c #757575", +"0 c #A90700", +"a c #B70800", +"b c #868686", +"c c #919191", +"d c #7C7C7C", +"e c #BF0800", +"f c #8E8E8E", +"g c #999999", +"h c #828282", +"i c #C60900", +"j c #959595", +"k c #A1A1A1", +"l c #BD0800", +"m c #CC0900", +"n c #9C9C9C", +"o c #A9A9A9", +"p c #909090", +"q c #C40800", +"r c #D40900", +"s c #A3A3A3", +"t c #B0B0B0", +"u c #969696", +"v c #CA0900", +"w c #DA0A00", +"x c #B7B7B7", +"y c #D00900", +"z c #E10A00", +"A c #AFAFAF", +"B c #BDBDBD", +"C c #D70900", +"D c #E80A00", +"E c #B4B4B4", +"F c #C3C3C3", +"G c #A6A6A6", +"H c #DB0900", +"I c #E10900", +"J c #454545", +"K c #CD0900", +"L c #A00700", +"M c #7B1510", +"N c #A40700", +"O c #A20700", +"P c #3E0300", +"Q c #4A0300", +"R c #A90800", +"S c #540400", +"T c #010000", +"U c #660400", +" ...... ", +" +@#$$$#. ", +" .......%&&&%.... ", +" .*====.-;;;-.=*. ", +" .>,',,.)!!!).,>. ", +" .~{]{{.^///^.{~. ", +" .(_:__.<[[[<._(. ", +" .}|,||.12221.|}. ", +" .34{44.56665.43. ", +" .78988.0aaa0.87. ", +" .bcdcc.6eee6.cb. ", +" .fghgg.aiiia.gf. ", +" .jk8kk.lmmml.kj. ", +" .nopoo.qrrrq.on. ", +" .stutt.vwwwv.ts. ", +" .oxnxx.yzzzy.xo. ", +" .ABkBB.CDDDC.BA. ", +" .EFGFF.HIIIH.FE. ", +" .(JJJJ.KLMNy.J(. ", +" .......OP.QR.... ", +" .S. TU. ", +" . . "}; + +/* Small icons here */ + +/* XPM */ +static char * left_s_xpm[] = { +"16 16 33 1", +" c None", +". c #000000", +"+ c #1F120A", +"@ c #FF9A59", +"# c #DE7E42", +"$ c #FF7A26", +"% c #DE8247", +"& c #FF8335", +"* c #FF8232", +"= c #FF6A0C", +"- c #FF9D5E", +"; c #FFB382", +"> c #FF9755", +", c #FF6605", +"' c #EC5E05", +") c #1F0F05", +"! c #DE691E", +"~ c #FF7F2E", +"{ c #180A00", +"] c #A44103", +"^ c #CB5104", +"/ c #F86305", +"( c #120700", +"_ c #7E3203", +": c #C54F04", +"< c #8C3803", +"[ c #582302", +"} c #893703", +"| c #B94A04", +"1 c #833403", +"2 c #C75004", +"3 c #110700", +"4 c #933B03", +" ", +" .. ", +" +@. ", +" +#$. ", +" +%&*.... ....", +" +%&=&-;>. .>>.", +" +%&=,,,,'. .''.", +")!~=,,,,,'. .''.", +"{]^/,,,,,'. .''.", +" (_:/,,,,'. .''.", +" (_:/:<[}. .}}.", +" (_:|.... ....", +" (12. ", +" 34. .....", +" .. .;. ", +" . "}; +/* XPM */ +static char * right_s_xpm[] = { +"16 16 56 1", +" c None", +". c #000000", +"+ c #569A59", +"@ c #0A120A", +"# c #228126", +"$ c #3F8342", +"% c #09130A", +"& c #2E9132", +"* c #319335", +"= c #448D47", +"- c #09140A", +"; c #52AC55", +"> c #80C282", +", c #5BB15E", +"' c #319C35", +") c #07880C", +"! c #449447", +"~ c #09150A", +"{ c #008905", +"] c #009405", +"^ c #07970C", +"/ c #31A935", +"( c #449E47", +"_ c #09160A", +": c #009705", +"< c #00A305", +"[ c #07A60C", +"} c #2AB22E", +"| c #1B971E", +"1 c #041505", +"2 c #00A605", +"3 c #00B305", +"4 c #00AE05", +"5 c #008E04", +"6 c #007303", +"7 c #001100", +"8 c #00B405", +"9 c #00C205", +"0 c #00BD05", +"a c #009604", +"b c #006003", +"c c #000D00", +"d c #007003", +"e c #004802", +"f c #00A104", +"g c #00CB05", +"h c #006803", +"i c #000E00", +"j c #00A304", +"k c #00AD04", +"l c #006F03", +"m c #000F00", +"n c #00B804", +"o c #007A03", +"p c #001000", +"q c #008E03", +" ", +" .. ", +" .+@ ", +" .#$% ", +".... ....&*=- ", +".;;. .;>,')'!~ ", +".{{. .{]]]]^/(_ ", +".::. .:<<<<<[}|1", +".22. .2333334567", +".88. .899990abc ", +".dd. .de6fgfhi ", +".... ....jklm ", +" .nop ", +" .qpb....", +" .. .>. ", +" . "}; +/* XPM */ +static char * home_s_xpm[] = { +"16 16 54 1", +" c None", +". c #170E0B", +"+ c #000000", +"@ c #170C09", +"# c #B0705B", +"$ c #B35636", +"% c #A13F20", +"& c #661B03", +"* c #AA3009", +"= c #190C09", +"- c #AC3F20", +"; c #721C03", +"> c #8E8875", +", c #C24F2D", +"' c #1A0C09", +") c #B93F20", +"! c #7C1C03", +"~ c #8F876F", +"{ c #A9A081", +"] c #A72403", +"^ c #CE4522", +"/ c #1B0C09", +"( c #C63F20", +"_ c #861C03", +": c #A9A080", +"< c #FDEDB3", +"[ c #631503", +"} c #CC2903", +"| c #1B0703", +"1 c #1D0C08", +"2 c #D03E20", +"3 c #871902", +"4 c #847C67", +"5 c #FFEFB2", +"6 c #847C68", +"7 c #851902", +"8 c #CF3A1B", +"9 c #A81E02", +"0 c #721402", +"a c #9A9070", +"b c #8B8163", +"c c #9C926D", +"d c #FDEDB1", +"e c #EADBA4", +"f c #696249", +"g c #ECDDA5", +"h c #C25834", +"i c #FFFFFF", +"j c #BE2D00", +"k c #BF0000", +"l c #898060", +"m c #58523D", +"n c #D90000", +"o c #BB0000", +" ", +" .. +++ ", +" @##@ +$+ ", +" @%&&%@+*+ ", +" =-;>>;-=,+ ", +" ')!~{{~!]^+ ", +" /(_~:<<:~[}| ", +" 1234:<55<:6781 ", +"+90a{<5555<:b09+", +"+++cd555555dc+++", +" +e5f+f5f+fe+ ", +" +g5+h+5+i+g+ ", +" +g5+j+5f+fg+ ", +" +g5+k+5555g+ ", +" +lm+n+mmmml+ ", +" ++++o+++++++ "}; +/* XPM */ +static char * reload_s_xpm[] = { +"16 16 74 1", +" c None", +". c #000000", +"+ c #282828", +"@ c #666666", +"# c #9B9B9B", +"$ c #BFBFBF", +"% c #4F4F4F", +"& c #A5A5A5", +"* c #BDBDBD", +"= c #B1B1B1", +"- c #A9A9A9", +"; c #4C4C4C", +"> c #B5B5B5", +", c #B4B4B4", +"' c #7F907F", +") c #436B43", +"! c #144F14", +"~ c #004800", +"{ c #262626", +"] c #9D9D9D", +"^ c #B8B8B8", +"/ c #839583", +"( c #577A57", +"_ c #7E917E", +": c #A7AAA7", +"< c #426D42", +"[ c #004600", +"} c #5D5D5D", +"| c #BEBEBE", +"1 c #B7B7B7", +"2 c #728C72", +"3 c #B0B2B0", +"4 c #8E8E8E", +"5 c #C2C2C2", +"6 c #BCBCBC", +"7 c #8EA18E", +"8 c #004B00", +"9 c #AFAFAF", +"0 c #C6C6C6", +"a c #C4C4C4", +"b c #CCCCCC", +"c c #7A5200", +"d c #B8AE9A", +"e c #868686", +"f c #D4D4D4", +"g c #7F5500", +"h c #D2D1CE", +"i c #B4A585", +"j c #575757", +"k c #845800", +"l c #A58A53", +"m c #C3B79F", +"n c #D9D7D3", +"o c #B09A6E", +"p c #C6BBA5", +"q c #222222", +"r c #878787", +"s c #865A00", +"t c #CCC1AA", +"u c #AC915B", +"v c #936C1C", +"w c #E0E0E0", +"x c #3A3A3A", +"y c #8C8C8C", +"z c #C3C3C3", +"A c #E2E2E2", +"B c #EAEAEA", +"C c #323232", +"D c #646464", +"E c #9E9E9E", +"F c #DCDCDC", +"G c #191919", +"H c #2F2F2F", +"I c #404040", +" ...... ", +" .+@#$$#@+. ", +" .%&*=--=*&%. ", +" .;>,')!!)'~>;. ", +" {]^/(_:_<[[^]{ ", +".}|123>>>~~~1|}.", +".45676668888654.", +".90aaaaaaaaaa09.", +".-abccccbbbdba-.", +".e$fgggfffhif$e.", +".j>fkklmnmopf>j.", +" qr5stuvvutw5rq ", +" .xyzABBBBAzyx. ", +" .CDE0FF0EDC. ", +" .GHI%%IHG. ", +" ...... "}; +/* XPM */ +static char * save_s_xpm[] = { +"16 16 51 1", +" c None", +". c #000000", +"+ c #638163", +"@ c #8CA28C", +"# c #A4BDA4", +"$ c #184318", +"% c #1A481A", +"& c #4C7F4C", +"* c #1A461A", +"= c #1C4C1C", +"- c #528552", +"; c #1B4A1B", +"> c #1D501D", +", c #598C59", +"' c #1F4E1F", +") c #215421", +"! c #619461", +"~ c #225122", +"{ c #255825", +"] c #699C69", +"^ c #275627", +"/ c #2A5D2A", +"( c #72A572", +"_ c #2B5A2B", +": c #2E612E", +"< c #2F5E2F", +"[ c #152A15", +"} c #336233", +"| c #838383", +"1 c #9D9D9D", +"2 c #B3B3B3", +"3 c #979797", +"4 c #376637", +"5 c #5A5A5A", +"6 c #585858", +"7 c #757575", +"8 c #6C6C6C", +"9 c #3A6A3A", +"0 c #8C8C8C", +"a c #828282", +"b c #345E34", +"c c #3E6D3E", +"d c #B6B6B6", +"e c #A5A5A5", +"f c #999999", +"g c #162616", +"h c #264126", +"i c #919191", +"j c #676767", +"k c #414141", +"l c #656565", +"................", +".+@##########@+.", +".$%&&&&&&&&&&%$.", +".*=----------=*.", +".;>,,,,,,,,,,>;.", +".')!!!!!!!!!!)'.", +".~{]]]]]]]]]]{~.", +".^/((((((((((/^.", +"._::::::::::::_.", +".<[..........[<.", +".}.|||1222223.}.", +".4.5..6777778.4.", +".9.0..a00000a.b.", +".c.d..2eeeeef.g.", +".h.iiijkkkkkl.. ", +" ............. "}; +/* XPM */ +static char * stop_s_xpm[] = { +"16 16 65 1", +" c None", +". c #000000", +"+ c #5A3435", +"@ c #BF7F80", +"# c #BF8081", +"$ c #562929", +"% c #A54344", +"& c #911A1C", +"* c #850002", +"= c #582929", +"- c #AA4344", +"; c #981A1C", +"> c #8C0002", +", c #551B1C", +"' c #B04345", +") c #9F1A1D", +"! c #940003", +"~ c #926667", +"{ c #661C1E", +"] c #A00407", +"^ c #A81A1D", +"/ c #9E0003", +"( c #B86B6D", +"_ c #FFFFFF", +": c #E2D5D5", +"< c #581C1D", +"[ c #9C0003", +"} c #A90003", +"| c #C24C4E", +"1 c #F4DDDE", +"2 c #E9DADA", +"3 c #A70003", +"4 c #B40003", +"5 c #C4383A", +"6 c #F5DEDE", +"7 c #B10004", +"8 c #BF0004", +"9 c #9A1C1F", +"0 c #F0D9D9", +"a c #BB0004", +"b c #CA0004", +"c c #971C1F", +"d c #ECD5D5", +"e c #FAE6E6", +"f c #AF0003", +"g c #BE0004", +"h c #D50004", +"i c #E87779", +"j c #FBE5E5", +"k c #EA7E80", +"l c #470001", +"m c #930003", +"n c #C70004", +"o c #E00004", +"p c #F5A7A9", +"q c #EC6568", +"r c #3F0001", +"s c #990003", +"t c #D00004", +"u c #EA0004", +"v c #410001", +"w c #D70004", +"x c #F20005", +"y c #370001", +"z c #560002", +" ........ ", +" .+@####@+. ", +" .$%&****&%$. ", +" .=-;>>>>>>;-=. ", +".,')!~{!!{~!)',.", +".]^/(_:<<:_(/^].", +".[}}|1_22_1|}}[.", +".344456__654443.", +".788890__098887.", +".abbcd_ee_dcbba.", +".fghi_jkkj_ihgf.", +".lmnopqooqponml.", +" .rstuuuuuutsr. ", +" .v/wxxxxw/v. ", +" .yzzzzzzy. ", +" ........ "}; + +/* XPM */ +static char * bm_s_xpm[] = { +"16 16 63 1", +" c None", +". c #000000", +"+ c #B15652", +"@ c #C68380", +"# c #979797", +"$ c #B3B3B3", +"% c #999999", +"& c #860600", +"* c #910600", +"= c #646464", +"- c #6C6C6C", +"; c #5C5C5C", +"> c #8B0600", +", c #960600", +"' c #6A6A6A", +") c #737373", +"! c #626262", +"~ c #920600", +"{ c #9E0700", +"] c #747474", +"^ c #7D7D7D", +"/ c #990600", +"( c #A50700", +"_ c #7E7E7E", +": c #888888", +"< c #A10600", +"[ c #AE0700", +"} c #939393", +"| c #A90700", +"1 c #B70800", +"2 c #9F9F9F", +"3 c #B30700", +"4 c #C10800", +"5 c #9D9D9D", +"6 c #AAAAAA", +"7 c #919191", +"8 c #BB0800", +"9 c #CA0900", +"0 c #A8A8A8", +"a c #B5B5B5", +"b c #9A9A9A", +"c c #C40800", +"d c #D40900", +"e c #B0B0B0", +"f c #BEBEBE", +"g c #A2A2A2", +"h c #CD0900", +"i c #DD0A00", +"j c #444444", +"k c #3A3A3A", +"l c #D50900", +"m c #DA0900", +"n c #C80800", +"o c #9C0700", +"p c #680500", +"q c #A00700", +"r c #CB0900", +"s c #3D0300", +"t c #490300", +"u c #A70800", +"v c #540400", +"w c #010000", +"x c #660400", +" ....... ", +" ......+@@@+... ", +" .#$%$.&***&.#. ", +" .=-;-.>,,,>.=. ", +" .')!).~{{{~.'. ", +" .]^'^./(((/.]. ", +" ._:]:.<[[[<._. ", +" .:}^}.|111|.:. ", +" .}2:2.34443.}. ", +" .5676.89998.5. ", +" .0aba.cdddc.0. ", +" .efgf.hiiih.e. ", +" .'jkj.lmmml.'. ", +" ......nopqr... ", +" .qs.tu. ", +" .v. wx. "}; + +/* XPM */ +static char * new_s_xpm[] = { +"11 11 35 1", +" c None", +". c #000000", +"+ c #482929", +"@ c #0F0808", +"# c #390606", +"$ c #A04242", +"% c #925050", +"& c #0F0707", +"* c #0B0000", +"= c #500000", +"- c #8C0101", +"; c #944444", +"> c #221515", +", c #0A0000", +"' c #560000", +") c #A10909", +"! c #AE3636", +"~ c #230000", +"{ c #AE0000", +"] c #B40000", +"^ c #180808", +"/ c #B43535", +"( c #C40000", +"_ c #960000", +": c #190606", +"< c #C02B2B", +"[ c #E00000", +"} c #6B0000", +"| c #120000", +"1 c #590000", +"2 c #A30000", +"3 c #680000", +"4 c #0F0000", +"5 c #350000", +"6 c #100000", +" . . ", +" .+@ @+. ", +".#$%& &%$#.", +" *=-;>;-=* ", +" ,')!)', ", +" ~{]{~ ", +" ^/(_(/^ ", +" :<[}|}[<: ", +".1234 4321.", +" .56 65. ", +" . . "}; + +/* XPM */ +static char * search_xpm[] = { +"14 16 11 1", +" c None", +". c #000000", +"+ c #EEEEEE", +"[ c #EE0000", +"} c #CC0000", +"| c #BB0000", +"1 c #AA0000", +"2 c #880000", +"3 c #660000", +"4 c #440000", +"5 c #330000", +" ..... ", +" . . ", +" . + . ", +". +++ . ", +". + . ", +". . ", +". . ", +" . . ", +" . . ", +" ...545 ", +" 424 ", +" 313 ", +" 2|2 ", +" 2}2 ", +" 2}2 ", +" 11 "}; + +/* XPM */ +static char * full_screen_on_xpm[] = { +"13 15 2 1", +" c None", +". c #000000", +" ", +".............", +". . .", +". ... .", +". ..... .", +". ....... .", +". . .", +". . .", +". . .", +". ....... .", +". ..... .", +". ... .", +". . .", +".............", +" "}; + +/* XPM */ +static char * full_screen_off_xpm[] = { +"13 15 2 1", +" c None", +". c #000000", +" ", +".............", +". . . . . . .", +".. . . . . ..", +".............", +". .", +". .", +". .", +". .", +". .", +". .", +".............", +". . . . . . .", +".............", +" "}; + +/* XPM */ +static char * mini_bug_xpm[] = { +"15 16 6 1", +" c None", +". c #000000000000", +"X c #BEFBC30BBEFB", +"o c #861782078617", +"O c #FFFFFFFFFFFF", +"+ c #30C230C230C2", +" ", +" . . ", +" ... ", +" X.....X ", +" o.O...o ", +" o.O...o.o ", +" ..OoXo... ", +" .....X..... ", +" ....X.... ", +" .o...X...o. ", +" ...X... ", +" .X..X..X. ", +" .o. ", +" .+++.", +" .o. ", +" . "}; + +/* XPM */ +static char * mini_ok_xpm[] = { +"15 15 5 1", +"@ c #000000", +"a c #808080", +"b c #303030", +"c c #606060", +" s none m none c none", +" ", +" ", +" @ ", +" @@ ", +" @@@ ", +" @@@ ", +" @@ @@@ ", +" @@@ @@@ ", +" @@@ @@@ ", +" @@@@@@ ", +" @@@@@ ", +" @@@@ ", +" @@ @bbb@", +" @a@ ", +" @ " +}; + +/* XPM */ +static char *left_i_xpm[] = { +"22 22 3 1", +" c None", +". c #000000", +"@ c gray70", +" ", +" ", +" ", +" ", +" @@ ", +" @@@ ", +" @@@@ ", +" @@@@@@@@@@ @@@@ ", +" @@@@@@@@@@@ @@@@ ", +" @@@@@@@@@@@@ @@@@ ", +" @@@@@@@@@@@@@ @@@@ ", +" @@@@@@@@@@@@@ @@@@ ", +" @@@@@@@@@@@@ @@@@ ", +" @@@@@@@@@@@ @@@@ ", +" @@@@@@@@@@ @@@@ ", +" @@@@ ", +" @@@ ", +" @@ ", +" @@@@@", +" @ @ ", +" @ ", +" "}; + +/* XPM */ +static char *right_i_xpm[] = { +"22 22 3 1", +" c None", +". c #000000", +"@ c gray70", +" ", +" ", +" ", +" ", +" @@ ", +" @@@ ", +" @@@@ ", +" @@@@ @@@@@@@@@@ ", +" @@@@ @@@@@@@@@@@ ", +" @@@@ @@@@@@@@@@@@ ", +" @@@@ @@@@@@@@@@@@@ ", +" @@@@ @@@@@@@@@@@@@ ", +" @@@@ @@@@@@@@@@@@ ", +" @@@@ @@@@@@@@@@@ ", +" @@@@ @@@@@@@@@@ ", +" @@@@ ", +" @@@ ", +" @@ ", +" @@@@@", +" @ @ ", +" @ ", +" "}; + +/* XPM */ +static char *stop_i_xpm[] = { +/* columns rows colors chars-per-pixel */ +"22 22 2 1", +" c None", +"@ c gray70", +/* pixels */ +" ", +" ", +" @@@@@@@@@ ", +" @@@@@@@@@@@ ", +" @@@@@@@@@@@@@ ", +" @@@@@@@@@@@@@@@ ", +" @@@@@@@@@@@@@@@@@ ", +" @@@@@@@@@@@@@@@@@@@ ", +" @@@@@@ @@@ @@@@@@ ", +" @@@@@@ @ @@@@@@ ", +" @@@@@@@ @@@@@@@ ", +" @@@@@@@@ @@@@@@@@ ", +" @@@@@@@ @@@@@@@ ", +" @@@@@@ @ @@@@@@ ", +" @@@@@@ @@@ @@@@@@ ", +" @@@@@@@@@@@@@@@@@@@ ", +" @@@@@@@@@@@@@@@@@ ", +" @@@@@@@@@@@@@@@ ", +" @@@@@@@@@@@@@ ", +" @@@@@@@@@@@ ", +" @@@@@@@@@ ", +" " +}; + +/* XPM */ +static char * stop_si_xpm[] = { +"16 16 2 1", +" c None", +"@ c gray70", +" @@@@@@@@ ", +" @@@@@@@@@@ ", +" @@@@@@@@@@@@ ", +" @@@@@@@@@@@@@@ ", +"@@@@@@@@@@@@@@@@", +"@@@@@ @@@@ @@@@@", +"@@@@@@ @@ @@@@@@", +"@@@@@@@ @@@@@@@", +"@@@@@@@ @@@@@@@", +"@@@@@@ @@ @@@@@@", +"@@@@@ @@@@ @@@@@", +"@@@@@@@@@@@@@@@@", +" @@@@@@@@@@@@@@ ", +" @@@@@@@@@@@@ ", +" @@@@@@@@@@ ", +" @@@@@@@@ "}; + +/* XPM */ +static char * left_si_xpm[] = { +"16 16 2 1", +" c None", +"@ c gray70", +" ", +" @@ ", +" @@@ ", +" @@@@ ", +" @@@@@@@@ @@@@", +" @@@@@@@@@ @@@@", +" @@@@@@@@@@ @@@@", +"@@@@@@@@@@@ @@@@", +"@@@@@@@@@@@ @@@@", +" @@@@@@@@@@ @@@@", +" @@@@@@@@@ @@@@", +" @@@@@@@@ @@@@", +" @@@@ ", +" @@@ @@@@@", +" @@ @ @ ", +" @ "}; + +/* XPM */ +static char * right_si_xpm[] = { +"16 16 2 1", +" c None", +"@ c gray70", +" ", +" @@ ", +" @@@ ", +" @@@@ ", +"@@@@ @@@@@@@@ ", +"@@@@ @@@@@@@@@ ", +"@@@@ @@@@@@@@@@ ", +"@@@@ @@@@@@@@@@@", +"@@@@ @@@@@@@@@@@", +"@@@@ @@@@@@@@@@ ", +"@@@@ @@@@@@@@@ ", +"@@@@ @@@@@@@@ ", +" @@@@ ", +" @@@@@@@@", +" @@ @ @ ", +" @ "}; + +#endif /* __PIXMAPS_H__ */ diff --git a/src/plain.cc b/src/plain.cc new file mode 100644 index 00000000..be7b1e4c --- /dev/null +++ b/src/plain.cc @@ -0,0 +1,233 @@ +/* + * File: plain.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * Module for decoding a text/plain object into a dw widget. + */ + +#include <string.h> /* for memcpy and memmove */ +#include <math.h> /* for rint() */ + +#include "prefs.h" +#include "cache.h" +#include "bw.h" +#include "web.hh" +#include "misc.h" +#include "history.h" +#include "nav.h" +#include "menu.hh" + +#include "uicmd.hh" + +#include "dw/core.hh" +#include "dw/textblock.hh" + +using namespace dw; +using namespace dw::core; + + +typedef struct _DilloPlain { + //DwWidget *dw; + Widget *dw; + size_t Start_Ofs; /* Offset of where to start reading next */ + //DwStyle *style; + style::Style *widgetStyle; + BrowserWindow *bw; + int state; +} DilloPlain; + +/* FSM states */ +enum { + ST_SeekingEol, + ST_Eol, + ST_Eof +}; + +/* + * Exported function with C linkage. + */ +extern "C" { +void *a_Plain_text(const char *type, void *P, CA_Callback_t *Call,void **Data); +} + +/* + * Forward declarations + */ +static void Plain_write(DilloPlain *plain, void *Buf, uint_t BufSize, int Eof); +static void Plain_callback(int Op, CacheClient_t *Client); + + + +/* + * Popup the page menu ("button_press_event" callback of the viewport) + */ +//static int Plain_page_menu(GtkWidget *viewport, GdkEventButton *event, +// BrowserWindow *bw) +//{ +// if (event->button == 3) { +// a_Menu_popup_set_url(bw, a_History_get_url(NAV_TOP(bw))); +// gtk_menu_popup(GTK_MENU(bw->menu_popup.over_page), NULL, NULL, +// NULL, NULL, event->button, event->time); +// return TRUE; +// } else +// return FALSE; +//} + +/* + * Create and initialize a new DilloPlain structure. + */ +static DilloPlain *Plain_new(BrowserWindow *bw) +{ + DilloPlain *plain; + //DwPage *page; + //DwStyle style_attrs; + //DwStyleFont font_attrs; + Textblock *textblock; + style::StyleAttrs styleAttrs; + style::FontAttrs fontAttrs; + + plain = dNew(DilloPlain, 1); + plain->state = ST_SeekingEol; + plain->Start_Ofs = 0; + plain->bw = bw; + //plain->dw = a_Dw_page_new(); + //page = (DwPage *) plain->dw; + textblock = new Textblock (false); + plain->dw = (Widget*) textblock; + + /* Create the font and attribute for the page. */ + //font_attrs.name = prefs.fw_fontname; + //font_attrs.size = rint(12.0 * prefs.font_factor); + //font_attrs.weight = 400; + //font_attrs.style = DW_STYLE_FONT_STYLE_NORMAL; + fontAttrs.name = "Courier"; + fontAttrs.size = (int) rint(12.0 * prefs.font_factor); + fontAttrs.weight = 400; + fontAttrs.style = style::FONT_STYLE_NORMAL; + + //a_Dw_style_init_values (&style_attrs); + //style_attrs.font = + // a_Dw_style_font_new (plain->bw->render_layout, &font_attrs); + //style_attrs.color = + // a_Dw_style_color_new (plain->bw->render_layout, prefs.text_color); + //plain->style = a_Dw_style_new (plain->bw->render_layout, &style_attrs); + //a_Dw_widget_set_style (plain->dw, plain->style); + Layout *layout = (Layout*)bw->render_layout; + styleAttrs.initValues (); + styleAttrs.margin.setVal (5); + styleAttrs.font = style::Font::create (layout, &fontAttrs); + styleAttrs.color = style::Color::createSimple (layout, 0x0000ff); + styleAttrs.backgroundColor = + style::Color::createSimple (layout, 0xdcd1ba); + plain->widgetStyle = style::Style::create (layout, &styleAttrs); + + /* The context menu */ +// gtk_signal_connect_while_alive +// (GTK_OBJECT(GTK_BIN(plain->bw->render_main_scroll)->child), +// "button_press_event", GTK_SIGNAL_FUNC(Plain_page_menu), +// plain->bw, GTK_OBJECT (page)); + + return plain; +} + +/* + * Set callback function and callback data for "text/" MIME major-type. + */ +void *a_Plain_text(const char *type, void *P, CA_Callback_t *Call, void **Data) +{ + DilloWeb *web = (DilloWeb*)P; + DilloPlain *plain = Plain_new(web->bw); + + *Call = (CA_Callback_t)Plain_callback; + *Data = (void*)plain; + + return (void*)plain->dw; +} + +/* + * This function is a cache client + */ +static void Plain_callback(int Op, CacheClient_t *Client) +{ + DilloPlain *plain = (DilloPlain*)Client->CbData; + Textblock *textblock = (Textblock*)plain->dw; + + if (Op) { + /* Do the last line: */ + if (plain->Start_Ofs < Client->BufSize) + Plain_write(plain, Client->Buf, Client->BufSize, 1); + /* remove this client from our active list */ + a_Bw_close_client(plain->bw, Client->Key); + /* set progress bar insensitive */ + a_UIcmd_set_page_prog(plain->bw, 0, 0); + + //a_Dw_style_unref (plain->bw->render_layout, plain->style); + plain->widgetStyle->unref(); + dFree(plain); + } else { + Plain_write(plain, Client->Buf, Client->BufSize, 0); + } + + textblock->flush(); +} + +/* + * Here we parse plain text and put it into the page structure. + * (This function is called by Plain_callback whenever there's new data) + */ +static void Plain_write(DilloPlain *plain, void *Buf, uint_t BufSize, int Eof) +{ + //DwPage *page = (DwPage *)plain->dw; + Textblock *textblock = (Textblock*)plain->dw; + char *Start; + char *data; + uint_t i, len, MaxBytes; + + Start = (char*)Buf + plain->Start_Ofs; + MaxBytes = BufSize - plain->Start_Ofs; + i = len = 0; + while ( i < MaxBytes ) { + switch ( plain->state ) { + case ST_SeekingEol: + if (Start[i] == '\n' || Start[i] == '\r') + plain->state = ST_Eol; + else { + ++i; ++len; + } + break; + case ST_Eol: + data = dStrndup(Start + i - len, len); + //a_Dw_page_add_text(page, a_Misc_expand_tabs(data), plain->style); + //a_Dw_page_add_parbreak(page, 0, plain->style); + textblock->addText(a_Misc_expand_tabs(data), plain->widgetStyle); + textblock->addParbreak(0, plain->widgetStyle); + dFree(data); + if (Start[i] == '\r' && Start[i + 1] == '\n') ++i; + if (i < MaxBytes) ++i; + plain->state = ST_SeekingEol; + len = 0; + break; + } + } + plain->Start_Ofs += i - len; + if (Eof && len) { + data = dStrndup(Start + i - len, len); + //a_Dw_page_add_text(page, a_Misc_expand_tabs(data), plain->style); + //a_Dw_page_add_parbreak(page, 0, plain->style); + textblock->addText(a_Misc_expand_tabs(data), plain->widgetStyle); + textblock->addParbreak(0, plain->widgetStyle); + dFree(data); + plain->Start_Ofs += len; + } + + if (plain->bw) + a_UIcmd_set_page_prog(plain->bw, plain->Start_Ofs, 1); +} diff --git a/src/png.c b/src/png.c new file mode 100644 index 00000000..43ef5519 --- /dev/null +++ b/src/png.c @@ -0,0 +1,472 @@ +/* + * The png decoder for Dillo. It is responsible for decoding PNG data + * and transferring it to the dicache. + * + * Geoff Lane nov 1999 zzassgl@twirl.mcc.ac.uk + * Luca Rota, Jorge Arellano Cid, Eric Gaudet 2000 + * + * "PNG: The Definitive Guide" by Greg Roelofs, O'Reilly + * ISBN 1-56592-542-4 + */ + +#include <config.h> +#ifdef ENABLE_PNG + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> /* For abort() */ + +#include <zlib.h> + +#ifdef HAVE_LIBPNG_PNG_H +#include <libpng/png.h> +#else +#include <png.h> +#endif + +#include "msg.h" +#include "image.hh" +#include "web.hh" +#include "cache.h" +#include "dicache.h" +#include "prefs.h" + +#define DEBUG_LEVEL 6 +#include "debug.h" + +enum prog_state { + IS_finished, IS_init, IS_nextdata +}; + +static char *prog_state_name[] = +{ + "IS_finished", "IS_init", "IS_nextdata" +}; + +/* + * This holds the data that must be saved between calls to this module. + * Each time it is called it is supplied with a vector of data bytes + * obtained from the web server. The module can process any amount of the + * supplied data. The next time the module is called, the vector may be + * extended with additional data bytes to be processed. The module must + * keep track of the current start and cursor position of the input data + * vector. As complete output rasters are determined they are sent out of + * the module for additional processing. + * + * NOTE: There is no external control of the splitting of the input data + * vector (only this module understands PNG format data.) This means that + * the complete state of a PNG image being processed must be held in the + * structure below so that processing can be suspended or resumed at any + * point within an input image. + * + * In the case of the libpng library, it maintains it's own state in + * png_ptr and into_ptr so the FSM is very simple - much simpler than the + * ones for XBM and PNM are. + */ + +typedef +struct _DilloPng { + DilloImage *Image; /* Image meta data */ + DilloUrl *url; /* Primary Key for the dicache */ + int version; /* Secondary Key for the dicache */ + + double display_exponent; /* gamma correction */ + ulong_t width; /* png image width */ + ulong_t height; /* png image height */ + png_structp png_ptr; /* libpng private data */ + png_infop info_ptr; /* libpng private info */ + uchar_t *image_data; /* decoded image data */ + uchar_t **row_pointers; /* pntr to row starts */ + jmp_buf jmpbuf; /* png error processing */ + int error; /* error flag */ + int rowbytes; /* No. bytes in image row */ + short passes; + short channels; /* No. image channels */ + +/* + * 0 last byte + * +-------+-+-----------------------------------+-+ + * | | | -- data to be processed -- | | + * +-------+-+-----------------------------------+-+ + * ^ ^ ^ + * ipbuf ipbufstart ipbufsize + */ + + uchar_t *ipbuf; /* image data in buffer */ + int ipbufstart; /* first valid image byte */ + int ipbufsize; /* size of valid data in */ + + enum prog_state state; /* FSM current state */ + + uchar_t *linebuf; /* o/p raster data */ + +} DilloPng; + +#define DATASIZE (png->ipbufsize - png->ipbufstart) +#define BLACK 0 +#define WHITE 255 + +/* + * Forward declarations + */ +/* exported function */ +void *a_Png_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data); + + +static +void Png_error_handling(png_structp png_ptr, png_const_charp msg) +{ + DilloPng *png; + + DEBUG_MSG(6, "Png_error_handling: %s\n", msg); + png = png_get_error_ptr(png_ptr); + + png->error = 1; + png->state = IS_finished; + + longjmp(png->jmpbuf, 1); +} + +static void +Png_datainfo_callback(png_structp png_ptr, png_infop info_ptr) +{ + DilloPng *png; + int color_type; + int bit_depth; + int interlace_type; + uint_t i; + double gamma; + + DEBUG_MSG(5, "Png_datainfo_callback:\n"); + + png = png_get_progressive_ptr(png_ptr); + dReturn_if_fail (png != NULL); + + png_get_IHDR(png_ptr, info_ptr, &png->width, &png->height, + &bit_depth, &color_type, &interlace_type, NULL, NULL); + + DEBUG_MSG(5, "Png_datainfo_callback: png->width = %ld\n" + "Png_datainfo_callback: png->height = %ld\n", + png->width, png->height); + + /* we need RGB/RGBA in the end */ + if (color_type == PNG_COLOR_TYPE_PALETTE && bit_depth <= 8) { + /* Convert indexed images to RGB */ + png_set_expand (png_ptr); + } else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { + /* Convert grayscale to RGB */ + png_set_expand (png_ptr); + } else if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS)) { + /* We have transparency header, convert it to alpha channel */ + png_set_expand(png_ptr); + } else if (bit_depth < 8) { + png_set_expand(png_ptr); + } + + if (bit_depth == 16) { + png_set_strip_16(png_ptr); + } + + /* Get and set gamma information. Beware: gamma correction 2.2 will + only work on PC's. todo: select screen gamma correction for other + platforms. */ + if (png_get_gAMA(png_ptr, info_ptr, &gamma)) + png_set_gamma(png_ptr, 2.2, gamma); + + /* Convert gray scale to RGB */ + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(png_ptr); + } + + /* Interlaced */ + if (interlace_type != PNG_INTERLACE_NONE) { + png->passes = png_set_interlace_handling(png_ptr); + } + + /* get libpng to update it's state */ + png_read_update_info(png_ptr, info_ptr); + + png_get_IHDR(png_ptr, info_ptr, &png->width, &png->height, + &bit_depth, &color_type, &interlace_type, NULL, NULL); + + png->rowbytes = png_get_rowbytes(png_ptr, info_ptr); + png->channels = png_get_channels(png_ptr, info_ptr); + + /* init Dillo specifics */ + DEBUG_MSG(5, "Png_datainfo_callback: rowbytes = %d\n" + "Png_datainfo_callback: width = %ld\n" + "Png_datainfo_callback: height = %ld\n", + png->rowbytes, png->width, png->height); + + png->image_data = (uchar_t *) dMalloc(png->rowbytes * png->height); + png->row_pointers = (uchar_t **) dMalloc(png->height * sizeof(uchar_t *)); + + for (i = 0; i < png->height; i++) + png->row_pointers[i] = png->image_data + (i * png->rowbytes); + + png->linebuf = dMalloc(3 * png->width); + + /* Initialize the dicache-entry here */ + a_Dicache_set_parms(png->url, png->version, png->Image, + (uint_t)png->width, (uint_t)png->height, + DILLO_IMG_TYPE_RGB); +} + +static void + Png_datarow_callback(png_structp png_ptr, png_bytep new_row, + png_uint_32 row_num, int pass) +{ + DilloPng *png; + uint_t i; + + if (!new_row) /* work to do? */ + return; + + DEBUG_MSG(5, "Png_datarow_callback: row_num = %ld\n", row_num); + + png = png_get_progressive_ptr(png_ptr); + + png_progressive_combine_row(png_ptr, png->row_pointers[row_num], new_row); + + switch (png->channels) { + case 3: + a_Dicache_write(png->Image, png->url, png->version, + png->image_data + (row_num * png->rowbytes), + 0, (uint_t)row_num); + break; + case 4: + { + /* todo: get the backgound color from the parent + * of the image widget -- Livio. */ + int a, bg_red, bg_green, bg_blue; + uchar_t *pl = png->linebuf; + uchar_t *data = png->image_data + (row_num * png->rowbytes); + + /* todo: maybe change prefs.bg_color to `a_Dw_widget_get_bg_color`, + * when background colors are correctly implementated */ + bg_blue = (png->Image->bg_color) & 0xFF; + bg_green = (png->Image->bg_color>>8) & 0xFF; + bg_red = (png->Image->bg_color>>16) & 0xFF; + + for (i = 0; i < png->width; i++) { + a = *(data+3); + + if (a == 255) { + *(pl++) = *(data++); + *(pl++) = *(data++); + *(pl++) = *(data++); + data++; + } else if (a == 0) { + *(pl++) = bg_red; + *(pl++) = bg_green; + *(pl++) = bg_blue; + data += 4; + } else { + png_composite(*(pl++), *(data++), a, bg_red); + png_composite(*(pl++), *(data++), a, bg_green); + png_composite(*(pl++), *(data++), a, bg_blue); + data++; + } + } + a_Dicache_write(png->Image, png->url, png->version, + png->linebuf, 0, (uint_t)row_num); + break; + } + default: + MSG("Png_datarow_callback: unexpected number of channels = %d\n", + png->channels); + abort(); + } +} + +static void + Png_dataend_callback(png_structp png_ptr, png_infop info_ptr) +{ + DilloPng *png; + + DEBUG_MSG(5, "Png_dataend_callback:\n"); + + png = png_get_progressive_ptr(png_ptr); + png->state = IS_finished; +} + + +/* + * Op: Operation to perform. + * If (Op == 0) + * start or continue processing an image if image data exists. + * else + * terminate processing, cleanup any allocated memory, + * close down the decoding process. + * + * Client->CbData : pointer to previously allocated DilloPng work area. + * This holds the current state of the image processing and is saved + * across calls to this routine. + * Client->Buf : Pointer to data start. + * Client->BufSize : the size of the data buffer. + * + * You have to keep track of where you are in the image data and + * how much has been processed. + * + * It's entirely possible that you will not see the end of the data. The + * user may terminate transfer via a Stop button or there may be a network + * failure. This means that you can't just wait for all the data to be + * presented before starting conversion and display. + */ +static void Png_callback(int Op, CacheClient_t *Client) +{ + DilloPng *png = Client->CbData; + + if (Op) { + /* finished - free up the resources for this image */ + a_Dicache_close(png->url, png->version, Client); + dFree(png->image_data); + dFree(png->row_pointers); + dFree(png->linebuf); + + if (setjmp(png->jmpbuf)) + MSG_WARN("PNG: can't destroy read structure\n"); + else if (png->png_ptr) + png_destroy_read_struct(&png->png_ptr, &png->info_ptr, NULL); + dFree(png); + return; + } + + /* Let's make some sound if we have been called with no data */ + dReturn_if_fail ( Client->Buf != NULL && Client->BufSize > 0 ); + + DEBUG_MSG(5, "Png_callback BufSize = %d\n", Client->BufSize); + + /* Keep local copies so we don't have to pass multiple args to + * a number of functions. */ + png->ipbuf = Client->Buf; + png->ipbufsize = Client->BufSize; + + /* start/resume the FSM here */ + while (png->state != IS_finished && DATASIZE) { + DEBUG_MSG(5, "State = %s\n", prog_state_name[png->state]); + + switch (png->state) { + case IS_init: + if (DATASIZE < 8) { + return; /* need MORE data */ + } + /* check the image signature - DON'T update ipbufstart! */ + if (!png_check_sig(png->ipbuf, DATASIZE)) { + /* you lied to me about it being a PNG image */ + png->state = IS_finished; + break; + } + /* OK, it looks like a PNG image, lets do some set up stuff */ + png->png_ptr = png_create_read_struct( + PNG_LIBPNG_VER_STRING, + png, + (png_error_ptr)Png_error_handling, + (png_error_ptr)Png_error_handling); + dReturn_if_fail (png->png_ptr != NULL); + png->info_ptr = png_create_info_struct(png->png_ptr); + dReturn_if_fail (png->info_ptr != NULL); + + setjmp(png->jmpbuf); + if (!png->error) { + png_set_progressive_read_fn( + png->png_ptr, + png, + Png_datainfo_callback, /* performs local init functions */ + Png_datarow_callback, /* performs per row action */ + Png_dataend_callback); /* performs cleanup actions */ + png->state = IS_nextdata; + } + break; + + case IS_nextdata: + if (setjmp(png->jmpbuf)) { + png->state = IS_finished; + } else if (!png->error) { + png_process_data( png->png_ptr, + png->info_ptr, + png->ipbuf + png->ipbufstart, + (png_size_t)DATASIZE); + + png->ipbufstart += DATASIZE; + } + break; + + default: + MSG_WARN("PNG decoder: bad state = %d\n", png->state); + abort(); + } + } +} + +/* + * Create the image state data that must be kept between calls + */ +static DilloPng *Png_new(DilloImage *Image, DilloUrl *url, int version) +{ + DilloPng *png = dNew0(DilloPng, 1); + + png->Image = Image; + png->url = url; + png->version = version; + png->error = 0; + png->ipbuf = NULL; + png->ipbufstart = 0; + png->ipbufsize = 0; + png->state = IS_init; + png->linebuf = NULL; + png->image_data = NULL; + png->row_pointers = NULL; + + return png; +} + +/* + * MIME handler for "image/png" type + * (Sets Png_callback or a_Dicache_callback as the cache-client) + */ +void *a_Png_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data) +{ +/* + * Type: MIME type + * Ptr: points to a Web structure + * Call: Dillo calls this with more data/eod + * Data: raw image data + */ + + DilloWeb *web = Ptr; + DICacheEntry *DicEntry; + + DEBUG_MSG(5, "a_Png_image: Type = %s\n" + "a_Png_image: libpng - Compiled %s; using %s.\n" + "a_Png_image: zlib - Compiled %s; using %s.\n", + Type, PNG_LIBPNG_VER_STRING, png_libpng_ver, + ZLIB_VERSION, zlib_version); + + if (!web->Image) + web->Image = a_Image_new(0, 0, NULL, prefs.bg_color); + + /* Add an extra reference to the Image (for dicache usage) */ + a_Image_ref(web->Image); + + DicEntry = a_Dicache_get_entry(web->url); + if (!DicEntry) { + /* Let's create an entry for this image... */ + DicEntry = a_Dicache_add_entry(web->url); + + /* ... and let the decoder feed it! */ + *Data = Png_new(web->Image, DicEntry->url, DicEntry->version); + *Call = (CA_Callback_t) Png_callback; + } else { + /* Let's feed our client from the dicache */ + a_Dicache_ref(DicEntry->url, DicEntry->version); + *Data = web->Image; + *Call = (CA_Callback_t) a_Dicache_callback; + } + return (web->Image->dw); +} + +#endif /* ENABLE_PNG */ diff --git a/src/prefs.c b/src/prefs.c new file mode 100644 index 00000000..1d5e6053 --- /dev/null +++ b/src/prefs.c @@ -0,0 +1,434 @@ +/* + * Preferences for dillo + * + * Copyright (C) 2006 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> /* for strchr */ +#include <fcntl.h> +#include <unistd.h> +#include <locale.h> /* for setlocale */ +#include <ctype.h> /* for isspace */ +#include "prefs.h" +#include "colors.h" +#include "misc.h" +#include "msg.h" + +#define RCNAME "dillorc2" + + +/* + * Global Data + */ +DilloPrefs prefs; + +typedef struct SymNode_ SymNode_t; + +struct SymNode_ { + char *name; + RcToken_t token; +}; + +/* Symbol array, sorted alphabetically */ +SymNode_t symbols[] = { + { "allow_white_bg", DRC_TOKEN_ALLOW_WHITE_BG }, + { "bg_color", DRC_TOKEN_BG_COLOR }, + { "contrast_visited_color", DRC_TOKEN_CONTRAST_VISITED_COLOR }, + { "enterpress_forces_submit", DRC_TOKEN_ENTERPRESS_FORCES_SUBMIT }, + { "font_factor", DRC_TOKEN_FONT_FACTOR }, + { "force_my_colors", DRC_TOKEN_FORCE_MY_COLORS }, + { "fullwindow_start", DRC_TOKEN_FULLWINDOW_START }, + { "fw_fontname", DRC_TOKEN_FW_FONT }, + { "generate_submit", DRC_TOKEN_GENERATE_SUBMIT }, + { "geometry", DRC_TOKEN_GEOMETRY }, + { "home", DRC_TOKEN_HOME }, + { "http_proxy", DRC_TOKEN_PROXY }, + { "http_proxyuser", DRC_TOKEN_PROXYUSER }, + { "limit_text_width", DRC_TOKEN_LIMIT_TEXT_WIDTH }, + { "link_color", DRC_TOKEN_LINK_COLOR }, + { "no_proxy", DRC_TOKEN_NOPROXY }, + { "panel_size", DRC_TOKEN_PANEL_SIZE }, + { "search_url", DRC_TOKEN_SEARCH_URL }, + { "show_back", DRC_TOKEN_SHOW_BACK }, + { "show_bookmarks", DRC_TOKEN_SHOW_BOOKMARKS }, + { "show_clear_url", DRC_TOKEN_SHOW_CLEAR_URL }, + { "show_extra_warnings", DRC_TOKEN_SHOW_EXTRA_WARNINGS }, + { "show_forw", DRC_TOKEN_SHOW_FORW }, + { "show_home", DRC_TOKEN_SHOW_HOME }, + { "show_menubar", DRC_TOKEN_SHOW_MENUBAR }, + { "show_msg", DRC_TOKEN_SHOW_MSG }, + { "show_progress_box", DRC_TOKEN_SHOW_PROGRESS_BOX }, + { "show_reload", DRC_TOKEN_SHOW_RELOAD }, + { "show_save", DRC_TOKEN_SHOW_SAVE }, + { "show_search", DRC_TOKEN_SHOW_SEARCH }, + { "show_stop", DRC_TOKEN_SHOW_STOP }, + { "show_tooltip", DRC_TOKEN_SHOW_TOOLTIP }, + { "show_url", DRC_TOKEN_SHOW_URL }, + { "small_icons", DRC_TOKEN_SMALL_ICONS }, + { "start_page", DRC_TOKEN_START_PAGE }, + { "text_color", DRC_TOKEN_TEXT_COLOR }, + { "transient_dialogs", DRC_TOKEN_TRANSIENT_DIALOGS }, + { "use_dicache", DRC_TOKEN_USE_DICACHE }, + { "use_oblique", DRC_TOKEN_USE_OBLIQUE }, + { "visited_color", DRC_TOKEN_VISITED_COLOR, }, + { "vw_fontname", DRC_TOKEN_VW_FONT }, + { "w3c_plus_heuristics", DRC_TOKEN_W3C_PLUS_HEURISTICS } +}; + +static const uint_t n_symbols = sizeof (symbols) / sizeof (symbols[0]); + +/* + *- Mini parser ------------------------------------------------------------- + */ + +/* + * Comparison function for binary search + */ +static int Prefs_symbol_cmp(const void *a, const void *b) +{ + return strcmp(((SymNode_t*)a)->name, ((SymNode_t*)b)->name); +} + +/* + * Take a dillo rc line and return 'name' and 'value' pointers to it. + * Notes: + * - line is modified! + * - it skips blank lines and lines starting with '#' + * + * Return value: 0 on successful value/pair, -1 otherwise + */ +static int Prefs_get_pair(char **line, char **name, char **value) +{ + char *eq, *p; + int len, ret = -1; + + dReturn_val_if_fail(*line, ret); + + *name = NULL; + *value = NULL; + dStrstrip(*line); + if (*line[0] != '#' && (eq = strchr(*line, '='))) { + /* get name */ + for (p = *line; *p && *p != '=' && !isspace(*p); ++p); + *p = 0; + *name = *line; + + /* get value */ + if (p == eq) { + for (++p; isspace(*p); ++p); + len = strlen(p); + if (len >= 2 && *p == '"' && p[len-1] == '"') { + p[len-1] = 0; + ++p; + } + *value = p; + ret = 0; + } + } + + if (*line[0] && *line[0] != '#' && (!*name || !*value)) { + MSG("prefs: Syntax error in %s: name=\"%s\" value=\"%s\"\n", + RCNAME, *name, *value); + } + return ret; +} + +/* + * Parse a name/value pair and set preferences accordingly. + */ +static int Prefs_parse_pair(char *name, char *value) +{ + int st; + SymNode_t key, *node; + + key.name = name; + node = bsearch(&key, symbols, n_symbols, + sizeof(SymNode_t), Prefs_symbol_cmp); + if (!node) { + MSG("prefs: {%s} is not a recognized token.\n", name); + return -1; + } + + switch (node->token) { + case DRC_TOKEN_GEOMETRY: + a_Misc_parse_geometry(value, &prefs.xpos, &prefs.ypos, + &prefs.width, &prefs.height); + break; + case DRC_TOKEN_PROXY: + a_Url_free(prefs.http_proxy); + prefs.http_proxy = a_Url_new(value, NULL, 0, 0, 0); + break; + case DRC_TOKEN_PROXYUSER: + dFree(prefs.http_proxyuser); + prefs.http_proxyuser = dStrdup(value); + break; + case DRC_TOKEN_NOPROXY: + dFree(prefs.no_proxy); + prefs.no_proxy = dStrdup(value); + break; + case DRC_TOKEN_LINK_COLOR: + prefs.link_color = a_Color_parse(value, prefs.link_color, &st); + break; + case DRC_TOKEN_VISITED_COLOR: + prefs.visited_color = a_Color_parse(value, prefs.visited_color, &st); + break; + case DRC_TOKEN_TEXT_COLOR: + prefs.text_color = a_Color_parse(value, prefs.text_color, &st); + break; + case DRC_TOKEN_BG_COLOR: + prefs.bg_color = a_Color_parse(value, prefs.bg_color, &st); + break; + case DRC_TOKEN_ALLOW_WHITE_BG: + prefs.allow_white_bg = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_FORCE_MY_COLORS: + prefs.force_my_colors = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_CONTRAST_VISITED_COLOR: + prefs.contrast_visited_color = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_USE_OBLIQUE: + prefs.use_oblique = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_PANEL_SIZE: + if (!dStrcasecmp(value, "tiny")) + prefs.panel_size = 1; + else if (!dStrcasecmp(value, "small")) + prefs.panel_size = 2; + else if (!dStrcasecmp(value, "medium")) + prefs.panel_size = 3; + else /* default to "large" */ + prefs.panel_size = 4; + break; + case DRC_TOKEN_SMALL_ICONS: + prefs.small_icons = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_START_PAGE: + a_Url_free(prefs.start_page); + prefs.start_page = a_Url_new(value, NULL, 0, 0, 0); + break; + case DRC_TOKEN_HOME: + a_Url_free(prefs.home); + prefs.home = a_Url_new(value, NULL, 0, 0, 0); + break; + case DRC_TOKEN_SHOW_TOOLTIP: + prefs.show_tooltip = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_FONT_FACTOR: + prefs.font_factor = strtod(value, NULL); + break; + case DRC_TOKEN_LIMIT_TEXT_WIDTH: + prefs.limit_text_width = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_W3C_PLUS_HEURISTICS: + prefs.w3c_plus_heuristics = (strcmp(value,"YES") == 0); + break; + case DRC_TOKEN_USE_DICACHE: + prefs.use_dicache = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_BACK: + prefs.show_back = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_FORW: + prefs.show_forw = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_HOME: + prefs.show_home = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_RELOAD: + prefs.show_reload = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_SAVE: + prefs.show_save = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_STOP: + prefs.show_stop = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_BOOKMARKS: + prefs.show_bookmarks = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_MENUBAR: + prefs.show_menubar = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_CLEAR_URL: + prefs.show_clear_url = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_URL: + prefs.show_url = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_SEARCH: + prefs.show_search = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_PROGRESS_BOX: + prefs.show_progress_box = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_FULLWINDOW_START: + prefs.fullwindow_start = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_TRANSIENT_DIALOGS: + prefs.transient_dialogs = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_FW_FONT: + dFree(prefs.fw_fontname); + prefs.fw_fontname = dStrdup(value); + break; + case DRC_TOKEN_VW_FONT: + dFree(prefs.vw_fontname); + prefs.vw_fontname = dStrdup(value); + break; + case DRC_TOKEN_GENERATE_SUBMIT: + prefs.generate_submit = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_ENTERPRESS_FORCES_SUBMIT: + prefs.enterpress_forces_submit = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SEARCH_URL: + dFree(prefs.search_url); + prefs.search_url = dStrdup(value); + break; + case DRC_TOKEN_SHOW_MSG: + prefs.show_msg = (strcmp(value, "YES") == 0); + break; + case DRC_TOKEN_SHOW_EXTRA_WARNINGS: + prefs.show_extra_warnings = (strcmp(value, "YES") == 0); + break; + default: + MSG_WARN("prefs: {%s} IS recognized but not handled!\n", name); + break; /* Not reached */ + } + + return 0; +} + +/* + * Parse dillorc and set the values in the prefs structure. + */ +static int Prefs_parse_dillorc(void) +{ + FILE *F_in; + char *filename, *line, *name, *value; + int ret = -1; + + filename = dStrconcat(dGethomedir(), "/.dillo/", RCNAME, NULL); + if (!(F_in = fopen(filename, "r"))) { + MSG("prefs: Can't open %s file: %s\n", RCNAME, filename); + if (!(F_in = fopen(DILLORC_SYS, "r"))) { + MSG("prefs: Can't open %s file: %s\n", RCNAME, DILLORC_SYS); + MSG("prefs: Using internal defaults.\n"); + } else { + MSG("prefs: Using %s\n", DILLORC_SYS); + } + } + + if (F_in) { + /* scan dillorc line by line */ + while ((line = dGetline(F_in)) != NULL) { + if (Prefs_get_pair(&line, &name, &value) == 0){ + _MSG("{%s}, {%s}\n", name, value); + Prefs_parse_pair(name, value); + } + dFree(line); + } + fclose(F_in); + ret = 0; + } + dFree(filename); + + return ret; +} + +/*---------------------------------------------------------------------------*/ + +void a_Prefs_init(void) +{ + char *old_locale; + + prefs.width = D_GEOMETRY_DEFAULT_WIDTH; + prefs.height = D_GEOMETRY_DEFAULT_HEIGHT; + prefs.xpos = D_GEOMETRY_DEFAULT_XPOS; + prefs.ypos = D_GEOMETRY_DEFAULT_YPOS; + prefs.http_proxy = NULL; + prefs.http_proxyuser = NULL; + prefs.no_proxy = NULL; + prefs.link_color = DW_COLOR_DEFAULT_BLUE; + prefs.visited_color = DW_COLOR_DEFAULT_PURPLE; + prefs.bg_color = DW_COLOR_DEFAULT_BGND; + prefs.text_color = DW_COLOR_DEFAULT_BLACK; + prefs.use_oblique = FALSE; + prefs.start_page = a_Url_new(DILLO_START_PAGE, NULL, 0, 0, 0); + prefs.home = a_Url_new(DILLO_HOME, NULL, 0, 0, 0); + prefs.allow_white_bg = TRUE; + prefs.force_my_colors = FALSE; + prefs.contrast_visited_color = FALSE; + prefs.show_tooltip = FALSE; + prefs.panel_size = 1; + prefs.small_icons = FALSE; + prefs.limit_text_width = FALSE; + prefs.w3c_plus_heuristics = TRUE; + prefs.font_factor = 1.0; + prefs.use_dicache = FALSE; + prefs.show_back=TRUE; + prefs.show_forw=TRUE; + prefs.show_home=TRUE; + prefs.show_reload=TRUE; + prefs.show_save=TRUE; + prefs.show_stop=TRUE; + prefs.show_bookmarks=TRUE; + prefs.show_menubar=TRUE; + prefs.show_clear_url=TRUE; + prefs.show_url=TRUE; + prefs.show_search=TRUE; + prefs.show_progress_box=TRUE; + prefs.fullwindow_start=FALSE; + prefs.transient_dialogs=FALSE; + prefs.vw_fontname = dStrdup("helvetica"); + prefs.fw_fontname = dStrdup("courier"); + prefs.generate_submit = FALSE; + prefs.enterpress_forces_submit = FALSE; + prefs.search_url = dStrdup("http://www.google.com/search?q=%s"); + prefs.show_msg = TRUE; + prefs.show_extra_warnings = FALSE; + + /* this locale stuff is to avoid parsing problems with float numbers */ + old_locale = dStrdup (setlocale (LC_NUMERIC, NULL)); + setlocale (LC_NUMERIC, "C"); + + Prefs_parse_dillorc(); + + setlocale (LC_NUMERIC, old_locale); + dFree (old_locale); + +} + +/* + * Preferences memory-deallocation + * (Call this one at exit time) + */ +void a_Prefs_freeall(void) +{ + dFree(prefs.http_proxyuser); + dFree(prefs.no_proxy); + a_Url_free(prefs.http_proxy); + dFree(prefs.fw_fontname); + dFree(prefs.vw_fontname); + a_Url_free(prefs.start_page); + a_Url_free(prefs.home); + dFree(prefs.search_url); +} diff --git a/src/prefs.h b/src/prefs.h new file mode 100644 index 00000000..79f06bc1 --- /dev/null +++ b/src/prefs.h @@ -0,0 +1,130 @@ +#ifndef __PREFS_H__ +#define __PREFS_H__ + +#include "url.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define DILLO_START_PAGE "about:splash" +#define DILLO_HOME "http://www.dillo.org/" +#define D_GEOMETRY_DEFAULT_WIDTH 640 +#define D_GEOMETRY_DEFAULT_HEIGHT 550 +#define D_GEOMETRY_DEFAULT_XPOS -9999 +#define D_GEOMETRY_DEFAULT_YPOS -9999 + +#define DW_COLOR_DEFAULT_GREY 0xd6d6d6 +#define DW_COLOR_DEFAULT_BLACK 0x000000 +#define DW_COLOR_DEFAULT_BLUE 0x0000ff +#define DW_COLOR_DEFAULT_PURPLE 0x800080 +#define DW_COLOR_DEFAULT_BGND 0xd6d6c0 + + +/* define enumeration values to be returned for specific symbols */ +typedef enum { + DRC_TOKEN_ALLOW_WHITE_BG, + DRC_TOKEN_BG_COLOR, + DRC_TOKEN_CONTRAST_VISITED_COLOR, + DRC_TOKEN_ENTERPRESS_FORCES_SUBMIT, + DRC_TOKEN_FONT_FACTOR, + DRC_TOKEN_FORCE_MY_COLORS, + DRC_TOKEN_FULLWINDOW_START, + DRC_TOKEN_FW_FONT, + DRC_TOKEN_GENERATE_SUBMIT, + DRC_TOKEN_GEOMETRY, + DRC_TOKEN_HOME, + DRC_TOKEN_LIMIT_TEXT_WIDTH, + DRC_TOKEN_LINK_COLOR, + DRC_TOKEN_NOPROXY, + DRC_TOKEN_PANEL_SIZE, + DRC_TOKEN_PROXY, + DRC_TOKEN_PROXYUSER, + DRC_TOKEN_SEARCH_URL, + DRC_TOKEN_SHOW_BACK, + DRC_TOKEN_SHOW_BOOKMARKS, + DRC_TOKEN_SHOW_CLEAR_URL, + DRC_TOKEN_SHOW_EXTRA_WARNINGS, + DRC_TOKEN_SHOW_FORW, + DRC_TOKEN_SHOW_HOME, + DRC_TOKEN_SHOW_MENUBAR, + DRC_TOKEN_SHOW_MSG, + DRC_TOKEN_SHOW_PROGRESS_BOX, + DRC_TOKEN_SHOW_RELOAD, + DRC_TOKEN_SHOW_SAVE, + DRC_TOKEN_SHOW_SEARCH, + DRC_TOKEN_SHOW_STOP, + DRC_TOKEN_SHOW_TOOLTIP, + DRC_TOKEN_SHOW_URL, + DRC_TOKEN_SMALL_ICONS, + DRC_TOKEN_START_PAGE, + DRC_TOKEN_TEXT_COLOR, + DRC_TOKEN_TRANSIENT_DIALOGS, + DRC_TOKEN_USE_DICACHE, + DRC_TOKEN_USE_OBLIQUE, + DRC_TOKEN_VISITED_COLOR, + DRC_TOKEN_VW_FONT, + DRC_TOKEN_W3C_PLUS_HEURISTICS +} RcToken_t; + +typedef struct _DilloPrefs DilloPrefs; + +struct _DilloPrefs { + int width; + int height; + int xpos; + int ypos; + DilloUrl *http_proxy; + char *http_proxyuser; + char *no_proxy; + DilloUrl *start_page; + DilloUrl *home; + int32_t link_color; + int32_t visited_color; + int32_t bg_color; + int32_t text_color; + bool_t allow_white_bg; + bool_t use_oblique; + bool_t force_my_colors; + bool_t contrast_visited_color; + bool_t show_tooltip; + int panel_size; + bool_t small_icons; + bool_t limit_text_width; + bool_t w3c_plus_heuristics; + double font_factor; + bool_t use_dicache; + bool_t show_back; + bool_t show_forw; + bool_t show_home; + bool_t show_reload; + bool_t show_save; + bool_t show_stop; + bool_t show_bookmarks; + bool_t show_menubar; + bool_t show_clear_url; + bool_t show_url; + bool_t show_search; + bool_t show_progress_box; + bool_t fullwindow_start; + bool_t transient_dialogs; + char *vw_fontname; + char *fw_fontname; + bool_t generate_submit; + bool_t enterpress_forces_submit; + char *search_url; + bool_t show_msg; + bool_t show_extra_warnings; +}; + +/* Global Data */ +extern DilloPrefs prefs; + +void a_Prefs_init(void); +void a_Prefs_freeall(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __PREFS_H__ */ diff --git a/src/srch b/src/srch new file mode 100755 index 00000000..06266382 --- /dev/null +++ b/src/srch @@ -0,0 +1,33 @@ +#!/bin/sh +# +# Find a token within source files ( *.[ch] ) +# Enjoy! +# Jorge.- + +if [ $# = 1 ]; then + FLAGS="-H" +elif [ $# = 2 ]; then + FLAGS="-H $1" + shift 1 +else + echo "Usage:" + echo " srch [-options] <token>" + exit 0 +fi + +# find "./" -name "*.[ch]" -print -exec grep $1 {} \; +egrep $FLAGS "$1" *.[ch][ch] +egrep $FLAGS "$1" *.[ch] +egrep $FLAGS "$1" IO/*.[ch][ch]* +egrep $FLAGS "$1" IO/*.[ch] +egrep $FLAGS "$1" ../dpi/*.[ch] +egrep $FLAGS "$1" ../dpi/*.[ch][ch] +egrep $FLAGS "$1" ../dpid/*.[ch] +egrep $FLAGS "$1" ../dpip/*.[ch] +egrep $FLAGS "$1" ../dlib/*.[ch] + +#egrep $FLAGS "$1" dw/*[ch] +#egrep $FLAGS "$1" lout/*[ch] +egrep $FLAGS "$1" ../../dw-testbed/dw/*[ch] +egrep $FLAGS "$1" ../../dw-testbed/lout/*[ch] + diff --git a/src/timeout.cc b/src/timeout.cc new file mode 100644 index 00000000..da944803 --- /dev/null +++ b/src/timeout.cc @@ -0,0 +1,46 @@ +/* + * File: timeout.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +// Simple ADT for timeout functions + +#include <fltk/run.h> +#include "timeout.hh" + +using namespace fltk; + + +// C++ functions with C linkage ---------------------------------------------- + +/* + * Hook a one-time timeout function 'cb' after 't' seconds + * with 'cbdata" as its data. + */ +void a_Timeout_add(float t, TimeoutCb_t cb, void *cbdata) +{ + add_timeout(t, cb, cbdata); +} + +/* + * To be called from iside the 'cb' function when it wants to keep running + */ +void a_Timeout_repeat(float t, TimeoutCb_t cb, void *cbdata) +{ + add_timeout(t, cb, cbdata); +} + +/* + * Stop running a timeout function + */ +void a_Timeout_remove() +{ + /* in FLTK, timeouts run one time by default */ +} + diff --git a/src/timeout.hh b/src/timeout.hh new file mode 100644 index 00000000..5b7f4759 --- /dev/null +++ b/src/timeout.hh @@ -0,0 +1,20 @@ +#ifndef __TIMEOUT_HH__ +#define __TIMEOUT_HH__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef void (*TimeoutCb_t)(void *data); + +void a_Timeout_add(float t, TimeoutCb_t cb, void *cbdata); +void a_Timeout_repeat(float t, TimeoutCb_t cb, void *cbdata); +void a_Timeout_remove(); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __TIMEOUT_HH__ */ + diff --git a/src/ui.cc b/src/ui.cc new file mode 100644 index 00000000..54523b88 --- /dev/null +++ b/src/ui.cc @@ -0,0 +1,912 @@ +/* + * File: ui.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +// UI for Dillo + +#include <stdlib.h> +#include <stdio.h> + +#include <fltk/Window.h> +#include <fltk/Widget.h> +#include <fltk/Button.h> +#include <fltk/HighlightButton.h> +#include <fltk/Input.h> +#include <fltk/Output.h> +#include <fltk/run.h> +#include <fltk/BarGroup.h> +#include <fltk/PackedGroup.h> +#include <fltk/xpmImage.h> +#include <fltk/MultiImage.h> +#include <fltk/events.h> // for mouse buttons +#include <fltk/Box.h> +#include <fltk/InvisibleBox.h> +#include <fltk/PopupMenu.h> +#include <fltk/Item.h> +#include <fltk/Divider.h> + +#include "ui.hh" +#include "msg.h" + +using namespace fltk; + + +// Include image data +#include "pixmaps.h" + +#include "uicmd.hh" + +/* + * Local sub class + * (Used to avoid certain shortcuts in the location bar) + */ + +class NewInput : public Input { +public: + NewInput (int x, int y, int w, int h, const char* l=0) : + Input(x,y,w,h,l) {}; + int handle(int e); +}; + +/* + * Disable: UpKey, DownKey, PageUpKey, PageDownKey and CTRL+{o,HomeKey,EndKey} + */ +int NewInput::handle(int e) +{ + int k = event_key(); + bool ctrl = event_state(CTRL); + + _MSG("NewInput::handle event=%d", e); + if (ctrl && (k == 'o' || k == HomeKey || k == EndKey)) + return 0; + if ((e == KEY || e == KEYUP) && + k == UpKey || k == DownKey || k == PageUpKey || k == PageDownKey) { + _MSG(" {Up|Down|PgUp|PgDn} ret = 1\n"); + // todo: one way to handle this is to send(SHORTCUT) to the viewport + // and return 1 here. A cleaner approach may be to ignore the event and + // only allow the location and render area to take focus. + // + // Currently, zero is returned, so the parent gets the event and moves + // focus to a button widget that's not interested in these keys; + // once this new widget is focused, Up and Down start to work. + return 0; + + //this->window()->send(SHORTCUT); + //this->window()->send(e); + //return 1; + } + _MSG("\n"); + + return Input::handle(e); +} + +//---------------------------------------------------------------------------- + +/* + * Used to handle "paste" within the toolbar's Clear button. + */ +class NewHighlightButton : public HighlightButton { +public: + NewHighlightButton(int x, int y, int w, int h, const char *l=0) : + HighlightButton(x,y,w,h,l) {}; + int handle(int e); +}; + +int NewHighlightButton::handle(int e) +{ + if (e == PASTE) { + const char* t = event_text(); + if (t && *t) { + a_UIcmd_set_location_text(this->window()->user_data(), t); + a_UIcmd_open_urlstr(this->window()->user_data(), t); + return 1; + } + } + return HighlightButton::handle(e); +} + +// +// Toolbar buttons ----------------------------------------------------------- +// +//static const char *button_names[] = { +// "Back", "Forward", "Home", "Reload", "Save", "Stop", "Bookmarks", +// "Clear", "Search" +//}; + +// +// Global event handler function --------------------------------------------- +// +int global_event_handler(int e, Window *win) +{ + int ret = 0; + + if (e == SHORTCUT) { + if (event_state(CTRL)) { + if (event_key() == 'l') { + a_UIcmd_open_url_dialog(win->user_data()); + ret = 1; + } else if (event_key() == 'n') { + a_UIcmd_browser_window_new(win->w(), win->h()); + ret = 1; + } else if (event_key() == 'o') { + a_UIcmd_open_file(win->user_data()); + ret = 1; + } else if (event_key() == 'q') { + a_UIcmd_close_bw(win->user_data()); + ret = 1; + } else if (event_key() == 's') { + a_UIcmd_search_dialog(win->user_data()); + ret = 1; + } + } + + if (event_state(ALT) && event_key() == 'q') { + a_UIcmd_close_all_bw(); + } + } + return ret; +} + +// +// Callback functions -------------------------------------------------------- +// + +/* + * Callback handler for the close window event. + */ +void close_window_cb(Widget *wid, void *data) +{ + a_UIcmd_close_bw(data); +} + +/* + * Callback for the search button. + */ +static void search_cb(Widget *wid, void *data) +{ + int k = event_key(); + if (k && k <= 7) + MSG("[Search], mouse button %d was pressed\n", k); + + if (k == 1) { + a_UIcmd_search_dialog(wid->window()->user_data()); + } else if (k == 2) { + ((UI*)data)->color_change_cb_i(); + } else if (k == 3) { + ((UI*)data)->panel_cb_i(); + } +} + +/* + * Callback for the location's clear-button. + */ +void clear_cb(Widget *w, void *data) +{ + UI *ui = (UI*)data; + + int k = event_key(); + if (k && k <= 7) + MSG("[Clear], mouse button %d was pressed\n", k); + if (k == 1) { + ui->set_location(""); + ui->focus_location(); + } if (k == 2) { + ui->paste_url(); + } +} + +/* + * Change the color of the location bar. + * +static void color_change_cb(Widget *wid, void *data) +{ + ((UI*)data)->color_change_cb_i(); +} + */ + + +/* + * Send the browser to the new URL in the location. + */ +void location_cb(Widget *wid, void *data) +{ + Input *i = (Input*)wid; + + /* This test is necessary because WHEN_ENTER_KEY also includes + * other events we're not interested in */ + if (event_key() == ReturnKey) { + a_UIcmd_open_urlstr(i->window()->user_data(), i->value()); + } +} + + +/* + * Callback handler for button press on the panel + */ +void b1_cb(Widget *wid, void *cb_data) +{ + int bn = (int)cb_data; + int k = event_key(); + if (k && k <= 7) { + _MSG("[%s], mouse button %d was pressed\n", button_names[bn], k); + MSG("mouse button %d was pressed\n", k); + } + switch (bn) { + case UI_BACK: + if (k == 1) { + a_UIcmd_back(wid->window()->user_data()); + } else if (k == 3) { + a_UIcmd_back_popup(wid->window()->user_data()); + } + break; + case UI_FORW: + if (k == 1) { + a_UIcmd_forw(wid->window()->user_data()); + } else if (k == 3) { + a_UIcmd_forw_popup(wid->window()->user_data()); + } + break; + case UI_HOME: + if (k == 1) { + a_UIcmd_home(wid->window()->user_data()); + } + break; + case UI_RELOAD: + if (k == 1) { + a_UIcmd_reload(wid->window()->user_data()); + } + break; + case UI_SAVE: + if (k == 1) { + a_UIcmd_save(wid->window()->user_data()); + } + break; + case UI_STOP: + if (k == 1) { + a_UIcmd_stop(wid->window()->user_data()); + } + break; + case UI_BOOK: + if (k == 1) { + a_UIcmd_book(wid->window()->user_data()); + } + break; + default: + break; + } +} + +/* + * Callback handler for fullscreen button press + */ +void fullscreen_cb(Widget *wid, void *data) +{ + ((UI*)data)->fullscreen_cb_i(); +} + +/* + * Callback for the bug meter button. + */ +void bugmeter_cb(Widget *w, void *data) +{ + int k = event_key(); + if (k && k <= 7) + MSG("[BugMeter], mouse button %d was pressed\n", k); + if (k == 1) { + a_UIcmd_view_page_bugs(((UI*)data)->user_data()); + } else if (k == 3) { + a_UIcmd_bugmeter_popup(((UI*)data)->user_data()); + } +} + +/* + * File menu item callback. + */ +void menu_cb(Widget* w, void*) +{ + Menu* menu = (Menu*)w; + Widget* item = menu->get_item(); + MSG("Callback for %s, item is %s\n", + menu->label() ? menu->label() : "menu bar", + item ? item->label() ? item->label() : "unnamed" : "none"); + //if (item) item->do_callback(); + menu->value(-1); +} + +////////////////////////////////////////////////////////////////////////////// +// UI class methods +// + +//---------------------------- +// Panel construction methods +//---------------------------- + +/* + * Create the archetipic browser buttons + */ +PackedGroup *UI::make_toolbar(int tw, int th) +{ + HighlightButton *b; + MultiImage *multi; + PackedGroup *p1=new PackedGroup(0,0,tw,th); + p1->begin(); + Back = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Back" : 0); + ImgLeftIns = new xpmImage(Small_Icons ? left_si_xpm : left_i_xpm); + ImgLeftSens = new xpmImage(Small_Icons ? left_s_xpm : left_xpm); + multi = new MultiImage(*ImgLeftSens, INACTIVE_R, *ImgLeftIns); + b->image(multi); + b->tooltip("Previous page"); + b->callback(b1_cb, (void *)UI_BACK); + HighlightButton::default_style->highlight_color(CuteColor); + + Forw = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Forw" : 0); + ImgRightIns = new xpmImage(Small_Icons ? right_si_xpm : right_i_xpm); + ImgRightSens = new xpmImage(Small_Icons ? right_s_xpm : right_xpm); + multi = new MultiImage(*ImgRightSens, INACTIVE_R, *ImgRightIns); + b->image(multi); + b->tooltip("Next page"); + b->callback(b1_cb, (void *)UI_FORW); + + Home = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Home" : 0); + b->image(new xpmImage(Small_Icons ? home_s_xpm : home_xpm)); + b->tooltip("Go to the Home page"); + b->callback(b1_cb, (void *)UI_HOME); + + Reload = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Reload" : 0); + b->image(new xpmImage(Small_Icons ? reload_s_xpm : reload_xpm)); + b->tooltip("Reload"); + b->callback(b1_cb, (void *)UI_RELOAD); + + Save = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Save" : 0); + b->image(new xpmImage(Small_Icons ? save_s_xpm : save_xpm)); + b->tooltip("Save this page"); + b->callback(b1_cb, (void *)UI_SAVE); + + Stop = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Stop" : 0); + ImgStopIns = new xpmImage(Small_Icons ? stop_si_xpm : stop_i_xpm); + ImgStopSens = new xpmImage(Small_Icons ? stop_s_xpm : stop_xpm); + multi = new MultiImage(*ImgStopSens, INACTIVE_R, *ImgStopIns); + b->image(multi); + b->tooltip("Stop loading"); + b->callback(b1_cb, (void *)UI_STOP); + + Bookmarks = b = new HighlightButton(xpos, 0, bw, bh, (lbl) ? "Book" : 0); + b->image(new xpmImage(Small_Icons ? bm_s_xpm : bm_xpm)); + b->tooltip("View bookmarks"); + b->callback(b1_cb, (void *)UI_BOOK); + + p1->type(PackedGroup::ALL_CHILDREN_VERTICAL); + p1->end(); + + return p1; +} + +/* + * Create the location box (Clear/Input/Search) + */ +PackedGroup *UI::make_location() +{ + Button *b; + PackedGroup *pg = new PackedGroup(0,0,0,0); + pg->begin(); + Clear = b = new NewHighlightButton(2,2,16,22,0); + b->image(new xpmImage(new_s_xpm)); + b->tooltip("Clear the URL box.\nMiddle-click to paste a URL."); + //b->callback(b1_cb, (void *)UI_CLEAR); + b->callback(clear_cb, (void *)this); + + Input *i = Location = new NewInput(0,0,0,0,0); + i->tooltip("Location"); + i->color(CuteColor); + i->when(WHEN_ENTER_KEY); + i->callback(location_cb, this); + + Search = b = new HighlightButton(0,0,16,22,0); + b->image(new xpmImage(search_xpm)); + b->tooltip("Search the Web"); + //b->callback(b1_cb, (void *)UI_SEARCH); + b->callback(search_cb, (void *)this); + + pg->type(PackedGroup::ALL_CHILDREN_VERTICAL); + pg->resizable(i); + pg->end(); + + return pg; +} + +/* + * Create the progress bars + */ +PackedGroup *UI::make_progress_bars(int wide, int thin_up) +{ + PackedGroup *pg = new PackedGroup(0,0,0,0); + pg->begin(); + // Images + IProg = new InvisibleBox(0,0,pr_w,0, + wide ? "Images\n0 of 0" : "0 of 0"); + IProg->box(thin_up ? THIN_UP_BOX : EMBOSSED_BOX); + IProg->labelcolor(GRAY10); + // Page + PProg = new InvisibleBox(0,0,pr_w,0, + wide ? "Page\n0.0KB" : "0.0KB"); + PProg->box(thin_up ? THIN_UP_BOX : EMBOSSED_BOX); + PProg->labelcolor(GRAY10); + + pg->type(PackedGroup::ALL_CHILDREN_VERTICAL); + pg->end(); + + return pg; +} + +/* + * Create the "File" menu + */ +Group *UI::make_menu(int tiny) +{ + Item *i; + + PopupMenu *pm = new PopupMenu(2,2,30,fh-4, tiny ? "&F" : "&File"); + pm->callback(menu_cb); + pm->begin(); + i = new Item("&New Browser"); + i->shortcut(CTRL+'n'); + i = new Item("&Open File..."); + i->shortcut(CTRL+'o'); + i = new Item("Open UR&L..."); + i->shortcut(CTRL+'l'); + i = new Item("Close Window"); + i->shortcut(CTRL+'q'); + new Divider(); + i = new Item("Exit Dillo"); + i->shortcut(ALT+'q'); + + pm->end(); + + return pm; +} + +/* + * Create the control panel + */ +Group *UI::make_panel(int ww) +{ + Widget *w; + Group *g1, *g2, *g3; + PackedGroup *pg; + + if (PanelSize > P_large) { + PanelSize = P_tiny; + Small_Icons = !Small_Icons; + } + + if (PanelSize == P_tiny) { + if (Small_Icons) + xpos = 0, bw = 22, bh = 22, fh = 0, lh = 22, lbl = 0, pr_w = 45; + else + xpos = 0, bw = 28, bh = 28, fh = 0, lh = 28, lbl = 0, pr_w = 45; + } else if (PanelSize == P_small) { + if (Small_Icons) + xpos = 0, bw = 20, bh = 20, fh = 0, lh = 20, lbl = 0, pr_w = 45; + else + xpos = 0, bw = 28, bh = 28, fh = 0, lh = 28, lbl = 0, pr_w = 45; + } else if (PanelSize == P_medium) { + if (Small_Icons) + xpos = 0, bw = 42, bh = 36, fh = 0, lh = 22, lbl = 1, pr_w = 60; + else + xpos = 0, bw = 45, bh = 45, fh = 0, lh = 28, lbl = 1, pr_w = 60; + } else { // P_large + if (Small_Icons) + xpos = 0, bw = 42, bh = 36, fh = 22, lh = 22, lbl = 1, pr_w = 60; + else + xpos = 0, bw = 45, bh = 45, fh = 28, lh = 28, lbl = 1, pr_w = 60; + } + + if (PanelSize == P_tiny) { + g1 = new Group(0,0,ww,bh); + g1->begin(); + // Toolbar + pg = make_toolbar(ww,bh); + pg->box(EMBOSSED_BOX); + //pg->box(BORDER_FRAME); + w = make_location(); + pg->add(w); + pg->resizable(w); + w = make_progress_bars(0,1); + pg->add(w); + + g1->resizable(pg); + g1->end(); + + } else { + g1 = new Group(0,0,ww,fh+lh+bh); + g1->begin(); + // File menu + if (PanelSize == P_large) { + g2 = new Group(0,0,ww,fh); + g2->begin(); + make_menu(0); + g2->box(EMBOSSED_BOX); + g2->end(); + } + + // Location + g2 = new Group(0,fh,ww,lh); + g2->begin(); + pg = make_location(); + pg->resize(ww,lh); + g2->resizable(pg); + g2->end(); + + // Toolbar + g3 = new Group(0,fh+lh,ww,bh); + g3->begin(); + pg = make_toolbar(ww,bh); + //w = new InvisibleBox(0,0,0,0,"i n v i s i b l e"); + w = new InvisibleBox(0,0,0,0,0); + pg->add(w); + pg->resizable(w); + + if (PanelSize == P_small) { + w = make_progress_bars(0,0); + } else { + w = make_progress_bars(1,0); + } + pg->add(w); + + g3->resizable(pg); // Better than 'w3' and it also works + pg->box(BORDER_FRAME); + //g3->box(EMBOSSED_BOX); + g3->end(); + + g1->resizable(g3); + g1->end(); + } + + return g1; +} + +/* + * User Interface constructor + */ +UI::UI(int win_w, int win_h, const char* label) : + Window(win_w, win_h, label) +{ + int s_h = 20; + resizable(this); + begin(); + TopGroup = this; + + // Set handler for the close window event + // (the argument is set later via user_data()) + TopGroup->callback(close_window_cb); + + // Set some default values + //PanelSize = P_tiny, CuteColor = 26, Small_Icons = 0; + PanelSize = P_medium, CuteColor = 206, Small_Icons = 0; + + // Control panel + Panel = make_panel(win_w); + + // Render area + Main = new Widget(0,Panel->h(),win_w,win_h-Panel->h()-s_h,"Welcome..."); + Main->box(FLAT_BOX); + Main->color(GRAY15); + Main->labelfont(HELVETICA_BOLD_ITALIC); + Main->labelsize(36); + Main->labeltype(SHADOW_LABEL); + Main->labelcolor(WHITE); + TopGroup->resizable(Main); + MainIdx = TopGroup->find(Main); + + // Status Panel + StatusPanel = new Group(0, win_h-s_h, win_w, s_h, 0); + StatusPanel->begin(); + // Status box + int bm_w = 16; + Status = new Output(0, 0, win_w-bm_w, s_h, 0); + Status->value(""); + //Status->box(UP_BOX); + Status->box(THIN_DOWN_BOX); + Status->clear_click_to_focus(); + Status->clear_tab_to_focus(); + //Status->throw_focus(); + + // Bug Meter + BugMeter = new HighlightButton(win_w-bm_w,0,bm_w,s_h,0); + ImgMeterOK = new xpmImage(mini_ok_xpm); + ImgMeterBug = new xpmImage(mini_bug_xpm); + BugMeter->image(ImgMeterOK); + BugMeter->box(THIN_DOWN_BOX); + BugMeter->align(ALIGN_INSIDE|ALIGN_CLIP|ALIGN_LEFT); + BugMeter->tooltip("Show HTML bugs\n(right-click for menu)"); + BugMeter->callback(bugmeter_cb, (void *)this); + + StatusPanel->resizable(Status); + StatusPanel->end(); + + end(); + + // Make the full screen button (to be attached to the viewport later) + // TODO: attach to the viewport + FullScreen = new HighlightButton(0,0,15,15); + ImgFullScreenOn = new xpmImage(full_screen_on_xpm); + ImgFullScreenOff = new xpmImage(full_screen_off_xpm); + FullScreen->image(ImgFullScreenOn); + FullScreen->tooltip("Hide Controls"); + FullScreen->callback(fullscreen_cb, (void *)this); + + add_event_handler(global_event_handler); + + //show(); +} + +/* + * FLTK event handler for this window. + */ +int UI::handle(int event) +{ + if (event == KEY || event == KEYUP) { + _MSG ("UI::handle %s key=%d\n", + event == KEY ? "KEY" : "KEYUP", event_key()); + return 0; + } + return Window::handle(event); +} + + +//---------------------------- +// API for the User Interface +//---------------------------- + +/* + * Get the text from the location input-box. + */ +const char *UI::get_location() +{ + return Location->value(); +} + +/* + * Set a new URL in the location input-box. + */ +void UI::set_location(const char *str) +{ + Location->static_value(""); + Location->insert(str); +} + +/* + * Focus location entry. + */ +void UI::focus_location() +{ + Location->take_focus(); +} + +/* + * Set a new message in the status bar. + */ +void UI::set_status(const char *str) +{ + Status->value(str); +} + +/* + * Set the page progress text + * cmd: 0 Deactivate, 1 Update, 2 Clear + */ +void UI::set_page_prog(size_t nbytes, int cmd) +{ + char str[32]; + + if (cmd == 0) { + PProg->deactivate(); + } else { + PProg->activate(); + if (cmd == 1) { + snprintf(str, 32, "%s%.1f KB", + (PanelSize == 0) ? "" : "Page\n", nbytes/1024.0); + } else if (cmd == 2) { + str[0] = '\0'; + } + PProg->copy_label(str); + PProg->redraw_label(); + } +} + +/* + * Set the image progress text + * cmd: 0 Deactivate, 1 Update, 2 Clear + */ +void UI::set_img_prog(int n_img, int t_img, int cmd) +{ + char str[32]; + + if (cmd == 0) { + IProg->deactivate(); + } else { + IProg->activate(); + if (cmd == 1) { + snprintf(str, 32, "%s%d of %d", + (PanelSize == 0) ? "" : "Images\n", n_img, t_img); + } else if (cmd == 2) { + str[0] = '\0'; + } + IProg->copy_label(str); + IProg->redraw_label(); + } +} + +/* + * Set the bug meter progress text + */ +void UI::set_bug_prog(int n_bug) +{ + char str[32]; + int new_w = 16; + + if (n_bug == 0) { + BugMeter->image(ImgMeterOK); + BugMeter->label(""); + } else if (n_bug >= 1) { + if (n_bug == 1) + BugMeter->image(ImgMeterBug); + snprintf(str, 32, "%d", n_bug); + BugMeter->copy_label(str); + BugMeter->redraw_label(); + new_w = strlen(str)*8 + 20; + } + Status->resize(0,0,StatusPanel->w()-new_w,Status->h()); + BugMeter->resize(StatusPanel->w()-new_w, 0, new_w, BugMeter->h()); + StatusPanel->init_sizes(); +} + +/* + * Customize the UI's panel (show/hide buttons) + */ +void UI::customize(int flags) +{ + Save->hide(); +} + +/* + * On-the-fly panel style change + */ +void UI::panel_cb_i() +{ + Group *NewPanel; + + // Create a new Panel + ++PanelSize; + NewPanel = make_panel(TopGroup->w()); + TopGroup->replace(*Panel, *NewPanel); + delete(Panel); + Panel = NewPanel; + // Scale the viewport + int p_h = Panel->h(); + Main->resize(0, p_h, TopGroup->w(), TopGroup->h() - p_h - Status->h()); + TopGroup->init_sizes(); + + Location->take_focus(); +} + +/* + * On-the-fly color style change + */ +void UI::color_change_cb_i() +{ + static int ncolor = 0, cols[] = {7,17,26,51,140,156,205,206,215,-1}; + + ncolor = (cols[ncolor+1] < 0) ? 0 : ncolor + 1; + CuteColor = cols[ncolor]; + MSG("Location color %d\n", CuteColor); + Location->color(CuteColor); + Location->redraw(); + HighlightButton::default_style->highlight_color(CuteColor); +} + +/* + * Toggle the Control Panel out of the way + */ +void UI::fullscreen_cb_i() +{ +#if 0 + // Works, but for unknown reasons it resizes hidden widgets on "Maximize" + if (Panel->visible_r()) { + Panel->hide(); + Status->hide(); + // Scale the viewport + Main->resize(0, 0, TopGroup->w(), TopGroup->h()); + TopGroup->init_sizes(); + } else { + // Scale the viewport + int p_h = Panel->h(); + Main->resize(0, p_h, TopGroup->w(), TopGroup->h() - p_h - Status->h()); + Panel->show(); + Status->show(); + TopGroup->init_sizes(); + } +#else + if (Panel->w() != 0) { + Panel_h = Panel->h(); + Status_h = Status->h(); + Panel->resize(0, 0); + StatusPanel->resize(0, 0); + // Scale the viewport + Main->resize(0, 0, TopGroup->w(), TopGroup->h()); + TopGroup->init_sizes(); + } else { + Panel->resize(TopGroup->w(), Panel_h); + Main->resize(0,Panel_h,TopGroup->w(),TopGroup->h()-Panel_h-Status_h); + StatusPanel->resize(0,TopGroup->h()-Status_h, TopGroup->w(), Status_h); + TopGroup->init_sizes(); + } +#endif +} + +/* + * Set 'nw' as the main render area widget + */ +void UI::set_render_layout(Widget &nw) +{ + // BUG: replace() is not working as it should. + // In our case, replacing the rendering area leaves the vertical + // scrollbar without events. + // + // We'll use a workaround in a_UIcmd_browser_window_new() instead. + + TopGroup->replace(MainIdx, nw); + delete(Main); + Main = &nw; + //TopGroup->box(DOWN_BOX); + //TopGroup->box(BORDER_FRAME); + TopGroup->resizable(TopGroup->child(MainIdx)); +} + +/* + * Set the window title + */ +void UI::set_page_title(const char *label) +{ + char title[128]; + + snprintf(title, 128, "Dillo: %s", label); + this->copy_label(title); + this->redraw_label(); +} + +/* + * Set button sensitivity (Back/Forw/Stop) + */ +void UI::button_set_sens(UIButton btn, int sens) +{ + switch (btn) { + case UI_BACK: + (sens) ? Back->activate() : Back->deactivate(); + break; + case UI_FORW: + (sens) ? Forw->activate() : Forw->deactivate(); + break; + case UI_STOP: + (sens) ? Stop->activate() : Stop->deactivate(); + break; + default: + break; + } +} + +/* + * Paste a middle-click-selection into "Clear" button as URL + */ +void UI::paste_url() +{ + paste(*Clear, false); +} + diff --git a/src/ui.hh b/src/ui.hh new file mode 100644 index 00000000..7e3527ab --- /dev/null +++ b/src/ui.hh @@ -0,0 +1,109 @@ +#ifndef __UI_HH__ +#define __UI_HH__ + +// UI for dillo -------------------------------------------------------------- + +#include <fltk/Window.h> +#include <fltk/Widget.h> +#include <fltk/Button.h> +#include <fltk/Input.h> +#include <fltk/PackedGroup.h> +#include <fltk/Output.h> +#include <fltk/xpmImage.h> + +using namespace fltk; + +// Panel sizes +enum { P_tiny = 0, P_small, P_medium, P_large }; + +typedef enum { + UI_BACK = 0, + UI_FORW, + UI_HOME, + UI_RELOAD, + UI_SAVE, + UI_STOP, + UI_BOOK, + UI_CLEAR, + UI_SEARCH +} UIButton; + +// +// UI class definition ------------------------------------------------------- +// +class UI : public fltk::Window { + Group *TopGroup; + Button *Back, *Forw, *Home, *Reload, *Save, *Stop, *Bookmarks, + *Clear, *Search, *FullScreen, *BugMeter; + Input *Location; + Widget *PProg, *IProg; + Image *ImgLeftIns, *ImgLeftSens, *ImgRightIns, *ImgRightSens, + *ImgStopIns, *ImgStopSens, *ImgFullScreenOn, *ImgFullScreenOff, + *ImgMeterOK, *ImgMeterBug; + Group *Panel, *StatusPanel; + Widget *Main; + Output *Status; + + int MainIdx; + // Panel customization variables + int PanelSize, CuteColor, Small_Icons; + int xpos, bw, bh, fh, lh, lbl, pr_w; + + // TODO: Hack for fullscreen mode + int Panel_h, Status_h; + + PackedGroup *make_toolbar(int tw, int th); + PackedGroup *make_location(); + PackedGroup *make_progress_bars(int wide, int thin_up); + Group *make_menu(int tiny); + Group *make_panel(int ww); + + +public: + + UI(int w, int h, const char* label = 0); + ~UI() {} // TODO: implement destructor + + // To manage what events to catch and which to let pass + int handle(int event); + + const char *get_location(); + void set_location(const char *str); + void focus_location(); + void set_status(const char *str); + void set_page_prog(size_t nbytes, int cmd); + void set_img_prog(int n_img, int t_img, int cmd); + void set_bug_prog(int n_bug); + void set_render_layout(Widget &nw); + void set_page_title(const char *label); + void customize(int flags); + void button_set_sens(UIButton btn, int sens); + void paste_url(); + + // Workaround functions for a non-working replace() in FLTK2 + void set_render_layout_begin() { + TopGroup->remove(MainIdx); + TopGroup->remove(TopGroup->find(StatusPanel)); + delete(Main); + Main = NULL; + TopGroup->begin(); + } + void set_render_layout_end() { + TopGroup->resizable(TopGroup->child(MainIdx)); + Main = TopGroup->child(MainIdx); + TopGroup->add(*StatusPanel); + TopGroup->end(); + } + int panel_h() { return Panel->h(); }; + int status_h() { return Status->h(); }; + Widget *fullscreen_button() { return FullScreen; } + void fullscreen_toggle() { FullScreen->do_callback(); } + + // Hooks to method callbacks + void panel_cb_i(); + void color_change_cb_i(); + void toggle_cb_i(); + void fullscreen_cb_i(); +}; + +#endif // __UI_HH__ diff --git a/src/uicmd.cc b/src/uicmd.cc new file mode 100644 index 00000000..d5f44e6b --- /dev/null +++ b/src/uicmd.cc @@ -0,0 +1,644 @@ +/* + * File: uicmd.cc + * + * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +// Functions/Methods for commands triggered from the UI + + +#include <stdio.h> +#include <stdarg.h> +#include <fltk/Widget.h> + +#include "dir.h" +#include "ui.hh" +#include "uicmd.hh" +#include "timeout.hh" +#include "menu.hh" +#include "dialog.hh" +#include "bookmark.h" +#include "history.h" +#include "msg.h" +#include "prefs.h" + +#include "dw/fltkviewport.hh" + +#include "nav.h" + +// Platform idependent part +using namespace dw::core; +// FLTK related +using namespace dw::fltk; + +typedef struct { + UI *ui; + BrowserWindow *bw; +} Uibw; + +/* + * Local data + */ +// A matching table for all open ui/bw pairs +// BUG: must be dynamic. +static Uibw uibws[32]; +static int uibws_num = 0, uibws_max = 32; + +static char *save_dir = NULL; + +using namespace fltk; + + +/* + * Create a new UI and its associated BrowserWindow data structure. + */ +BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh) +{ + if (ww <= 0 || wh <= 0) { + // TODO: set default geometry from dillorc. + ww = 780; + wh = 580; + } + + // Create and set the UI + UI *new_ui = new UI(ww, wh, "Dillo: UI"); + new_ui->set_status("http://www.dillo.org/"); + //new_ui->set_location("http://dillo.org/"); + //new_ui->customize(12); + + // Now create the Dw render layout and viewport + FltkPlatform *platform = new FltkPlatform (); + Layout *layout = new Layout (platform); + + // BUG: This is a workaround for FLTK's non-working replace(). + new_ui->set_render_layout_begin(); + int p_h = new_ui->panel_h(); + int s_h = new_ui->status_h(); + FltkViewport *viewport = new FltkViewport (0, p_h, ww, wh-p_h-s_h); + layout->attachView (viewport); + //viewport->addGadget(new_ui->fullscreen_button()); + new_ui->set_render_layout_end(); + // This was the original code. + // Set the render_layout widget into the UI + // new_ui->set_render_layout(*viewport); + + // Now, create a new browser window structure + BrowserWindow *new_bw = a_Bw_new(ww, wh, 0); + + // Set new_bw as callback data for UI + new_ui->user_data(new_bw); + // Reference the UI from the bw + new_bw->ui = (void *)new_ui; + // Copy the layout pointer into the bw data + new_bw->render_layout = (void*)layout; + + // insert the new ui/bw pair in the table + if (uibws_num < uibws_max) { + uibws[uibws_num].ui = new_ui; + uibws[uibws_num].bw = new_bw; + uibws_num++; + } + + new_ui->show(); + + return new_bw; +} + +/* + * Close one browser window + */ +void a_UIcmd_close_bw(void *vbw) +{ + BrowserWindow *bw = (BrowserWindow *)vbw; + UI *ui = (UI*)bw->ui; + Layout *layout = (Layout*)bw->render_layout; + + MSG("a_UIcmd_close_bw\n"); + ui->destroy(); + delete(layout); + a_Bw_free(bw); +} + +/* + * Close all the browser windows + */ +void a_UIcmd_close_all_bw() +{ + BrowserWindow *bw; + + while ((bw = a_Bw_get())) + a_UIcmd_close_bw((void*)bw); +} + +/* + * Open a new URL in the given browser window. + * + * our custom "file:" URIs are normalized here too. + */ +void a_UIcmd_open_urlstr(void *vbw, const char *urlstr) +{ + char *new_urlstr; + DilloUrl *url; + int ch; + BrowserWindow *bw = (BrowserWindow*)vbw; + + if (urlstr && *urlstr) { + /* Filter URL string */ + new_urlstr = a_Url_string_strip_delimiters(urlstr); + + if (!dStrncasecmp(new_urlstr, "file:", 5)) { + /* file URI */ + ch = new_urlstr[5]; + if (!ch || ch == '.') { + url = a_Url_new(a_Dir_get_owd(), "file:", 0, 0, 0); + } else if (ch == '~') { + url = a_Url_new(dGethomedir(), "file:", 0, 0, 0); + } else { + url = a_Url_new(new_urlstr, "file:", 0, 0, 0); + } + + } else { + /* common case */ + url = a_Url_new(new_urlstr, NULL, 0, 0, 0); + } + dFree(new_urlstr); + + if (url) { + a_Nav_push(bw, url); + a_Url_free(url); + } + } + + /* let the rendered area have focus */ + //gtk_widget_grab_focus(GTK_BIN(bw->render_main_scroll)->child); +} + +/* + * Open a new URL in the given browser window + */ +void a_UIcmd_open_url_nw(BrowserWindow *bw, DilloUrl *url) +{ + a_Nav_push_nw(bw, url); +} + +/* + * Send the browser back to previous page + */ +void a_UIcmd_back(void *vbw) +{ + a_Nav_back((BrowserWindow*)vbw); +} + +/* + * Popup the navigation menu of the Back button + */ +void a_UIcmd_back_popup(void *vbw) +{ + a_Menu_history_popup((BrowserWindow*)vbw, -1); +} + +/* + * Send the browser to next page in the history list + */ +void a_UIcmd_forw(void *vbw) +{ + a_Nav_forw((BrowserWindow*)vbw); +} + +/* + * Popup the navigation menu of the Forward button + */ +void a_UIcmd_forw_popup(void *vbw) +{ + a_Menu_history_popup((BrowserWindow*)vbw, 1); +} + +/* + * Send the browser to home URL + */ +void a_UIcmd_home(void *vbw) +{ + a_Nav_home((BrowserWindow*)vbw); +} + +/* + * Reload current URL + */ +void a_UIcmd_reload(void *vbw) +{ + a_Nav_reload((BrowserWindow*)vbw); +} + +/* + * Return a suitable filename for a given URL. + */ +char *UIcmd_make_save_filename(const char *urlstr) +{ + size_t MaxLen = 64; + char *FileName, *name; + const char *dir = a_UIcmd_get_save_dir(); + + if ((name = strrchr(urlstr, '/'))) { + if (strlen(++name) > MaxLen) { + name = name + strlen(name) - MaxLen; + } + FileName = dStrconcat(dir ? dir : "", name, NULL); + } else { + FileName = dStrconcat(dir ? dir : "", urlstr, NULL); + } + return FileName; +} + +/* + * Get the default directory for saving files. + */ +const char *a_UIcmd_get_save_dir() +{ + return save_dir; +} + +/* + * Set the default directory for saving files. + */ +void a_UIcmd_set_save_dir(const char *dir) +{ + char *p; + + if (dir && (p = strrchr(dir, '/'))) { + dFree(save_dir); + // assert a trailing '/' + save_dir = dStrconcat(dir, (p[1] != 0) ? "/" : "", NULL); + } +} + +/* + * Save current URL + */ +void a_UIcmd_save(void *vbw) +{ + const char *name; + char *SuggestedName, *urlstr; + DilloUrl *url; + + // BUG: this should be set by preferences. + a_UIcmd_set_save_dir("/tmp/k/"); + + urlstr = a_UIcmd_get_location_text((BrowserWindow*)vbw); + url = a_Url_new(urlstr, NULL, 0, 0, 0); + SuggestedName = UIcmd_make_save_filename(urlstr); + name = a_Dialog_save_file("Save Page as File", NULL, SuggestedName); + MSG("a_UIcmd_save: %s\n", name); + dFree(SuggestedName); + dFree(urlstr); + + if (name) { + a_Nav_save_url((BrowserWindow*)vbw, url, name); + } + + a_Url_free(url); +} + +/* + * Stop network activity on this bw. + * The stop button was pressed: stop page (and images) downloads. + */ +void a_UIcmd_stop(void *vbw) +{ + BrowserWindow *bw = (BrowserWindow *)vbw; + + MSG("a_UIcmd_stop()\n"); + a_Bw_stop_clients(bw, BW_Root + BW_Img + Bw_Force); + a_UIcmd_set_buttons_sens(bw); +} + +/* + * Open URL with dialog chooser + */ +void a_UIcmd_open_file(void *vbw) +{ + char *name; + DilloUrl *url; + + name = a_Dialog_open_file("Open File", NULL, ""); + + if (name) { + url = a_Url_new(name, "file:", 0, 0, 0); + a_Nav_push((BrowserWindow*)vbw, url); + a_Url_free(url); + dFree(name); + } +} + +/* + * Get an URL from a dialog and open it + */ +void a_UIcmd_open_url_dialog(void *vbw) +{ + const char *urlstr; + + if ((urlstr = a_Dialog_input("Please enter a URL:"))) { + a_UIcmd_open_urlstr(vbw, urlstr); + } +} + +/* + * Returns a newly allocated string holding a search url generated from + * a string of keywords (separarated by blanks) and prefs.search_url. + * The search string is urlencoded. + */ +char *UIcmd_make_search_str(const char *str) +{ + char *keys = a_Url_encode_hex_str(str), *c = prefs.search_url; + Dstr *ds = dStr_sized_new(128); + char *search_url; + + for (; *c; c++) { + if (*c == '%') + switch(*++c) { + case 's': + dStr_append(ds, keys); break;; + case '%': + dStr_append_c(ds, '%'); break;; + case 0: + MSG_WARN("search_url ends with '%%'\n"); c--; break;; + default: + MSG_WARN("illegal specifier '%%%c' in search_url\n", *c); + } + else + dStr_append_c(ds, *c); + } + dFree(keys); + + search_url = ds->str; + dStr_free(ds, 0); + return search_url; +} + +/* + * Get a query from a dialog and open it + */ +void a_UIcmd_search_dialog(void *vbw) +{ + const char *query, *url_str; + + if ((query = a_Dialog_input("Search the Web:"))) { + url_str = UIcmd_make_search_str(query); + a_UIcmd_open_urlstr(vbw, url_str); + } +} + +/* + * Save link URL + */ +void a_UIcmd_save_link(BrowserWindow *bw, const DilloUrl *url) +{ + const char *name; + char *SuggestedName; + + // BUG: this should be set by preferences. + a_UIcmd_set_save_dir("/tmp/k/"); + + SuggestedName = UIcmd_make_save_filename(URL_STR(url)); + name = a_Dialog_save_file("Save Link as File", NULL, SuggestedName); + MSG("a_UIcmd_save_link: %s\n", name); + dFree(SuggestedName); + + if (name) { + a_Nav_save_url(bw, url, name); + } +} + +/* + * Request the bookmarks page + */ +void a_UIcmd_book(void *vbw) +{ + DilloUrl *url = a_Url_new("dpi:/bm/", NULL, 0, 0, 0); + a_Nav_push((BrowserWindow*)vbw, url); + a_Url_free(url); +} + +/* + * Add a bookmark for a certain URL + */ +void a_UIcmd_add_bookmark(BrowserWindow *bw, DilloUrl *url) +{ + a_Bookmarks_add(bw, url); +} + + +/* + * Popup the page menu + */ +void a_UIcmd_page_popup(void *vbw, DilloUrl *url, const char *bugs_txt) +{ + a_Menu_page_popup((BrowserWindow*)vbw, url, bugs_txt); +} + +/* + * Popup the link menu + */ +void a_UIcmd_link_popup(void *vbw, DilloUrl *url) +{ + a_Menu_link_popup((BrowserWindow*)vbw, url); +} + +/* + * Show a text window with the URL's source + */ +void a_UIcmd_view_page_source(DilloUrl *url) +{ + char *buf; + int buf_size; + + if (a_Nav_get_buf(url, &buf, &buf_size)) { + a_Dialog_text_window(buf, "View Page source"); + } +} + +/* + * Show a text window with the URL's source + */ +void a_UIcmd_view_page_bugs(void *vbw) +{ + BrowserWindow *bw = (BrowserWindow*)vbw; + + if (bw->num_page_bugs > 0) { + a_Dialog_text_window(bw->page_bugs->str, "Detected HTML errors"); + } else { + a_Dialog_msg("Zero detected HTML errors!"); + } +} + +/* + * Popup the bug meter menu + */ +void a_UIcmd_bugmeter_popup(void *vbw) +{ + BrowserWindow *bw = (BrowserWindow*)vbw; + + a_Menu_bugmeter_popup(bw, a_History_get_url(NAV_TOP(bw))); +} + +/* + * Make a list of URL indexes for the history popup + * based on direction (-1 = back, 1 = forward) + */ +int *a_UIcmd_get_history(BrowserWindow *bw, int direction) +{ + int i, j, n; + int *hlist; + + // Count the number of URLs + i = a_Nav_stack_ptr(bw) + direction; + for (n = 0 ; i >= 0 && i < a_Nav_stack_size(bw); i+=direction) + ++n; + hlist = dNew(int, n + 1); + + // Fill the list + i = a_Nav_stack_ptr(bw) + direction; + for (j = 0 ; i >= 0 && i < a_Nav_stack_size(bw); i+=direction, j += 1) { + hlist[j] = NAV_IDX(bw,i); + } + hlist[j] = -1; + + return hlist; +} + +/* + * Jump to a certain URL in the navigation stack. + */ +void a_UIcmd_nav_jump(BrowserWindow *bw, int offset, int new_bw) +{ + a_Nav_jump(bw, offset, new_bw); +} + +// UI binding functions ------------------------------------------------------- + +#define BW2UI(bw) ((UI*)(bw->ui)) + +/* + * Return browser window width and height + */ +void a_UIcmd_get_wh(BrowserWindow *bw, int *w, int *h) +{ + *w = BW2UI(bw)->w(); + *h = BW2UI(bw)->h(); + _MSG("a_UIcmd_wh: w=%d, h=%d\n", *w, *h); +} + +/* + * Get location's text + */ +char *a_UIcmd_get_location_text(BrowserWindow *bw) +{ + return dStrdup(BW2UI(bw)->get_location()); +} + +/* + * Set location's text + */ +void a_UIcmd_set_location_text(void *vbw, const char *text) +{ + BrowserWindow *bw = (BrowserWindow*)vbw; + BW2UI(bw)->set_location(text); +} + +/* + * Set the page progress bar + * cmd: 0 Deactivate, 1 Update, 2 Clear + */ +void a_UIcmd_set_page_prog(BrowserWindow *bw, size_t nbytes, int cmd) +{ + BW2UI(bw)->set_page_prog(nbytes, cmd); +} + +/* + * Set the images progress bar + * cmd: 0 Deactivate, 1 Update, 2 Clear + */ +void a_UIcmd_set_img_prog(BrowserWindow *bw, int n_img, int t_img, int cmd) +{ + BW2UI(bw)->set_img_prog(n_img, t_img, cmd); +} + +/* + * Set the bug meter progress label + */ +void a_UIcmd_set_bug_prog(BrowserWindow *bw, int n_bug) +{ + BW2UI(bw)->set_bug_prog(n_bug); +} + +/* + * Set the page title. + * now it goes to the window titlebar (maybe to TAB label in the future). + */ +void a_UIcmd_set_page_title(BrowserWindow *bw, const char *label) +{ + BW2UI(bw)->set_page_title(label); +} + +/* + * Set a printf-like status string on the bottom of the dillo window. + * Beware: The safe way to set an arbitrary string is + * a_UIcmd_set_msg(bw, "%s", str) + */ +void a_UIcmd_set_msg(BrowserWindow *bw, const char *format, ...) +{ + va_list argp; + Dstr *ds = dStr_sized_new(128); + va_start(argp, format); + dStr_vsprintf(ds, format, argp); + va_end(argp); + BW2UI(bw)->set_status(ds->str); + dStr_free(ds, 1); +} + +/* + * Set the sensitivity of back/forw/stop buttons. + */ +static void a_UIcmd_set_buttons_sens_cb(void *vbw) +{ + int sens; + BrowserWindow *bw = (BrowserWindow*)vbw; + + // Stop + sens = (dList_length(bw->ImageClients) || dList_length(bw->RootClients)); + BW2UI(bw)->button_set_sens(UI_STOP, sens); + // Back + sens = (a_Nav_stack_ptr(bw) > 0); + BW2UI(bw)->button_set_sens(UI_BACK, sens); + // Forward + sens = (a_Nav_stack_ptr(bw) < a_Nav_stack_size(bw) - 1 && + !bw->nav_expecting); + BW2UI(bw)->button_set_sens(UI_FORW, sens); + + bw->sens_idle_up = 0; +} + + +/* + * Set the timeout function for button sensitivity + */ +void a_UIcmd_set_buttons_sens(BrowserWindow *bw) +{ + if (bw->sens_idle_up == 0) { + a_Timeout_add(0.0, a_UIcmd_set_buttons_sens_cb, bw); + bw->sens_idle_up = 1; + } +} + +/* + * Toggle control panel (aka. fullscreen) + */ +void a_UIcmd_fullscreen_toggle(BrowserWindow *bw) +{ + BW2UI(bw)->fullscreen_toggle(); +} + diff --git a/src/uicmd.hh b/src/uicmd.hh new file mode 100644 index 00000000..8ebbb086 --- /dev/null +++ b/src/uicmd.hh @@ -0,0 +1,62 @@ +#ifndef __UICMD_HH__ +#define __UICMD_HH__ + +#include "bw.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +BrowserWindow *a_UIcmd_browser_window_new(int ww, int wh); +void a_UIcmd_open_urlstr(void *vbw, const char *urlstr); +void a_UIcmd_open_url_nw(BrowserWindow *bw, DilloUrl *url); +void a_UIcmd_back(void *vbw); +void a_UIcmd_back_popup(void *vbw); +void a_UIcmd_forw(void *vbw); +void a_UIcmd_forw_popup(void *vbw); +void a_UIcmd_home(void *vbw); +void a_UIcmd_reload(void *vbw); +void a_UIcmd_save(void *vbw); +void a_UIcmd_stop(void *vbw); +void a_UIcmd_save_link(BrowserWindow *bw, const DilloUrl *url); +void a_UIcmd_open_file(void *vbw); +void a_UIcmd_open_url_dialog(void *vbw); +void a_UIcmd_search_dialog(void *vbw); +void a_UIcmd_book(void *vbw); +void a_UIcmd_add_bookmark(BrowserWindow *bw, DilloUrl *url); +void a_UIcmd_fullscreen_toggle(BrowserWindow *bw); +void a_UIcmd_page_popup(void *vbw, DilloUrl *url, const char *bugs_txt); +void a_UIcmd_link_popup(void *vbw, DilloUrl *url); +void a_UIcmd_view_page_source(DilloUrl *url); +void a_UIcmd_view_page_bugs(void *vbw); +void a_UIcmd_bugmeter_popup(void *vbw); +int *a_UIcmd_get_history(BrowserWindow *bw, int direction); +void a_UIcmd_nav_jump(BrowserWindow *bw, int offset, int new_bw); + +void a_UIcmd_close_bw(void *vbw); +void a_UIcmd_close_all_bw(); + +const char *a_UIcmd_get_save_dir(); +void a_UIcmd_set_save_dir(const char *dir); + + +// UI binding functions ------------------------------------------------------- + +void a_UIcmd_get_wh(BrowserWindow *bw, int *w, int *h); +char *a_UIcmd_get_location_text(BrowserWindow *bw); +void a_UIcmd_set_location_text(void *vbw, const char *text); +void a_UIcmd_set_page_prog(BrowserWindow *bw, size_t nbytes, int cmd); +void a_UIcmd_set_img_prog(BrowserWindow *bw, int n_img, int t_img, int cmd); +void a_UIcmd_set_bug_prog(BrowserWindow *bw, int n_bug); +void a_UIcmd_set_page_title(BrowserWindow *bw, const char *label); +void a_UIcmd_set_msg(BrowserWindow *bw, const char *format, ...); +void a_UIcmd_set_buttons_sens(BrowserWindow *bw); +void a_UIcmd_fullscreen_toggle(BrowserWindow *bw); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // __UICMD_HH__ diff --git a/src/url.c b/src/url.c new file mode 100644 index 00000000..6e1805bf --- /dev/null +++ b/src/url.c @@ -0,0 +1,632 @@ +/* + * File: url.c + * + * Copyright (C) 2001 Jorge Arellano Cid <jcid@dillo.org> + * 2001 Livio Baldini Soares <livio@linux.ime.usp.br> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +/* + * Parse and normalize all URL's inside Dillo. + * - <scheme> <authority> <path> <query> and <fragment> point to 'buffer'. + * - 'url_string' is built upon demand (transparent to the caller). + * - 'hostname' and 'port' are also being handled on demand. + */ + +/* + * Regular Expression as given in RFC2396 for URL parsing. + * + * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? + * 12 3 4 5 6 7 8 9 + * + * scheme = $2 + * authority = $4 + * path = $5 + * query = $7 + * fragment = $9 + * + * + * RFC-2396 BNF: + * + * absoluteURI = scheme ":" (hier_part | opaque_part) + * hier_part = (net_path | abs_path) ["?" query] + * net_path = "//" authority[abs_path] + * abs_path = "/" path_segments + * + * Notes: + * - "undefined" means "preceeding separator does not appear". + * - path is never "undefined" though it may be "empty". + */ + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include "url.h" + +//#define DEBUG_LEVEL 2 +#include "debug.h" + + +/* + * Return the url as a string. + * (initializing 'url_string' camp if necessary) + */ +char *a_Url_str(const DilloUrl *u) +{ + /* Internal url handling IS transparent to the caller */ + DilloUrl *url = (DilloUrl *) u; + + dReturn_val_if_fail (url != NULL, NULL); + + if (!url->url_string) { + url->url_string = dStr_sized_new(60); + dStr_sprintf( + url->url_string, "%s%s%s%s%s%s%s%s%s%s", + url->scheme ? url->scheme : "", + url->scheme ? ":" : "", + url->authority ? "//" : "", + url->authority ? url->authority : "", + // (url->path && url->path[0] != '/' && url->authority) ? "/" : "", + (url->authority && (!url->path || *url->path != '/')) ? "/" : "", + url->path ? url->path : "", + url->query ? "?" : "", + url->query ? url->query : "", + url->fragment ? "#" : "", + url->fragment ? url->fragment : ""); + } + + return url->url_string->str; +} + +/* + * Return the hostname as a string. + * (initializing 'hostname' and 'port' camps if necessary) + * Note: a similar approach can be taken for user:password auth. + */ +const char *a_Url_hostname(const DilloUrl *u) +{ + char *p; + /* Internal url handling IS transparent to the caller */ + DilloUrl *url = (DilloUrl *) u; + + if (!url->hostname && url->authority) { + if ((p = strchr(url->authority, ':'))) { + url->port = strtol(p + 1, NULL, 10); + url->hostname = dStrndup(url->authority,(uint_t)(p - url->authority)); + } else + url->hostname = url->authority; + } + + return url->hostname; +} + +/* + * Create a DilloUrl object and initialize it. + * (buffer, scheme, authority, path, query and fragment). + */ +static DilloUrl *Url_object_new(const char *uri_str) +{ + DilloUrl *url; + char *s, *p; + + dReturn_val_if_fail (uri_str != NULL, NULL); + + url = dNew0(DilloUrl, 1); + + /* remove leading & trailing space from buffer */ + url->buffer = dStrstrip(dStrdup(uri_str)); + + s = (char *) url->buffer; + p = strpbrk(s, ":/?#"); + if (p && p[0] == ':' && p > s) { /* scheme */ + *p = 0; + url->scheme = s; + s = ++p; + } + /* p = strpbrk(s, "/"); */ + if (p == s && p[0] == '/' && p[1] == '/') { /* authority */ + s = p + 2; + p = strpbrk(s, "/?#"); + if (p) { + memmove(s - 2, s, (size_t)MAX(p - s, 1)); + url->authority = s - 2; + p[-2] = 0; + s = p; + } else if (*s) { + url->authority = s; + return url; + } + } + + p = strpbrk(s, "?#"); + if (p) { /* path */ + url->path = (p > s) ? s : NULL; + s = p; + } else if (*s) { + url->path = s; + return url; + } + + p = strpbrk(s, "?#"); + if (p && p[0] == '?') { /* query */ + *p = 0; + s = p + 1; + url->query = s; + p = strpbrk(s, "#"); + } + if (p && p[0] == '#') { /* fragment */ + *p = 0; + s = p + 1; + url->fragment = s; + } + + return url; +} + +/* + * Free a DilloUrl + */ +void a_Url_free(DilloUrl *url) +{ + if (url) { + if (url->url_string) + dStr_free(url->url_string, TRUE); + if (url->hostname != url->authority) + dFree((char *)url->hostname); + dFree((char *)url->buffer); + dFree((char *)url->data); + dFree((char *)url->alt); + dFree(url); + } +} + +/* + * Resolve the URL as RFC2396 suggests. + */ +static Dstr *Url_resolve_relative(const char *RelStr, + DilloUrl *BaseUrlPar, + const char *BaseStr) +{ + char *p, *s, *e; + int i; + Dstr *SolvedUrl, *Path; + DilloUrl *RelUrl, *BaseUrl = NULL; + + /* parse relative URL */ + RelUrl = Url_object_new(RelStr); + + if (BaseUrlPar) { + BaseUrl = BaseUrlPar; + } else if (RelUrl->scheme == NULL) { + /* only required when there's no <scheme> in RelStr */ + BaseUrl = Url_object_new(BaseStr); + } + + SolvedUrl = dStr_sized_new(64); + Path = dStr_sized_new(64); + + /* path empty && scheme, authority and query undefined */ + if (!RelUrl->path && !RelUrl->scheme && + !RelUrl->authority && !RelUrl->query) { + dStr_append(SolvedUrl, BaseStr); + + if (RelUrl->fragment) { /* fragment */ + if (BaseUrl->fragment) + dStr_truncate(SolvedUrl, BaseUrl->fragment-BaseUrl->buffer-1); + dStr_append_c(SolvedUrl, '#'); + dStr_append(SolvedUrl, RelUrl->fragment); + } + goto done; + + } else if (RelUrl->scheme) { /* scheme */ + dStr_append(SolvedUrl, RelStr); + goto done; + + } else if (RelUrl->authority) { /* authority */ + // Set the Path buffer and goto "STEP 7"; + if (RelUrl->path) + dStr_append(Path, RelUrl->path); + + } else if (RelUrl->path && RelUrl->path[0] == '/') { /* path */ + dStr_append(Path, RelUrl->path); + + } else { + // solve relative path + if (BaseUrl->path) { + dStr_append(Path, BaseUrl->path); + for (i = Path->len; --i >= 0 && Path->str[i] != '/'; ); + if (Path->str[i] == '/') + dStr_truncate(Path, ++i); + } + if (RelUrl->path) + dStr_append(Path, RelUrl->path); + + // erase "./" + while ((p=strstr(Path->str, "./")) && + (p == Path->str || p[-1] == '/')) + dStr_erase(Path, p - Path->str, 2); + // erase last "." + if (Path->len && Path->str[Path->len - 1] == '.' && + (Path->len == 1 || Path->str[Path->len - 2] == '/')) + dStr_truncate(Path, Path->len - 1); + + // erase "<segment>/../" and "<segment>/.." + s = p = Path->str; + while ( (p = strstr(p, "/..")) != NULL ) { + if ((p[3] == '/' || !p[3]) && (p - s)) { // "/../" | "/.." + + for (e = p + 3 ; p[-1] != '/' && p > s; --p); + if (p[0] != '.' || p[1] != '.' || p[2] != '/') { + dStr_erase(Path, p - Path->str, e - p + (*e != 0)); + p -= (p > Path->str); + } else + p = e; + } else + p += 3; + } + } + + /* STEP 7 + */ + + /* scheme */ + if (BaseUrl->scheme) { + dStr_append(SolvedUrl, BaseUrl->scheme); + dStr_append_c(SolvedUrl, ':'); + } + + /* authority */ + if (RelUrl->authority) { + dStr_append(SolvedUrl, "//"); + dStr_append(SolvedUrl, RelUrl->authority); + } else if (BaseUrl->authority) { + dStr_append(SolvedUrl, "//"); + dStr_append(SolvedUrl, BaseUrl->authority); + } + + /* path */ + if ((RelUrl->authority || BaseUrl->authority) && + ((Path->len == 0 && (RelUrl->query || RelUrl->fragment)) || + (Path->len && Path->str[0] != '/'))) + dStr_append_c(SolvedUrl, '/'); /* hack? */ + dStr_append(SolvedUrl, Path->str); + + /* query */ + if (RelUrl->query) { + dStr_append_c(SolvedUrl, '?'); + dStr_append(SolvedUrl, RelUrl->query); + } + + /* fragment */ + if (RelUrl->fragment) { + dStr_append_c(SolvedUrl, '#'); + dStr_append(SolvedUrl, RelUrl->fragment); + } + +done: + dStr_free(Path, TRUE); + a_Url_free(RelUrl); + if (BaseUrl != BaseUrlPar) + a_Url_free(BaseUrl); + return SolvedUrl; +} + +/* + * Transform (and resolve) an URL string into the respective DilloURL. + * If URL = "http://dillo.sf.net:8080/index.html?long#part2" + * then the resulting DilloURL should be: + * DilloURL = { + * url_string = "http://dillo.sf.net:8080/index.html?long#part2" + * scheme = "http" + * authority = "dillo.sf.net:8080: + * path = "/index.html" + * query = "long" + * fragment = "part2" + * hostname = "dillo.sf.net" + * port = 8080 + * flags = 0 + * data = NULL + * alt = NULL + * ismap_url_len = 0 + * scrolling_position = 0 + * } + * + * Return NULL if URL is badly formed. + */ +DilloUrl* a_Url_new(const char *url_str, const char *base_url, + int flags, int32_t posx, int32_t posy) +{ + DilloUrl *url; + char *urlstr = (char *)url_str; /* auxiliar variable, don't free */ + char *p, *str1 = NULL, *str2 = NULL; + Dstr *SolvedUrl; + int i, n_ic, n_ic_spc; + + dReturn_val_if_fail (url_str != NULL, NULL); + + /* Count illegal characters (0x00-0x1F, 0x7F and space) */ + n_ic = n_ic_spc = 0; + for (p = (char*)url_str; *p; p++) { + n_ic_spc += (*p == ' ') ? 1 : 0; + n_ic += (*p != ' ' && *p > 0x1F && *p != 0x7F) ? 0 : 1; + } + if (n_ic) { + /* Strip illegal characters (they could also be encoded). + * There's no standard for illegal chars; we chose to strip. */ + p = str1 = dNew(char, strlen(url_str)); /* Yes, enough memory! */ + for (i = 0; url_str[i]; ++i) + if (url_str[i] > 0x1F && url_str[i] != 0x7F && url_str[i] != ' ') + *p++ = url_str[i]; + *p = 0; + urlstr = str1; + } + + /* let's use a heuristic to set http: as default */ + if (!base_url) { + base_url = "http:"; + if (urlstr[0] != '/') { + p = strpbrk(urlstr, "/#?:"); + if (!p || *p != ':') + urlstr = str2 = dStrconcat("//", urlstr, NULL); + } else if (urlstr[1] != '/') + urlstr = str2 = dStrconcat("/", urlstr, NULL); + } + + /* Resolve the URL */ + SolvedUrl = Url_resolve_relative(urlstr, NULL, base_url); + DEBUG_MSG(2, "SolvedUrl = %s\n", SolvedUrl->str); + + /* Fill url data */ + url = Url_object_new(SolvedUrl->str); + url->url_string = SolvedUrl; + url->flags = flags; + url->scrolling_position_x = posx; + url->scrolling_position_y = posy; + url->illegal_chars = n_ic; + url->illegal_chars_spc = n_ic_spc; + + dFree(str1); + dFree(str2); + return url; +} + + +/* + * Duplicate a Url structure + */ +DilloUrl* a_Url_dup(const DilloUrl *ori) +{ + DilloUrl *url; + + url = Url_object_new(URL_STR_(ori)); + dReturn_val_if_fail (url != NULL, NULL); + + url->url_string = dStr_new(URL_STR(ori)); + url->port = ori->port; + url->flags = ori->flags; + url->data = dStrdup(ori->data); + url->alt = dStrdup(ori->alt); + url->ismap_url_len = ori->ismap_url_len; + url->scrolling_position_x = ori->scrolling_position_x; + url->scrolling_position_y = ori->scrolling_position_y; + url->illegal_chars = ori->illegal_chars; + url->illegal_chars_spc = ori->illegal_chars_spc; + + return url; +} + +/* + * Compare two Url's to check if they're the same, or which one is bigger. + * + * The fields which are compared here are: + * <scheme>, <authority>, <path>, <query> and <data> + * Other fields are left for the caller to check + * + * Return value: 0 if equal, > 0 if A > B, < 0 if A < B. + * + * Note: this function defines a sorting order different from strcmp! + */ +int a_Url_cmp(const DilloUrl *A, const DilloUrl *B) +{ + int st; + + dReturn_val_if_fail(A && B, 1); + + if (A == B || + ((st = URL_STRCAMP_I_CMP(A->authority, B->authority)) == 0 && + (st = strcmp(A->path ? A->path + (*A->path == '/') : "", + B->path ? B->path + (*B->path == '/') : "")) == 0 && + //(st = URL_STRCAMP_CMP(A->path, B->path)) == 0 && + (st = URL_STRCAMP_CMP(A->query, B->query)) == 0 && + (st = URL_STRCAMP_CMP(A->data, B->data)) == 0 && + (st = URL_STRCAMP_I_CMP(A->scheme, B->scheme) == 0))) + return 0; + return st; +} + +/* + * Set DilloUrl flags + */ +void a_Url_set_flags(DilloUrl *u, int flags) +{ + if (u) + u->flags = flags; +} + +/* + * Set DilloUrl data (like POST info, etc.) + */ +void a_Url_set_data(DilloUrl *u, char *data) +{ + if (u) { + dFree((char *)u->data); + u->data = dStrdup(data); + } +} + +/* + * Set DilloUrl alt (alternate text to the URL. Used by image maps) + */ +void a_Url_set_alt(DilloUrl *u, const char *alt) +{ + if (u) { + dFree((char *)u->alt); + u->alt = dStrdup(alt); + } +} + +/* + * Set DilloUrl scrolling position + */ +void a_Url_set_pos(DilloUrl *u, int32_t posx, int32_t posy) +{ + if (u) { + u->scrolling_position_x = posx; + u->scrolling_position_y = posy; + } +} + +/* + * Set DilloUrl ismap coordinates + * (this is optimized for not hogging the CPU) + */ +void a_Url_set_ismap_coords(DilloUrl *u, char *coord_str) +{ + dReturn_if_fail (u && coord_str); + + if (!u->ismap_url_len) { + /* Save base-url length (without coords) */ + u->ismap_url_len = URL_STR_(u) ? u->url_string->len : 0; + a_Url_set_flags(u, URL_FLAGS(u) | URL_Ismap); + } + if (u->url_string) { + dStr_truncate(u->url_string, u->ismap_url_len); + dStr_append(u->url_string, coord_str); + u->query = u->url_string->str + u->ismap_url_len + 1; + } +} + +/* + * Given an hex octet (e.g., e3, 2F, 20), return the corresponding + * character if the octet is valid, and -1 otherwise + */ +static int Url_decode_hex_octet(const char *s) +{ + int hex_value; + char *tail, hex[3]; + + if (s && (hex[0] = s[0]) && (hex[1] = s[1])) { + hex[2] = 0; + hex_value = strtol(hex, &tail, 16); + if (tail - hex == 2) + return hex_value; + } + return -1; +} + +/* + * Parse possible hexadecimal octets in the URI path. + * Returns a new allocated string. + */ +char *a_Url_decode_hex_str(const char *str) +{ + char *new_str, *dest; + int i, val; + + if (!str) + return NULL; + + /* most cases won't have hex octets */ + if (!strchr(str, '%')) + return dStrdup(str); + + dest = new_str = dNew(char, strlen(str) + 1); + + for (i = 0; str[i]; i++) { + *dest++ = (str[i] == '%' && (val = Url_decode_hex_octet(str+i+1)) >= 0) ? + i+=2, val : str[i]; + } + *dest++ = 0; + + new_str = dRealloc(new_str, sizeof(char) * (dest - new_str)); + return new_str; +} + +/* + * Urlencode 'str' + * -RL :: According to the RFC 1738, only alphanumerics, the special + * characters "$-_.+!*'(),", and reserved characters ";/?:@=&" used + * for their *reserved purposes* may be used unencoded within a URL. + * We'll escape everything but alphanumeric and "-_.*" (as lynx). --Jcid + * + * Note: the content type "application/x-www-form-urlencoded" is used: + * i.e., ' ' -> '+' and '\n' -> CR LF (see HTML 4.01, Sec. 17.13.4) + */ +char *a_Url_encode_hex_str(const char *str) +{ + static const char *verbatim = "-_.*"; + static const char *hex = "0123456789ABCDEF"; + char *newstr, *c; + + if (!str) + return NULL; + + newstr = dNew(char, 6*strlen(str)+1); + + for (c = newstr; *str; str++) + if ((isalnum(*str) && !(*str & 0x80)) || strchr(verbatim, *str)) + /* we really need isalnum for the "C" locale */ + *c++ = *str; + else if (*str == ' ') + *c++ = '+'; + else if (*str == '\n') { + *c++ = '%'; + *c++ = '0'; + *c++ = 'D'; + *c++ = '%'; + *c++ = '0'; + *c++ = 'A'; + } else { + *c++ = '%'; + *c++ = hex[(*str >> 4) & 15]; + *c++ = hex[*str & 15]; + } + *c = 0; + + return newstr; +} + + +/* + * RFC-2396 suggests this stripping when "importing" URLs from other media. + * Strip: "URL:", enclosing < >, and embedded whitespace. + * (We also strip illegal chars: 00-1F and 7F) + */ +char *a_Url_string_strip_delimiters(const char *str) +{ + char *p, *new_str, *text; + + new_str = text = dStrdup(str); + + if (new_str) { + if (strncmp(new_str, "URL:", 4) == 0) + text += 4; + if (*text == '<') + text++; + + for (p = new_str; *text; text++) + if (*text > 0x1F && *text != 0x7F && *text != ' ') + *p++ = *text; + if (p > new_str && p[-1] == '>') + --p; + *p = 0; + } + return new_str; +} diff --git a/src/url.h b/src/url.h new file mode 100644 index 00000000..5f331881 --- /dev/null +++ b/src/url.h @@ -0,0 +1,144 @@ +/* + * File : url.h - Dillo + * + * Copyright (C) 2001 Jorge Arellano Cid <jcid@dillo.org> + * 2001 Livio Baldini Soares <livio@linux.ime.usp.br> + * + * Parse and normalize all URL's inside Dillo. + */ + +#ifndef __URL_H__ +#define __URL_H__ + +#include <string.h> /* for strcmp */ +#include "d_size.h" +#include "../dlib/dlib.h" + + +#define DILLO_URL_HTTP_PORT 80 +#define DILLO_URL_HTTPS_PORT 443 +#define DILLO_URL_FTP_PORT 21 +#define DILLO_URL_MAILTO_PORT 25 +#define DILLO_URL_NEWS_PORT 119 +#define DILLO_URL_TELNET_PORT 23 +#define DILLO_URL_GOPHER_PORT 70 + + +/* + * Values for DilloUrl->flags. + * Specifies which which action to perform with an URL. + */ +#define URL_Get (1 << 0) +#define URL_Post (1 << 1) +#define URL_ISindex (1 << 2) +#define URL_Ismap (1 << 3) +#define URL_RealmAccess (1 << 4) + +#define URL_E2EReload (1 << 5) +#define URL_ReloadImages (1 << 6) +#define URL_ReloadPage (1 << 7) +#define URL_ReloadFromCache (1 << 8) + +#define URL_ReloadIncomplete (1 << 9) +#define URL_SpamSafe (1 << 10) + +/* + * Access methods to fields inside DilloURL. + * (non '_'-ended macros MUST use these for initialization sake) + */ +/* these MAY return NULL: */ +#define URL_SCHEME_(u) u->scheme +#define URL_AUTHORITY_(u) u->authority +#define URL_PATH_(u) u->path +#define URL_QUERY_(u) u->query +#define URL_FRAGMENT_(u) u->fragment +#define URL_HOST_(u) a_Url_hostname(u) +#define URL_DATA_(u) u->data +#define URL_ALT_(u) u->alt +#define URL_STR_(u) a_Url_str(u) +/* these return an integer */ +#define URL_PORT_(u) (URL_HOST(u) ? u->port : u->port) +#define URL_FLAGS_(u) u->flags +#define URL_POSX_(u) u->scrolling_position_x +#define URL_POSY_(u) u->scrolling_position_y +#define URL_ILLEGAL_CHARS_(u) url->illegal_chars +#define URL_ILLEGAL_CHARS_SPC_(u) url->illegal_chars_spc + +/* + * Access methods that always return a string: + * When the "empty" and "undefined" concepts of RFC-2396 are irrelevant to + * the caller, and a string is required, use these methods instead: + */ +#define NPTR2STR(p) ((p) ? (p) : "") +#define URL_SCHEME(u) NPTR2STR(URL_SCHEME_(u)) +#define URL_AUTHORITY(u) NPTR2STR(URL_AUTHORITY_(u)) +#define URL_PATH(u) NPTR2STR(URL_PATH_(u)) +#define URL_QUERY(u) NPTR2STR(URL_QUERY_(u)) +#define URL_FRAGMENT(u) NPTR2STR(URL_FRAGMENT_(u)) +#define URL_HOST(u) NPTR2STR(URL_HOST_(u)) +#define URL_DATA(u) NPTR2STR(URL_DATA_(u)) +#define URL_ALT(u) NPTR2STR(URL_ALT_(u)) +#define URL_STR(u) NPTR2STR(URL_STR_(u)) +#define URL_PORT(u) URL_PORT_(u) +#define URL_FLAGS(u) URL_FLAGS_(u) +#define URL_POSX(u) URL_POSX_(u) +#define URL_POSY(u) URL_POSY_(u) +#define URL_ILLEGAL_CHARS(u) URL_ILLEGAL_CHARS_(u) +#define URL_ILLEGAL_CHARS_SPC(u) URL_ILLEGAL_CHARS_SPC_(u) + + +/* URL-camp compare methods */ +#define URL_STRCAMP_CMP(s1,s2) \ + (s1) && (s2) ? strcmp(s1,s2) : !(s1) && !(s2) ? 0 : (s1) ? 1 : -1 +#define URL_STRCAMP_I_CMP(s1,s2) \ + (s1) && (s2) ? dStrcasecmp(s1,s2) : !(s1) && !(s2) ? 0 : (s1) ? 1 : -1 + + +typedef struct _DilloUrl DilloUrl; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +struct _DilloUrl { + Dstr *url_string; + const char *buffer; + const char *scheme; // + const char *authority; // + const char *path; // These are references only + const char *query; // (no need to free them) + const char *fragment; // + const char *hostname; // + int port; + int flags; + const char *data; /* POST */ + const char *alt; /* "alt" text (used by image maps) */ + int ismap_url_len; /* Used by server side image maps */ + int32_t scrolling_position_x, /* remember position of visited urls */ + scrolling_position_y; + int illegal_chars; /* number of illegal chars */ + int illegal_chars_spc; /* number of illegal space chars */ +}; + + +DilloUrl* a_Url_new(const char *url_str, const char *base_url, + int flags, int32_t posx, int32_t posy); +void a_Url_free(DilloUrl *u); +char *a_Url_str(const DilloUrl *url); +const char *a_Url_hostname(const DilloUrl *u); +DilloUrl* a_Url_dup(const DilloUrl *u); +int a_Url_cmp(const DilloUrl *A, const DilloUrl *B); +void a_Url_set_flags(DilloUrl *u, int flags); +void a_Url_set_data(DilloUrl *u, char *data); +void a_Url_set_alt(DilloUrl *u, const char *alt); +void a_Url_set_pos(DilloUrl *u, int32_t posx, int32_t posy); +void a_Url_set_ismap_coords(DilloUrl *u, char *coord_str); +char *a_Url_decode_hex_str(const char *str); +char *a_Url_encode_hex_str(const char *str); +char *a_Url_string_strip_delimiters(const char *str); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __URL_H__ */ diff --git a/src/web.cc b/src/web.cc new file mode 100644 index 00000000..345a3263 --- /dev/null +++ b/src/web.cc @@ -0,0 +1,175 @@ +/* + * File: web.cc + * + * Copyright 2005 Jorge Arellano Cid <jcid@dillo.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> /* for rint */ + +#include "msg.h" +#include "nav.h" + +#include "uicmd.hh" + +#include "IO/IO.h" +#include "IO/mime.h" + +#include "dw/core.hh" +#include "prefs.h" +#include "web.hh" + +#define DEBUG_LEVEL 5 +#include "debug.h" + +// Platform idependent part +using namespace dw::core; + + +/* + * Local data + */ +static Dlist *ValidWebs; /* Active web structures list; it holds + * pointers to DilloWeb structures. */ + +/* + * Initialize local data + */ +void a_Web_init(void) +{ + ValidWebs = dList_new(32); +} + +/* + * Given the MIME content type, and a fd to read it from, + * this function connects the proper MIME viewer to it. + * Return value: + * 1 on success (and Call and Data properly set). + * -1 for unhandled MIME types (and Call and Data untouched). + */ +int a_Web_dispatch_by_type (const char *Type, DilloWeb *Web, + CA_Callback_t *Call, void **Data) +{ + Widget *dw = NULL; + style::StyleAttrs styleAttrs; + style::Style *widgetStyle; + style::FontAttrs fontAttrs; + + DEBUG_MSG(1, "a_Web_dispatch_by_type\n"); + + dReturn_val_if_fail(Web->bw != NULL, -1); + + // get the Layout object from the bw structure. + Layout *layout = (Layout*)Web->bw->render_layout; + + if (Web->flags & WEB_RootUrl) { + /* We have RootUrl! */ + dw = (Widget*) a_Mime_set_viewer(Type, Web, Call, Data); + if (dw == NULL) + return -1; + + /* Set a style for the widget */ + fontAttrs.name = "Bitstream Charter"; + fontAttrs.size = (int) rint(12.0 * prefs.font_factor); + fontAttrs.weight = 400; + fontAttrs.style = style::FONT_STYLE_NORMAL; + + styleAttrs.initValues (); + styleAttrs.margin.setVal (5); + styleAttrs.font = style::Font::create (layout, &fontAttrs); + styleAttrs.color = style::Color::createSimple (layout, 0xff0000); + styleAttrs.backgroundColor = + style::Color::createSimple (layout, 0xdcd1ba); + widgetStyle = style::Style::create (layout, &styleAttrs); + dw->setStyle (widgetStyle); + widgetStyle->unref (); + + /* This method frees the old dw if any */ + layout->setWidget(dw); + + if (URL_POSX(Web->url) || URL_POSY(Web->url)) { + layout->scrollTo(HPOS_LEFT, VPOS_TOP, + URL_POSX(Web->url), URL_POSY(Web->url), + 0, 0); + } else { + char *pf = a_Url_decode_hex_str(URL_FRAGMENT_(Web->url)); + if (pf) { + layout->setAnchor(pf); + dFree(pf); + } + } + + /* Clear the title bar for pages without a <TITLE> tag */ + a_UIcmd_set_page_title(Web->bw, ""); + a_UIcmd_set_location_text(Web->bw, URL_STR(Web->url)); + /* Reset both progress bars */ + a_UIcmd_set_page_prog(Web->bw, 0, 2); + a_UIcmd_set_img_prog(Web->bw, 0, 0, 2); + /* Reset the bug meter */ + a_UIcmd_set_bug_prog(Web->bw, 0); + + /* Let the Nav module know... */ + a_Nav_expect_done(Web->bw); + + } else { + /* A non-RootUrl. At this moment we only handle image-children */ + if (!dStrncasecmp(Type, "image/", 6)) + dw = (Widget*) a_Mime_set_viewer(Type, Web, Call, Data); + } + + if (!dw) { + MSG_HTTP("unhandled MIME type: \"%s\"\n", Type); + } + return (dw ? 1 : -1); +} + + +/* + * Allocate and set safe values for a DilloWeb structure + */ +DilloWeb* a_Web_new(const DilloUrl *url) +{ + DilloWeb *web= dNew(DilloWeb, 1); + + _MSG(" a_Web_new: ValidWebs ==> %d\n", dList_length(ValidWebs)); + web->url = a_Url_dup(url); + web->bw = NULL; + web->flags = 0; + web->Image = NULL; + web->filename = NULL; + web->stream = NULL; + web->SavedBytes = 0; + + dList_append(ValidWebs, (void *)web); + return web; +} + +/* + * Validate a DilloWeb pointer + */ +int a_Web_valid(DilloWeb *web) +{ + return (dList_find(ValidWebs, web) != NULL); +} + +/* + * Deallocate a DilloWeb structure + */ +void a_Web_free(DilloWeb *web) +{ + if (!web) return; + if (web->url) + a_Url_free(web->url); + if (web->Image) + a_Image_unref(web->Image); + dFree(web->filename); + dList_remove(ValidWebs, (void *)web); + dFree(web); +} + diff --git a/src/web.hh b/src/web.hh new file mode 100644 index 00000000..a5e05a2f --- /dev/null +++ b/src/web.hh @@ -0,0 +1,45 @@ +#ifndef __WEB_H__ +#define __WEB_H__ + +#include <stdio.h> /* for FILE */ +#include "bw.h" /* for BrowserWindow */ +#include "cache.h" /* for CA_Callback_t */ +#include "image.hh" /* for DilloImage */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Flag defines + */ +#define WEB_RootUrl 1 +#define WEB_Image 2 +#define WEB_Download 4 /* Half implemented... */ + + +typedef struct _DilloWeb DilloWeb; + +struct _DilloWeb { + DilloUrl *url; /* Requested URL */ + BrowserWindow *bw; /* The requesting browser window [reference] */ + int flags; /* Additional info */ + + DilloImage *Image; /* For image urls [reference] */ + + char *filename; /* Variables for Local saving */ + FILE *stream; + int SavedBytes; +}; + +void a_Web_init(void); +DilloWeb* a_Web_new (const DilloUrl* url); +int a_Web_valid(DilloWeb *web); +void a_Web_free (DilloWeb*); +int a_Web_dispatch_by_type (const char *Type, DilloWeb *web, + CA_Callback_t *Call, void **Data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __WEB_H__ */ |